@vzn/vx 0.0.636 → 0.0.638
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -11
- package/docs/caching.md +246 -213
- package/docs/cli.md +791 -705
- package/docs/execution.md +68 -76
- package/docs/features.md +47 -42
- package/docs/patterns.md +26 -25
- package/docs/schema.md +392 -205
- package/docs/security.md +76 -39
- package/launcher.sh +43 -0
- package/package.json +5 -5
- package/schemas/cache.json +41 -19
- package/schemas/info.json +23 -11
- package/schemas/last.json +48 -2
- package/schemas/lock.json +1 -1
- package/schemas/plan.json +4 -4
- package/schemas/show.json +5 -3
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +28 -13
- package/src/bin.ts +30 -9
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +124 -53
- package/src/cache/chained-cache.ts +4 -0
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +15 -1
- package/src/cache/inputs.ts +102 -36
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +15 -18
- package/src/cache/layered-cache.ts +20 -102
- package/src/cache/plugin-cache.ts +248 -0
- package/src/cache/policy.ts +32 -27
- package/src/cache/run-history.ts +104 -14
- package/src/cache/run-store.ts +77 -0
- package/src/cache/schema.ts +58 -31
- package/src/cache/scoped-cache.ts +237 -0
- package/src/cli/cache.ts +173 -34
- package/src/cli/completions.ts +11 -7
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +110 -70
- package/src/cli/index.ts +67 -24
- package/src/cli/info.ts +43 -27
- package/src/cli/init.ts +115 -407
- package/src/cli/last.ts +157 -9
- package/src/cli/lock.ts +356 -122
- package/src/cli/plan-format.ts +7 -5
- package/src/cli/plugin-commands.ts +81 -14
- package/src/cli/plugin-templates.ts +11 -11
- package/src/cli/run.ts +116 -160
- package/src/cli/select.ts +50 -47
- package/src/cli/show.ts +83 -59
- package/src/cli/task-verb.ts +6 -13
- package/src/cli/upgrade.ts +415 -149
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +39 -61
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +18 -4
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +227 -230
- package/src/cli/why.ts +38 -18
- package/src/cli/workspace-config.ts +33 -70
- package/src/config.ts +169 -70
- package/src/exec/env.ts +1 -31
- package/src/exec/executor.ts +1 -5
- package/src/exec/kill-tree.ts +2 -1
- package/src/exec/sandbox-runtime.ts +12 -1
- package/src/exec/sandbox-violations.ts +9 -1
- package/src/graph/dependency-spec.ts +1 -1
- package/src/graph/index.ts +1 -0
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +27 -7
- package/src/graph/task-graph.ts +76 -24
- package/src/handoff.ts +169 -0
- package/src/index.ts +35 -58
- package/src/orchestrator/affected-tasks.ts +15 -8
- package/src/orchestrator/command-windows.ts +44 -0
- package/src/orchestrator/deferred-outputs.ts +12 -10
- package/src/orchestrator/doctor.ts +98 -110
- package/src/orchestrator/download-policy.ts +3 -3
- package/src/orchestrator/events.ts +19 -3
- package/src/orchestrator/excluded-keys.ts +0 -2
- package/src/orchestrator/execute-task.ts +147 -69
- package/src/orchestrator/failure-mode.ts +19 -10
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +9 -5
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +126 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +20 -5
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +80 -41
- package/src/orchestrator/metrics.ts +78 -9
- package/src/orchestrator/miss-save.ts +3 -0
- package/src/orchestrator/options.ts +42 -29
- package/src/orchestrator/output-host.ts +72 -0
- package/src/orchestrator/plan.ts +6 -8
- package/src/orchestrator/plugin-host.ts +100 -66
- package/src/orchestrator/plugin.ts +173 -67
- package/src/orchestrator/prepare.ts +163 -78
- package/src/orchestrator/projects.ts +105 -99
- package/src/orchestrator/remote-prefetch.ts +11 -6
- package/src/orchestrator/run-context.ts +25 -6
- package/src/orchestrator/run-failures.ts +17 -8
- package/src/orchestrator/run-lock.ts +1 -1
- package/src/orchestrator/run-records.ts +1 -1
- package/src/orchestrator/run-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +244 -119
- package/src/orchestrator/sandbox-fix.ts +39 -20
- package/src/orchestrator/sandbox-request.ts +84 -13
- package/src/orchestrator/signals.ts +1 -1
- package/src/orchestrator/stable-keys.ts +13 -11
- package/src/orchestrator/status-line.ts +49 -7
- package/src/orchestrator/summary.ts +115 -106
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +4 -4
- package/src/settings.ts +428 -0
- package/src/{orchestrator → util}/colors.ts +5 -0
- package/src/util/config-frame.ts +31 -2
- package/src/util/errors.ts +33 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +16 -3
- package/src/util/paths.ts +10 -0
- package/src/util/refusal-notice.ts +111 -0
- package/src/util/report.ts +31 -0
- package/src/util/settle.ts +49 -30
- package/src/util/size.ts +23 -4
- package/src/util/timing.ts +3 -2
- package/src/util/ui.ts +186 -0
- package/src/util/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +84 -232
- package/src/workspace/config-eval.ts +15 -20
- package/src/workspace/config-imports.ts +78 -3
- package/src/workspace/config-schema.ts +209 -268
- package/src/workspace/dependency-rules.ts +65 -0
- package/src/workspace/filter.ts +2 -2
- package/src/workspace/fingerprint.ts +11 -121
- package/src/workspace/index.ts +20 -27
- package/src/workspace/load-reads.ts +4 -4
- package/src/workspace/lockfile.ts +96 -140
- package/src/workspace/migration.ts +307 -215
- package/src/workspace/package-graph.ts +30 -216
- package/src/workspace/project-loader.ts +80 -285
- package/src/workspace/repo-id.ts +2 -1
- package/src/workspace/workspace.ts +363 -831
- package/src/cache/config-evals.ts +0 -130
- package/src/workspace/config-cache.ts +0 -661
- package/src/workspace/migrate-scripts.ts +0 -1121
package/docs/cli.md
CHANGED
|
@@ -8,7 +8,8 @@ in `src/cli/index.ts` dispatches to per-subcommand handlers under
|
|
|
8
8
|
so existing Turbo users can swap in with minimal muscle-memory churn.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
# Standalone binary via npm (no Bun required on target)
|
|
11
|
+
# Standalone binary via npm (no Bun required on target); inside a project
|
|
12
|
+
# that installs @vzn/vx, it runs that one (§ Which vx runs):
|
|
12
13
|
npm install -g @vzn/vx
|
|
13
14
|
|
|
14
15
|
# From source (Bun ≥ 1.4):
|
|
@@ -28,15 +29,15 @@ released binary carries its own Bun and the row never says it.
|
|
|
28
29
|
```
|
|
29
30
|
# Core
|
|
30
31
|
vx run [OPTIONS] [TASK | PKG#TASK ...] [-- forwarded-args...]
|
|
31
|
-
vx watch [OPTIONS] TASK [-- forwarded-args...]
|
|
32
|
-
vx cache
|
|
32
|
+
vx watch [OPTIONS] [--poll] TASK [-- forwarded-args...]
|
|
33
|
+
vx cache clean [--older-than <duration>] [--max-size <size>] [--global] [--dry-run] [--format pretty|json] [--cache-dir <path>]
|
|
33
34
|
vx lock [--check] [--format pretty|json]
|
|
34
35
|
vx init [--dry [--format pretty|json]] [--force] [--mjs] [--native|--keep] [--plugin <seam>]
|
|
35
36
|
vx show [PROJECT[#TASK] | TASK] [--filter <pattern>] [--affected[=<ref>]] [--format pretty|json]
|
|
36
37
|
vx info [--format pretty|json] [--cache-dir <path>]
|
|
37
38
|
vx why (TASK | PKG#TASK) [--run RUNID] [--format pretty|json] [--cache-dir <path>]
|
|
38
|
-
vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--format pretty|json] [--cache-dir <path>]
|
|
39
|
-
vx upgrade [TAG] #
|
|
39
|
+
vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]
|
|
40
|
+
vx upgrade [TAG] # upgrade vx the way it was installed
|
|
40
41
|
vx docs <query> [--limit N] [--format pretty|json]
|
|
41
42
|
vx completions bash|zsh|fish
|
|
42
43
|
|
|
@@ -68,7 +69,8 @@ declare task(s): <name>.` on stderr, a run and `--dry` / `--graph` alike
|
|
|
68
69
|
declared task (or, for `pkg#task`, a runnable spec) is within two edits,
|
|
69
70
|
and with no name that near, what exists: `Tasks: build, test.`, `No
|
|
70
71
|
project is named zzz; projects: a, b.`, or `a's tasks: build, test.`
|
|
71
|
-
(M-56),
|
|
72
|
+
(M-56), each followed by ``A run reads the lock: after editing a
|
|
73
|
+
vx.config, run `vx lock`.`` (a task just added is not in the lock yet),
|
|
72
74
|
and with `Only projects outside the selection declare <name> — pass
|
|
73
75
|
--all, or --filter to pick them.` when the run was scoped (the cwd's
|
|
74
76
|
project, a `--filter`) and a project outside the scope declares it
|
|
@@ -128,11 +130,11 @@ If no task name is given:
|
|
|
128
130
|
|
|
129
131
|
Exit codes:
|
|
130
132
|
|
|
131
|
-
| Code | When
|
|
132
|
-
| --------------------- |
|
|
133
|
-
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task.
|
|
134
|
-
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error.
|
|
135
|
-
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM), `
|
|
133
|
+
| Code | When |
|
|
134
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
135
|
+
| `0` | Every task finished `success` or `cache-hit` (local or remote); or `--affected` left no project that declares the task. |
|
|
136
|
+
| `1` | At least one task ended `failed` or `skipped`; a persistent task exited non-zero after it was ready; a task name no project declares; or parse/setup error. |
|
|
137
|
+
| `130` / `143` / `129` | Interrupted (SIGINT / SIGTERM / SIGHUP): each task's process group (the task and what it forked) gets vx's signal once (a SIGHUP as a SIGTERM), `run.killGrace` (2 s) to go, then SIGKILL; a second signal skips the grace. vx then dies of the signal, so a shell script running it stops too (a run that handed a task the terminal exits with the code, so the terminal is restored). |
|
|
136
138
|
|
|
137
139
|
A task runs in its own session, so a terminal's Ctrl-C reaches vx alone,
|
|
138
140
|
and each task hears it once: from vx, as SIGINT.
|
|
@@ -182,7 +184,7 @@ facing summary.
|
|
|
182
184
|
A filter that matches nothing refuses the run (`no projects matched
|
|
183
185
|
filter(s): …`) with `Did you mean <name>?` when a project name is within
|
|
184
186
|
two edits, or when exactly one scoped project's name after its `/` is
|
|
185
|
-
(`--filter vx-
|
|
187
|
+
(`--filter vx-otel` hints `@vzn/vx-otel`), and `Projects: a, b` otherwise
|
|
186
188
|
(M-56). A list names eight, then a count. An unmatched `tag:` filter
|
|
187
189
|
hints the nearest tag instead (`Did you mean tag:scope:web?`), else
|
|
188
190
|
lists the tags. When every pattern matched and an exclusion took back
|
|
@@ -208,19 +210,15 @@ selected: !app excluded every project the other filters matched`).
|
|
|
208
210
|
| `!<pattern>` | Exclude packages matching `<pattern>`, from everything the includes select, in any order. |
|
|
209
211
|
| `[<git-ref>]` | Projects whose files changed since `<git-ref>` (`main`, `HEAD~5`, …). |
|
|
210
212
|
|
|
211
|
-
An edge is a `
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
cross-project `dependsOn` entry (`e2e`'s `test: { dependsOn:
|
|
221
|
-
['app#build'] }` makes `e2e` a dependent of `app`). The task graph knows
|
|
222
|
-
both, so selection follows both: `vx run test --filter '...app'` runs
|
|
223
|
-
`e2e#test` even though `e2e` has no manifest dependency on `app`. There
|
|
213
|
+
An edge is one a plugin supplies — a `discover` entry's `deps` (order
|
|
214
|
+
and selection) or `reach` (selection only); the lockfile plugins
|
|
215
|
+
(`pnpm()`, `bun()`, `npm()`, `yarn()` from `@vzn/vx-lockfile`) supply
|
|
216
|
+
what the package manager installed, and core reads no edge from a
|
|
217
|
+
`package.json` — OR a cross-project `dependsOn` entry (`e2e`'s `test: {
|
|
218
|
+
dependsOn: ['app#build'] }` makes `e2e` a dependent of `app`). The task
|
|
219
|
+
graph knows both, so selection follows both: `vx run test --filter
|
|
220
|
+
'...app'` runs `e2e#test` even though no plugin gives `e2e` an edge to
|
|
221
|
+
`app`. There
|
|
224
222
|
is no `implicitDependencies` field — declare the edge where the task
|
|
225
223
|
needs it.
|
|
226
224
|
|
|
@@ -244,7 +242,7 @@ vx run build --filter '[origin/main]' # projects with files changed sinc
|
|
|
244
242
|
|
|
245
243
|
Run the task only in projects whose files changed since `<base>`.
|
|
246
244
|
|
|
247
|
-
- `--affected` (no value) uses the workspace's `affectedBase` when it
|
|
245
|
+
- `--affected` (no value) uses the workspace's `run.affectedBase` when it
|
|
248
246
|
names one (`nx()` and `turbo()` set it from `NX_BASE` / nx.json's
|
|
249
247
|
`defaultBase` and `TURBO_SCM_BASE`, `turbo()` on GitHub Actions from
|
|
250
248
|
the pull request's base or the push's `before`, as Turbo does;
|
|
@@ -301,12 +299,13 @@ change seeds tasks in the projects it touches:
|
|
|
301
299
|
(`files`, `workspaceFiles`), or a changed submodule or embedded
|
|
302
300
|
repository is one its `workspaceFiles` may reach into (git reports it
|
|
303
301
|
as one path; a glob whose fixed prefix is above or inside it counts);
|
|
304
|
-
- every task of a project whose `
|
|
302
|
+
- every task of a project whose `vx.config.*`
|
|
305
303
|
changed, or that holds a changed path no cached task of its declares
|
|
306
304
|
(vx cannot prove that path re-keys nothing), or that holds a changed
|
|
307
305
|
submodule or embedded repository (git reports it as one path, and no
|
|
308
306
|
task's globs can say whether they reach inside), or that a lockfile
|
|
309
|
-
claim
|
|
307
|
+
claim or a config import names (a dependency an install added or
|
|
308
|
+
removed reaches here as the lockfile's change);
|
|
310
309
|
- an uncached task when a changed path lies in its project or the
|
|
311
310
|
project is reached whole; a root file another task of its declares
|
|
312
311
|
(`workspaceFiles`) is no change there. A group seeds nothing.
|
|
@@ -354,7 +353,7 @@ change to it, and every project under it is selected, the project that
|
|
|
354
353
|
holds it included. A repository's own request to hide submodules from a diff
|
|
355
354
|
(`diff.ignoreSubmodules`, `submodule.<name>.ignore`) does not apply:
|
|
356
355
|
the key sees the change whatever git is told to show.
|
|
357
|
-
|
|
356
|
+
`.vx/projects.lock.json` is filtered out of the changed set — a `vx lock`
|
|
358
357
|
re-write never marks every project affected.
|
|
359
358
|
|
|
360
359
|
**A lockfile change selects everything.** The root lockfiles,
|
|
@@ -370,7 +369,9 @@ plugin CLAIMS (`VxPlugin.fingerprint`, e.g. `pnpm-lock.yaml` under
|
|
|
370
369
|
`@vzn/vx-lockfile`) is the exception on both sides: the key folds what the
|
|
371
370
|
plugin says per project, so `--affected` asks the plugin which projects
|
|
372
371
|
the change touches — given the bytes at the base ref and in the working
|
|
373
|
-
tree — and selects those; only a plugin that cannot tell widens
|
|
372
|
+
tree — and selects those; only a plugin that cannot tell widens, as
|
|
373
|
+
`@vzn/vx-lockfile` does for the install settings it claims (`.npmrc`,
|
|
374
|
+
`bunfig.toml`, `pnpm-workspace.yaml`, `.yarnrc.yml`).
|
|
374
375
|
|
|
375
376
|
**A dropped edge selects its dependent.** A package the change deleted
|
|
376
377
|
(its `package.json` was there at the base and is gone) is no project
|
|
@@ -393,8 +394,7 @@ too. "New" is judged at the base: no manifest there, or one with no
|
|
|
393
394
|
`name`.
|
|
394
395
|
|
|
395
396
|
**A workspace config change selects everything.** An edit to
|
|
396
|
-
|
|
397
|
-
every project: the `config` and `project` stages its plugins install
|
|
397
|
+
`.vx/workspace.*` selects every project: the `config` and `project` stages its plugins install
|
|
398
398
|
shape every resolved config, so the edit can re-key any task, and
|
|
399
399
|
selection cannot tell which. It is not in the fingerprint (a key moves
|
|
400
400
|
only when a stage's output does). A root file a plugin's stages read
|
|
@@ -470,33 +470,37 @@ than run with the args unheard.
|
|
|
470
470
|
|
|
471
471
|
### Flags
|
|
472
472
|
|
|
473
|
-
| Flag
|
|
474
|
-
|
|
|
475
|
-
| `--filter <pattern>`
|
|
476
|
-
| `--all`
|
|
477
|
-
| `--affected[=<base>]`
|
|
478
|
-
| `--exclude-dependencies[=<names>]`
|
|
479
|
-
| `--concurrency <n>`
|
|
480
|
-
| `--
|
|
481
|
-
| `--
|
|
482
|
-
| `--cache
|
|
483
|
-
| `--
|
|
484
|
-
| `--cache-
|
|
485
|
-
| `--
|
|
486
|
-
| `--
|
|
487
|
-
| `--
|
|
488
|
-
| `--
|
|
489
|
-
| `--
|
|
490
|
-
| `--
|
|
491
|
-
| `--
|
|
492
|
-
| `--
|
|
493
|
-
| `--
|
|
494
|
-
| `--
|
|
495
|
-
| `--
|
|
496
|
-
| `--
|
|
497
|
-
| `--
|
|
498
|
-
| `--
|
|
499
|
-
| `--
|
|
473
|
+
| Flag | Type | Default | Description |
|
|
474
|
+
| ------------------------------------------ | -------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
475
|
+
| `--filter <pattern>` | repeatable | (none) | pnpm-style filter DSL (see above). `--filter=<pattern>` form too. |
|
|
476
|
+
| `--all` | boolean | off | Select every project that declares the task. |
|
|
477
|
+
| `--affected[=<base>]` | optional value | off | Select the tasks a change since `<base>` reaches: the ones it touches and those whose `dependsOn` closure holds one (default `run.affectedBase`, else `origin/HEAD`, else `main` or `master`, else `HEAD~1`); candidates are `--filter "...[<base>]"`'s. |
|
|
478
|
+
| `--exclude-dependencies[=<names>]` | optional value | off | Drop `dependsOn` edges. No value = all (just the requested task runs; a group's members run as the group); comma-list = drop only those names, each of which some project must declare (a typo is refused with the nearest name, item 1026). An edge to a task the run schedules anyway (`--all` requests it) stays, so the two still run in order, and so does the order through a dropped task: with `gen` dropped from `test → gen → build` and `build` requested, `test` still waits for `build`. An empty `=` value is a parse error (ambiguous — see below). A dropped dependency does not run but is still keyed, so every key is the one a full run derives; a task keyed on one may hit but does not save (`caching.md` step 10). |
|
|
479
|
+
| `--concurrency <n>` | int or `<n>%` | `run.concurrency`, else the cores (cgroup-capped) | Maximum parallel tasks that EXECUTE; confirmed cache-hit restores are disk work and run on their own lane, up to twice this. `1` serializes both; `50%` is half the CPUs (rounded, never below 1; over 100% is allowed for I/O-bound work). `--concurrency=<n>` form too. |
|
|
480
|
+
| `--schedule <rule>` | value | `run.schedule`, else `most-work` | Which ready task starts first when more are ready than workers: `most-work`, `critical-path`, `direct-dependents`, `ready-order` (`docs/schema.md` § `run.schedule`). |
|
|
481
|
+
| `--branch <name>` | value | `VX_RUN_BRANCH`, else git's branch, then the CI provider's | The branch the run records in its history and forecasts from. No config key: a committed branch would mislabel every other branch's runs. |
|
|
482
|
+
| `--no-cache` | boolean | off | Disable caching entirely (no reads, no writes); output globs are NOT cleaned. |
|
|
483
|
+
| `--force` | boolean | off | Re-execute everything (skip cache reads) but still REFRESH the cache (writes stay on). Output globs are cleaned (so the saved snapshot is clean). |
|
|
484
|
+
| `--cache-policy <spec>` (`--cache-policy`) | value | `VX_CACHE_POLICY` / `cache.policy` | Per-layer read/write control. See below. An EMPTY spec (`--cache-policy=`) is a parse error — it applied nothing and left every axis on; pass `--no-cache` to disable them all. |
|
|
485
|
+
| `--cache-scope <name>` | value | `VX_CACHE_SCOPE` / workspace `cache.scope` | This run's `cache.scope` (`pr-123`), above `VX_CACHE_SCOPE`, `.vx/workspace`'s and `github()`'s. A name `cache.scope` would refuse is refused. `--cache-scope=<name>` form too. Never folded into a cache key. |
|
|
486
|
+
| `--cache-dir <path>` | value | `VX_CACHE_DIR` / `cache.dir` / `~/.vx/<id>/cache` | Cache directory override, resolved relative to cwd (absolute paths used as-is). Beats `VX_CACHE_DIR`, the workspace's `cache.dir` and the default; like them, the directory then holds the whole cache, shared through no store, for every cache the run opens, so the workspace's default dir is not created beside it. A per-run knob — never folded into a cache key. `--cache-dir=<path>` form too; the space form rejects a value starting with `-`. A directory this user cannot write into fails the run before any task with `cache directory <path> is not writable (EACCES: …)` — every run records its history there. |
|
|
487
|
+
| `--retries <n>` | value | `run.retries`, else `0` | Re-run a failed task up to `n` more times; never a persistent one. A task's own `exec.retries` wins (even an explicit `0`). Never affects cache keys. `--retries=<n>` form too. |
|
|
488
|
+
| `--continue[=<mode>]` | value | `deps-ok` | What a failed task takes down with it. `never` stops dispatch on the first failure; `deps-ok` (default) skips only its dependents; `always` (bare `--continue`) runs dependents anyway. See § Failure propagation. |
|
|
489
|
+
| `--timeout <duration>` | duration | `run.timeout`, else none | A task's time limit (`30s`, `10m`), for tasks without their own `exec.timeout`, which always wins. A runaway task is killed + `failed`. A bare number is refused naming the forms with units. Never affects cache keys. `--timeout=<duration>` form too. |
|
|
490
|
+
| `--kill-grace <duration>` | duration | `run.killGrace`, else `2s` | How long a task that ignores SIGTERM gets before SIGKILL: on a timeout, on a signal, and at the end-of-run shutdown of persistent tasks. |
|
|
491
|
+
| `--teardown-timeout <duration>` | duration | `run.teardownTimeout`, else `3s` | The bound on each plugin's end-of-run flush or teardown, and on its `telemetry()` setup, so a third party's I/O cannot hold the run. |
|
|
492
|
+
| `--ready-notice <duration>` | duration | `run.readyNotice`, else `10s` | How long a persistent task may take to match `readyWhen` before vx says once what it waits for. |
|
|
493
|
+
| `--output-logs <mode>` | value | `VX_RUN_OUTPUT_LOGS`, else flow-derived | `full` \| `errors-only` \| `hash-only` \| `none` — explicit output override. See § `--output-logs`. `--output-logs=<mode>` form too. |
|
|
494
|
+
| `--cache-download <mode>` | value | `cache.download`, else `all` | `all` \| `toplevel` \| `none` — where a REMOTELY-executed task's outputs land. `none` leaves them in the remote CAS and fetches lazily, only when a locally-placed task needs them. Never affects cache keys. See § `--cache-download`. `--cache-download=<mode>` form too. |
|
|
495
|
+
| `--verbosity <n>` | int (0+) | `0` | `1` or more prints a per-task table (groups left out) after the framed blocks, above the footer. `--verbosity=<n>` form too. |
|
|
496
|
+
| `--dry[=text\|json]` | optional value | off | Print the task graph + predicted cache hit/miss; skip execution. `VX_DEBUG_TIMING=1` prints the stage table here as it does for a run. |
|
|
497
|
+
| `--graph[=<path>]` | optional value | off | Emit Graphviz DOT (stdout if no path, its directory made if missing); skip execution. A path it cannot write is one line and exit 1. |
|
|
498
|
+
| `--format <fmt>` | value | `pretty` | `json` prints the `--summarize` document on stdout when the run ends and moves every other line to stderr ([Machine-readable output](#machine-readable-output)). `--format=<fmt>` form too. |
|
|
499
|
+
| `--summarize[=<path>]` | optional value | off | Write per-run JSON to `<cacheDir>/runs/<run_id>.json` (or the explicit path). |
|
|
500
|
+
| `--profile[=<path>]` | optional value | off (`profile.json` when set) | Write Chrome-trace JSON of the run's wallclock spans. |
|
|
501
|
+
| `--tag <k=v>` | repeatable | (none) | Label this invocation. Recorded on the run's `invocations` row so dashboards can filter runs. `--tag=k=v` form too. |
|
|
502
|
+
| `--report[=markdown]` | optional value | off | At the end of the run, print a markdown run report to stdout, above the footer. Only `markdown` is supported (`json` is reserved). |
|
|
503
|
+
| `--report-file <path>` | value | off | After the run, APPEND the same markdown report to `<path>`, making its directory as the other output paths do. Use this for `$GITHUB_STEP_SUMMARY` — redirecting stdout captures the whole run log too. `--report-file=<path>` form too. |
|
|
500
504
|
|
|
501
505
|
Mutual exclusion:
|
|
502
506
|
|
|
@@ -551,11 +555,13 @@ shell variable), never a path or task id. Use the `=` form for a literal
|
|
|
551
555
|
leading dash.
|
|
552
556
|
|
|
553
557
|
**Numeric flags take a plain decimal integer.** `--concurrency`,
|
|
554
|
-
`--
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
558
|
+
`--retries` and `--verbosity` reject hex (`0x10`), exponent (`1e3`),
|
|
559
|
+
fractional (`2.7`), signed (`+4`) and space-padded forms, plus anything
|
|
560
|
+
past `2^53` (it would parse to a number you did not type). These all
|
|
561
|
+
used to be silently reinterpreted — `--concurrency 0x10` ran 16
|
|
562
|
+
workers. A duration flag (`--timeout`, `--kill-grace`,
|
|
563
|
+
`--teardown-timeout`, `--ready-notice`) takes its unit (`500ms`, `30s`,
|
|
564
|
+
`10m`): a bare `500` is refused, naming both readings.
|
|
559
565
|
|
|
560
566
|
An empty `=` value means "no value" on four flags, which then act like
|
|
561
567
|
their bare forms: `--profile=` writes `profile.json`, `--summarize=`
|
|
@@ -563,26 +569,34 @@ writes `<cacheDir>/runs/<run_id>.json`, `--graph=` prints to stdout and
|
|
|
563
569
|
`--affected=` uses the default base. Every other flag refuses an empty
|
|
564
570
|
value: `--dry=`, `--report=`, `--continue=` and
|
|
565
571
|
`--exclude-dependencies=` as well as the ones with no bare form
|
|
566
|
-
(`--
|
|
572
|
+
(`--retries=`, `--timeout=`, `--cache-dir=`, `--filter=`, `--cache-policy=`).
|
|
567
573
|
|
|
568
|
-
#### Cache control: `--cache`, `--no-cache`, `--force`
|
|
574
|
+
#### Cache control: `--cache-policy`, `--no-cache`, `--force`
|
|
569
575
|
|
|
570
576
|
The cache has four independent axes — **localRead**, **localWrite**,
|
|
571
577
|
**remoteRead**, **remoteWrite** — and the three flags above resolve
|
|
572
578
|
them in this precedence order:
|
|
573
579
|
|
|
574
580
|
1. Start with every axis **on** (the default).
|
|
575
|
-
2. Apply each `--cache=<spec>` segment (the base), or, with no
|
|
576
|
-
`--cache`, the `
|
|
581
|
+
2. Apply each `--cache-policy=<spec>` segment (the base), or, with no
|
|
582
|
+
`--cache-policy`, the `VX_CACHE_POLICY` environment variable's spec, or, with
|
|
583
|
+
neither, `.vx/workspace`'s `cache.policy` (`docs/schema.md`).
|
|
577
584
|
3. If `--no-cache` was passed, force **all four off**.
|
|
578
585
|
4. If `--force` was passed, force **both reads off** (writes stay
|
|
579
|
-
whatever the base / `--cache` left them).
|
|
586
|
+
whatever the base / `--cache-policy` left them).
|
|
587
|
+
5. Off CI (`CI` unset, `0` or `false`), unless `--cache-policy`,
|
|
588
|
+
`VX_CACHE_POLICY` or `cache.policy` names the remote, force
|
|
589
|
+
**remoteWrite off**: a key holds only declared inputs, so a laptop's
|
|
590
|
+
write could replay on CI. `cache.scope` never changes an axis.
|
|
580
591
|
|
|
581
592
|
A spec that names a remote axis (`remote:r`, `remote:w`, `remote:rw`)
|
|
582
593
|
in a workspace whose plugins supply no remote layer gets one status
|
|
583
594
|
line saying so — the axes are inert without a layer to serve them, and
|
|
584
595
|
a CI job that believes it is filling a shared cache should be told.
|
|
585
596
|
|
|
597
|
+
Whatever the axes, a lookup asks the local cache first, then each
|
|
598
|
+
remote in the order the plugins declare them.
|
|
599
|
+
|
|
586
600
|
So `--no-cache` always wins over `--force`. The common cases:
|
|
587
601
|
|
|
588
602
|
- `--no-cache` → nothing reads, nothing writes, and declared output
|
|
@@ -593,29 +607,30 @@ So `--no-cache` always wins over `--force`. The common cases:
|
|
|
593
607
|
before each task so the saved snapshot is clean. This is the "rebuild
|
|
594
608
|
and refresh the cache" flag.
|
|
595
609
|
|
|
596
|
-
`--cache=<spec>` is a comma-separated list of `<layer>:<flags>`
|
|
610
|
+
`--cache-policy=<spec>` is a comma-separated list of `<layer>:<flags>`
|
|
597
611
|
segments. `layer` is `local` or `remote`; `flags` is any subset of `r`
|
|
598
612
|
(read) and `w` (write), order-independent and possibly empty. A
|
|
599
613
|
**mentioned** layer is set EXACTLY to its flags; an **unmentioned**
|
|
600
|
-
layer keeps its current value. Both `--cache=<spec>` and the space form
|
|
601
|
-
`--cache <spec>` are accepted.
|
|
614
|
+
layer keeps its current value. Both `--cache-policy=<spec>` and the space form
|
|
615
|
+
`--cache-policy <spec>` are accepted.
|
|
602
616
|
|
|
603
|
-
| Spec
|
|
604
|
-
|
|
|
605
|
-
| `--cache=local:rw,remote:r` | remote read-only (won't upload); local full |
|
|
606
|
-
| `--cache=local:r` | local read-only; remote untouched (still full) |
|
|
607
|
-
| `--cache=remote:` | remote fully off; local untouched |
|
|
608
|
-
| `--cache=local:,remote:rw` | don't touch the local cache, but still upload to remote |
|
|
617
|
+
| Spec | Effect |
|
|
618
|
+
| ---------------------------------- | ------------------------------------------------------- |
|
|
619
|
+
| `--cache-policy=local:rw,remote:r` | remote read-only (won't upload); local full |
|
|
620
|
+
| `--cache-policy=local:r` | local read-only; remote untouched (still full) |
|
|
621
|
+
| `--cache-policy=remote:` | remote fully off; local untouched |
|
|
622
|
+
| `--cache-policy=local:,remote:rw` | don't touch the local cache, but still upload to remote |
|
|
609
623
|
|
|
610
624
|
`local:` means "don't serve hits out of the pre-existing local cache" —
|
|
611
625
|
a remote hit is still delivered (the artifact has to land on disk to be
|
|
612
626
|
extracted), it just never short-circuits the remote read.
|
|
613
627
|
|
|
614
628
|
Combine with `--force` for "re-execute and refresh only the remote":
|
|
615
|
-
`--cache=local: --force` (local off, reads off, remote write-only).
|
|
629
|
+
`--cache-policy=local: --force` (local off, reads off, remote write-only).
|
|
616
630
|
|
|
617
631
|
Invalid layers/flags are a parse error
|
|
618
|
-
(
|
|
632
|
+
(`--cache-policy: invalid layer 'disk'`, `--cache-policy: invalid flag 'x'`, …);
|
|
633
|
+
`VX_CACHE_POLICY` and `cache.policy` say their own name instead.
|
|
619
634
|
|
|
620
635
|
### Output
|
|
621
636
|
|
|
@@ -731,7 +746,7 @@ tracks the run live. Top to bottom:
|
|
|
731
746
|
persistent tasks it depends on, until it — or, with several, the
|
|
732
747
|
first of them — exits (one kept only as a dependency that exits 0,
|
|
733
748
|
a daemon that forked and returned, does not end it); the rest still up are then torn
|
|
734
|
-
down (SIGTERM, `
|
|
749
|
+
down (SIGTERM, `run.killGrace`, SIGKILL), one status line names
|
|
735
750
|
the task and its code (`vx: app#dev exited with code 1; stopping 1
|
|
736
751
|
other persistent task`), and a non-zero exit makes the run exit 1
|
|
737
752
|
and is what `vx last` records.
|
|
@@ -744,15 +759,18 @@ other persistent task`), and a non-zero exit makes the run exit 1
|
|
|
744
759
|
each one's id (`app#dev │ Local: http://localhost:5173`), except
|
|
745
760
|
under `--output-logs errors-only`, `hash-only` or `none`; a single
|
|
746
761
|
requested server in the focused flow streams raw from its frame.
|
|
747
|
-
3. **Worker rows** — one per worker slot (
|
|
762
|
+
3. **Worker rows** — one per worker slot that has been busy (at most
|
|
748
763
|
`min(concurrency, 10)`), no glyph and no spinner: the live ticking
|
|
749
764
|
elapsed time leads (` 568ms running <id>`). A task stays in
|
|
750
|
-
its row for its whole life;
|
|
751
|
-
nothing ever jumps; overflow shows as `+k more`. A second in, a row
|
|
765
|
+
its row for its whole life; a row whose task ended holds its place
|
|
766
|
+
dimmed (`idle`), so nothing ever jumps; overflow shows as `+k more`. A second in, a row
|
|
752
767
|
says what waits on it: `critical path` (yellow) on the running task
|
|
753
768
|
the longest unfinished chain waits on, by the same p50s as the
|
|
754
769
|
forecast below and only while two or more run, and a dim
|
|
755
|
-
`blocks N`, its unfinished dependents direct and transitive.
|
|
770
|
+
`blocks N`, its unfinished dependents direct and transitive. Where
|
|
771
|
+
local history knows the task, its row also shows its usual duration
|
|
772
|
+
(its executed p50) in the cache column, fixed while the elapsed time
|
|
773
|
+
counts up toward it (`3.33s running ~5.2s solid-js#types`).
|
|
756
774
|
4. **The live summary section** — the SAME meters the final footer
|
|
757
775
|
prints (`tasks` + `cache` bars with legends, `time`), filling in as
|
|
758
776
|
the run progresses, under a bare `vx` wordmark rule. A second in,
|
|
@@ -762,7 +780,10 @@ other persistent task`), and a non-zero exit makes the run exit 1
|
|
|
762
780
|
workers, a running task's spent time taken off its own. It says
|
|
763
781
|
nothing when history knows none of what is left, or once a running
|
|
764
782
|
task is 25% past its p50. A run that ends inside the second never
|
|
765
|
-
reads history for it, and the final summary never shows it.
|
|
783
|
+
reads history for it, and the final summary never shows it. A p50
|
|
784
|
+
here is the median of the task's last five timings on this branch,
|
|
785
|
+
else on any branch; the timings are shared by every worktree of the
|
|
786
|
+
repo, so a new one has a forecast on its first run.
|
|
766
787
|
|
|
767
788
|
In a terminal known to read them, the region also sends zero-width
|
|
768
789
|
escapes: tab or taskbar progress (OSC 9;4: Ghostty, iTerm2 3.6+,
|
|
@@ -783,7 +804,8 @@ text is the task's output unchanged; a successful task's frame is never
|
|
|
783
804
|
linked.
|
|
784
805
|
|
|
785
806
|
The region is redrawn in place (cursor-up + clear; not a TUI — no
|
|
786
|
-
alternate screen) and erased before the final summary prints.
|
|
807
|
+
alternate screen) and erased before the final summary prints. On a
|
|
808
|
+
terminal shorter than the region, it shows only the rows that fit. In the
|
|
787
809
|
focused flow it only lives while dependencies run; it disappears for
|
|
788
810
|
good the moment the requested task starts streaming.
|
|
789
811
|
|
|
@@ -850,7 +872,7 @@ down with it:
|
|
|
850
872
|
The mode is the local scheduler's: a task a plugin executor runs
|
|
851
873
|
elsewhere is one dispatch, failed or not, like any other.
|
|
852
874
|
|
|
853
|
-
### `--download <mode>`
|
|
875
|
+
### `--cache-download <mode>`
|
|
854
876
|
|
|
855
877
|
Where a **remotely-executed** task's outputs land. `all` (default)
|
|
856
878
|
downloads every task's outputs to this machine as it completes —
|
|
@@ -864,7 +886,7 @@ bytes"). A CI job that only wants the verdict moves no output bytes at
|
|
|
864
886
|
all.
|
|
865
887
|
|
|
866
888
|
Locally-executed tasks always write in place and ignore the flag, and
|
|
867
|
-
`exec.remote: 'only'` still means never — `--download` cannot override
|
|
889
|
+
`exec.remote: 'only'` still means never — `--cache-download` cannot override
|
|
868
890
|
it in either direction. **It never affects a cache key**: transfer
|
|
869
891
|
tuning cannot change what a command produces.
|
|
870
892
|
|
|
@@ -976,8 +998,11 @@ $ vx run build lint --affected=main --dry
|
|
|
976
998
|
```
|
|
977
999
|
|
|
978
1000
|
`kind` is `input` (a changed `file` is a declared input), `project` (its
|
|
979
|
-
project changed and the task reads it whole: an uncached task, or
|
|
980
|
-
|
|
1001
|
+
project changed and the task reads it whole: an uncached task, or a changed
|
|
1002
|
+
`file` no cached task of the project declares, often an output git does not
|
|
1003
|
+
ignore; no `file` for a config import), `workspace` (a workspace-wide
|
|
1004
|
+
`file` changed: the workspace config, or a root file a plugin claims such
|
|
1005
|
+
as a lockfile, reached every task of the project),
|
|
981
1006
|
`package` (a `^` edge passes a changed `project` with no such task),
|
|
982
1007
|
`named` (`pkg#task` beside a bare task) or `selected` (another filter
|
|
983
1008
|
selected its project). `via` is the `dependsOn` chain, nearest first, to
|
|
@@ -1089,7 +1114,7 @@ outcome bucket or `total`, but they make the run red, so they are
|
|
|
1089
1114
|
listed separately and counted as `summary.aborted`.
|
|
1090
1115
|
|
|
1091
1116
|
**`noCache: true`** marks a task no cache answered for: it declares no
|
|
1092
|
-
`cache` block (it executes every run by design), or the run's `--cache`
|
|
1117
|
+
`cache` block (it executes every run by design), or the run's `--cache-policy`
|
|
1093
1118
|
read and wrote nothing (`--no-cache`), so a hit rate should leave it out
|
|
1094
1119
|
of the denominator. The key is present only when true; every other row is
|
|
1095
1120
|
unchanged. Its `hash` is still set: dependents fold it.
|
|
@@ -1192,14 +1217,14 @@ or a timeout's reason, `failed (timed out, exit 143)`, a persistent
|
|
|
1192
1217
|
task's `never ready: …`, or a sandboxed task's violation count, or
|
|
1193
1218
|
`skipped`, naming what blocked it: `skipped (blocked by lib#build)`);
|
|
1194
1219
|
`Cache` is its provenance (`miss` / `no-cache` for a task no cache
|
|
1195
|
-
answered for — no `cache` block, or a `--no-cache` / `--cache=local:` run —
|
|
1220
|
+
answered for — no `cache` block, or a `--no-cache` / `--cache-policy=local:` run —
|
|
1196
1221
|
which never consulted one / `local` / `remote` / `up-to-date` /
|
|
1197
1222
|
`—`). A stopped run (a Ctrl-C teardown) reads `## vx run — interrupted`,
|
|
1198
1223
|
and its tasks keep the terminal's words: one the signal killed is
|
|
1199
1224
|
`aborted` with its time so far, one the stop reached before it ran is
|
|
1200
|
-
`not run` with no time.
|
|
1201
|
-
|
|
1202
|
-
|
|
1225
|
+
`not run` with no time. Both count in the header's task total, listed
|
|
1226
|
+
as `1 aborted · 1 not run`, so a red report with no failing row still
|
|
1227
|
+
says why. Group tasks (no `exec`) get neither — they are not
|
|
1203
1228
|
work, and the header's counts match the terminal summary and
|
|
1204
1229
|
`--summarize` exactly.
|
|
1205
1230
|
|
|
@@ -1321,7 +1346,7 @@ sandbox run as usual, and the failure's dependents follow `--continue`.
|
|
|
1321
1346
|
## `vx watch`
|
|
1322
1347
|
|
|
1323
1348
|
```
|
|
1324
|
-
vx watch [OPTIONS] TASK [-- forwarded-args...]
|
|
1349
|
+
vx watch [OPTIONS] [--poll] TASK [-- forwarded-args...]
|
|
1325
1350
|
```
|
|
1326
1351
|
|
|
1327
1352
|
Run the named task, then re-run it on every filesystem change in the
|
|
@@ -1347,11 +1372,11 @@ run...` precedes it.
|
|
|
1347
1372
|
`dependsOn` edges included — is watched recursively, less any project
|
|
1348
1373
|
nested inside it, with a config or not (a root project's key leaves a nested
|
|
1349
1374
|
project's files out, so an edit there is no cycle; X-42, X-57). The workspace root is
|
|
1350
|
-
watched (non-recursively) for lockfile
|
|
1351
|
-
|
|
1375
|
+
watched (non-recursively) for a claimed file (a lockfile), for
|
|
1376
|
+
`.vx/projects.lock.json` and for an edit to `.vx/workspace.*` — the one root file that
|
|
1352
1377
|
shapes a run (plugins, `config` stage, concurrency) without being any
|
|
1353
1378
|
task's input; the cycle after it re-evaluates the file. A `.gitignore`
|
|
1354
|
-
there or in a
|
|
1379
|
+
there or in a project's parent directory is a cycle too: inputs are
|
|
1355
1380
|
gitignore-aware, so it changes what keys read (X-136). A file a
|
|
1356
1381
|
config imports by relative path from outside the watched projects (a
|
|
1357
1382
|
shared preset) is watched too, and its edit is a cycle that re-reads
|
|
@@ -1359,20 +1384,23 @@ run...` precedes it.
|
|
|
1359
1384
|
config's own edit does (WD-14). A file the workspace config imports is loaded once per
|
|
1360
1385
|
process, so its edit is named with the restart it needs rather than
|
|
1361
1386
|
run stale (item 949). An import whose file is gone stays on the list
|
|
1362
|
-
by the name it would have, so its return is a cycle (X-133). The
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1387
|
+
by the name it would have, so its return is a cycle (X-133). The
|
|
1388
|
+
projects are the lock's: a new `.vx/projects.lock.json` is the
|
|
1389
|
+
event that adds or drops one, and its directory is watched
|
|
1390
|
+
from before that cycle runs, so an edit made during it queues
|
|
1391
|
+
(X-138). A config added and not locked is no project yet. With no
|
|
1392
|
+
lock at start the first cycle fails saying so, and the watch waits
|
|
1393
|
+
for one. A project directory, or one a config imports from,
|
|
1394
|
+
removed and made again (a checkout that restores `packages/`) is
|
|
1395
|
+
heard from its nearest directory that exists and each one above it,
|
|
1396
|
+
and a watch whose
|
|
1397
|
+
directory was replaced is armed again (an OS watch holds the deleted
|
|
1398
|
+
one; `watch-recreated-dirs.test.ts`). There, a project directory
|
|
1399
|
+
that is still the same one starts no cycle: a task writing at its
|
|
1400
|
+
top level moved its mtime, which macOS reports to the parent
|
|
1401
|
+
(`watch-project-dir-write.test.ts`). Under `--filter` or
|
|
1402
|
+
`--affected` the scope is the one resolved at start, and a new
|
|
1403
|
+
project joins it only as a dependency of it. A task's own declared outputs (`cache.outputs.files`,
|
|
1376
1404
|
`outputs.workspaceFiles`; a plugin's `project` stage counts, as in
|
|
1377
1405
|
a run) never trigger a re-run — a cycle that writes `dist/` is not
|
|
1378
1406
|
an edit, nor is the `dist` directory itself coming and going (a
|
|
@@ -1410,7 +1438,10 @@ run...` precedes it.
|
|
|
1410
1438
|
starts a cycle, since no cache key can see it (a user's edit to one
|
|
1411
1439
|
still does in a project with a task that has a command and no cache,
|
|
1412
1440
|
which reads what it likes, item 947, outside any project nested
|
|
1413
|
-
in it
|
|
1441
|
+
in it; a write the last cycle made is not, read off the later of
|
|
1442
|
+
its mtime and ctime, so an output a cache hit restores with its
|
|
1443
|
+
saved mtime is the run's, and so is the restore's own `.vx-tmp-*`
|
|
1444
|
+
file, `watch-restored-output.test.ts`) — or declared an
|
|
1414
1445
|
output, or the loop re-runs on it; after three such cycles in a row
|
|
1415
1446
|
watch names the path and the remedy, once, and keeps going. A dev
|
|
1416
1447
|
server the last cycle left running counts as that cycle for as long
|
|
@@ -1427,7 +1458,8 @@ run...` precedes it.
|
|
|
1427
1458
|
at the root, a `coverage/` or `.turbo/` tree, an editor's scratch
|
|
1428
1459
|
file are dropped before the trigger — so `vx watch … > build.log`
|
|
1429
1460
|
inside the repo settles instead of feeding itself a cycle per line
|
|
1430
|
-
of its own output.
|
|
1461
|
+
of its own output. The opening line, `watching N projects · M tasks`
|
|
1462
|
+
(after the initial run's rows), is
|
|
1431
1463
|
printed only after every watcher has reported a probe file written
|
|
1432
1464
|
under it (`.vx-watch-probe`, re-written on a short backoff until its
|
|
1433
1465
|
event arrives, then removed): on macOS a directory watcher can return
|
|
@@ -1439,10 +1471,16 @@ run...` precedes it.
|
|
|
1439
1471
|
watcher whose probe is not heard within 2 s is replaced by a poller
|
|
1440
1472
|
that checks every 250 ms, with the notice `vx watch: <dir>: no OS
|
|
1441
1473
|
watch events within 2000 ms; polling every 250 ms instead`.
|
|
1442
|
-
3. **On change.**
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1474
|
+
3. **On change.** A cycle opens with a blank line and
|
|
1475
|
+
`changed <project> <relpath>`, plus ` (+N more)` when more paths
|
|
1476
|
+
changed, then its task rows as `vx run` prints them, with no footer.
|
|
1477
|
+
The named path is the first changed one that still exists, so an
|
|
1478
|
+
editor's temporary file renamed away names nothing; a deletion names
|
|
1479
|
+
the cycle when nothing else changed. The `watching` line is said
|
|
1480
|
+
once: a cycle that re-reads the watched set (a member came or went,
|
|
1481
|
+
the lock or a plugin changed) says nothing more; `VX_DEBUG_WATCH=1`
|
|
1482
|
+
says each arm on stderr as `vx watch: armed N projects`. Errors and
|
|
1483
|
+
refusals go to stderr. The
|
|
1446
1484
|
orchestrator is invoked again with the same options, save one: the
|
|
1447
1485
|
`--affected` diff is read once, for the initial run. A later cycle
|
|
1448
1486
|
runs the requested task in every project of the scope fixed at start,
|
|
@@ -1457,15 +1495,16 @@ watch events within 2000 ms; polling every 250 ms instead`.
|
|
|
1457
1495
|
|
|
1458
1496
|
Always ignored (no re-trigger):
|
|
1459
1497
|
|
|
1460
|
-
- `node_modules/`, `.git/`, `.vx/` anywhere in the path.
|
|
1498
|
+
- `node_modules/`, `.git/`, `.vx/cache/` anywhere in the path.
|
|
1461
1499
|
- A path git ignores (`git check-ignore`, asked once per debounce
|
|
1462
1500
|
window, never per event): invisible to every cache key — inputs are
|
|
1463
1501
|
tracked + untracked-not-ignored — so a cycle it started could change
|
|
1464
1502
|
nothing. A tracked file that matches a pattern is not ignored, by
|
|
1465
1503
|
git's own rule; outside a repository nothing is.
|
|
1466
|
-
- Files ending in
|
|
1467
|
-
-
|
|
1468
|
-
|
|
1504
|
+
- Files ending in `~` (editor swap files), and a restore's own
|
|
1505
|
+
`.vx-tmp-*` file.
|
|
1506
|
+
- The run's **resolved cache directory**, wherever it is. `.vx/cache/` covers the
|
|
1507
|
+
default, but `defineWorkspace({ cache: { dir } })` and `--cache-dir` can put it
|
|
1469
1508
|
anywhere; watching it would let vx's own cache writes trigger the next
|
|
1470
1509
|
cycle, which writes again — a loop that never settles.
|
|
1471
1510
|
|
|
@@ -1494,19 +1533,20 @@ It samples each file's later clock of mtime and ctime, so a replacement
|
|
|
1494
1533
|
that carries the old file's mtime (`cp -p`, `rsync -a`, a `mv`) is an
|
|
1495
1534
|
edit to it as it is to the OS watcher.
|
|
1496
1535
|
|
|
1497
|
-
###
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
`pnpm-
|
|
1501
|
-
|
|
1502
|
-
Watch mode hears
|
|
1503
|
-
(non-recursively)
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
(`vx lock`) is a cycle that
|
|
1536
|
+
### Claimed root file changes
|
|
1537
|
+
|
|
1538
|
+
A root file a plugin claims — a lockfile or install setting under
|
|
1539
|
+
`@vzn/vx-lockfile` (`pnpm-lock.yaml`, `bun.lock`, `.npmrc`, …),
|
|
1540
|
+
`turbo.json` under `turbo()` (item 961) — triggers a cycle, and so does
|
|
1541
|
+
`.vx/projects.lock.json`. Watch mode hears them because it watches the
|
|
1542
|
+
workspace root (non-recursively); a plugin added to the workspace
|
|
1543
|
+
config mid-watch counts from the cycle that loads it. The keys then
|
|
1544
|
+
decide which projects actually re-run: core keys no package file, so a
|
|
1545
|
+
lockfile edit re-keys only through its claimant
|
|
1546
|
+
([cache key derivation](./caching.md#cache-key-derivation)). Every
|
|
1547
|
+
cycle's configs come from `.vx/projects.lock.json`, so a config edit
|
|
1548
|
+
alone changes no command, and a re-lock (`vx lock`) is a cycle that
|
|
1549
|
+
re-reads the watched set (item 971).
|
|
1510
1550
|
|
|
1511
1551
|
### Constraints
|
|
1512
1552
|
|
|
@@ -1541,7 +1581,7 @@ re-runs itself and what it depends on.
|
|
|
1541
1581
|
- `0` — clean Ctrl+C / SIGTERM exit. A cycle in flight is torn down
|
|
1542
1582
|
first — its children, and the dev server held between cycles, get the
|
|
1543
1583
|
signal watch received (a Ctrl-C as SIGINT; a SIGTERM or SIGHUP as SIGTERM),
|
|
1544
|
-
`
|
|
1584
|
+
`run.killGrace` (2 s), then SIGKILL — and the process leaves only
|
|
1545
1585
|
once it has returned, so a CI cancellation never orphans a task.
|
|
1546
1586
|
Also `0`, with no watch, when `--affected` selects nothing: watch
|
|
1547
1587
|
prints `vx watch: nothing affected since <ref>` and exits as `vx run`
|
|
@@ -1560,32 +1600,34 @@ the watch loop — a failed cycle just prints the framed FAILED block
|
|
|
1560
1600
|
and waits for the next change. This matches `turbo watch` / `nx
|
|
1561
1601
|
watch`.
|
|
1562
1602
|
|
|
1563
|
-
## `vx cache
|
|
1603
|
+
## `vx cache clean`
|
|
1564
1604
|
|
|
1565
1605
|
Evict old or oversized cache entries. Operates on the entries and
|
|
1566
1606
|
`<hash>.tar.zst` artifacts of the store the workspace's index records
|
|
1567
1607
|
(`~/.vx/<id>/cache`, shared by every checkout of the repository), or of
|
|
1568
|
-
`<cacheDir>` itself when the workspace names one (`
|
|
1608
|
+
`<cacheDir>` itself when the workspace names one (`cache.dir`,
|
|
1569
1609
|
`VX_CACHE_DIR`, `--cache-dir`).
|
|
1570
1610
|
|
|
1571
|
-
`
|
|
1611
|
+
`clean` is the only `vx cache` subcommand: the statistics other runners
|
|
1572
1612
|
put under a `cache` verb — the directory, the entry count, the size —
|
|
1573
1613
|
are part of [`vx info`](#vx-info), and `vx cache stats`, `list`, `ls`,
|
|
1574
|
-
`
|
|
1575
|
-
`vx
|
|
1614
|
+
`gc` and their neighbours say so (`list` also names `vx last --list`;
|
|
1615
|
+
`clear`, `purge`, `reset` and `wipe` name a bare `vx cache clean`) rather than printing a bare "unknown subcommand".
|
|
1576
1616
|
|
|
1577
1617
|
```
|
|
1578
|
-
vx cache
|
|
1579
|
-
vx cache
|
|
1580
|
-
vx cache
|
|
1581
|
-
vx cache
|
|
1582
|
-
vx cache
|
|
1618
|
+
vx cache clean --older-than <duration> # Drop entries last used before now - duration (up to 1h slack).
|
|
1619
|
+
vx cache clean --max-size <size> # After age-based pruning, evict LRU until under <size>.
|
|
1620
|
+
vx cache clean ... --dry-run # Say what either policy would reap; delete nothing.
|
|
1621
|
+
vx cache clean ... --format json # { dryRun, evicted, bytesFreed, orphans, orphanBytes }
|
|
1622
|
+
vx cache clean ... --cache-dir <path> # The cache a run with the same flag uses.
|
|
1623
|
+
vx cache clean # No bound: clear the whole cache (below).
|
|
1624
|
+
vx cache clean --global # Also clear every other repository's store.
|
|
1583
1625
|
```
|
|
1584
1626
|
|
|
1585
|
-
|
|
1586
|
-
be combined: age-based eviction runs first, then LRU eviction if the
|
|
1627
|
+
With no bound it clears the cache ([below](#with-no-bound-clear-the-cache)).
|
|
1628
|
+
Both bounds may be combined: age-based eviction runs first, then LRU eviction if the
|
|
1587
1629
|
total is still over the size cap. The same policy runs unattended at
|
|
1588
|
-
the end of every run when
|
|
1630
|
+
the end of every run when `.vx/workspace.ts` declares
|
|
1589
1631
|
`cacheRetention: { olderThan, maxSize }` (same spellings; see
|
|
1590
1632
|
[schema](./schema.md)).
|
|
1591
1633
|
|
|
@@ -1601,7 +1643,7 @@ vx version sharing the store may still use it. A temp goes once it is an
|
|
|
1601
1643
|
hour old; nothing younger than an hour is taken. Only the names vx writes are taken:
|
|
1602
1644
|
`<hash>` is the key's 16 lowercase hex digits and the temp suffix is
|
|
1603
1645
|
the one a save makes, so a `release.tar.zst` beside the index in a
|
|
1604
|
-
`
|
|
1646
|
+
`cache.dir` you share is never touched (item 968). The converse, an
|
|
1605
1647
|
index row whose artifact is gone (deleted by hand), is dropped by the
|
|
1606
1648
|
same prune, and its bytes count toward neither `--max-size` nor the
|
|
1607
1649
|
evicted total: they are on no disk, and counting them evicted real
|
|
@@ -1609,7 +1651,7 @@ entries to make room for them (item 975). A row used within the hour
|
|
|
1609
1651
|
is kept until a later prune, but its bytes are left out all the same
|
|
1610
1652
|
(item 1081). The sweep runs on every prune, under
|
|
1611
1653
|
either flag, and reports separately from the policy's evictions. A
|
|
1612
|
-
workspace's `
|
|
1654
|
+
workspace's `cache.retention` runs it too, at the end of a run, at most
|
|
1613
1655
|
once an hour even when nothing the index holds is due (the policy sums
|
|
1614
1656
|
index rows, so orphans alone never make it due).
|
|
1615
1657
|
|
|
@@ -1626,23 +1668,24 @@ write `10G`, or `10B` when bytes really are the unit.
|
|
|
1626
1668
|
|
|
1627
1669
|
**A zero bound is rejected.** `--max-size 0` and `--older-than 0d` would
|
|
1628
1670
|
evict every entry in the cache, which is far more often a
|
|
1629
|
-
computed-to-zero retention than an intent
|
|
1630
|
-
|
|
1631
|
-
full prune). Delete the cache store `vx info` names when that is really
|
|
1632
|
-
what you want: the entries live there, not in the workspace's `.vx`.
|
|
1671
|
+
computed-to-zero retention than an intent. `vx cache clean` with no
|
|
1672
|
+
bound clears the whole cache when that is really what you want.
|
|
1633
1673
|
|
|
1634
1674
|
```
|
|
1635
|
-
$ vx cache
|
|
1636
|
-
|
|
1675
|
+
$ vx cache clean --older-than 30d
|
|
1676
|
+
✓ vx cache clean freed 1.3 GB
|
|
1677
|
+
|
|
1678
|
+
42 entries
|
|
1637
1679
|
|
|
1638
|
-
$ vx cache
|
|
1639
|
-
|
|
1680
|
+
$ vx cache clean --older-than 30d # after a SCHEMA_VERSION bump
|
|
1681
|
+
✓ vx cache clean freed 1.3 GB
|
|
1640
1682
|
|
|
1641
|
-
|
|
1642
|
-
Pruned 0 entries (0 B freed), reaped 42 orphaned artifacts (1.3 GB)
|
|
1683
|
+
42 orphaned artifacts
|
|
1643
1684
|
|
|
1644
|
-
$ vx cache
|
|
1645
|
-
|
|
1685
|
+
$ vx cache clean --older-than 30d --dry-run
|
|
1686
|
+
› vx cache clean would free 1.3 GB (dry run)
|
|
1687
|
+
|
|
1688
|
+
42 entries · 3 orphaned artifacts
|
|
1646
1689
|
```
|
|
1647
1690
|
|
|
1648
1691
|
`--dry-run` picks the victims under the same policy and counts the
|
|
@@ -1666,53 +1709,134 @@ artifact that vanishes before its restore is a miss, and the task runs
|
|
|
1666
1709
|
Exit codes:
|
|
1667
1710
|
|
|
1668
1711
|
- `0` — pruning completed (zero or more entries evicted). A workspace
|
|
1669
|
-
that never ran has no cache:
|
|
1670
|
-
nothing is created.
|
|
1712
|
+
that never ran has no cache: `› vx cache clean nothing to free`,
|
|
1713
|
+
and nothing is created.
|
|
1671
1714
|
- `1` — parse error, missing policy, or workspace-discovery error.
|
|
1672
1715
|
|
|
1673
|
-
`vx cache
|
|
1674
|
-
`defineWorkspace({
|
|
1716
|
+
`vx cache clean` resolves the workspace root from cwd and honors a
|
|
1717
|
+
`defineWorkspace({ cache: { dir } })` override — it prunes the same
|
|
1675
1718
|
directory a run would use. `--cache-dir <path>` (cwd-relative, as on
|
|
1676
1719
|
`vx run`) names another one, so a run that wrote elsewhere can be
|
|
1677
1720
|
pruned there.
|
|
1678
1721
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
`vx
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1722
|
+
### With no bound: clear the cache
|
|
1723
|
+
|
|
1724
|
+
`vx cache clean` with neither `--older-than` nor `--max-size` clears
|
|
1725
|
+
the whole cache: the cache directory (`.vx/cache`, or `cache.dir` /
|
|
1726
|
+
`--cache-dir`: the index and this workspace's run history, so `vx last`
|
|
1727
|
+
and `vx why` start over) and the store its index names, where the
|
|
1728
|
+
entries live (`~/.vx/<id>/cache`, shared by this repository's
|
|
1729
|
+
checkouts, so their entries go too). `--global` also clears every other
|
|
1730
|
+
repository's store under `~/.vx` (`~/.vx/*/cache`); it takes no bound
|
|
1731
|
+
and no `--cache-dir`. `.vx/workspace.ts`, the lock and `.vx/.gitignore`
|
|
1732
|
+
sit outside them and stay; a `cache.dir` that holds them (`.vx`, the
|
|
1733
|
+
workspace root) is refused. A `cache.dir` you named may be shared
|
|
1734
|
+
(`build/`, `~/.cache`), so there only what vx wrote goes (the index,
|
|
1735
|
+
its artifacts, run history, its `*` `.gitignore`); your own files stay,
|
|
1736
|
+
and the directory goes only if that empties it. A directory with no
|
|
1737
|
+
`cache.db` is not a vx cache and is left alone, and the store an index
|
|
1738
|
+
names goes only if it is one (`.vx/<id>/cache` holding a `store.db`): the
|
|
1739
|
+
index is a file in the checkout, which a commit or a restored CI cache
|
|
1740
|
+
could point at any directory. A workspace that never ran says
|
|
1741
|
+
`› vx cache clean nothing to free`. The next run misses every task once
|
|
1742
|
+
and saves again.
|
|
1743
|
+
|
|
1744
|
+
```
|
|
1745
|
+
$ vx cache clean
|
|
1746
|
+
✓ vx cache clean freed 1.4 GB
|
|
1747
|
+
|
|
1748
|
+
.vx/cache
|
|
1749
|
+
/home/me/.vx/3f2a9c01b4d5e6f7/cache
|
|
1750
|
+
|
|
1751
|
+
$ vx cache clean --global --dry-run
|
|
1752
|
+
› vx cache clean would free 3.1 GB (dry run)
|
|
1753
|
+
|
|
1754
|
+
.vx/cache
|
|
1755
|
+
/home/me/.vx/3f2a9c01b4d5e6f7/cache
|
|
1756
|
+
/home/me/.vx/9b8e7d6c5a4f3e2d/cache
|
|
1757
|
+
```
|
|
1758
|
+
|
|
1759
|
+
It waits for a `vx run` on this workspace to finish first (the run's
|
|
1760
|
+
lock), as a trim does. With `--format json` it prints
|
|
1761
|
+
`{ dryRun, cleared, bytesFreed }` (`schemas/cache.json`), `cleared`
|
|
1762
|
+
listing the directories.
|
|
1763
|
+
|
|
1764
|
+
## Runs read the lock
|
|
1765
|
+
|
|
1766
|
+
Every run, `vx watch` cycle, `--affected` selection and the picker takes
|
|
1767
|
+
its projects and their configs only from the committed
|
|
1768
|
+
`.vx/projects.lock.json` (owner, 2026-10-14). A run never walks for
|
|
1769
|
+
configs, never evaluates one, never checks the graph rules
|
|
1770
|
+
(`run.rules`) and never writes the lock: only `vx lock` does. With no
|
|
1771
|
+
lock, a run fails with `no lock: run \`vx lock\``. A run trusts the
|
|
1772
|
+
lock: it performs NO staleness check, so after adding or editing a
|
|
1773
|
+
config, run `vx lock`. In a pipeline, `vx lock --check` first re-evaluates every
|
|
1774
|
+
config and checks the rules.
|
|
1689
1775
|
|
|
1690
1776
|
## `vx lock`
|
|
1691
1777
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
vx
|
|
1778
|
+
Find every project, and freeze each one's **resolved** config into
|
|
1779
|
+
`.vx/projects.lock.json` at the workspace root. A project is a directory
|
|
1780
|
+
holding a `vx.config.*`, in any language: `vx lock` lists them with git
|
|
1781
|
+
(`git ls-files`, tracked and untracked, git's ignore rules applied; a
|
|
1782
|
+
nested repository's own configs too), or walks the tree outside git,
|
|
1783
|
+
and leaves out any under a nested `.vx/workspace.*`. Configs are
|
|
1784
|
+
programs; `vx lock` evaluates them in the current environment and
|
|
1785
|
+
stores the post-evaluation objects, by directory (version 3; a lock an
|
|
1786
|
+
earlier vx wrote is refused, "re-run `vx lock`"). A project is named by
|
|
1787
|
+
its config's `name`, else by a plugin's `discover` stage (vx-lockfile
|
|
1788
|
+
gives `package.json`'s `name`), else by its root-relative path (`//` for
|
|
1789
|
+
the root); two projects of one name are refused naming both
|
|
1790
|
+
directories. The plugin `discover` stage runs here as in a run, and may
|
|
1791
|
+
add directories (X-143). Before writing, it
|
|
1792
|
+
builds the graph of every task the workspace declares, `graph` stage
|
|
1793
|
+
included, and checks the graph rules (`run.rules`), which no run
|
|
1794
|
+
checks, `dependencies` (which projects a project may depend on,
|
|
1795
|
+
`VX_E_DEPENDENCY_RULE`) among them: a broken one prints `✗ vx lock lock not written`
|
|
1796
|
+
with the rule's refusal under it and writes nothing.
|
|
1797
|
+
|
|
1798
|
+
```
|
|
1799
|
+
vx lock # Evaluate all vx.config.* now; write .vx/projects.lock.json.
|
|
1800
|
+
vx lock --check # Audit: full re-evaluation vs the lock. Exit 1 on drift.
|
|
1701
1801
|
vx lock --check --format json # The audit as {upToDate, audited, notAudited, problems}.
|
|
1702
1802
|
```
|
|
1703
1803
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1804
|
+
**When to run it.** Run `vx lock` and commit `.vx/projects.lock.json` after
|
|
1805
|
+
editing any `vx.config.*` or `.vx/workspace.*`, after updating
|
|
1806
|
+
dependencies or plugins, and after changing a file a config imports.
|
|
1807
|
+
Runs read their configs from the lock, so until then they keep the old
|
|
1808
|
+
ones. `vx lock --check` as CI's first step fails a stale lock.
|
|
1809
|
+
The full workflow: the site's CI guide and `examples/ci`.
|
|
1810
|
+
|
|
1811
|
+
`.vx/projects.lock.json` is minified: it is the one file vx reads, and it loads
|
|
1812
|
+
faster that way (10,000 projects: 15.6 ms against 22.5 ms pretty). For
|
|
1813
|
+
review, `vx lock` also writes each project's resolved config (plugins
|
|
1814
|
+
applied), pretty, to `vx.lock.json` beside its `vx.config`; vx never reads
|
|
1815
|
+
it, it is no task's input, and `lock.reviewFiles: false`
|
|
1816
|
+
(`VX_LOCK_REVIEW_FILES`) turns it off. `vx lock` also writes
|
|
1817
|
+
`.vx/.gitattributes` (`projects.lock.json linguist-generated=true`) when it is
|
|
1818
|
+
missing, so GitHub collapses the machine lock in a diff; `vx init` does
|
|
1819
|
+
too, and an existing one is never edited.
|
|
1820
|
+
|
|
1821
|
+
Every run consumes it: configs come from the
|
|
1822
|
+
lock with no evaluation, no staleness check and no schema validation
|
|
1823
|
+
of its entries, which `vx lock` validated when it wrote them (frozen-env
|
|
1707
1824
|
semantics: env reads in a config keep their lock-time values; a
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1825
|
+
locked directory whose config file is gone (X-144) or that this user
|
|
1826
|
+
may not search (EACCES, named as such), or a missing lock, is a hard error; a config added since the lock is no project until
|
|
1827
|
+
`vx lock`). An entry edited by
|
|
1828
|
+
hand into a shape `vx lock` could not write fails the run with
|
|
1829
|
+
`.vx/projects.lock.json is invalid, run \`vx lock\``, checked only once
|
|
1830
|
+
something has crashed, so a normal run pays nothing for it.
|
|
1831
|
+
|
|
1832
|
+
`--check` is the audit: it re-evaluates every config in the current
|
|
1833
|
+
environment and `Bun.deepEquals`-compares it with the frozen object. A
|
|
1834
|
+
project that differs gets one message, whether its config, a file it
|
|
1835
|
+
imports or the environment it reads changed. A lock an earlier vx wrote
|
|
1836
|
+
fails the shape check and asks for `vx lock`. It also checks the graph rules over the configs as they
|
|
1837
|
+
evaluate now, and that each `vx.lock.json` is what its project resolves to
|
|
1838
|
+
here, and that none is left beside a deleted `vx.config` (delete that one
|
|
1839
|
+
yourself: vx never deletes a committed file), unless `lock.reviewFiles` is off. The CI recipe is `vx lock --check && vx run …`.
|
|
1716
1840
|
`--format json` prints the audit as one document
|
|
1717
1841
|
(`schemas/lock.json`, exit code unchanged; a missing lock is one of
|
|
1718
1842
|
its `problems`); it answers `--check` only.
|
|
@@ -1722,18 +1846,18 @@ Full design: `docs/design/config-lock-2026-06.md`. A lock written by a
|
|
|
1722
1846
|
A project with no `vx.config.*` — its tasks from a plugin's `project`
|
|
1723
1847
|
stage (`turbo()` from `@vzn/vx-migrate`), or none at all — has nothing
|
|
1724
1848
|
to freeze: the lock records nothing for it, `--check` does not audit
|
|
1725
|
-
it, and
|
|
1726
|
-
|
|
1727
|
-
vx
|
|
1728
|
-
|
|
1729
|
-
|
|
1849
|
+
it, and a run still loads its tasks live. Both verbs name such
|
|
1850
|
+
projects that have tasks (`0 project configs → .vx/projects.lock.json (no
|
|
1851
|
+
vx.config, so not locked: app, lib)`, five names then `and N more`),
|
|
1852
|
+
so an empty lock on a plugin-only workspace never reads like an audit.
|
|
1853
|
+
One with no task (core's default `build` aside) is not named.
|
|
1730
1854
|
|
|
1731
1855
|
The lock is committed, so `vx lock` refuses to write one that holds a
|
|
1732
1856
|
secret: a config that evaluated to the value of a secret-named variable
|
|
1733
1857
|
(or one a task lists in `exec.env.secret`), say
|
|
1734
1858
|
`` `--token ${process.env.API_TOKEN}` ``. It names each place
|
|
1735
1859
|
(`a: tasks.deploy.exec.command holds $API_TOKEN`) and writes nothing.
|
|
1736
|
-
Masking it instead would freeze a `***` that
|
|
1860
|
+
Masking it instead would freeze a `***` that every run runs. Let the
|
|
1737
1861
|
shell expand it: `$API_TOKEN` in the command, the name in
|
|
1738
1862
|
`exec.env.passThrough` (L-42).
|
|
1739
1863
|
|
|
@@ -1824,8 +1948,8 @@ One-time setup, per package, on npmjs.com → package → Settings →
|
|
|
1824
1948
|
Trusted Publisher → GitHub Actions: owner `vznjs`, repository `vx`,
|
|
1825
1949
|
workflow `npm.yml`, environment left blank. Do this for `@vzn/vx`,
|
|
1826
1950
|
`@vzn/vx-darwin-x64`, `@vzn/vx-darwin-arm64`, `@vzn/vx-linux-x64`,
|
|
1827
|
-
`@vzn/vx-linux-arm64` and the
|
|
1828
|
-
`@vzn/vx-lockfile`, `@vzn/vx-
|
|
1951
|
+
`@vzn/vx-linux-arm64` and the six plugins: `@vzn/vx-ci`,
|
|
1952
|
+
`@vzn/vx-lockfile`, `@vzn/vx-migrate`, `@vzn/vx-otel`,
|
|
1829
1953
|
`@vzn/vx-reapi` and `@vzn/vx-schedule-history`.
|
|
1830
1954
|
|
|
1831
1955
|
Each platform package carries `LICENSE` and `THIRD_PARTY_NOTICES.txt`:
|
|
@@ -1854,308 +1978,180 @@ direct publishing (the `E401 token is invalid` that stopped v0.0.17).
|
|
|
1854
1978
|
Every `uses:` in both workflows is pinned to a commit SHA with the
|
|
1855
1979
|
version in a comment; bump the SHA and the comment together.
|
|
1856
1980
|
|
|
1981
|
+
## The workspace root
|
|
1982
|
+
|
|
1983
|
+
Every verb works on one workspace: the directory whose `.vx` holds the
|
|
1984
|
+
nearest `.vx/workspace.ts` (or `.mts`, `.js`, `.mjs`, `.cts`, `.cjs`) from
|
|
1985
|
+
the working directory up. vx never looks above the git top-level (a
|
|
1986
|
+
worktree is its own; a submodule looks on into its superproject), and
|
|
1987
|
+
outside git it looks up to `/`. Nothing else marks the root: not a
|
|
1988
|
+
`package.json`, `pnpm-workspace.yaml` or a lockfile (owner, 2026-10-13).
|
|
1989
|
+
Without one, every verb but `vx init` stops with `VX_E_NO_WORKSPACE`;
|
|
1990
|
+
`vx init` writes one (`@vzn/vx-migrate` at the package manager's root,
|
|
1991
|
+
in a nested workspace the outer one does not list too). A project inside the
|
|
1992
|
+
root is a directory holding a `vx.config.*`, which `vx lock` finds
|
|
1993
|
+
(see [`vx lock`](#vx-lock)); a `discover` plugin adds others.
|
|
1994
|
+
|
|
1995
|
+
## Which vx runs
|
|
1996
|
+
|
|
1997
|
+
A global vx (installed with a package manager, or a compiled binary
|
|
1998
|
+
installed by hand) hands every call to the project's own `@vzn/vx` when
|
|
1999
|
+
there is one, so a project runs the version its lockfile pins (owner,
|
|
2000
|
+
2026-10-13). There is no opt-out. The workspace root's
|
|
2001
|
+
`node_modules/@vzn/vx` runs, through its
|
|
2002
|
+
`node_modules/.bin/vx`. One hop: the receiving vx sees `VX_HANDED_OFF`
|
|
2003
|
+
and does not look again. A vx never hands off to itself (the same
|
|
2004
|
+
package, a pnpm symlink included), nor to a `.bin/vx` another user owns
|
|
2005
|
+
(one planted above your checkout would run as you). An npm install's launcher does the
|
|
2006
|
+
walk in the shell, a test per level and no fork, and `exec`s the local
|
|
2007
|
+
vx; the compiled binary checks one path per level before it loads
|
|
2008
|
+
anything, then runs the local vx with its stdio, passes on a signal sent
|
|
2009
|
+
to it alone, and exits with its code. From source (`bun src/bin.ts`) vx
|
|
2010
|
+
never hands off.
|
|
2011
|
+
|
|
2012
|
+
`vx --version` says when it was handed the call (`vx 0.0.640
|
|
2013
|
+
(/repo/node_modules/@vzn/vx-linux-x64/vx, handed off from
|
|
2014
|
+
/usr/local/bin/vx)`), and `vx info` shows the file that ran and who handed off
|
|
2015
|
+
(`vxPath`, `handedOffFrom`).
|
|
2016
|
+
|
|
1857
2017
|
## `vx upgrade`
|
|
1858
2018
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
`
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2019
|
+
Brings vx to a newer release (`vx upgrade TAG` pins one, `v0.4.0` or
|
|
2020
|
+
`0.4.0`; default latest; a second tag is refused, not dropped), the way
|
|
2021
|
+
it was installed. Named `upgrade` per CLI convention (`bun upgrade`,
|
|
2022
|
+
`deno upgrade`). Refuses when running from source: use `git pull`.
|
|
2023
|
+
|
|
2024
|
+
**A compiled binary installed by hand** replaces itself. It reads its
|
|
2025
|
+
platform package (`@vzn/vx-<os>-<arch>`) from the npm registry and
|
|
2026
|
+
checks npm's registry signature over `<name>@<version>:<integrity>`
|
|
2027
|
+
against npm's signing key, pinned in vx (key id
|
|
2028
|
+
`SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U`), then the
|
|
2029
|
+
tarball's SHA-512 against that signed integrity, and only then extracts
|
|
2030
|
+
`package/vx`, stages it in a fresh directory beside the binary and
|
|
2031
|
+
renames it over. The host that serves the tarball cannot make that
|
|
2032
|
+
signature, so a swapped tarball, or a document with a swapped integrity,
|
|
2033
|
+
is refused: `the npm registry signature for … is not valid — refusing
|
|
2034
|
+
to install; nothing replaced`, or `the download did not match the
|
|
2035
|
+
integrity npm signed … nothing replaced`. A release signed with a key
|
|
2036
|
+
id vx does not pin says so: `… is signed with an npm key this vx does
|
|
2037
|
+
not know (…) — if npm rotated its signing key, this vx's installer
|
|
2038
|
+
needs updating`. A release with no signature or no integrity is refused
|
|
2039
|
+
before the download. Every refusal comes before anything is written.
|
|
2040
|
+
There is no fallback to GitHub's release digest: when the registry is
|
|
2041
|
+
unreachable, the upgrade is refused.
|
|
2042
|
+
|
|
2043
|
+
A host it cannot reach (no route, a proxy that is down) is one line,
|
|
2044
|
+
`could not reach registry.npmjs.org to read the release (…) — check the
|
|
2045
|
+
network or the proxy and re-run`, never a stack; so is a document cut
|
|
2046
|
+
after the headers or not JSON (a captive portal's page served with a
|
|
2047
|
+
200), and a tarball cut the same way. A binary whose directory this user
|
|
2048
|
+
cannot write (a root-owned `/usr/local/bin`) is refused before the
|
|
1885
2049
|
download: `cannot write to <dir> (EACCES), where this vx lives — nothing
|
|
1886
|
-
downloaded; re-run as a user who can (sudo vx upgrade), …`. The new
|
|
1887
|
-
keeps the old one's mode (and, as root, its owner), and must
|
|
1888
|
-
`--version` before the upgrade reports it installed: one that
|
|
1889
|
-
start on this machine
|
|
1890
|
-
for the previous vx, and the command exits 1 saying so
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2050
|
+
downloaded; re-run as a user who can (sudo vx upgrade), …`. The new
|
|
2051
|
+
binary keeps the old one's mode (and, as root, its owner), and must
|
|
2052
|
+
answer `--version` before the upgrade reports it installed: one that
|
|
2053
|
+
does not start on this machine, or does not answer within 10 s, is
|
|
2054
|
+
swapped back for the previous vx, and the command exits 1 saying so
|
|
2055
|
+
(item 1097): `✓ vx upgrade installed 0.0.2`. On the version it
|
|
2056
|
+
already is it says `› vx upgrade already at <version>` and downloads
|
|
2057
|
+
nothing.
|
|
2058
|
+
|
|
2059
|
+
**A vx a package manager installed** (the npm package's launcher runs
|
|
2060
|
+
`…/node_modules/@vzn/vx-<os>-<arch>/vx`) is upgraded by that package
|
|
2061
|
+
manager, never by a rename over its file, which the next install would
|
|
2062
|
+
undo. The path names the install:
|
|
2063
|
+
|
|
2064
|
+
| Install | Detected by | Runs |
|
|
2065
|
+
| ---------------------- | ------------------------------------- | ----------------------------- |
|
|
2066
|
+
| npm global | `<prefix>/lib/node_modules` | `npm install -g @vzn/vx@<v>` |
|
|
2067
|
+
| pnpm global | `…/pnpm/global/…` | `pnpm add -g @vzn/vx@<v>` |
|
|
2068
|
+
| bun global | `~/.bun/install/global` | `bun add -g @vzn/vx@<v>` |
|
|
2069
|
+
| yarn global | `…/yarn/global/…` | `yarn global add @vzn/vx@<v>` |
|
|
2070
|
+
| a project's dependency | the package.json that lists `@vzn/vx` | its manager's add, same field |
|
|
2071
|
+
|
|
2072
|
+
A project's package.json is the tree's root, or for a hoisted workspace
|
|
2073
|
+
the nearest one above the current directory that lists `@vzn/vx`. Its
|
|
2074
|
+
manager is its lockfile (`pnpm-lock.yaml`, `bun.lock`, `yarn.lock`,
|
|
2075
|
+
`package-lock.json`), else `packageManager`, else npm; the field it is
|
|
2076
|
+
listed in is kept (`--save-dev`, `--dev`, …), and pnpm at a workspace
|
|
2077
|
+
root adds `--workspace-root`. The lockfile is updated by that manager.
|
|
2078
|
+
Run inside a project that installs `@vzn/vx`, a global vx hands
|
|
2079
|
+
`vx upgrade` to the project's own (§ Which vx runs), so the project's
|
|
2080
|
+
dependency is what moves.
|
|
2081
|
+
vx prints what it runs, runs it there, and exits with an error if it
|
|
2082
|
+
fails:
|
|
2083
|
+
|
|
2084
|
+
```
|
|
2085
|
+
› vx upgrade 0.0.1 → 0.0.2
|
|
2086
|
+
|
|
2087
|
+
bun add @vzn/vx@0.0.2 in /repo
|
|
2088
|
+
```
|
|
2089
|
+
|
|
2090
|
+
vx never installs Node or a package manager: one not on `PATH` is refused with the command to run. A vx run from an npx or
|
|
2091
|
+
bunx cache has nothing installed to upgrade, and says to ask for
|
|
2092
|
+
`@vzn/vx@latest` instead.
|
|
2093
|
+
|
|
2094
|
+
Exit codes: `0` installed, or already at that version; `1` anything
|
|
2095
|
+
refused or swapped back: from source, a cache install, a host it cannot
|
|
2096
|
+
reach, a directory it cannot write, a signature or integrity that does
|
|
2097
|
+
not verify, a binary that does not start, a package manager that failed
|
|
2098
|
+
or is missing.
|
|
1900
2099
|
|
|
1901
2100
|
## `vx init`
|
|
1902
2101
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
`
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
`
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
and
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
named
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
`
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
package.json scripts are `init`'s. A workspace with no scripts at
|
|
1950
|
-
all still gets the workspace file, a printed example config, and the
|
|
1951
|
-
next command to run. A root
|
|
1952
|
-
`package.json` with no `workspaces` field is single-project mode, and
|
|
1953
|
-
when `packages/*/package.json` files sit below it unreached, both
|
|
1954
|
-
`init` and a run that finds no config say so instead ("package.json
|
|
1955
|
-
declares no workspaces … Add "workspaces": ["packages/*"] to
|
|
1956
|
-
package.json and re-run") rather than "no scripts" or "run vx init". A
|
|
1957
|
-
workspace whose globs match no `package.json` gets a note naming them
|
|
1958
|
-
(M-42); a nameless member, or two sharing a name, is matched and
|
|
1959
|
-
named on its own line instead (M-46). When such skipped scripts are all there
|
|
1960
|
-
is, the report opens `no package.json scripts became tasks.` rather than
|
|
1961
|
-
saying there are none (M-59). When `pnpm-workspace.yaml` lists the
|
|
1962
|
-
members and the root `package.json`'s `workspaces` lists other globs,
|
|
1963
|
-
the note names the unread list (M-51): pnpm reads only the yaml, while
|
|
1964
|
-
bun, npm and yarn read `package.json`. With
|
|
1965
|
-
no `package.json` here or above, `init` says to create one (`bun init` or
|
|
1966
|
-
`npm init -y`) first. Every
|
|
1967
|
-
generated config is typed for the editor through
|
|
1968
|
-
`import type { ProjectConfig } from '@vzn/vx/config'` and `satisfies
|
|
1969
|
-
ProjectConfig` — a type-only import Bun erases, so the file loads in a
|
|
1970
|
-
workspace that runs the `vx` binary without the package installed. The
|
|
1971
|
-
workspace file takes the same form (`satisfies WorkspaceConfig`). A
|
|
1972
|
-
runtime `import { defineWorkspace } from '@vzn/vx'` costs nothing extra
|
|
1973
|
-
since 2026-09-10: the running `vx` serves its own core to that
|
|
1974
|
-
specifier (bin.ts registers the alias), so a config or a plugin package
|
|
1975
|
-
never loads a second copy, and a workspace that runs the binary needs
|
|
1976
|
-
no `@vzn/vx` installed for it — the type-only form is still what the
|
|
1977
|
-
scaffold writes, because it types the same with no import to resolve
|
|
1978
|
-
in an editor without the package. A workspace that imports a PLUGIN
|
|
1979
|
-
package at runtime without having installed it is told so, and to
|
|
1980
|
-
install its dependencies. `--mjs` writes the same objects as `vx.config.mjs` and
|
|
1981
|
-
`vx.workspace.mjs`, with no type import and no `satisfies`: a package whose
|
|
1982
|
-
own `tsconfig` includes every `.ts` under it compiles a `vx.config.ts`
|
|
1983
|
-
into its dist (TanStack/query's `tsc --build`, 2026-09-11), and an
|
|
1984
|
-
`.mjs` is outside that include. `@vzn/vx-migrate` takes the same flag.
|
|
1985
|
-
A file it writes that `.gitignore` drops (vueuse ignores
|
|
1986
|
-
`packages/**/*.mjs`) gets a `note:` naming it and the `!vx.config.mjs`
|
|
1987
|
-
line that keeps it, since a commit would leave it behind unnoticed.
|
|
1988
|
-
|
|
1989
|
-
Each script becomes a task with its command verbatim. `build` gets
|
|
1990
|
-
`dependsOn: ['^build']` (a `build` that only delegates, `pnpm run
|
|
1991
|
-
compile`, is a group, and the edge goes on the script that does the
|
|
1992
|
-
work) and **no cache block** — under a
|
|
1993
|
-
`TODO(vx-migrate)` showing the block to add with the package's real
|
|
1994
|
-
inputs and outputs, and the same block commented as the task's first
|
|
1995
|
-
line: set the real globs and delete the `// `. A task without a cache block always runs; a block
|
|
1996
|
-
with EMPTY outputs is not "uncached" but a no-output task that hits on
|
|
1997
|
-
unchanged inputs and skips the build with nothing to restore (a deleted
|
|
1998
|
-
`dist` stays deleted under a green `up-to-date` run — what `init`
|
|
1999
|
-
generated until 2026-09-04), and a guessed `dist/**` would restore the
|
|
2000
|
-
wrong tree for every package that writes elsewhere. The block the TODO
|
|
2001
|
-
shows names `dist/**`, or the default output of the framework the command
|
|
2002
|
-
runs: `.next/**` minus `!.next/cache/**` for `next build`, `.output/**`
|
|
2003
|
-
for Nuxt, `build/**` for Remix, React Router, Create React App and
|
|
2004
|
-
Docusaurus, `public/**` for Gatsby, `storybook-static/**` for Storybook;
|
|
2005
|
-
for any other command, the directory it names with `--outDir` / `--out-dir`
|
|
2006
|
-
/ `-d`, else the ones it cleans first (`del-cli distribution`, `rimraf lib
|
|
2007
|
-
types`), unless it makes one again with `mkdir` (D-90).
|
|
2008
|
-
A package in a cycle of builds (nuxt's `@nuxt/nitro-server` devDepends
|
|
2009
|
-
on `nuxt`, which depends on it; pnpm sorts it away) gets, instead of
|
|
2010
|
-
`^build`, an edge to each build outside its cycle that `^build` would
|
|
2011
|
-
reach, and a TODO to order the cycle: `^build` there refuses the run.
|
|
2012
|
-
`test` / `typecheck` wait for
|
|
2013
|
-
`build` when the package has one (`lint` reads sources and gets no
|
|
2014
|
-
edge); `dev` / `start` / `serve` / `watch` /
|
|
2015
|
-
`preview` become persistent tasks, with a TODO to add `readyWhen` when
|
|
2016
|
-
another task depends on one, and so does a watcher: a `watch` segment in the script's name (`build:watch`),
|
|
2017
|
-
a `--watch` flag, `tsc -w` / `rollup -w`, or nodemon (D-40), and a server:
|
|
2018
|
-
`serve <dir>`, `http-server`, bare `vite`, a tool's `dev` / `serve` /
|
|
2019
|
-
`start` / `preview` verb (`next start`, `netlify dev`), or a script that
|
|
2020
|
-
runs such a script of its package by name (`cross-env X=1 pnpm start`) or
|
|
2021
|
-
through a runner (`run-p web api`, `concurrently "npm:web" "npm:api"`, D-113),
|
|
2022
|
-
outside quotes and not sent to the background with `&` (D-91), read past
|
|
2023
|
-
a launcher's `--package <name>` / `-p <name>` (`pnpm dlx --package
|
|
2024
|
-
netlify-cli netlify dev`, D-112). A
|
|
2025
|
-
script whose name no task may carry (`lint#fix`, `^up`; the schema's
|
|
2026
|
-
rule, item 1000) is left out with a TODO rather than written into a
|
|
2027
|
-
config every later command refuses, and a `__proto__` script is written
|
|
2028
|
-
as a computed key, since a literal `__proto__:` sets the prototype. An
|
|
2029
|
-
existing `vx.workspace.*` in any extension the loader reads (`.mts`
|
|
2030
|
-
included) is kept, and `--force` REPLACES a package's config of another
|
|
2031
|
-
extension (`replaced:` in the report) rather than writing a second one
|
|
2032
|
-
the loader would choose between by its order (item 1033). Without
|
|
2033
|
-
`--force`, `init` never overwrites: a package that already has a vx
|
|
2034
|
-
config keeps it, untouched, the rest get theirs, and the report lists
|
|
2035
|
-
the kept ones under `kept`, so a half-adopted workspace adopts the rest
|
|
2036
|
-
(M-52). `@vzn/vx-migrate` still refuses an existing config.
|
|
2037
|
-
|
|
2038
|
-
A missing `vx.workspace.*` is not an error. A run where no package has
|
|
2039
|
-
a config fails before any task, exit 1:
|
|
2040
|
-
``vx run: no projects declare task(s): build. No package declares a vx.config — run `vx init` to write one per package from its package.json scripts.``
|
|
2041
|
-
|
|
2042
|
-
Two npm conventions are mapped rather than copied, because copying them
|
|
2043
|
-
loses behaviour. `pre<x>` / `post<x>` hooks, which npm runs around `x`
|
|
2044
|
-
without being named, are folded into `x`'s command in that order, each
|
|
2045
|
-
in its own subshell, so the chain stops at the first that fails whatever
|
|
2046
|
-
a part holds (a `;`, an `exit`), and forwarded `--` args reach `x` alone,
|
|
2047
|
-
as npm hands them to the script and not its hooks (item 905), appended
|
|
2048
|
-
as text, so no part sees them as `$1` (X-60). The
|
|
2049
|
-
command is a small shell function, `vx_script`, around the three parts;
|
|
2050
|
-
it carries a TODO saying so; a `pre<x>` with no `x` stays a task of its own, and
|
|
2051
|
-
npm's lifecycle hooks (`prepack`, `prepublishOnly`, …) are never tasks.
|
|
2052
|
-
A member with no script to run (none, or only lifecycle hooks: a
|
|
2053
|
-
types-only or config package) gets no `vx.config.ts` and is named in
|
|
2054
|
-
the report; it is still a project, and a task declared in its own
|
|
2055
|
-
`vx.config.ts` runs. A package with no `build` script whose `prepack`, `prepublishOnly`,
|
|
2056
|
-
`prepublish` or `prepare` runs a builder (`bob build`, `tsc`, `tsup`, …)
|
|
2057
|
-
is named in the report, which says to add a `build` script running it
|
|
2058
|
-
(react-navigation's twelve packages, whose root `build` is `lerna run
|
|
2059
|
-
prepack`, mapped with no build at all).
|
|
2060
|
-
Where the package's manager runs no such hooks every `pre<x>` and
|
|
2061
|
-
`post<x>` is a task of its own: Yarn 2+ (the nearest `packageManager:
|
|
2062
|
-
yarn@2+`, or a Berry `yarn.lock`, D-31; a `packageManager` naming none of npm,
|
|
2063
|
-
pnpm, yarn or bun defers to the lockfile, D-96), npm under `ignore-scripts=true`
|
|
2064
|
-
in the `.npmrc` beside its lockfile, pnpm under
|
|
2065
|
-
`enable-pre-post-scripts=false` there or `enablePrePostScripts: false` in
|
|
2066
|
-
`pnpm-workspace.yaml` (D-33). Bun and Yarn 1 run them whatever those say.
|
|
2067
|
-
Under Yarn 2+ a segment's `run <script>`, Yarn's shell builtin, is written
|
|
2068
|
-
`yarn run <script>`: vx's shell has no `run` (D-92).
|
|
2069
|
-
A Yarn 2+ repo on Plug'n'Play (no `nodeLinker: node-modules` or `pnpm` in
|
|
2070
|
-
`.yarnrc.yml`) keeps its bins in `.pnp.cjs`, where vx's PATH finds none;
|
|
2071
|
-
the report says so and names `nodeLinker: node-modules` or `yarn exec
|
|
2072
|
-
'<command>'` (D-108).
|
|
2073
|
-
A script reading `$npm_package_version`, `$npm_package_name` or
|
|
2074
|
-
`$npm_lifecycle_event`, which every manager sets and vx does not, gets
|
|
2075
|
-
them under `exec.env.define`, the first two read from an imported
|
|
2076
|
-
`package.json` so a version bump reaches them; any other `$npm_*` it
|
|
2077
|
-
reads gets a TODO (D-34). Among several packages, a workspace root
|
|
2078
|
-
script that runs the members (`pnpm -r build`, `--filter`, `-C`, pnpm's `pn` alias
|
|
2079
|
-
included (D-97), Yarn's
|
|
2080
|
-
`--cwd` and `yarn workspace <name>`, npm's `--prefix`, npm's
|
|
2081
|
-
and Yarn's workspace flags, a `cd` into or above a member, turbo, nx, lerna, `vp run`, vx itself) is not mapped
|
|
2082
|
-
(a flag counts on the package manager, or after `node <bin> run`, and not on the program it
|
|
2083
|
-
runs: berry's `yarn node -r ./setup.ts` is node's `--require`, D-81; bun's
|
|
2084
|
-
`cd test && …` enters no member, D-83),
|
|
2085
|
-
nor is one that runs such a script by name (vite's `ci-docs`: `pnpm build &&
|
|
2086
|
-
pnpm docs-build`; through `run-s` / `run-p` / `npm-run-all` or `concurrently
|
|
2087
|
-
"npm:x"` too, lexical's `ci-check`, D-95), and neither is one whose name a member's task carries, so `--all` never runs
|
|
2088
|
-
a check twice (D-45). The rest check the whole repo (`lint: oxlint .`,
|
|
2089
|
-
`test: vitest`) and become the root's own tasks in a root vx.config, when
|
|
2090
|
-
the root has a `"name"` (vx skips a nameless root's config) and no config
|
|
2091
|
-
of its own; one already there, hand-written or from an earlier
|
|
2092
|
-
`vx init`, stays as written, `--force` included, and is listed under
|
|
2093
|
-
`kept`. The report names each script left out and why
|
|
2094
|
-
(a `pre` / `post` hook goes with its script, D-85), its examples of running
|
|
2095
|
-
the members spelled by the repo's manager (`--workspaces` under npm, `yarn
|
|
2096
|
-
workspaces foreach` under Yarn 2+), and says which;
|
|
2097
|
-
with nothing mapped it names the root whenever it has a script, a member
|
|
2098
|
-
or not (pnpm's root is not), and why: its scripts run the workspace, or
|
|
2099
|
-
share a member's task name (listed), or both; it tells a root with no `"name"` to add one
|
|
2100
|
-
first (vuejs/core), naming the scripts that would then map (react, D-87), and a root whose
|
|
2101
|
-
`"name"` a member also carries to rename it, since a root config would make the
|
|
2102
|
-
workspace refuse every run for the duplicate (insomnia, D-129). A single-package repo's root is its project and maps.
|
|
2103
|
-
A lone root beside a `lerna.json` (Lerna-classic: packages listed there, not
|
|
2104
|
-
in `workspaces`) gets a note naming the `workspaces` globs to add, Lerna's
|
|
2105
|
-
`packages/*` default when it lists none (D-111).
|
|
2106
|
-
A member whose package.json has no `name` is no project; one with a
|
|
2107
|
-
script is named in the report, to be given a name (remix's
|
|
2108
|
-
`packages/component/bench`, D-106).
|
|
2109
|
-
A member's script that runs another member's work (`pnpm -C ../pinia
|
|
2110
|
-
run build`, `yarn workspace <name>`, `-r` / `--filter`, a `cd` or `-C`
|
|
2111
|
-
/ `--cwd` / `--prefix` into another member) keeps its command and gets a
|
|
2112
|
-
TODO naming that part: the graph runs that member's task once, so name it
|
|
2113
|
-
under `dependsOn` and drop it from the command (pinia). A directory inside
|
|
2114
|
-
the member or the root is no other member.
|
|
2115
|
-
A script that is nothing but `npm run <other>` (`pnpm <other>`, `yarn
|
|
2116
|
-
<other>`, `bun run <other>`, `npm test`, `npm start`) becomes a **group**
|
|
2117
|
-
over `<other>` — `dependsOn` and no command — so the graph runs and
|
|
2118
|
-
caches the target instead of a package-manager subprocess it cannot
|
|
2119
|
-
see. When `build` is such a group, the task it reaches that runs a
|
|
2120
|
-
command takes `^build` and the cache TODO, since a group has nothing
|
|
2121
|
-
to cache; it and the groups on the way drop the wait on `build` a
|
|
2122
|
-
`typecheck` or `test` would carry, since they are the build (D-16). Bare, a package manager's own command is not a script: `bun test`
|
|
2123
|
-
is Bun's test runner, `bun build` its bundler, `pnpm install` and `yarn
|
|
2124
|
-
add` the managers' verbs, so each stays a command (item 908), and so do
|
|
2125
|
-
the names each manager reserves or hands on: `pnpm docs` and `pnpm
|
|
2126
|
-
version` (npm's), `bun deploy`, `yarn check` (D-32); `pnpm test`, `yarn
|
|
2127
|
-
test` and `bun lint` do run the script. Arguments, flags or a `&&`
|
|
2128
|
-
chain make it a real command again and
|
|
2129
|
-
it is left verbatim, and so is one whose target becomes no task (a
|
|
2130
|
-
lifecycle script, or a hook folded into another script): a group over
|
|
2131
|
-
it would name a task nothing defines (D-12). A verbatim chain whose
|
|
2132
|
-
parts run this package's own scripts (`check: pnpm run build && pnpm run
|
|
2133
|
-
lint`) gets a TODO naming those parts: each ran again inside the command,
|
|
2134
|
-
beside its own task, so `vx run check` built twice (M-41). So does a
|
|
2135
|
-
part that runs them through `run-s`, `run-p` or `npm-run-all`
|
|
2136
|
-
(`build: run-p build:*`), unless the task is persistent (M-48). Its
|
|
2137
|
-
order may matter, so it is not made a group.
|
|
2138
|
-
|
|
2139
|
-
The report lists each TODO once per reason: tasks that share one are
|
|
2140
|
-
named together (the first five, then a count; the files carry each),
|
|
2141
|
-
and when no task caches it says uncommenting a TODO task's cache line
|
|
2142
|
-
makes the second run a hit. Its `next:` line is a command the user can type: the
|
|
2143
|
-
runner that started vx (`npx`, `pnpm`, `yarn`, `bunx`, read from
|
|
2144
|
-
`npm_config_user_agent`) with the installed `vx` bin, else the
|
|
2145
|
-
`@vzn/vx` package (`npx` under Yarn 1, which has no `dlx`); with no
|
|
2146
|
-
runner, a bare `vx`. Outside a git work tree it starts with
|
|
2147
|
-
`git init`, and with no script mapped with declaring a task, since the
|
|
2148
|
-
run would refuse without either. It runs `build`, else the first task that
|
|
2149
|
-
neither serves (`persistent`) nor changes the repo (`clean`, `release`,
|
|
2150
|
-
`publish`, `deploy`, `version`, `format`, `fix`, …), else the first task
|
|
2151
|
-
(react-navigation was told `vx run clean --all`).
|
|
2102
|
+
`vx init` reads no `package.json` (M, 2026-10-14). When the directory it
|
|
2103
|
+
runs in, or one above it up to the git top-level, holds a
|
|
2104
|
+
`package.json`, `pnpm-workspace.yaml`, `turbo.json`, `turbo.jsonc` or
|
|
2105
|
+
`nx.json`, it runs `@vzn/vx-migrate` there, so `vx init` is the one
|
|
2106
|
+
command (owner, 2026-10-08): the installed one (the nearest
|
|
2107
|
+
`node_modules/@vzn/vx-migrate` up from the working directory), else
|
|
2108
|
+
this vx's version through `bun x` (`BUN_BE_BUN=1` makes the compiled
|
|
2109
|
+
binary that runtime). `--dry`, `--format`, `--force`, `--mjs`,
|
|
2110
|
+
`--native` and `--keep` pass through, and the exit is vx-migrate's.
|
|
2111
|
+
vx-migrate maps a Turbo, Nx or Vite Task repo, else the `package.json`
|
|
2112
|
+
scripts, one `vx.config.ts` per package; writes the workspace file with
|
|
2113
|
+
the plugins the repo calls for (the lockfile's, a remote cache the repo
|
|
2114
|
+
shows); installs them; and ends with `vx lock`. Its README holds the
|
|
2115
|
+
rules; [`bunx @vzn/vx-migrate`](#bunx-vznvx-migrate) below sums them up.
|
|
2116
|
+
|
|
2117
|
+
With none of those files, it writes a workspace whose one project is
|
|
2118
|
+
the root, at the nearest workspace root up from the working directory,
|
|
2119
|
+
else there:
|
|
2120
|
+
|
|
2121
|
+
```ts
|
|
2122
|
+
// .vx/workspace.ts
|
|
2123
|
+
import { defineWorkspace } from '@vzn/vx'
|
|
2124
|
+
|
|
2125
|
+
export default defineWorkspace({ plugins: [] })
|
|
2126
|
+
```
|
|
2127
|
+
|
|
2128
|
+
```ts
|
|
2129
|
+
// vx.config.ts
|
|
2130
|
+
import { defineProject } from '@vzn/vx'
|
|
2131
|
+
|
|
2132
|
+
export default defineProject({ tasks: {} })
|
|
2133
|
+
```
|
|
2134
|
+
|
|
2135
|
+
plus `.vx/.gitignore` and `.vx/.gitattributes`. It then runs `vx lock`'s
|
|
2136
|
+
step, and `next:` says to declare a task and run `vx lock && vx run
|
|
2137
|
+
<task>`, preceded by `git init` outside a git work tree. The `@vzn/vx`
|
|
2138
|
+
import needs no install: the running vx serves its own core to that
|
|
2139
|
+
specifier (bin.ts registers the alias). A file already there is kept
|
|
2140
|
+
and named under `kept`; `--force` replaces it, a file of another
|
|
2141
|
+
extension included (`replaced`). `--mjs` writes `.vx/workspace.mjs` and
|
|
2142
|
+
`vx.config.mjs`. `--dry` prints the files and writes nothing;
|
|
2143
|
+
`--dry --format json` prints the plan as one JSON document
|
|
2144
|
+
(`schemas/init.json`): `files` (each path and its whole text), `kept`,
|
|
2145
|
+
`replaced`, `todos`, `notes` and `next`. `--native` and `--keep` answer
|
|
2146
|
+
a Turbo or Nx repo's question, so they are refused here. `--format json`
|
|
2147
|
+
without `--dry`, or with `--plugin`, is refused (`VX_E_USAGE`).
|
|
2152
2148
|
|
|
2153
2149
|
`vx init --plugin <seam>` writes a plugin instead: `plugins/<seam>.ts`,
|
|
2154
2150
|
a small runnable plugin for that seam (`executor`, `cache`,
|
|
2155
2151
|
`telemetry`, `schedule`, `admit`, `commands`, `project`, `graph`,
|
|
2156
2152
|
`key`), and `plugins/<seam>.test.ts`, which drives it through `run()`
|
|
2157
2153
|
(`bun test`, with `@vzn/vx` installed). It prints the line that
|
|
2158
|
-
declares it in the workspace file a run reads (
|
|
2154
|
+
declares it in the workspace file a run reads (`.vx/workspace.ts` when
|
|
2159
2155
|
there is none). The templates are TypeScript, so `--mjs` with
|
|
2160
2156
|
`--plugin` is refused. An existing file is refused without
|
|
2161
2157
|
`--force`; `--dry` names the files and writes nothing. The two files are
|
|
@@ -2163,25 +2159,35 @@ there is none). The templates are TypeScript, so `--mjs` with
|
|
|
2163
2159
|
core (`src/cli/plugin-templates.ts`) and held equal by
|
|
2164
2160
|
`tests/plugin-templates.unsafe.test.ts` (H-21).
|
|
2165
2161
|
|
|
2166
|
-
Exit codes: `0` the files written (or, with `--dry`, printed); `1`
|
|
2167
|
-
|
|
2168
|
-
|
|
2162
|
+
Exit codes: `0` the files written (or, with `--dry`, printed); `1` a
|
|
2163
|
+
refused flag, a parse error (`--mjs` with `--plugin` among them), or a
|
|
2164
|
+
plugin file already there without `--force`. Handed to
|
|
2165
|
+
`@vzn/vx-migrate`, the exit is its own.
|
|
2169
2166
|
|
|
2170
|
-
## `vx
|
|
2167
|
+
## `bunx @vzn/vx-migrate`
|
|
2171
2168
|
|
|
2172
|
-
|
|
2173
|
-
|
|
2169
|
+
Mapping a repo's own config to vx is `@vzn/vx-migrate`, its own
|
|
2170
|
+
package, run before a workspace file exists (it writes one); `vx init`
|
|
2171
|
+
runs it in any repo with a `package.json` —
|
|
2174
2172
|
|
|
2175
2173
|
```
|
|
2176
|
-
bunx @vzn/vx-migrate # turbo.json, an Nx graph
|
|
2174
|
+
bunx @vzn/vx-migrate # turbo.json, an Nx graph, vite-plus run.tasks, else package.json scripts → vx.config.ts
|
|
2177
2175
|
bunx @vzn/vx-migrate --dry # print the generated files instead of writing
|
|
2178
2176
|
bunx @vzn/vx-migrate --force # overwrite existing vx.config.* / vx-preset.ts
|
|
2179
|
-
bunx @vzn/vx-migrate --from nx # disambiguate when
|
|
2180
|
-
```
|
|
2181
|
-
|
|
2182
|
-
—
|
|
2183
|
-
|
|
2184
|
-
|
|
2177
|
+
bunx @vzn/vx-migrate --from nx # disambiguate when several sources are checked in (turbo, nx, vite-task, scripts)
|
|
2178
|
+
```
|
|
2179
|
+
|
|
2180
|
+
— In a Turbo or Nx repo it asks a terminal native (a `vx.config.ts`
|
|
2181
|
+
per package, the default) or keep (a workspace file declaring `turbo()`
|
|
2182
|
+
or `nx()`, which read the repo's config live); `--native` or `--keep`
|
|
2183
|
+
answer it, and without a terminal it is native. Both declare
|
|
2184
|
+
`turboCache()` / `nxCache()` when the repo shows a remote cache
|
|
2185
|
+
(turbo.json's `remoteCache`, a `turbo link`ed `.turbo/config.json`, or
|
|
2186
|
+
a CI file setting `TURBO_TOKEN` / `NX_SELF_HOSTED_REMOTE_CACHE_SERVER`),
|
|
2187
|
+
and name Nx Cloud, whose wire vx does not speak. Elsewhere each
|
|
2188
|
+
package's `package.json` scripts become its tasks, and the workspace
|
|
2189
|
+
file declares the `@vzn/vx-lockfile` plugin for the root's lockfile.
|
|
2190
|
+
What the package writes goes through core's migration seam
|
|
2185
2191
|
(`applyMigration`, exported from `@vzn/vx`), which renders, guards
|
|
2186
2192
|
against overwriting, writes and reports. The mapping rules live in the
|
|
2187
2193
|
package's README.
|
|
@@ -2196,7 +2202,7 @@ none is dropped in silence. An Nx flag's camelCase spelling (`--nxBail`,
|
|
|
2196
2202
|
kebab-case row. A `[=<bool>]` row takes `=true` (the flag bare) and `=false` (left out), as Turbo's and Nx's parsers do. `vx run-many` and `vx affected` name the
|
|
2197
2203
|
`vx run` that does the same, and the other verbs a hand types from
|
|
2198
2204
|
either tool name what does it here: `graph` (`--graph`), `ls`
|
|
2199
|
-
(`vx show`), `query` (`--dry=json`), `reset` (`vx cache
|
|
2205
|
+
(`vx show`), `query` (`--dry=json`), `reset` (`vx cache clean`), and
|
|
2200
2206
|
`daemon`, `login`, `logout`, `link` and `unlink`, which vx has no use
|
|
2201
2207
|
for. Nx's `project:target` (`vx run web:build`,
|
|
2202
2208
|
when `web` declares `build`) is answered with `vx run web#build`: from
|
|
@@ -2222,8 +2228,8 @@ copy to the source.
|
|
|
2222
2228
|
| turbo | `--summarize[=<bool>]` | same | `--summarize[=<path>]` |
|
|
2223
2229
|
| turbo | `--output-logs=new-only` | refuse | use `--output-logs=full` (a hit replays its log) or `errors-only` |
|
|
2224
2230
|
| turbo | `--output-logs <v>` | same | `--output-logs full\|errors-only\|hash-only\|none` |
|
|
2225
|
-
| turbo | `--no-cache` | same | `--no-cache` reads nothing either; Turbo's (reads, no writes) is `--cache local:r,remote:r`
|
|
2226
|
-
| turbo | `--cache <v>` |
|
|
2231
|
+
| turbo | `--no-cache` | same | `--no-cache` reads nothing either; Turbo's (reads, no writes) is `--cache-policy local:r,remote:r` |
|
|
2232
|
+
| turbo | `--cache <v>` | alias | `--cache-policy`, the same spec (`local:rw,remote:r`) |
|
|
2227
2233
|
| turbo | `--cache-dir <v>` | same | `--cache-dir <path>` |
|
|
2228
2234
|
| turbo | `--profile` | same | `--profile[=<path>]` (Chrome trace) |
|
|
2229
2235
|
| turbo | `--only` | alias | `--exclude-dependencies` |
|
|
@@ -2234,13 +2240,13 @@ copy to the source.
|
|
|
2234
2240
|
| turbo | `--no-update-notifier` | refuse | vx prints no update notice: drop it |
|
|
2235
2241
|
| turbo | `--skip-infer` | refuse | vx runs the binary it is: drop it |
|
|
2236
2242
|
| turbo | `--root-turbo-json <v>` | refuse | `turbo()` reads the `turbo.json` at the workspace root: move it there |
|
|
2237
|
-
| turbo | `--experimental-otel-*` | refuse | telemetry is a plugin: `otel()` from @vzn/vx-otel in vx
|
|
2243
|
+
| turbo | `--experimental-otel-*` | refuse | telemetry is a plugin: `otel()` from @vzn/vx-otel in .vx/workspace.ts |
|
|
2238
2244
|
| nx | `--parallel <n>` | alias | `--concurrency <n>` (`--parallel=false` is 1) |
|
|
2239
2245
|
| turbo | `--parallel` | refuse | vx always honours `dependsOn`; `--concurrency <n>` sets how many run at once |
|
|
2240
2246
|
| turbo | `--scope <v>` | refuse | use `--filter <pkg>` |
|
|
2241
2247
|
| turbo | `--since <v>` | refuse | use `--filter '[<ref>]'` or `--affected=<ref>` |
|
|
2242
|
-
| turbo | `--remote-only[=<bool>]` | refuse | use `--cache local:,remote:rw`
|
|
2243
|
-
| turbo | `--remote-cache-read-only[=<bool>]` | refuse | use `--cache local:rw,remote:r`
|
|
2248
|
+
| turbo | `--remote-only[=<bool>]` | refuse | use `--cache-policy local:,remote:rw` |
|
|
2249
|
+
| turbo | `--remote-cache-read-only[=<bool>]` | refuse | use `--cache-policy local:rw,remote:r` |
|
|
2244
2250
|
| turbo | `--anon-profile` | refuse | use `--profile[=<path>]`; vx has no redacting variant, so read it before sharing it |
|
|
2245
2251
|
| turbo | `--cache-workers <v>` | refuse | vx sizes its own cache I/O: drop it |
|
|
2246
2252
|
| turbo | `--cwd <v>` | refuse | run vx from that directory: `cd <dir> && vx run …` |
|
|
@@ -2253,7 +2259,7 @@ copy to the source.
|
|
|
2253
2259
|
| turbo | `--preflight` | refuse | set `turboCache({ preflight: true })` or `TURBO_PREFLIGHT=1` |
|
|
2254
2260
|
| turbo | `--remote-cache-timeout <v>` | refuse | set `turboCache({ timeoutMs })` or `TURBO_REMOTE_CACHE_TIMEOUT` |
|
|
2255
2261
|
| turbo | `--single-package` | refuse | a repo with no workspaces is one project already: drop it |
|
|
2256
|
-
| turbo | `--token <v>`, `--team <v>`, `--api <v>` | refuse | a remote cache is a plugin: `turboCache()` from @vzn/vx-migrate in vx
|
|
2262
|
+
| turbo | `--token <v>`, `--team <v>`, `--api <v>` | refuse | a remote cache is a plugin: `turboCache()` from @vzn/vx-migrate in .vx/workspace.ts reads TURBO_TOKEN / TURBO_TEAM / TURBO_API |
|
|
2257
2263
|
| turbo | `--no-daemon`, `--daemon` | refuse | vx has no daemon: drop it |
|
|
2258
2264
|
| turbo | `--ui <v>`, `--log-order <v>`, `--log-prefix <v>` | refuse | vx frames each task’s output: `--output-logs <mode>` sets how much |
|
|
2259
2265
|
| nx | `-t <v>`, `--targets <v>`, `--target <v>` | alias | the task names, positional: `vx run build test` |
|
|
@@ -2270,13 +2276,13 @@ copy to the source.
|
|
|
2270
2276
|
| nx | `--uncommitted`, `--untracked` | refuse | use `--affected=HEAD` (the working tree against the last commit) |
|
|
2271
2277
|
| nx | `--max-parallel <v>` | alias | `--concurrency <n>` |
|
|
2272
2278
|
| nx | `--exclude-task-dependencies[=<bool>]` | alias | `--exclude-dependencies` |
|
|
2273
|
-
| nx | `--skip-remote-cache[=<bool>]` | alias | `--cache local:rw,remote:`
|
|
2279
|
+
| nx | `--skip-remote-cache[=<bool>]` | alias | `--cache-policy local:rw,remote:` |
|
|
2274
2280
|
| nx | `--verbose` | refuse | use `--verbosity <n>` (1 adds the summary table) |
|
|
2275
2281
|
| nx | `--files <v>` | refuse | vx asks git what changed: `--affected=<base>` |
|
|
2276
2282
|
| nx | `--batch` | refuse | vx runs one command per task: drop it |
|
|
2277
2283
|
| nx | `--dte`, `--use-agents` | refuse | vx distributes nothing: drop it |
|
|
2278
2284
|
| nx | `--nx-ignore-cycles` | refuse | vx refuses a task cycle by name: break it |
|
|
2279
|
-
| nx | `--runner <v>` | refuse | a remote cache is a plugin: `nxCache()` from @vzn/vx-migrate in vx
|
|
2285
|
+
| nx | `--runner <v>` | refuse | a remote cache is a plugin: `nxCache()` from @vzn/vx-migrate in .vx/workspace.ts |
|
|
2280
2286
|
| nx | `--skip-sync` | refuse | vx never runs sync generators: drop it |
|
|
2281
2287
|
| nx | `--tui`, `--no-tui`, `--tui-auto-exit` | refuse | vx frames each task’s output: `--output-logs <mode>` sets how much |
|
|
2282
2288
|
| nx | `--no-cloud` | refuse | vx has no cloud: drop it |
|
|
@@ -2290,8 +2296,9 @@ copy to the source.
|
|
|
2290
2296
|
|
|
2291
2297
|
## Machine-readable output
|
|
2292
2298
|
|
|
2293
|
-
`vx show`, `vx info`, `vx why`, `vx last` and `vx cache
|
|
2294
|
-
`--format json`. Each prints one JSON document
|
|
2299
|
+
`vx show`, `vx info`, `vx why`, `vx last` and `vx cache clean` take
|
|
2300
|
+
`--format json`. Each prints one JSON document on one line, as
|
|
2301
|
+
`vx run --format json` does, whose shape is a
|
|
2295
2302
|
checked-in JSON Schema (draft 2020-12) shipped with the package:
|
|
2296
2303
|
`schemas/show.json`, `schemas/info.json`, `schemas/why.json`,
|
|
2297
2304
|
`schemas/last.json`, `schemas/cache.json`
|
|
@@ -2323,27 +2330,30 @@ prose on stderr do not change. Branch on the code; the message may change.
|
|
|
2323
2330
|
|
|
2324
2331
|
#### `VX_E_USAGE`
|
|
2325
2332
|
|
|
2326
|
-
A flag, argument or
|
|
2327
|
-
it, and a close spelling the verb does take when there is one;
|
|
2333
|
+
A flag, argument, selection or `VX_*` environment value the verb does
|
|
2334
|
+
not take. The message names it, and a close spelling the verb does take when there is one;
|
|
2328
2335
|
`vx <verb> --help` lists every flag.
|
|
2329
2336
|
|
|
2330
2337
|
#### `VX_E_UNKNOWN_COMMAND`
|
|
2331
2338
|
|
|
2332
2339
|
No verb of that name here, core's or a declared plugin's. `vx --help`
|
|
2333
2340
|
lists core's verbs. A plugin's verb exists once its plugin is in
|
|
2334
|
-
|
|
2341
|
+
`.vx/workspace.ts` `plugins`; a task runs as `vx run <task>`.
|
|
2335
2342
|
|
|
2336
2343
|
#### `VX_E_UNKNOWN_TASK`
|
|
2337
2344
|
|
|
2338
2345
|
No project in scope declares a requested task. The message lists the
|
|
2339
2346
|
tasks it found; `vx show --format json` lists each project's. Check the
|
|
2340
2347
|
spelling, and the scope: `--filter`, `--all`, or the project the working
|
|
2341
|
-
directory is in.
|
|
2348
|
+
directory is in. A run reads its configs from the lock, so a task
|
|
2349
|
+
added to a `vx.config` since the last `vx lock` is not there yet: run
|
|
2350
|
+
`vx lock`.
|
|
2342
2351
|
|
|
2343
2352
|
#### `VX_E_UNKNOWN_PROJECT`
|
|
2344
2353
|
|
|
2345
2354
|
`vx show` names a project the workspace does not have. A project is named
|
|
2346
|
-
by its
|
|
2355
|
+
by its config's `name`, else by a plugin (a lockfile plugin names it from
|
|
2356
|
+
its `package.json`), else by its directory; `vx show --format json` lists them.
|
|
2347
2357
|
|
|
2348
2358
|
#### `VX_E_NO_HISTORY`
|
|
2349
2359
|
|
|
@@ -2357,20 +2367,24 @@ the task once (`vx run <task>`); history is this checkout's own.
|
|
|
2357
2367
|
|
|
2358
2368
|
#### `VX_E_CONFIG`
|
|
2359
2369
|
|
|
2360
|
-
A `vx.config.*` or
|
|
2370
|
+
A `vx.config.*` or `.vx/workspace.*` that does not load or is refused. The
|
|
2361
2371
|
message names the file and the field; fix that field
|
|
2362
2372
|
(`vx docs <field>` prints its reference). `vx info --format json` lists
|
|
2363
|
-
every config error at once.
|
|
2373
|
+
every config error at once. A project config's refusal is `vx lock`'s,
|
|
2374
|
+
not a run's: a run never evaluates one.
|
|
2364
2375
|
|
|
2365
2376
|
#### `VX_E_CYCLE`
|
|
2366
2377
|
|
|
2367
2378
|
The task graph has a cycle. The message names the loop; drop one of its
|
|
2368
2379
|
`dependsOn` edges.
|
|
2380
|
+
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2369
2381
|
|
|
2370
2382
|
#### `VX_E_NO_WORKSPACE`
|
|
2371
2383
|
|
|
2372
|
-
No workspace
|
|
2373
|
-
|
|
2384
|
+
No `.vx/workspace.*` in the working directory or a parent, up to the git
|
|
2385
|
+
top-level (a file above the checkout never counts). The file marks the
|
|
2386
|
+
workspace root, so run `vx init` to create one. Outside git, one another
|
|
2387
|
+
user owns does not count (`security.md`); the message names it.
|
|
2374
2388
|
|
|
2375
2389
|
#### `VX_E_CACHE_VERSION`
|
|
2376
2390
|
|
|
@@ -2387,7 +2401,7 @@ message is that plugin's or check's: do what it says.
|
|
|
2387
2401
|
#### `VX_E_FS`
|
|
2388
2402
|
|
|
2389
2403
|
The file system refused a write: permissions, a full disk. The message
|
|
2390
|
-
names the path. Free space (`vx cache
|
|
2404
|
+
names the path. Free space (`vx cache clean --max-size <size>` trims the
|
|
2391
2405
|
cache) or grant the write, then re-run.
|
|
2392
2406
|
|
|
2393
2407
|
#### `VX_E_FDS`
|
|
@@ -2416,12 +2430,22 @@ A task's `dependsOn` names a task or project that is not declared, or uses
|
|
|
2416
2430
|
a form vx does not take (a bare wildcard, a negation, a pattern in
|
|
2417
2431
|
`pkg#task`). The message names the task and the entry: declare the
|
|
2418
2432
|
target, fix its spelling, or drop the entry.
|
|
2433
|
+
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2434
|
+
|
|
2435
|
+
A `^task` with no edge source is refused too: ``<id>: ^build needs
|
|
2436
|
+
project edges; add pnpm()/bun()/npm()/yarn() or a plugin that returns
|
|
2437
|
+
`deps` ``. Core reads no edge from a manifest, so with no plugin
|
|
2438
|
+
returning `deps` every `^` would resolve to nothing and fold no
|
|
2439
|
+
upstream into the key. Declare the lockfile plugin in
|
|
2440
|
+
`.vx/workspace.ts` (or a plugin of your own that returns `deps`); a run
|
|
2441
|
+
refuses it as well when the plugin left the workspace after the lock.
|
|
2419
2442
|
|
|
2420
2443
|
#### `VX_E_OUTPUT_OVERLAP`
|
|
2421
2444
|
|
|
2422
2445
|
Two tasks declare overlapping outputs with no `dependsOn` edge between
|
|
2423
2446
|
them, so whichever runs second deletes the other's files. Give each task
|
|
2424
2447
|
its own output path, or make one depend on the other.
|
|
2448
|
+
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2425
2449
|
|
|
2426
2450
|
#### `VX_E_INPUT_IS_OUTPUT`
|
|
2427
2451
|
|
|
@@ -2430,6 +2454,13 @@ a run, so it would delete its sources) or another task's (its key must
|
|
|
2430
2454
|
not read them; `dependsOn` already cascades). Exclude the outputs from
|
|
2431
2455
|
the inputs with a `!` glob, as the message shows, or declare no outputs
|
|
2432
2456
|
for a task that rewrites files in place.
|
|
2457
|
+
`vx lock` refuses it, not a run: a run trusts the graph the lock holds.
|
|
2458
|
+
|
|
2459
|
+
#### `VX_E_DEPENDENCY_RULE`
|
|
2460
|
+
|
|
2461
|
+
`vx lock` found a project depending on one a `run.rules.dependencies`
|
|
2462
|
+
rule denies or does not allow. A header counts the broken edges and
|
|
2463
|
+
each indented line under it names both projects and the rule. Drop the dependency, or change the project's tags or the rule.
|
|
2433
2464
|
|
|
2434
2465
|
#### `VX_E_INTERNAL`
|
|
2435
2466
|
|
|
@@ -2449,14 +2480,13 @@ product verb to it under a notice.
|
|
|
2449
2480
|
|
|
2450
2481
|
## `vx show`
|
|
2451
2482
|
|
|
2452
|
-
Introspect the workspace's **
|
|
2453
|
-
|
|
2454
|
-
plugin `config` and `project` stages
|
|
2455
|
-
gives tasks to (`turbo()` or `nx()` from
|
|
2456
|
-
temporary start toward native config) shows them
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
it directly if you want the frozen view).
|
|
2483
|
+
Introspect the workspace's **resolved configs** — what a run would see
|
|
2484
|
+
right now. Configs load through the same path a run uses: from
|
|
2485
|
+
`.vx/projects.lock.json`, plugin `config` and `project` stages applied,
|
|
2486
|
+
so a package a plugin gives tasks to (`turbo()` or `nx()` from
|
|
2487
|
+
`@vzn/vx-migrate`, a temporary start toward native config) shows them.
|
|
2488
|
+
`vx show` evaluates no config: an edit shows after `vx lock`, and with
|
|
2489
|
+
no lock it refuses as a run does (`no lock: run \`vx lock\``).
|
|
2460
2490
|
|
|
2461
2491
|
```
|
|
2462
2492
|
vx show # list every project
|
|
@@ -2484,23 +2514,25 @@ Nx's spellings name these: `vx show projects` (when no project or task
|
|
|
2484
2514
|
has that name) and `vx show project <name>` say `vx show` and
|
|
2485
2515
|
`vx show <name>`.
|
|
2486
2516
|
|
|
2487
|
-
No target: one
|
|
2488
|
-
and
|
|
2489
|
-
tasks all come from plugins reads `N tasks (no vx
|
|
2490
|
-
|
|
2491
|
-
vx config
|
|
2492
|
-
`^build` (X-48). A
|
|
2517
|
+
No target: a table, one row per project — name, root-relative dir,
|
|
2518
|
+
task count, and `no vx config` for config-less packages; one whose
|
|
2519
|
+
tasks all come from plugins reads `N tasks from plugins (no vx
|
|
2520
|
+
config)`, and one a configured project depends on reads `1 task,
|
|
2521
|
+
default build (no vx config)`: a run loads it for that dependant's
|
|
2522
|
+
`^build` (X-48). A `tags` column appears when any project has tags. With `--format json`
|
|
2493
2523
|
it's an array of `{ name, dir, tags: string[], tasks: string[] }`.
|
|
2494
2524
|
|
|
2495
2525
|
```
|
|
2496
2526
|
$ vx show
|
|
2497
|
-
|
|
2498
|
-
|
|
2527
|
+
project dir tasks tags
|
|
2528
|
+
app packages/app 3 tasks scope:web
|
|
2529
|
+
bare packages/bare no vx config
|
|
2499
2530
|
```
|
|
2500
2531
|
|
|
2501
|
-
|
|
2502
|
-
`
|
|
2503
|
-
|
|
2532
|
+
A report naming the project opens it: its `dir`, its `tags`, and
|
|
2533
|
+
`config none` for a package with no config file. Then
|
|
2534
|
+
`vx show <project>` prints a block per task, headed by its
|
|
2535
|
+
`project#task`, with every field the run reads: description, command (`(group)` for group tasks), `dependsOn`,
|
|
2504
2536
|
`timeout`, `retries`, `env.passThrough` / `env.define` / `env.secret`,
|
|
2505
2537
|
`remote`, `sandbox`, `persistent`, `interactive` (`yes` / `no`), and the
|
|
2506
2538
|
cache block
|
|
@@ -2518,15 +2550,15 @@ list and `vx show <project>`, but `vx show build`, like
|
|
|
2518
2550
|
|
|
2519
2551
|
```
|
|
2520
2552
|
$ vx show app#build
|
|
2521
|
-
app — packages/app
|
|
2522
2553
|
|
|
2523
|
-
build
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
inputs.
|
|
2529
|
-
|
|
2554
|
+
─ app#build ──────────────────────────────────────────────────
|
|
2555
|
+
dir packages/app
|
|
2556
|
+
description compile the app
|
|
2557
|
+
command tsc -b
|
|
2558
|
+
dependsOn ^build
|
|
2559
|
+
inputs.files src/**
|
|
2560
|
+
inputs.env NODE_ENV
|
|
2561
|
+
outputs.files dist/**
|
|
2530
2562
|
```
|
|
2531
2563
|
|
|
2532
2564
|
Unknown project / task names exit `1` with the same near-miss hint
|
|
@@ -2551,7 +2583,7 @@ share `TMPDIR`: a `nix develop` shell sets its own.
|
|
|
2551
2583
|
Every verb: a path vx must write that this user cannot (`EACCES`,
|
|
2552
2584
|
`EPERM`, `EROFS` — a read-only checkout, another user's files) or that
|
|
2553
2585
|
the disk has no room for (`ENOSPC`, `EDQUOT`) exits 1 with one line
|
|
2554
|
-
naming the path, `vx: EACCES: permission denied, open '
|
|
2586
|
+
naming the path, `vx: EACCES: permission denied, open '…/.vx/projects.lock.json'
|
|
2555
2587
|
— a path vx must write is not writable by this user` (or `— the disk
|
|
2556
2588
|
that path is on is full`), never a stack. Inside a run the task's line
|
|
2557
2589
|
says the same. A process out of file descriptors (`EMFILE`, `ENFILE`;
|
|
@@ -2562,38 +2594,36 @@ is out of file descriptors; raise the limit (ulimit -n 4096) and re-run`.
|
|
|
2562
2594
|
|
|
2563
2595
|
Workspace doctor — one screen of facts for bug reports and sanity
|
|
2564
2596
|
checks. The task count and the sandbox row's declared count come from
|
|
2565
|
-
the same load a run uses, plugin stages included
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
row per config with the message `vx run` stops on):
|
|
2597
|
+
the same load a run uses: the lock, plugin stages included. `vx info`
|
|
2598
|
+
evaluates no config; with no lock, or one a run would refuse, both
|
|
2599
|
+
counts are zero and `projects lock` says `no` — a broken config is
|
|
2600
|
+
`vx lock`'s to report:
|
|
2570
2601
|
|
|
2571
2602
|
```
|
|
2572
2603
|
$ vx info
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
cache
|
|
2585
|
-
cache
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
with the loader's message — the same line `vx run` would stop on.
|
|
2604
|
+
|
|
2605
|
+
─ vx info ────────────────────────────────────────────────────
|
|
2606
|
+
vx 0.0.0
|
|
2607
|
+
bun 1.4.2
|
|
2608
|
+
git 2.53.0
|
|
2609
|
+
git status cache core.fsmonitor, core.untrackedCache off
|
|
2610
|
+
workspace root /work/repo
|
|
2611
|
+
projects 12 (34 tasks)
|
|
2612
|
+
plugins 2 — @vzn/vx-reapi (executor, cache); @vzn/vx-otel (telemetry)
|
|
2613
|
+
workers 2 — cgroup CPU quota 2 of 8 cores
|
|
2614
|
+
memory 13 GB usable — cgroup limit; the machine has 16 GB
|
|
2615
|
+
cache dir /work/repo/.vx/cache (8.2 MB, this workspace's run history)
|
|
2616
|
+
cache store /home/me/.vx/3f2a9c1e7b4d5a60/cache (1.3 GB, shared by this repo's checkouts)
|
|
2617
|
+
cache versions keys vx-cache-v43 · index schema v38
|
|
2618
|
+
cache entries 42 (1.3 GB)
|
|
2619
|
+
orphans 3 artifacts (12 MB) the index does not know — `vx cache clean` reaps them
|
|
2620
|
+
task runs (24h) 7 (5 cache hits)
|
|
2621
|
+
flaky tasks 1 — web#test (3 of 11 runs failed on unchanged inputs)
|
|
2622
|
+
sandbox available (9 tasks declare exec.sandbox)
|
|
2623
|
+
projects lock yes
|
|
2624
|
+
```
|
|
2625
|
+
|
|
2626
|
+
- `git` shows `(not found)` when the binary is missing.
|
|
2597
2627
|
- `git status cache`: vx runs ONE `git status` per run to find dirty
|
|
2598
2628
|
and untracked files, and on a large tree that walk is the warm run's
|
|
2599
2629
|
critical path. git's `core.fsmonitor` (a daemon that watches the
|
|
@@ -2613,13 +2643,13 @@ exec.sandbox and will fail` says it first: root inside a container
|
|
|
2613
2643
|
unreported`: the sandbox still enforces, but a task that tolerates a
|
|
2614
2644
|
denied read passes and caches with no word of it. The `--format json` fact is
|
|
2615
2645
|
`sandbox.untraced`, the reason or `null`.
|
|
2616
|
-
- `plugins` names every plugin
|
|
2646
|
+
- `plugins` names every plugin `.vx/workspace.*` declares and the seams
|
|
2617
2647
|
each fills, in pipeline order (`config`, `discover`, `project`, `graph`, `key`,
|
|
2618
2648
|
`fingerprint`, `schedule`, `admit`, `executor`, `cache`, `telemetry`,
|
|
2619
|
-
`setup`, `commands`), or `none`. It reads the declarations: a plugin
|
|
2649
|
+
`output`, `setup`, `commands`), or `none`. It reads the declarations: a plugin
|
|
2620
2650
|
that declines a task at run time still lists its seam here.
|
|
2621
2651
|
- `workers` is the count a run defaults to and where it comes from:
|
|
2622
|
-
|
|
2652
|
+
`.vx/workspace.ts`'s `run.concurrency` when set, else the cores this
|
|
2623
2653
|
process may use — the CPU count, capped by the cgroup CPU quota a
|
|
2624
2654
|
container runs under (`8 — the CPU count`, `2 — cgroup CPU quota 2 of
|
|
2625
2655
|
8 cores`). `memory` is what a memory-packing policy budgets
|
|
@@ -2640,15 +2670,22 @@ unreported`: the sandbox still enforces, but a task that tolerates a
|
|
|
2640
2670
|
the hits are a share of it: three `vx run` of two tasks are six. An
|
|
2641
2671
|
invocation is what `vx last` calls a run; `vx last --list` counts
|
|
2642
2672
|
those.
|
|
2643
|
-
- `cache
|
|
2644
|
-
store
|
|
2645
|
-
|
|
2646
|
-
|
|
2673
|
+
- `cache dir` holds the index and this workspace's run history; its size
|
|
2674
|
+
is on disk. `cache store` is where the entries and their artifacts
|
|
2675
|
+
live: the store shared by this repo's checkouts (`docs/caching.md`),
|
|
2676
|
+
with its size on disk, or `none: the cache dir holds the entries` when
|
|
2677
|
+
the workspace names its cache dir. Before a first run it is the store a
|
|
2678
|
+
run would open. A bare `vx cache clean` frees both sizes.
|
|
2647
2679
|
- `cache entries` counts the entries and their size; with a
|
|
2648
2680
|
`cacheRetention.maxSize` it reads against it (`812 (3.0 GB of 10 GB,
|
|
2649
2681
|
30%)`), and a `cache retention` row says what each run's end evicts.
|
|
2650
2682
|
Paths under the home directory print as `~/…`; the JSON keeps them
|
|
2651
2683
|
absolute.
|
|
2684
|
+
- `cache policy` is the policy a `vx run` takes when no flag names one,
|
|
2685
|
+
and where it comes from (`VX_CACHE_POLICY`, `.vx/workspace`'s `cache.policy`,
|
|
2686
|
+
or the default, every layer on, remote writes off off CI); `cache scope`
|
|
2687
|
+
is the same for the scope (`VX_CACHE_SCOPE`, `cache.scope`, which
|
|
2688
|
+
`github()` may set, or none: the default keys).
|
|
2652
2689
|
- `cache versions` are the two constants a bug report needs and the
|
|
2653
2690
|
reset notice names: the key prefix (`CACHE_VERSION`; a bump orphans
|
|
2654
2691
|
every entry) and the index schema (`SCHEMA_VERSION`; a mismatch drops
|
|
@@ -2657,19 +2694,23 @@ unreported`: the sandbox still enforces, but a task that tolerates a
|
|
|
2657
2694
|
save temps the index has no row for, older than an hour (what a
|
|
2658
2695
|
`SCHEMA_VERSION` reset leaves behind; a fresh one is a save in
|
|
2659
2696
|
flight). A task that asks for one's key again indexes it and hits;
|
|
2660
|
-
the rest only `vx cache
|
|
2697
|
+
the rest only `vx cache clean` reclaims, so the doctor says so.
|
|
2661
2698
|
- `--format json` prints the same facts as one typed object, for a
|
|
2662
|
-
script or a bug-report template: `vx`, `
|
|
2699
|
+
script or a bug-report template: `vx`, `vxPath` (the file that ran),
|
|
2700
|
+
`handedOffFrom` (the vx that handed the call to the project's own, else
|
|
2701
|
+
null), `bun`, `bunSupported` (false
|
|
2663
2702
|
below Bun 1.4.0), `git` (null when not
|
|
2664
2703
|
found), `gitStatusCache` (`{ fsmonitor, untrackedCache }`, null when
|
|
2665
2704
|
git could not answer), `workspaceRoot`, `projects`, `tasks`,
|
|
2666
|
-
`
|
|
2667
|
-
empty when all did), `plugins` (`[{ name, seams }]`), `workers` (`{ count, source, cores,
|
|
2705
|
+
`plugins` (`[{ name, seams }]`), `workers` (`{ count, source, cores,
|
|
2668
2706
|
cpuQuota }`, the source one of `workspace` / `cgroup` / `cores`,
|
|
2669
2707
|
`cpuQuota` in cores or null), `memory` (`{ usableBytes, totalBytes,
|
|
2670
|
-
cgroupLimitBytes }`, the limit null when none binds), `cacheDir`, `
|
|
2671
|
-
(
|
|
2672
|
-
|
|
2708
|
+
cgroupLimitBytes }`, the limit null when none binds), `cacheDir`, `cacheDirBytes`
|
|
2709
|
+
(on disk: the index and run history), `cacheStore` (null when the cache
|
|
2710
|
+
dir holds the entries), `cacheStoreBytes` (on disk, null with no store), `cacheVersion`,
|
|
2711
|
+
`schemaVersion`, `cacheEntries`, `cacheBytes`, `settings` (every
|
|
2712
|
+
setting by path, `{ value, source }`, `source` one of `env` /
|
|
2713
|
+
`workspace` / `default`, `value` null for the default), `cacheRetention`
|
|
2673
2714
|
(`{ olderThan, maxSize, maxBytes }`, each only when set, null when none is declared),
|
|
2674
2715
|
`orphans` (`{ artifacts, bytes }`, always present), `runs24h`, `hits24h` (task
|
|
2675
2716
|
runs, as the row), `restored24h` (of those hits, the ones that restored
|
|
@@ -2680,10 +2721,10 @@ declared, untraced }`, `declared` the count of tasks with `exec.sandbox`,
|
|
|
2680
2721
|
pretty rows render this object;
|
|
2681
2722
|
there is no second source.
|
|
2682
2723
|
- `--cache-dir <path>` reads the cache a run with the same flag uses, as
|
|
2683
|
-
`vx why`, `vx last` and `vx cache
|
|
2724
|
+
`vx why`, `vx last` and `vx cache clean` do.
|
|
2684
2725
|
|
|
2685
|
-
Exit codes: `0` the facts printed,
|
|
2686
|
-
|
|
2726
|
+
Exit codes: `0` the facts printed, with no lock included; `1` no
|
|
2727
|
+
workspace here or above, or a parse error.
|
|
2687
2728
|
|
|
2688
2729
|
## `vx why`
|
|
2689
2730
|
|
|
@@ -2721,7 +2762,7 @@ cache (nothing re-ran), it re-executed on the same key, or it recorded
|
|
|
2721
2762
|
no cache outcome at all, in which case vx says so instead of guessing.
|
|
2722
2763
|
A re-execution names its cause when the index shows one: the previous
|
|
2723
2764
|
run on the key failed and saved nothing, the run did not read the cache
|
|
2724
|
-
(`--force`, or a `--cache` without read), no entry for the key was
|
|
2765
|
+
(`--force`, or a `--cache-policy` without read), no entry for the key was
|
|
2725
2766
|
there when it ran (pruned or evicted), the previous run was not saved
|
|
2726
2767
|
because it ran beside a failed task (a task run past a failed dependency
|
|
2727
2768
|
under `--continue` is never cached; said ahead of a prune, and as
|
|
@@ -2767,6 +2808,18 @@ which names the `vx why` to run next; a `config` line names the
|
|
|
2767
2808
|
`vx show` that prints the task's config now. `--format json` carries
|
|
2768
2809
|
the walk as `roots` (`chain`, `entries`).
|
|
2769
2810
|
|
|
2811
|
+
A moved input file whose last change fell inside a recorded task's run
|
|
2812
|
+
window names that task: a task that writes a file another key reads (a
|
|
2813
|
+
build rewriting a translation file in another package) re-keys that key
|
|
2814
|
+
on every run, so the miss is right and only moving the write ends it.
|
|
2815
|
+
`--format json` carries these as `writers` (`taskId`, `file`,
|
|
2816
|
+
`writers`); an edit made outside a run names none.
|
|
2817
|
+
|
|
2818
|
+
```
|
|
2819
|
+
changed during a run:
|
|
2820
|
+
packages/lib/src/gen.txt while app#build ran
|
|
2821
|
+
```
|
|
2822
|
+
|
|
2770
2823
|
A hit's line is `cache-hit · key …` (or `cache-hit-remote`): the status
|
|
2771
2824
|
names the hit and its tier, so only an executed run carries the word.
|
|
2772
2825
|
A skipped task never ran and derived no key: its line is `skipped · no key`.
|
|
@@ -2774,11 +2827,10 @@ A row's kind is what the key folded: `file` (an input file, by blob
|
|
|
2774
2827
|
id), `env` (a declared variable, by digest), `runtime` and
|
|
2775
2828
|
`ws-runtime` (a declared command's output, `inputs.runtime` and
|
|
2776
2829
|
`inputs.workspaceRuntime`), `forward` (the argv forwarded after `--`),
|
|
2777
|
-
`
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
lockfile
|
|
2781
|
-
fingerprint's), `plugin` (a `key` plugin's material, by name) and
|
|
2830
|
+
`config` (the evaluated task config), `upstream` (a dependency's input
|
|
2831
|
+
key — a lockfile change moves it too, so that row rides with the
|
|
2832
|
+
lockfile plugin's), `plugin` (a `key` plugin's material, by name; a
|
|
2833
|
+
lockfile plugin's dependency closure) and
|
|
2782
2834
|
`format` (the key format vx itself folds: it moves only with an
|
|
2783
2835
|
upgrade, and re-keys every task once, X-142).
|
|
2784
2836
|
The component-level rows come from the `entry_inputs` input
|
|
@@ -2791,39 +2843,13 @@ task with no `cache` block derives a key too — it is what dependents
|
|
|
2791
2843
|
fold — but saves no entry, so for it the verb can only report the key
|
|
2792
2844
|
change and says so.
|
|
2793
2845
|
`--format json` emits one machine-readable object (`{ taskId, runId,
|
|
2794
|
-
why, diff, roots }`); when the task's recorded runs predate run ids, `{ taskId,
|
|
2846
|
+
why, diff, roots, writers }`); when the task's recorded runs predate run ids, `{ taskId,
|
|
2795
2847
|
why, diff, explanation }`, `why` and `diff` null and `explanation` the
|
|
2796
2848
|
latest cache entry's key.
|
|
2797
2849
|
|
|
2798
2850
|
Exit codes: `0` the task's run explained; `1` no recorded run of the
|
|
2799
2851
|
task, a `--run` with no row for it, or a parse error.
|
|
2800
2852
|
|
|
2801
|
-
## `vx prune`
|
|
2802
|
-
|
|
2803
|
-
```
|
|
2804
|
-
vx prune <project...> [--out-dir <dir>] [--docker] [--production]
|
|
2805
|
-
```
|
|
2806
|
-
|
|
2807
|
-
A plugin verb of `@vzn/vx-lockfile`: declaring `pnpm()`, `bun()`,
|
|
2808
|
-
`npm()` or `yarn()` adds it. It copies the named projects, their
|
|
2809
|
-
transitive workspace dependencies, the root manifests (workspace lists
|
|
2810
|
-
rewritten to the subset), the vx configs and each lockfile pruned to what
|
|
2811
|
-
the subset installs into `--out-dir` (default `out/`), so a Docker build
|
|
2812
|
-
installs it with a frozen lockfile; `--docker` splits `json/` (the
|
|
2813
|
-
install layer) from `full/`; `--production` leaves out a workspace
|
|
2814
|
-
package only a dev dependency reaches and strikes it from the kept
|
|
2815
|
-
manifests and lockfiles. `turbo prune`. Reference:
|
|
2816
|
-
`packages/vx-lockfile/README.md` § `vx prune`. Without the plugin, typing
|
|
2817
|
-
`vx prune` says where it comes from and exits 1. History: core verb
|
|
2818
|
-
2026-08-25, `@vzn/vx-prune` 2026-09-10, removed 2026-09-11 (it copied the
|
|
2819
|
-
lockfile unpruned), back in `@vzn/vx-lockfile` 2026-10-03.
|
|
2820
|
-
|
|
2821
|
-
## `vx stats`
|
|
2822
|
-
|
|
2823
|
-
Removed (H-19). It was an alias of `vx info`, which absorbed it, and
|
|
2824
|
-
warned on every use. Typing `vx stats` points at `vx info` and exits 1 —
|
|
2825
|
-
unless a declared plugin claims the verb through the `commands` seam.
|
|
2826
|
-
|
|
2827
2853
|
## `vx last`
|
|
2828
2854
|
|
|
2829
2855
|
Replay a recorded run's summary from the local history — no
|
|
@@ -2833,7 +2859,7 @@ surface. A run a Ctrl-C (or SIGTERM, SIGHUP) stopped records nothing,
|
|
|
2833
2859
|
so `vx last` still shows the run before it.
|
|
2834
2860
|
|
|
2835
2861
|
```
|
|
2836
|
-
vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--format pretty|json] [--cache-dir <path>]
|
|
2862
|
+
vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]
|
|
2837
2863
|
```
|
|
2838
2864
|
|
|
2839
2865
|
Bare `vx last` replays the most recent run: a header (verdict, command,
|
|
@@ -2875,7 +2901,12 @@ passed, so a replayed failure that is already fixed says so. A
|
|
|
2875
2901
|
sandboxed task's row adds `sandboxFix`, the `exec.sandbox.allow`
|
|
2876
2902
|
entries (`read`, `write`, `network`, `systemInfo`) that would have
|
|
2877
2903
|
granted its denials, the same grants the terminal prints as `fix:`;
|
|
2878
|
-
`withheld` lists the credential paths it denied and left out
|
|
2904
|
+
`withheld` lists the credential paths it denied and left out (a `.env` file, or a store under home the sandbox denies, `security.md`), and, for
|
|
2905
|
+
a cached task, `inputs` the reads of its project no `cache.inputs.files`
|
|
2906
|
+
glob matches (the terminal's `and, if the output depends on them, …`
|
|
2907
|
+
line). A task
|
|
2908
|
+
failed by `inputStability.severity: 'error'` adds `inputMoved`, the
|
|
2909
|
+
input that changed during the run. They live in `<cacheDir>/failures/<runId>.json`, the
|
|
2879
2910
|
newest 50 failed runs; past those, or when the write was refused, a
|
|
2880
2911
|
failed row reads `output: ''` and `locations: []`. `--log <task>`
|
|
2881
2912
|
prints one task's output instead of the summary: in the latest run that
|
|
@@ -2886,12 +2917,28 @@ or in the run that saved it. An uncached task that passed, or a skip,
|
|
|
2886
2917
|
kept none: stdout is empty and stderr says so, exit 0. With
|
|
2887
2918
|
`--format json` it is `{ runId, taskId, status, source, output }`,
|
|
2888
2919
|
`source` `'failure'`, `'cache'` or `null`. A task no run recorded is
|
|
2889
|
-
refused, exit 1; `--log` does not combine with `--list` or `--failed`.
|
|
2920
|
+
refused, exit 1; `--log` does not combine with `--list` or `--failed`.
|
|
2921
|
+
`vx last app#build --log` reads the same as `--log app#build`: a `--log`
|
|
2922
|
+
with no value takes the positional as its task.
|
|
2923
|
+
`--stats [<task>]` prints per-task numbers over the last 50 runs, the
|
|
2924
|
+
window the scheduler and `--dry` learn from: runs, p50 (the forecast a
|
|
2925
|
+
run shows: the median of the task's last five timings on the branch,
|
|
2926
|
+
else on any branch), p99 of the executed successes, pass and hit rate, failure mode (`stable`,
|
|
2927
|
+
`flaky-recoverable`, `flaky-fatal`), the largest peak RSS and CPU
|
|
2928
|
+
parallelism (a hit counts what its producing run used), one table row
|
|
2929
|
+
per task. A task id narrows to that task, a task name to it in every
|
|
2930
|
+
project; one the window has no run of is refused, exit 1. The branch is
|
|
2931
|
+
`--branch`, else `VX_RUN_BRANCH`, else git's, then the CI provider's, as
|
|
2932
|
+
a run's. With `--format json` it is `{ window, branch, stats }`, each row `{ taskId, runs,
|
|
2933
|
+
p50DurationMs, p99DurationMs, successRate, hitRate, failureMode,
|
|
2934
|
+
maxPeakRssBytes, maxCpuParallelism }`, `null` where nothing was
|
|
2935
|
+
measured. `--stats` does not combine with `--list`, `--failed` or
|
|
2936
|
+
`--log`; `--branch` combines only with `--stats`. `--list --format json`
|
|
2890
2937
|
an array of the same `invocation` objects, newest first. An unknown run id fails
|
|
2891
2938
|
loud and points at `--list`. Before any run, every form says so
|
|
2892
|
-
(
|
|
2893
|
-
|
|
2894
|
-
`--list --failed` prints
|
|
2939
|
+
(`› vx last no runs yet` with `fix vx run`, exit 1; `--list` prints
|
|
2940
|
+
`› vx last no runs yet`, exit 0), and past green runs only,
|
|
2941
|
+
`--list --failed` prints `› vx last no failed runs`. A run id may be typed as a unique prefix; a
|
|
2895
2942
|
prefix several runs share fails and lists them. A replayed run with
|
|
2896
2943
|
failures ends with the command that re-runs them (`re-run what failed:
|
|
2897
2944
|
vx run app#test -- …`, with the arguments the run forwarded to the
|
|
@@ -2900,14 +2947,14 @@ line of its own without them, X-140). The
|
|
|
2900
2947
|
replayed `$ vx run …` or `$ vx watch …` line and that one keep each
|
|
2901
2948
|
argument shell-quoted (`-- 'a b'`), so either pastes back as it ran (X-49).
|
|
2902
2949
|
|
|
2903
|
-
`vx why`, `vx last`, `vx info` and `vx cache
|
|
2950
|
+
`vx why`, `vx last`, `vx info` and `vx cache clean` all read the cache
|
|
2904
2951
|
a run wrote, so each takes `--cache-dir <path>` with `vx run`'s rules
|
|
2905
2952
|
(cwd-relative, absolute used as-is): a run that wrote its history
|
|
2906
2953
|
elsewhere is replayed, explained, reported on and pruned there. Without
|
|
2907
|
-
the flag they open the workspace's cache (`defineWorkspace({
|
|
2954
|
+
the flag they open the workspace's cache (`defineWorkspace({ cache: { dir } })`
|
|
2908
2955
|
or `.vx/cache`). A `--cache-dir` that is not there is refused by name
|
|
2909
2956
|
(`--cache-dir .vx/cahce: no such directory`). In a workspace that never
|
|
2910
|
-
ran, `vx why`, `vx last`, `vx info`, `vx show` and `vx cache
|
|
2957
|
+
ran, `vx why`, `vx last`, `vx info`, `vx show` and `vx cache clean`
|
|
2911
2958
|
(dry or not) read it as empty and create nothing (item 900, E-7, E-38).
|
|
2912
2959
|
|
|
2913
2960
|
Exit codes: `0` the run (or the list) printed, `--list` with no runs
|
|
@@ -2949,10 +2996,10 @@ of each verb, read from the same help text `vx <verb> --help` prints:
|
|
|
2949
2996
|
the verb's own Usage line, and for `run` and `watch` the run option
|
|
2950
2997
|
lines, less the ones `watch` refuses. So a flag cannot be documented
|
|
2951
2998
|
and not completed, nor completed and then refused: a flag another
|
|
2952
|
-
verb's line names in passing (`
|
|
2999
|
+
verb's line names in passing (`--check` in a line about `vx lock`) is
|
|
2953
3000
|
not one. After a flag with a fixed value set the script offers that
|
|
2954
3001
|
set: `--format` pretty or json, `--output-logs` full, errors-only,
|
|
2955
|
-
hash-only or none, `--download` all, toplevel or none — each value one
|
|
3002
|
+
hash-only or none, `--cache-download` all, toplevel or none — each value one
|
|
2956
3003
|
the flag's parser takes (`tests/completions.test.ts` holds it). A plugin verb completes `--help` only. Task and project
|
|
2957
3004
|
names are not completed (they are the workspace's, and a completion
|
|
2958
3005
|
that evaluates configs on every Tab is the wrong price). The zsh script
|
|
@@ -2967,7 +3014,7 @@ Exit codes: `0` the script printed; `1` an unknown shell or flag.
|
|
|
2967
3014
|
|
|
2968
3015
|
## Plugin commands
|
|
2969
3016
|
|
|
2970
|
-
A plugin declared in
|
|
3017
|
+
A plugin declared in `.vx/workspace.ts` can add verbs:
|
|
2971
3018
|
|
|
2972
3019
|
```ts
|
|
2973
3020
|
import { definePlugin, type VxPlugin } from '@vzn/vx'
|
|
@@ -2978,7 +3025,7 @@ export function mcp(): VxPlugin {
|
|
|
2978
3025
|
mcp: {
|
|
2979
3026
|
description: 'serve the run history to an AI agent over stdio',
|
|
2980
3027
|
async run(argv, ctx) {
|
|
2981
|
-
// ctx.workspaceRoot, ctx.cacheDir, ctx.concurrency, ctx.warn(...)
|
|
3028
|
+
// ctx.workspaceRoot, ctx.cacheDir, ctx.concurrency, ctx.cache, ctx.warn(...)
|
|
2982
3029
|
return 0 // the process exit code
|
|
2983
3030
|
},
|
|
2984
3031
|
},
|
|
@@ -2989,19 +3036,19 @@ export function mcp(): VxPlugin {
|
|
|
2989
3036
|
|
|
2990
3037
|
The plugin's name is its package name, which `definePlugin` reads from
|
|
2991
3038
|
`import.meta`; a plain object is refused when the workspace loads.
|
|
3039
|
+
`ctx.cache` reads the plugin's own entries as `setup` writes them (the
|
|
3040
|
+
local store, then each remote the workspace's cache plugins supply,
|
|
3041
|
+
under the run's scope), read-only, opened on first use.
|
|
2992
3042
|
|
|
2993
|
-
(`@vzn/vx-
|
|
2994
|
-
|
|
2995
|
-
over the resolved task catalog, one the workspace doctor's facts.
|
|
2996
|
-
`@vzn/vx-schedule-history` adds `vx history`: what it learned per task
|
|
2997
|
-
and the reservation its `admit` hook packs.) The dispatcher tries core's verbs
|
|
3043
|
+
(`@vzn/vx-schedule-history` ships exactly this: `vx history`, what it
|
|
3044
|
+
learned per task and the reservation its `admit` hook packs.) The dispatcher tries core's verbs
|
|
2998
3045
|
first and consults plugins only for a word core does not know, loading
|
|
2999
3046
|
the workspace config from the cwd to find them (outside a workspace the
|
|
3000
3047
|
verb is simply unknown). A plugin verb that names a core verb, or one
|
|
3001
3048
|
plugins of two packages both declare, is refused when the workspace
|
|
3002
3049
|
loads — such a verb could never run, or would hide the other plugin's.
|
|
3003
|
-
Plugins of one package are one owner (
|
|
3004
|
-
|
|
3050
|
+
Plugins of one package are one owner (two of its plugins may carry the
|
|
3051
|
+
same verb): the first declared runs. A plugin verb's
|
|
3005
3052
|
return value is the exit code, an integer 0–255 (anything else — nothing,
|
|
3006
3053
|
a fraction, 256, which the OS would keep as 0 — fails naming the plugin
|
|
3007
3054
|
and the verb), and a thrown `UserError` prints as
|
|
@@ -3013,33 +3060,62 @@ An unknown verb is answered with what this workspace knows: the "did you
|
|
|
3013
3060
|
mean" set is core's verbs AND the ones its plugins declare (the lookup
|
|
3014
3061
|
that just failed already loaded them, so it costs nothing), and when
|
|
3015
3062
|
nothing is close enough to guess, a second line says where a verb can
|
|
3016
|
-
come from — the verbs declared here, or that
|
|
3063
|
+
come from — the verbs declared here, or that `.vx/workspace.ts` is what
|
|
3017
3064
|
would declare one, or that there is no workspace here at all. Core still
|
|
3018
3065
|
names no package: it lists what the workspace itself declares.
|
|
3019
3066
|
|
|
3020
3067
|
## Environment variables vx reads
|
|
3021
3068
|
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
is
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
|
3034
|
-
|
|
|
3035
|
-
| `
|
|
3036
|
-
| `
|
|
3037
|
-
| `
|
|
3038
|
-
| `
|
|
3039
|
-
| `
|
|
3040
|
-
| `
|
|
3041
|
-
| `
|
|
3042
|
-
| `
|
|
3069
|
+
Every setting has a variable derived from its path (`run.killGrace` →
|
|
3070
|
+
`VX_RUN_KILL_GRACE`), beating `.vx/workspace` and beaten by the flag; the
|
|
3071
|
+
table in `docs/schema.md` § Settings gives each value's form and
|
|
3072
|
+
default. Each is checked once before any verb runs: an empty one is
|
|
3073
|
+
unset, a value its kind does not take is refused naming the form, and a
|
|
3074
|
+
variable vx does not read is ignored. A
|
|
3075
|
+
plugin's own (the REAPI endpoints, the GitHub token) are in its README;
|
|
3076
|
+
what a TASK sees is `exec.env` in `docs/schema.md`.
|
|
3077
|
+
|
|
3078
|
+
<!-- variables table: generated by tests/settings.test.ts -->
|
|
3079
|
+
|
|
3080
|
+
| Variable | Setting | What it does |
|
|
3081
|
+
| ----------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3082
|
+
| `VX_RUN_CONCURRENCY` | [`run.concurrency`](schema.md#settings) | Tasks at once: a positive integer, or a share of the cores (`'50%'`). |
|
|
3083
|
+
| `VX_RUN_SCHEDULE` | [`run.schedule`](schema.md#settings) | Which ready task starts first when there are more than workers. |
|
|
3084
|
+
| `VX_RUN_AFFECTED_BASE` | [`run.affectedBase`](schema.md#settings) | The git ref a bare `--affected` compares with. |
|
|
3085
|
+
| `VX_RUN_BRANCH` | [`run.branch`](schema.md#settings) | The branch a run records in its history and forecasts from. Flag and env only: a committed branch would be wrong on every other branch. |
|
|
3086
|
+
| `VX_RUN_TIMEOUT` | [`run.timeout`](schema.md#settings) | A task's time limit, for tasks without their own `exec.timeout` (`'30m'`). |
|
|
3087
|
+
| `VX_RUN_RETRIES` | [`run.retries`](schema.md#settings) | Re-runs of a failed task, for tasks without their own `exec.retries`. |
|
|
3088
|
+
| `VX_RUN_CONTINUE` | [`run.continue`](schema.md#settings) | What a failed task takes down: `never` stops the run, `deps-ok` skips its dependents, `always` runs them too. |
|
|
3089
|
+
| `VX_RUN_OUTPUT_LOGS` | [`run.outputLogs`](schema.md#settings) | Which task output the run prints. Chosen before the workspace loads, so flag and env only. |
|
|
3090
|
+
| `VX_RUN_KILL_GRACE` | [`run.killGrace`](schema.md#settings) | How long a task that ignores SIGTERM gets before SIGKILL. |
|
|
3091
|
+
| `VX_RUN_TEARDOWN_TIMEOUT` | [`run.teardownTimeout`](schema.md#settings) | The bound on each plugin's end-of-run flush and teardown. |
|
|
3092
|
+
| `VX_RUN_READY_NOTICE` | [`run.readyNotice`](schema.md#settings) | How long a persistent task may take to match `readyWhen` before vx says it waits. |
|
|
3093
|
+
| `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | [`run.rules.exclusiveOutputs`](schema.md#settings) | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
3094
|
+
| `VX_RUN_RULES_UPFRONT_KEYS` | [`run.rules.upfrontKeys`](schema.md#settings) | Refuse a task whose inputs can match another task's outputs. |
|
|
3095
|
+
| `VX_CACHE_DIR` | [`cache.dir`](schema.md#settings) | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
|
|
3096
|
+
| `VX_CACHE_RETENTION_MAX_SIZE` | [`cache.retention.maxSize`](schema.md#settings) | Evict least-recently-used entries at each run's end until the cache is under this (`'10GB'`). |
|
|
3097
|
+
| `VX_CACHE_RETENTION_OLDER_THAN` | [`cache.retention.olderThan`](schema.md#settings) | Evict entries unused for this long at each run's end (`'30d'`). |
|
|
3098
|
+
| `VX_CACHE_POLICY` | [`cache.policy`](schema.md#settings) | Which layers a run reads and writes (`'local:rw,remote:r'`); `--no-cache` and `--force` apply on top. |
|
|
3099
|
+
| `VX_CACHE_SCOPE` | [`cache.scope`](schema.md#settings) | Where cache writes land (`'pr-123'`): reads try the default key, then the scope's. |
|
|
3100
|
+
| `VX_CACHE_DOWNLOAD` | [`cache.download`](schema.md#settings) | Which remote hits land on disk: every one, only the requested tasks, or none. |
|
|
3101
|
+
| `VX_CACHE_INPUT_STABILITY_STRATEGY` | [`cache.inputStability.strategy`](schema.md#settings) | What a write to an input during the command means for the save, for tasks without their own. |
|
|
3102
|
+
| `VX_CACHE_INPUT_STABILITY_SEVERITY` | [`cache.inputStability.severity`](schema.md#settings) | Whether a skipped save warns or fails the task, for tasks without their own. |
|
|
3103
|
+
| `VX_LOCK_REVIEW_FILES` | [`lock.reviewFiles`](schema.md#settings) | `vx lock` writes each project's resolved config, pretty, to `vx.lock.json` beside its `vx.config`, for review; vx never reads it. |
|
|
3104
|
+
| `VX_WATCH_POLL` | [`watch.poll`](schema.md#settings) | Poll for changes from the start instead of the OS watcher. |
|
|
3105
|
+
| `VX_CONFIG_WORKER_TIMEOUT` | [`config.workerTimeout`](schema.md#settings) | How long one config evaluation may take. |
|
|
3106
|
+
| `VX_DEBUG_TIMING` | [`debug.timing`](schema.md#settings) | Print the run's stage timings to stderr. |
|
|
3107
|
+
| `VX_DEBUG_WATCH` | [`debug.watch`](schema.md#settings) | `vx watch`: say on stderr each time the loop arms, with the projects it watches. |
|
|
3108
|
+
| `VX_DEBUG_DUMPABLE` | [`debug.dumpable`](schema.md#settings) | Linux: keep vx dumpable, so strace or a debugger can read its memory and `/proc/<pid>/fd`, and an unsandboxed task its env. |
|
|
3109
|
+
| `VX_HANDED_OFF` | — | Set by a global vx that hands the call to the project's own (§ Which vx runs): the path of the vx that handed off, so the receiver does not hand off again. |
|
|
3110
|
+
| `VX_RUN_WORKSPACE`, `VX_RUN_TASK` | — | Set by vx on every task (below); a `vx run` a task starts reads them back. |
|
|
3111
|
+
|
|
3112
|
+
<!-- /variables table -->
|
|
3113
|
+
|
|
3114
|
+
vx sets two variables on every task's environment: `VX_RUN_WORKSPACE`
|
|
3115
|
+
(the workspace root) and `VX_RUN_TASK` (`project#task`). A `vx run` a
|
|
3116
|
+
task starts reads them back: one in the same workspace is refused, since
|
|
3117
|
+
a nested run is invisible to the outer graph and a loop back to its own
|
|
3118
|
+
task forks without bound (`docs/schema.md` § `env`).
|
|
3043
3119
|
|
|
3044
3120
|
Colors are the two conventions in § Output format › Colors (`NO_COLOR`,
|
|
3045
3121
|
`FORCE_COLOR`). A truthy `CI` picks the CI output flow and
|
|
@@ -3109,7 +3185,9 @@ marked with a cyan `▸` after `┌─`/`└─`, and its close reads `running`
|
|
|
3109
3185
|
died on its own before the run stopped it.
|
|
3110
3186
|
|
|
3111
3187
|
There is **no top-of-run banner** — the run context lives in the
|
|
3112
|
-
footer.
|
|
3188
|
+
footer. The task log opens with one blank line before its first row,
|
|
3189
|
+
on a terminal or not; a note vx prints before it is not part of it.
|
|
3190
|
+
A broad run looks like:
|
|
3113
3191
|
|
|
3114
3192
|
```
|
|
3115
3193
|
⇢ 4ms success local @vzn/vx#format-check
|
|
@@ -3117,30 +3195,37 @@ footer. A broad run looks like:
|
|
|
3117
3195
|
|
|
3118
3196
|
─ vx 0.0.0 ───────────────────────────────────────────────────
|
|
3119
3197
|
projects ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱
|
|
3120
|
-
1
|
|
3198
|
+
1 affected · 3 total
|
|
3121
3199
|
tasks ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰
|
|
3122
3200
|
2 success · 2 total
|
|
3123
3201
|
cache ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰
|
|
3124
|
-
1
|
|
3202
|
+
local 1↓ 1↑
|
|
3125
3203
|
|
|
3126
3204
|
info 8 workers · local cache
|
|
3127
|
-
time 5.34s ·
|
|
3128
|
-
result 2 tasks · 1 cached (50%) · 900ms saved · 5.34s
|
|
3205
|
+
time 5.34s · 900ms saved
|
|
3129
3206
|
```
|
|
3130
3207
|
|
|
3131
3208
|
The three bars are meters: `projects` is what the run covered against
|
|
3132
3209
|
the workspace, `tasks` is failed / success / skipped, `cache` is miss /
|
|
3133
3210
|
no-cache / up-to-date / local / remote (a skipped task rides it too, so
|
|
3134
|
-
the two
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3211
|
+
the two bars fill alike; its count is on the tasks legend only). Its
|
|
3212
|
+
legend reads per layer: `local 1● 1○ 2↓ 4↑ · remote 3↓ 4↑`.
|
|
3213
|
+
`●` is a fresh hit (outputs already on disk, whichever layer answered,
|
|
3214
|
+
so it counts under local), `○` a task with no cache, `↓` is restored
|
|
3215
|
+
from that layer and `↑` saved to it (local `↑` counts only tasks that ran here; remote `↑` counts
|
|
3216
|
+
uploads that landed). A layer shows only when the run's
|
|
3217
|
+
`--cache-policy` uses it. `N not saved` (ran, succeeded, saved nothing),
|
|
3218
|
+
`N not downloaded` (outputs left in the remote store) and `N ↑ failed`
|
|
3219
|
+
show only when non-zero. The
|
|
3220
|
+
`time` row is the wall time, then what the cache saved (the hits' stored
|
|
3221
|
+
exec times, never the restore), then the spread over executed tasks
|
|
3222
|
+
only — a hit's restore time never enters it. The footer never says a
|
|
3223
|
+
fact twice: a spread value already on the row is left out, so one
|
|
3224
|
+
executed task shows no spread, and there is no closing `result` row
|
|
3225
|
+
repeating the tasks, cache and time rows. A task with no `cache` block,
|
|
3226
|
+
or any task of a run whose `--cache-policy` reads and writes nothing
|
|
3227
|
+
(`--no-cache`), could never hit, so it is counted apart (`1 no-cache`
|
|
3228
|
+
for `vx run dev`), as `--dry` labels it. A test renders this run and
|
|
3144
3229
|
checks it against this page, byte for byte.
|
|
3145
3230
|
|
|
3146
3231
|
Group tasks emit no framed block by design (they aren't real tasks);
|
|
@@ -3151,8 +3236,8 @@ What a run has to say about one task rides that task's row; what it
|
|
|
3151
3236
|
says about the run (a server that died, outputs left remote, a file
|
|
3152
3237
|
`--summarize` / `--profile` wrote, a history, upload, plugin or sandbox
|
|
3153
3238
|
warning) prints above the footer: it prints once the run's closing work
|
|
3154
|
-
is done, the `
|
|
3155
|
-
(`
|
|
3239
|
+
is done, the `VX_DEBUG_TIMING=1` table included. Cache housekeeping
|
|
3240
|
+
(`cache.retention`) prints nothing. Only a kept server's own output
|
|
3156
3241
|
follows it.
|
|
3157
3242
|
|
|
3158
3243
|
**Skipped rows.** A red run lists every task that never started as a
|
|
@@ -3179,9 +3264,9 @@ what it reached first (`not run`). Neither reached the cache:
|
|
|
3179
3264
|
◌ not run web#test
|
|
3180
3265
|
```
|
|
3181
3266
|
|
|
3182
|
-
The tasks legend
|
|
3183
|
-
|
|
3184
|
-
|
|
3267
|
+
The tasks legend counts them in the total, each state its own segment
|
|
3268
|
+
of the bar: `2 success · 1 aborted · 2 not run · 5 total`. The total is
|
|
3269
|
+
always last on its line. A framed task (focused or `full`) that
|
|
3185
3270
|
printed while it stopped shows its frame instead of a row.
|
|
3186
3271
|
|
|
3187
3272
|
**Flaky rows.** A task the run just proved nondeterministic says so
|
|
@@ -3195,7 +3280,7 @@ run history alone, no service:
|
|
|
3195
3280
|
|
|
3196
3281
|
A task is flaky when its exact cache key FAILED AFTER IT HAD PASSED
|
|
3197
3282
|
(this run counted; a cache hit is a pass, it replayed one), or when it
|
|
3198
|
-
needed a retry (`exec.retries` / `--
|
|
3283
|
+
needed a retry (`exec.retries` / `--retries`) this run. A failure on a
|
|
3199
3284
|
key that never passed is a break and carries no note — a changed input
|
|
3200
3285
|
that fails is what a red run usually means — and the first pass after
|
|
3201
3286
|
only failures is a recovery (a missing tool installed), not a flake.
|
|
@@ -3241,7 +3326,7 @@ Only a run's own output (`vx run`, `vx watch`) is painted, and the
|
|
|
3241
3326
|
help on a terminal: headings bold, `vx <verb>` and each flag or example
|
|
3242
3327
|
in cyan; `NO_COLOR` or `FORCE_COLOR=0` turns it off
|
|
3243
3328
|
(`tests/help-colors.test.ts`). Every other verb (`show`, `info`, `why`,
|
|
3244
|
-
`last`, a `--dry` plan, `--graph`, `cache
|
|
3329
|
+
`last`, a `--dry` plan, `--graph`, `cache clean`), and the help off a
|
|
3245
3330
|
terminal, prints plain text whatever `FORCE_COLOR` says, so a script
|
|
3246
3331
|
that forces colour for a run's log still parses them
|
|
3247
3332
|
(`tests/cli-colors-e2e.test.ts`).
|
|
@@ -3249,8 +3334,9 @@ that forces colour for a run's log still parses them
|
|
|
3249
3334
|
## Remote cache (plugin-driven)
|
|
3250
3335
|
|
|
3251
3336
|
Core ships **no remote-cache wire client** — the remote cache is a
|
|
3252
|
-
plugin concern. A `cache` plugin
|
|
3253
|
-
|
|
3337
|
+
plugin concern. A `cache` plugin returns a wire client (a
|
|
3338
|
+
`RemoteCacheLayer`) and core layers it over the local cache;
|
|
3339
|
+
`@vzn/vx-reapi` provides one for any Bazel REAPI server
|
|
3254
3340
|
(ActionCache + CAS).
|
|
3255
3341
|
|
|
3256
3342
|
Reads try local first, then remote (hydrating local on remote hit),
|
|
@@ -3264,7 +3350,7 @@ implement core's `RemoteCacheLayer` interface in a plugin's `cache`
|
|
|
3264
3350
|
capability — the recipe lives in the plugins guide. Embedders
|
|
3265
3351
|
holding a wire client can inject it per-run via
|
|
3266
3352
|
`RunOptions.remoteCache` (explicit injection wins over the plugin
|
|
3267
|
-
consult).
|
|
3353
|
+
consult).
|
|
3268
3354
|
|
|
3269
3355
|
## Run analytics
|
|
3270
3356
|
|