feat(cli): vx watch — initial run then re-run on file changes - #67
Merged
Conversation
New subcommand mirroring `turbo watch` / `nx watch`. Initial run uses the same orchestrator path as `vx run`. After it completes: - Per-project recursive `fs.watch` (Bun supports recursive on every platform). - Non-recursive watch of the workspace root for fingerprint files (`pnpm-lock.yaml`, `bun.lock`, `pnpm-workspace.yaml`, …) so an install bump triggers a cycle even when no project dir saw it. - Path filter ignores `node_modules` / `.git` / `.vx` segments and `.tsbuildinfo` / `~` (editor swap) suffixes. - Events debounce ~150 ms after the last one before triggering. - Reentrancy guard: while a cycle is running, further events set a `pending` flag; the loop drains it after the current cycle so two bursty events collapse into one re-run. - Ctrl+C closes watchers, exits 0. Cache is the workhorse — most re-runs are cache hits (~tens of ms). We deliberately don't filter events through `cache.inputs.files` globs: the cache key is the source of truth, and pre-filtering would redo the glob + boundary work on every event. Rejected at parse time (would be nonsensical in a loop): --dry, --graph, --summarize, --profile Flag surface is otherwise identical to `vx run` — scope resolution (`--all` / `--filter` / `--affected` / `pkg#task`), `--concurrency`, `--no-cache`, `--excludeDependencies`, `--verbosity`, forwarded `--` args. Extracted `resolveRunOptions(parsed, cwd, tasks)` from `cli/run.ts` so both subcommands share the scope resolution. Persistent tasks re-spawn each cycle (the orchestrator SIGTERMs them at end-of-run, then the next cycle launches fresh). For dev-server workflows where the server should stay up across changes, use the tool's own watch (vite, tsc -b -w, bun --watch); `vx watch` is for `vx watch test` / `vx watch lint` / `vx watch build`. Tests: - 6 parser-side validations (no task, --dry / --graph / --summarize / --profile rejection, parser error prefix). - 1 end-to-end re-run-on-change: fixture workspace with a task that `cat`s a file; the test writes a new value mid-watch and asserts the re-run surfaces it; SIGINT exits 0. Docs: - docs/cli.md: new `## vx watch` section with lifecycle, path filter, constraints, exit codes. - docs/comparison.md: watch flipped from gap to shipped in both matrices; gap-list item rewritten as shipped. - docs/modules/README.md + new docs/modules/cli-watch.md. - docs/modules/cli-run.md: documents the new exported `resolveRunOptions`. - src/cli/help.ts: added watch line + section. - CLAUDE.md: decision-log entry + roadmap updated (watch removed from list; "Recently shipped" entry added).
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
New subcommand mirroring
turbo watch/nx watch. Closes the last open Turbo-alignment gap.vx run.fs.watch(Bun supports recursive on every platform).pnpm-workspace.yamlchanges — so abun installbump triggers a cycle even when no project dir saw the change.node_modules/.git/.vxsegments and.tsbuildinfo/~(editor swap) suffixes.pending = true; the loop drains them after the current cycle so two bursty events collapse into one re-run.Cache is the workhorse — most re-runs are cache hits (~tens of ms). We deliberately don't filter events through
cache.inputs.filesglobs: the cache key is the source of truth, and pre-filtering would redo the glob + boundary work on every event.Rejected flags
Parser exits 1 before the initial run for flags that don't make sense in a loop:
--dry/--graph— skip execution.--summarize/--profile— would overwrite the target file per cycle.Otherwise the flag surface is identical to
vx run. ExtractedresolveRunOptions(parsed, cwd, tasks)fromcli/run.tsso both subcommands share the scope resolution.Persistent tasks
Re-spawn each cycle. For dev servers that should stay up, use the tool's own watch (
vite,tsc -b -w,bun --watch);vx watchis forvx watch test/vx watch lint/vx watch build.Files
src/cli/watch.ts,tests/cli.test.ts(7 new tests),docs/modules/cli-watch.mdsrc/cli.ts(dispatcher),src/cli/run.ts(extractedresolveRunOptions),src/cli/help.ts,docs/cli.md,docs/comparison.md,docs/modules/README.md,docs/modules/cli-run.md,CLAUDE.mdTest plan
--dry/--graph/--summarize/--profilerejection, parser error prefix).cats a file; the test writes a new value mid-watch and asserts the re-run surfaces it; SIGINT exits 0.bun src/bin.ts run lint— cleanbun src/bin.ts run format-check— clean (oxfmt ran)bun test— 338 pass, 0 fail (was 331)vx watchwith no task /--dry/--helptext all behave correctly.Generated by Claude Code