diff --git a/AGENTS.md b/AGENTS.md index efaf33a..453ee8c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,14 @@ webtty/ └── README.md ``` +## Development Workflow + +Always build and stop the old server before testing: + +``` +bun run build && bun run webtty stop +``` + ## Rules - **Match existing patterns** — read 2-3 similar files before writing new ones. diff --git a/README.md b/README.md index b58bb7d..f7203c2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ # webtty A web TTY for running CLI/TUI applications in a browser tab, across platforms. + +## Debugging + +Build emits source maps (`dist/**/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved. + +``` +bun run build +bun --inspect run dist/server/index.js +# or +node --inspect dist/server/index.js +``` diff --git a/docs/adrs/001.webtty.bootstrap.md b/docs/adrs/001.webtty.bootstrap.md index 66d6302..f3a61d1 100644 --- a/docs/adrs/001.webtty.bootstrap.md +++ b/docs/adrs/001.webtty.bootstrap.md @@ -75,7 +75,7 @@ The original package requires `node-gyp` compilation on install. `@lydell/node-p ## Consequences - Full round-trip working on both Bun and Node.js runtimes -- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty` +- `bun run dev` uses `Bun.Terminal` (native); `npm run dev:node` uses `node-pty` _(these scripts were removed in a later cleanup; see `package.json` for current scripts)_ - No config file yet — all values hardcoded; acceptable for this slice - No auth — localhost-only, same security posture as ghostty-web/demo - Frontend is plain HTML/JS — no TypeScript in browser until a build step is added diff --git a/package.json b/package.json index 8016d64..08b7036 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,8 @@ "lint:fix": "tsc --noEmit -p tsconfig.lint.json && bunx biome check --write .", "test": "bun test", "build": "bun run scripts/build.ts", - "dev": "bun run src/server/index.ts", - "prod": "bun run dist/server/index.js", - "dev:node": "tsx src/server/index.ts", - "prod:node": "node dist/server/index.js", + "server": "bun run dist/server/index.js", + "server:node": "node dist/server/index.js", "webtty": "bun run dist/cli/index.js", "prepack": "bun scripts/clean-pkg-scripts.ts strip", "postpack": "bun scripts/clean-pkg-scripts.ts restore" diff --git a/src/cli/http.ts b/src/cli/http.ts index 1ecd0f2..7724858 100644 --- a/src/cli/http.ts +++ b/src/cli/http.ts @@ -19,12 +19,19 @@ export async function isServerRunning(): Promise { } export async function startServer(): Promise { - const isTs = __filename.endsWith('.ts'); + // When running from source (e.g. `bun run src/cli/index.ts` during development), + // __filename ends with .ts and Bun is the runtime, so we can point at the .ts + // server entry directly. Built output always lands in .js, so Node is never + // asked to execute TypeScript. In production isTs is always false. + const isBun = typeof (globalThis as Record).Bun !== 'undefined'; + const isTs = isBun && __filename.endsWith('.ts'); const serverEntry = path.resolve(__dirname, isTs ? '../server/index.ts' : '../server/index.js'); if (!fs.existsSync(serverEntry)) { console.error(`webtty: server entry not found at ${serverEntry}`); process.exit(1); } + // Reuse the current runtime (bun, node, etc.) to spawn the server so the + // server always runs under the same runtime as the CLI. const child = spawn(process.execPath, [serverEntry], { detached: true, stdio: 'ignore', diff --git a/src/server/client.ts b/src/server/client.ts index b7a150d..2553df0 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -82,6 +82,11 @@ export function render(sessionId: string): string { setTimeout(() => window.close(), 500); return; } + if (event.code === 1001) { + term.write('\\r\\n\\x1b[33mServer stopped.\\x1b[0m\\r\\n'); + setTimeout(() => window.close(), 500); + return; + } console.log('[webtty] disconnected, reconnecting in 2s...'); term.write('\\r\\n\\x1b[31mConnection closed. Reconnecting in 2s...\\x1b[0m\\r\\n'); setTimeout(connect, 2000); diff --git a/src/server/index.ts b/src/server/index.ts index efd77c8..68ded18 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -12,7 +12,7 @@ const httpServer = http.createServer((req, res) => { handleRequest(req, res, distPath, wasmPath, () => { for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(); + for (const client of session.clients) client.close(1001, 'server stopped'); } wss.close(); httpServer.close(() => process.exit(0)); @@ -25,10 +25,15 @@ process.on('SIGINT', () => { console.log('\n\nShutting down...'); for (const session of sessionRegistry.values()) { session.pty?.kill(); - for (const client of session.clients) client.close(); + for (const client of session.clients) client.close(1001, 'server stopped'); } wss.close(); - process.exit(0); + const exit = () => process.exit(0); + const shutdownTimeout = setTimeout(exit, 1000); + httpServer.close(() => { + clearTimeout(shutdownTimeout); + exit(); + }); }); httpServer.listen(HTTP_PORT, '127.0.0.1', () => {