-
Notifications
You must be signed in to change notification settings - Fork 0
feat: basic cli support #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
1e9370e
refactor: remove unused compiler options from tsconfig files
jesse23 8432a30
docs: update CLI specification with ADR reference and add pull reques…
jesse23 7ccf4c2
feat: implement automated release process with semantic-release
jesse23 00dbb9f
feat: enhance release process with npm publishing and clean-publish i…
jesse23 260a071
feat: update release process to use npm publish and remove clean-publ…
jesse23 29954f5
fix: use template literal in clean-pkg-scripts
jesse23 2486ada
test: add integration tests for server endpoints
jesse23 a4efdf4
test: add integration tests for CLI start/stop
jesse23 6d51de9
ci: add actions/checkout@v4 step before Copilot review
jesse23 cfa0b27
ci: remove copilot-review workflow
jesse23 d3fa276
fix: add TypeScript ambient module declaration for @lydell/node-pty
jesse23 4afd418
refactor: move TypeScript declarations from src/env.d.ts to src/pty/n…
jesse23 677c85b
fix: resolve server entry and fetch error handling in CLI
jesse23 b5e6856
test: use dynamic port allocation via getFreePort()
jesse23 3728aab
fix: update release pipeline to use npm publish
jesse23 ce4e07e
docs: update ADR status to Accepted and fix wording
jesse23 bc97036
fix: restore package.json on exit via process handlers
jesse23 bbbe019
fix: implement package.json strip/restore on publish
jesse23 b942474
chore: rename BACKUP_PATH from package.json.publish-backup to package…
jesse23 8a8665f
fix: wrap restore block in try/catch to handle missing backup gracefully
jesse23 5a1812a
fix: rename npm scripts and guard backup strip against stale .bak files
jesse23 9b284d1
fix: use 127.0.0.1 instead of localhost in CLI and build
jesse23 1bcc558
test: replace Bun.sleep with waitForServerDown polling
jesse23 0141cf5
chore: add Node.js engine requirement and update release process
jesse23 3451bfe
fix: update server log message to use 127.0.0.1 instead of localhost
jesse23 966aed2
fix: add trailing newline to CLI banner string
jesse23 ad9043c
fix: check serverEntry exists before spawning, exit 1 with error mess…
jesse23 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| ## Description | ||
|
|
||
| <!-- What does this PR do and why? --> | ||
|
|
||
| ## ADRs | ||
|
|
||
| <!-- List any ADRs introduced or implemented by this PR. Remove if not applicable. --> | ||
| - |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ dist/ | |
|
|
||
| # npm publish artifacts | ||
| *.tgz | ||
| package.json.bak | ||
|
|
||
| # Logs | ||
| *.log | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # source | ||
| src/ | ||
| scripts/ | ||
|
|
||
| # config | ||
| tsconfig*.json | ||
| biome.json | ||
| .releaserc.json | ||
| Makefile | ||
|
|
||
| # publish backup | ||
| package.json.bak | ||
|
|
||
| # docs | ||
| docs/ | ||
|
|
||
| # misc | ||
| .github/ | ||
| .agents/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "branches": ["main"], | ||
| "tagFormat": "v${version}", | ||
| "plugins": [ | ||
| "@semantic-release/commit-analyzer", | ||
| "@semantic-release/release-notes-generator", | ||
| "@semantic-release/npm", | ||
| "@semantic-release/github" | ||
| ] | ||
| } |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # ADR 002: CLI — `wtty start` / `wtty stop` | ||
|
|
||
| **SPEC:** [cli](../specs/cli.md) | ||
| **Status:** Accepted | ||
| **Date:** 2026-03-21 | ||
|
|
||
| --- | ||
|
|
||
| ## Context | ||
|
|
||
| wtty currently has one entry point: `src/server.ts`, run directly via `bun run src/server.ts` or `tsx src/server.ts`. There is no CLI, no daemon mode, and no way to stop the server other than `Ctrl-C` in the terminal that started it. | ||
|
|
||
| The goal for this slice is `npx wtty start` / `npx wtty stop` — the minimal lifecycle commands. `start` forks the server as a background daemon and exits. `stop` terminates that daemon. | ||
|
|
||
| Two sub-questions drive this ADR: | ||
|
|
||
| 1. **How does `start` fork a background process?** Node.js `child_process.spawn` with `detached: true` + `stdio: 'ignore'` + `unref()` — the parent exits, the child keeps running. | ||
| 2. **How does `stop` terminate the server?** `POST /api/server/stop` — the server handles its own shutdown. No PID file, no signals, works identically on Mac, Linux, and Windows. | ||
|
|
||
| ## Decision | ||
|
|
||
| Add a CLI entry point (`src/cli.ts`) and a `POST /api/server/stop` endpoint to `src/server.ts`. No new runtime dependencies — Commander.js is deferred until there are enough subcommands to justify it. | ||
|
|
||
| **`wtty start`**: | ||
| - Spawns the server with `detached: true`, `stdio: 'ignore'`, `unref()`. | ||
| - Immediately prints `wtty started` and exits. Does not wait for the server to be ready. | ||
|
|
||
| **`wtty stop`**: | ||
| - Sends `POST http://localhost:PORT/api/server/stop`. | ||
| - If the request succeeds: prints `wtty stopped`. | ||
| - If the request fails (connection refused): prints `wtty is not running`. | ||
|
|
||
| **`server.ts` change**: Add `POST /api/server/stop` — kills all PTY sessions, closes the WebSocket server, and calls `process.exit(0)`. The server owns its own shutdown on all platforms. | ||
|
|
||
| ## Considered Options | ||
|
|
||
| ### Option A: HTTP stop endpoint (chosen) | ||
|
|
||
| `wtty stop` sends `POST /api/server/stop`. Server cleans up and exits itself. | ||
|
|
||
| - **Pros**: Fully cross-platform — no signals, no PID file. Server owns its cleanup. Connection refused = not running, no separate state to track. Consistent with the existing HTTP surface of the server. | ||
| - **Cons**: Requires the server to be responsive. If the server is hung, HTTP stop won't work — acceptable as a degraded edge case; a hard `kill` fallback can be added later if needed. | ||
|
|
||
| ### Option B: PID file + SIGTERM | ||
|
|
||
| Write the server PID to `~/.wtty/server.pid`. `stop` reads it and sends `SIGTERM`. | ||
|
|
||
| - **Pros**: Works without an HTTP round-trip. PID file is human-readable. | ||
| - **Cons**: SIGTERM on Windows calls `TerminateProcess()` — hard kill, server cleanup handler never runs. Stale PID files if the server crashes. Requires PID file management in both the CLI and server. More state to track. | ||
|
|
||
| ### Option C: Commander.js from the start | ||
|
|
||
| Add Commander.js now and model `start`/`stop` as commands. | ||
|
|
||
| - **Pros**: Consistent with the CLI spec's stated direction. Auto-generates `--help`. | ||
| - **Cons**: Adds a dependency for two `if` branches. Commander.js is justified when there are multiple subcommands with options — for this slice it's overhead. Deferred to the session management slice. | ||
|
|
||
| ### Option D: Shell script wrapper | ||
|
|
||
| A `wtty.sh` that does `node dist/server.js &`. | ||
|
|
||
| - **Pros**: Trivial. | ||
| - **Cons**: Platform-specific (no Windows). Doesn't compose with `npx`. | ||
|
|
||
| ## Consequences | ||
|
|
||
| **Good**: `npx wtty start` works from any directory. Daemon runs in background, terminal is free. `npx wtty stop` works identically on Mac, Linux, and Windows — no signals, no PID files, no platform-specific code. Server owns its cleanup. No new runtime dependencies. | ||
|
|
||
| **Bad**: If the server is hung, `wtty stop` fails silently — acceptable for this slice; hard-kill fallback deferred. Port is hardcoded at 2346 — will become configurable when the config file slice lands. | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| ### `package.json` changes | ||
|
|
||
| ```json | ||
| { | ||
| "bin": { | ||
| "wtty": "./dist/cli.js" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Add `src/cli.ts` as a second build entrypoint alongside `src/server.ts`. | ||
|
|
||
| ### File layout | ||
|
|
||
| ``` | ||
| src/ | ||
| cli.ts ← new: wtty start / wtty stop | ||
| server.ts ← add: POST /api/server/stop endpoint | ||
| ``` | ||
|
|
||
| ### `src/cli.ts` sketch | ||
|
|
||
| ```ts | ||
| const command = process.argv[2]; | ||
| const BASE_URL = `http://localhost:${PORT}`; | ||
|
|
||
| if (command === 'start') { | ||
| const child = spawn(process.execPath, [serverEntry], { | ||
| detached: true, | ||
| stdio: 'ignore', | ||
| }); | ||
| child.unref(); | ||
| console.log('wtty started'); | ||
| } else if (command === 'stop') { | ||
| try { | ||
| await fetch(`${BASE_URL}/api/server/stop`, { method: 'POST' }); | ||
| console.log('wtty stopped'); | ||
| } catch { | ||
| console.log('wtty is not running'); | ||
| } | ||
| } else { | ||
| console.error('Usage: wtty start | wtty stop'); | ||
| process.exit(1); | ||
| } | ||
| ``` | ||
|
|
||
| ### `POST /api/server/stop` in `server.ts` | ||
|
|
||
| ```ts | ||
| if (req.method === 'POST' && pathname === '/api/server/stop') { | ||
| res.writeHead(200); | ||
| res.end('stopping'); | ||
| for (const [ws, session] of sessions.entries()) { | ||
| session.pty.kill(); | ||
| ws.close(); | ||
| } | ||
| wss.close(); | ||
| httpServer.close(() => process.exit(0)); | ||
| return; | ||
| } | ||
| ``` | ||
|
|
||
| ### Daemon spawn | ||
|
|
||
| ```ts | ||
| const child = spawn(process.execPath, [serverEntry], { | ||
| detached: true, | ||
| stdio: 'ignore', | ||
| }); | ||
| child.unref(); | ||
| ``` | ||
|
|
||
| `process.execPath` is the Node.js or Bun binary that launched the CLI — ensures the server runs under the same runtime. | ||
|
|
||
| ## Related Decisions | ||
|
|
||
| - [ADR 001 — Bootstrap](001.wtty.bootstrap.md): `src/server.ts` is the server entry point extended by this ADR | ||
| - [ADR 003 — Release Process](003.release-process.semantic-release.md): CLI entry point is part of the binary artifact built and released | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| # ADR 003: Release Process — semantic-release on GitHub | ||
|
|
||
| **SPEC:** [release-process](../specs/release-process.md) | ||
| **Status:** Accepted | ||
| **Date:** 2026-03-21 | ||
|
jesse23 marked this conversation as resolved.
|
||
|
|
||
| --- | ||
|
|
||
| ## Context | ||
|
|
||
| wtty has a stub release workflow (`.github/workflows/release.yml`) that runs `bun run build` then `echo "TODO - publish step"`. No versioning, no published artifacts. | ||
|
|
||
| wtty targets developers who already have Node.js or Bun installed. Distribution via `npx wtty` is the natural fit — no binary downloads needed. A release means publishing to npm and creating a GitHub Release with release notes. The version should be derived automatically from what changed, not set manually. | ||
|
|
||
| The cfx repo solved the same versioning problem with semantic-release (ADR 004): conventional commit prefixes in PR/MR titles drive version bumps post-merge, git tags are the sole version record, no release commits. That pattern maps directly to wtty, with `@semantic-release/npm` added since wtty publishes to the public npm registry. | ||
|
|
||
| ## Decision | ||
|
|
||
| Use **semantic-release** with `@semantic-release/npm` and `@semantic-release/github`, running post-merge on `main`. Developers write a conventional commit prefix in the PR title. GitHub squash merge uses the PR title as the commit message on `main`. semantic-release reads that message, determines the version bump, publishes to npm, creates a GitHub Release with release notes, and pushes a git tag. No manual versioning, no release commits, no extra files per PR. | ||
|
|
||
| ## Considered Options | ||
|
|
||
| ### Option A: semantic-release post-merge on main, tag-only (chosen) | ||
|
|
||
| - **Pros**: Zero developer overhead beyond PR title. npm publish + GitHub Release + release notes fully automated. Battle-tested. No version conflicts between concurrent PRs. No release commits — clean one-commit-per-PR history on `main`. `GITHUB_TOKEN` is provided automatically. Only `NPM_TOKEN` needs manual setup. | ||
| - **Cons**: `package.json` version doesn't reflect the released version (use `0.0.0-development` placeholder). No in-repo `CHANGELOG.md` (release notes live on GitHub Releases). Requires conventional commit discipline in PR titles (mitigate: squash merge uses PR title — one line to get right). | ||
|
|
||
| ### Option B: Manual GitHub Release | ||
|
|
||
| - **Pros**: Full control over release notes. No tooling. | ||
| - **Cons**: Manual — error-prone, inconsistent, easy to forget. | ||
|
|
||
| ### Option C: release-it | ||
|
|
||
| - **Pros**: Simpler config surface, base version frozen on prerelease. | ||
| - **Cons**: wtty has no prerelease channel need. semantic-release's automatic version derivation from commit type is the better fit — release-it requires a manual bump type or extra config to replicate this. | ||
|
|
||
| ### Option D: Changesets | ||
|
|
||
| - **Pros**: Human-written changelogs, 1 dependency. | ||
| - **Cons**: Extra file per PR, developer must run `npx changeset`, duplicates PR title. Same reasons rejected in cfx ADR 004. | ||
|
|
||
| ## Consequences | ||
|
|
||
| **Good**: Zero developer overhead — write a conventional PR title, everything else is automated. Publishes to npm. GitHub Release auto-created with release notes. Clean `main` history. Follows semantic-release's own recommended approach (tag-only, no commit-back). | ||
|
|
||
| **Bad**: `package.json` version stays at `0.0.0-development` in repo. No `CHANGELOG.md` in repo. Requires conventional commit discipline in PR titles. `NPM_TOKEN` must be set as a GitHub Actions secret. | ||
|
|
||
| ## Implementation Notes | ||
|
|
||
| ### Plugin Chain | ||
|
|
||
| ```json | ||
| { | ||
| "branches": ["main"], | ||
| "tagFormat": "v${version}", | ||
| "plugins": [ | ||
| "@semantic-release/commit-analyzer", | ||
| "@semantic-release/release-notes-generator", | ||
| "@semantic-release/npm", | ||
| "@semantic-release/github" | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| `@semantic-release/npm` publishes to npm atomically within semantic-release's publish phase — before `@semantic-release/github` creates the tag and GitHub Release. This ensures a failed npm publish prevents the tag from being created (no partial releases). `prepack`/`postpack` scripts in `package.json` temporarily strip `scripts` and `devDependencies` before the package is packed, then restore them after. `@semantic-release/git` and `@semantic-release/changelog` intentionally omitted (tag-only). | ||
|
|
||
| `prepack`/`postpack` in `package.json`: | ||
|
|
||
| ```json | ||
| "prepack": "bun scripts/clean-pkg-scripts.ts strip", | ||
| "postpack": "bun scripts/clean-pkg-scripts.ts restore" | ||
| ``` | ||
|
|
||
| What consumers receive: | ||
|
|
||
| ```json | ||
| { | ||
| "name": "wtty", | ||
| "bin": { "wtty": "./dist/cli.js" }, | ||
| "files": ["dist"], | ||
| "type": "module", | ||
| "dependencies": { ... } | ||
| } | ||
| ``` | ||
|
|
||
| ### Release Workflow | ||
|
|
||
| ```yaml | ||
| on: | ||
| push: | ||
| branches: [main] | ||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
| - uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: 1.3.10 | ||
| - run: bun install --frozen-lockfile | ||
| - run: bun run build | ||
| - name: Release | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| run: bunx semantic-release | ||
| ``` | ||
|
|
||
| - `fetch-depth: 0` — semantic-release needs full git history to find previous tags | ||
| - `persist-credentials: false` — semantic-release manages its own git auth via `GITHUB_TOKEN` | ||
| - `permissions: contents: write` — `GITHUB_TOKEN` needs to push tags and create releases | ||
| - `@semantic-release/npm` publishes first, then `@semantic-release/github` creates the tag — no partial release risk | ||
| - `NPM_TOKEN` — must be set as a GitHub Actions secret. Generate from npmjs.com → Access Tokens → Automation token. | ||
|
|
||
| ### Why Tag-Only (No Release Commits) | ||
|
|
||
| Omitting `@semantic-release/git` and `@semantic-release/changelog`: | ||
|
|
||
| - No branch push permissions needed — `GITHUB_TOKEN` only needs to push tags | ||
| - Clean history — one commit per PR, no interleaved `chore(release):` commits | ||
| - No `[skip ci]` dance — no recursive pipeline triggering | ||
| - Fewer dependencies — 1 devDependency instead of 3+ | ||
|
|
||
| ### `package.json` Version Placeholder | ||
|
|
||
| ```json | ||
| { "version": "0.0.0-development" } | ||
| ``` | ||
|
|
||
| Git tags are the source of truth for the released version. | ||
|
|
||
| ## Related Decisions | ||
|
|
||
| - [ADR 001 — Bootstrap](001.wtty.bootstrap.md): Established the build pipeline that the release workflow extends | ||
| - [ADR 002 — CLI start/stop](002.cli.start-stop.md): CLI entry point distributed via `npx wtty` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.