@vzn/vx 0.0.636 → 0.0.637
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 +213 -207
- package/docs/cli.md +699 -655
- package/docs/execution.md +67 -75
- package/docs/features.md +44 -40
- package/docs/patterns.md +26 -25
- package/docs/schema.md +376 -198
- 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 +2 -2
- package/schemas/show.json +5 -3
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +16 -12
- package/src/bin.ts +17 -2
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +105 -53
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +7 -1
- package/src/cache/inputs.ts +125 -35
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +8 -17
- package/src/cache/layered-cache.ts +16 -102
- package/src/cache/policy.ts +25 -29
- package/src/cache/run-history.ts +36 -14
- package/src/cache/schema.ts +21 -23
- package/src/cache/scoped-cache.ts +224 -0
- package/src/cli/cache.ts +150 -28
- package/src/cli/completions.ts +2 -2
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +94 -63
- package/src/cli/index.ts +42 -13
- package/src/cli/info.ts +38 -23
- package/src/cli/init.ts +115 -406
- package/src/cli/last.ts +145 -4
- package/src/cli/lock.ts +333 -111
- package/src/cli/plan-format.ts +2 -2
- package/src/cli/plugin-commands.ts +5 -9
- package/src/cli/plugin-templates.ts +10 -10
- package/src/cli/run.ts +103 -149
- package/src/cli/select.ts +33 -39
- package/src/cli/show.ts +17 -19
- package/src/cli/task-verb.ts +2 -9
- package/src/cli/upgrade.ts +402 -145
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +33 -60
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +12 -3
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +188 -215
- package/src/cli/why.ts +28 -17
- package/src/cli/workspace-config.ts +31 -68
- 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 +19 -7
- package/src/graph/task-graph.ts +44 -20
- package/src/handoff.ts +169 -0
- package/src/index.ts +24 -57
- package/src/orchestrator/affected-tasks.ts +7 -5
- 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 +158 -63
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +8 -3
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +70 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +18 -4
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +71 -39
- package/src/orchestrator/metrics.ts +71 -6
- 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 +87 -63
- package/src/orchestrator/plugin.ts +94 -60
- package/src/orchestrator/prepare.ts +113 -72
- package/src/orchestrator/projects.ts +102 -99
- package/src/orchestrator/remote-prefetch.ts +2 -1
- 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-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +121 -95
- 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 +48 -6
- package/src/orchestrator/summary.ts +46 -62
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +3 -3
- package/src/settings.ts +417 -0
- package/src/util/errors.ts +1 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +8 -2
- package/src/util/paths.ts +10 -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/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +77 -222
- 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 +57 -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 +242 -146
- 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 +362 -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/schema.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Config schema
|
|
2
2
|
|
|
3
3
|
Complete reference for every field accepted by `vx.config.{ts,mts,js,mjs,cts,cjs}`
|
|
4
|
-
and the
|
|
4
|
+
and the workspace-level `.vx/workspace.{ts,mts,js,mjs,cts,cjs}`, which is
|
|
5
|
+
required: it marks the workspace root (cli.md § The workspace root). The
|
|
5
6
|
authoritative TypeScript definitions live in `src/config.ts` and are
|
|
6
7
|
re-exported from `@vzn/vx`.
|
|
7
8
|
|
|
@@ -9,18 +10,30 @@ re-exported from `@vzn/vx`.
|
|
|
9
10
|
|
|
10
11
|
```
|
|
11
12
|
<workspaceRoot>/
|
|
12
|
-
├──
|
|
13
|
-
├── vx
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
├── vx.config.ts (optional: the root project, named `//`)
|
|
14
|
+
├── .vx/
|
|
15
|
+
│ ├── workspace.ts (required: marks the root; workspace-wide settings)
|
|
16
|
+
│ ├── projects.lock.json (`vx lock`, minified; committed)
|
|
17
|
+
│ ├── .gitattributes (`projects.lock.json linguist-generated=true`; written when missing)
|
|
18
|
+
│ ├── .gitignore (`/cache`; written by `vx init` and the cache when missing)
|
|
19
|
+
│ └── cache/ (machine-local state; the only part git ignores)
|
|
20
|
+
└── <any>/<dir>/
|
|
21
|
+
├── vx.config.ts (this directory is a project; its tasks)
|
|
22
|
+
└── vx.lock.json (`vx lock`: its resolved config, pretty, for review)
|
|
18
23
|
```
|
|
19
24
|
|
|
20
|
-
A project is
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
**A project is a directory holding a `vx.config.*`**, in any language;
|
|
26
|
+
core reads no `package.json` and no package-manager member list. `vx lock`
|
|
27
|
+
finds them (git's tracked and unignored files; a config at or under a
|
|
28
|
+
nested `.vx/workspace.*` is that workspace's) and records each by
|
|
29
|
+
directory; a run takes its projects from the lock, so a config added since
|
|
30
|
+
runs once `vx lock` records it. A `discover` plugin may add directories
|
|
31
|
+
with no config (`@vzn/vx-lockfile` adds the package manager's members).
|
|
32
|
+
|
|
33
|
+
A project's name is its config's `name`, else the name a `discover` plugin
|
|
34
|
+
gives (`@vzn/vx-lockfile` gives `package.json#name`), else its
|
|
35
|
+
root-relative path (`apps/web`); the root is `//`. Two projects of one
|
|
36
|
+
name are refused, naming both directories.
|
|
24
37
|
|
|
25
38
|
## Project config
|
|
26
39
|
|
|
@@ -28,6 +41,7 @@ tasks.
|
|
|
28
41
|
import { defineProject } from '@vzn/vx/config'
|
|
29
42
|
|
|
30
43
|
export default defineProject({
|
|
44
|
+
name: 'web',
|
|
31
45
|
tags: ['scope:web', 'type:app'],
|
|
32
46
|
tasks: {
|
|
33
47
|
<taskName>: TaskConfig,
|
|
@@ -38,6 +52,7 @@ export default defineProject({
|
|
|
38
52
|
|
|
39
53
|
```ts
|
|
40
54
|
interface ProjectConfig {
|
|
55
|
+
name?: string // what `<name>#<task>`, filters and `dependsOn` address; no "#"
|
|
41
56
|
tags?: readonly string[] // labels for `--filter tag:<name>`
|
|
42
57
|
tasks?: Record<string, TaskConfig> // keyed by task name
|
|
43
58
|
}
|
|
@@ -115,7 +130,8 @@ bare `vx run build` does not select it: run where no selected project
|
|
|
115
130
|
declares `build`, it is refused as any undeclared name is, while
|
|
116
131
|
`lib#build` names it and a dependant's `^build` reaches it. `vx show`
|
|
117
132
|
and `vx info` count it; `vx show build` and the `vx build` hint, which
|
|
118
|
-
name declared tasks, do not.
|
|
133
|
+
name declared tasks, do not. Its `^build` needs an edge source as any
|
|
134
|
+
`^` does: with no plugin returning `deps`, running it is refused.
|
|
119
135
|
|
|
120
136
|
### `description` (optional)
|
|
121
137
|
|
|
@@ -195,7 +211,7 @@ Maximum time in milliseconds before vx SIGTERMs the child. Omitted →
|
|
|
195
211
|
no limit.
|
|
196
212
|
|
|
197
213
|
```ts
|
|
198
|
-
build: { exec: { command: 'tsc -b', timeout:
|
|
214
|
+
build: { exec: { command: 'tsc -b', timeout: '2m' } }
|
|
199
215
|
```
|
|
200
216
|
|
|
201
217
|
- For a **normal task**, `timeout` bounds the total run time, counted
|
|
@@ -221,15 +237,15 @@ limit" — the platform silently reduces it to **1 ms**, so the task would
|
|
|
221
237
|
be killed the moment it spawns and reported `failed`. vx refuses it at
|
|
222
238
|
load rather than let that happen. **Omit `timeout` entirely for no
|
|
223
239
|
limit.** The same bound applies to `--timeout` and to the workspace-level
|
|
224
|
-
`timeout`; the `
|
|
240
|
+
`run.timeout`; the `VX_RUN_TIMEOUT` env rung is clamped to it instead of
|
|
225
241
|
refused, since that rung never fails a run.
|
|
226
242
|
|
|
227
243
|
A task with **no** `exec.timeout` falls back to a run-level default, if
|
|
228
244
|
one is set. Precedence, highest first: **per-task `exec.timeout` →
|
|
229
|
-
`--timeout <
|
|
230
|
-
workspace `timeout`** (see workspace config below). Per-task always
|
|
245
|
+
`--timeout <duration>` / `RunOptions.timeout` → `VX_RUN_TIMEOUT` →
|
|
246
|
+
workspace `run.timeout`** (see workspace config below). Per-task always
|
|
231
247
|
wins. The run-level defaults are threaded as run options, so — like
|
|
232
|
-
`--
|
|
248
|
+
`--retries` — they never touch a cache key: a `--timeout` run cache-hits a
|
|
233
249
|
plain run's entry.
|
|
234
250
|
|
|
235
251
|
**Why no multi-step `commands: string[]`?** Per-task caching is the
|
|
@@ -272,7 +288,7 @@ test: { exec: { command: 'bun test', retries: 1 } }
|
|
|
272
288
|
- Not allowed with `persistent` — a persistent task has no exit to
|
|
273
289
|
retry (config error, like `cache` + `persistent`).
|
|
274
290
|
|
|
275
|
-
The run-level default is `vx run --
|
|
291
|
+
The run-level default is `run.retries` (`vx run --retries <n>`, `VX_RUN_RETRIES`) — it applies to tasks
|
|
276
292
|
that don't declare their own `retries`, never to a persistent one (a
|
|
277
293
|
server that exits before it is ready fails at once); explicit config
|
|
278
294
|
always wins, including an explicit `retries: 0`. The CLI flag never
|
|
@@ -360,14 +376,14 @@ for it to exit. Instead it considers the task "ready":
|
|
|
360
376
|
The trailing partial line is tested too, so prompt-style banners
|
|
361
377
|
without a newline (`printf 'Listening on :3000'`) count, and so does
|
|
362
378
|
output read up to 250 ms after the shell exits. A server
|
|
363
|
-
not ready after 10 s (`
|
|
379
|
+
not ready after 10 s (`run.readyNotice`) is said once, naming
|
|
364
380
|
the pattern it waits for, and whether `exec.timeout` bounds the wait.
|
|
365
381
|
|
|
366
382
|
```ts
|
|
367
383
|
dev: {
|
|
368
384
|
exec: {
|
|
369
385
|
command: 'vite',
|
|
370
|
-
timeout:
|
|
386
|
+
timeout: '30s', // bound the readiness wait
|
|
371
387
|
persistent: { readyWhen: 'Local:' },
|
|
372
388
|
},
|
|
373
389
|
}
|
|
@@ -565,7 +581,7 @@ does: npm-run-all calls that manager back and falls back to a global `npm`
|
|
|
565
581
|
without it. It names a binary on this machine, so it is not in the key.
|
|
566
582
|
|
|
567
583
|
Anything outside these three layers, the two variables vx sets for
|
|
568
|
-
the run (`VX_RUN_WORKSPACE`, `VX_RUN_TASK`) and `npm_execpath`, is
|
|
584
|
+
the run (`VX_RUN_WORKSPACE`, `VX_RUN_TASK`) and `npm_execpath`, is absent from the child's environment:
|
|
569
585
|
a host credential (`SSH_AUTH_SOCK`, `GITHUB_TOKEN`) reaches a task only
|
|
570
586
|
when `passThrough` names it, held end to end by `env.test.ts` (a
|
|
571
587
|
sandboxed task with a restricted network also gets the sandbox's own
|
|
@@ -579,6 +595,10 @@ matches Turbo's `passThroughEnv` semantics and exists for two reasons:
|
|
|
579
595
|
with different `FOO=bar` set would produce different outputs and
|
|
580
596
|
the user would never know why.
|
|
581
597
|
|
|
598
|
+
This is hygiene, not a boundary: an unsandboxed task runs as you, so it
|
|
599
|
+
can read your files. vx hides its own environment from a task
|
|
600
|
+
(`modules/util-hide-env.md`), not your files. Only `exec.sandbox` keeps them out (`security.md` § What the sandbox stops).
|
|
601
|
+
|
|
582
602
|
**Masking.** The value of a variable whose name holds `TOKEN`, `SECRET`,
|
|
583
603
|
`KEY`, `PASSWORD`, `PASSWD` or `CREDENTIAL` (vx's own environment or a
|
|
584
604
|
task's `define`, six characters or more; not a name ending `_FILE`,
|
|
@@ -588,13 +608,13 @@ shows it: the task's output and the line vx adds under a shell's 127 or
|
|
|
588
608
|
replays, the command a cache entry stores (what `vx why` prints and a
|
|
589
609
|
remote cache receives), the `$ command` line, telemetry records,
|
|
590
610
|
`vx show`, the hashes `vx why` gives for such a variable in
|
|
591
|
-
`cache.inputs.env` (its value, unsalted: the row names it and its change), an executor's error, a plugin's warning or failure (a remote's reply; the failure masked in the error `run()` rejects with),
|
|
611
|
+
`cache.inputs.env` (its value, unsalted: the row names it and its change), an executor's error, a plugin's warning or failure (a remote's reply; the failure masked in the error `run()` rejects with), a failed `cache.inputs` runtime probe's error (its command and output), and the run's own invocation line that `vx last` prints (a
|
|
592
612
|
secret passed after `--`) and its `--tag`s. So are every string of a
|
|
593
613
|
task `vx show` prints (a runtime probe, `readyWhen`, the description),
|
|
594
614
|
the description `--dry` and the task picker print, a runtime probe's
|
|
595
615
|
command as the run history stores it and `vx why` names its row (two
|
|
596
616
|
probes that mask alike are numbered, `echo *** (2)`), the readiness
|
|
597
|
-
notice's pattern, a sandbox violation line,
|
|
617
|
+
notice's pattern, a sandbox violation line,
|
|
598
618
|
the error a verb ends on (a plugin's failure, a config's own throw) and
|
|
599
619
|
`vx watch`'s `cycle failed` line. A multi-line value (a PEM
|
|
600
620
|
key) is also masked line by line, each line of six characters or more,
|
|
@@ -622,13 +642,13 @@ Turbo/Nx-style micro-syntax — a flat array of strings:
|
|
|
622
642
|
dependsOn?: readonly string[]
|
|
623
643
|
```
|
|
624
644
|
|
|
625
|
-
| Form | Meaning
|
|
626
|
-
| ------------ |
|
|
627
|
-
| `'name'` | Same-project task `name`.
|
|
628
|
-
| `'^name'` | The `name` task in the nearest
|
|
629
|
-
| `'pkg#name'` | The `name` task in a specific other package (cross-project edge).
|
|
630
|
-
| `'name.*'` | Every OTHER same-project task matching the pattern (`*` = any).
|
|
631
|
-
| `'^name.*'` | All matching tasks of the nearest deps that declare any (pattern).
|
|
645
|
+
| Form | Meaning |
|
|
646
|
+
| ------------ | ------------------------------------------------------------------------------- |
|
|
647
|
+
| `'name'` | Same-project task `name`. |
|
|
648
|
+
| `'^name'` | The `name` task in the nearest project deps (a plugin's edges) that declare it. |
|
|
649
|
+
| `'pkg#name'` | The `name` task in a specific other package (cross-project edge). |
|
|
650
|
+
| `'name.*'` | Every OTHER same-project task matching the pattern (`*` = any). |
|
|
651
|
+
| `'^name.*'` | All matching tasks of the nearest deps that declare any (pattern). |
|
|
632
652
|
|
|
633
653
|
Examples:
|
|
634
654
|
|
|
@@ -649,14 +669,16 @@ Semantics:
|
|
|
649
669
|
**type-checked against this config's task keys** — a typo is a
|
|
650
670
|
compile error (`'^name'` / `'pkg#name'` reference other projects
|
|
651
671
|
and stay free strings).
|
|
652
|
-
- **`'^name'`** — nearest-holder frontier. Walk the
|
|
653
|
-
from this project's direct deps
|
|
654
|
-
`
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
672
|
+
- **`'^name'`** — nearest-holder frontier. Walk the project graph
|
|
673
|
+
from this project's direct deps: the edges a plugin's `discover`
|
|
674
|
+
returns as `deps` (the lockfile plugins `pnpm()` / `bun()` / `npm()` /
|
|
675
|
+
`yarn()` return what the package manager installed; core reads no
|
|
676
|
+
edge from a `package.json`) and the cross-project `pkg#task` edges.
|
|
677
|
+
With no plugin returning `deps` the workspace has no edge source and
|
|
678
|
+
a `'^name'` is refused (`VX_E_DEPENDS_ON`: "`^build` needs project
|
|
679
|
+
edges; add pnpm()/bun()/npm()/yarn() or a plugin that returns
|
|
680
|
+
`deps`"): it would resolve to nothing and fold no upstream into the
|
|
681
|
+
key. With one, a project with no deps is a leaf; each path stops at the first dep
|
|
660
682
|
that declares the task and an edge is added to it (Turbo/Nx
|
|
661
683
|
direct-deps parity). The holder's own `dependsOn` is responsible
|
|
662
684
|
for anything deeper — chain `'^name'` in the holder to keep the
|
|
@@ -667,8 +689,8 @@ Semantics:
|
|
|
667
689
|
too, as long as SOME project in the workspace declares `name`: a
|
|
668
690
|
preset spreads `'^build'` over packages whose deps have no `build`.
|
|
669
691
|
A `'^name'` that **no project in the whole workspace declares** is a
|
|
670
|
-
hard error at graph-build time, since it can only be a typo: "
|
|
671
|
-
|
|
692
|
+
hard error at graph-build time, since it can only be a typo: "app#test:
|
|
693
|
+
dependsOn ^biuld but no project in the workspace declares
|
|
672
694
|
biuld". Until 2026-09-24 it resolved to no edges and the run went
|
|
673
695
|
green (Nx#32779 is the same bug). A scoped run (an anchored
|
|
674
696
|
`app#test`, `--filter`, a project directory) loads only its projects
|
|
@@ -721,7 +743,12 @@ means. `strategy` says which writes count:
|
|
|
721
743
|
be told from no edit.
|
|
722
744
|
- `'mutable'`: a file rewritten to the bytes the key folded counts as
|
|
723
745
|
unchanged, for a task that rewrites its own input identically
|
|
724
|
-
(tsdown's `package.json`); different bytes still count
|
|
746
|
+
(tsdown's `package.json`); different bytes still count, and so does
|
|
747
|
+
any write while another task that may write the file was running (one
|
|
748
|
+
of the same project, or one whose sandbox grants writes elsewhere in
|
|
749
|
+
the workspace; a server `vx watch` keeps from an earlier run counts),
|
|
750
|
+
since it may have changed the file and put it back.
|
|
751
|
+
The line names that task; order the two with `dependsOn` to save.
|
|
725
752
|
- `'unchecked'`: none. The entry is saved under the key taken before the
|
|
726
753
|
command, whatever the command did to its inputs; `severity` is ignored.
|
|
727
754
|
A key that no longer held before the command started still withholds
|
|
@@ -729,19 +756,21 @@ means. `strategy` says which writes count:
|
|
|
729
756
|
|
|
730
757
|
A write that counts withholds the entry, and `severity` says what else
|
|
731
758
|
happens: `'warn'` (the default) keeps the result, says why on the status
|
|
732
|
-
line, and the run goes on; `'error'` fails the task, so the run fails
|
|
759
|
+
line, and the run goes on; `'error'` fails the task, so the run fails,
|
|
760
|
+
and its label names the file: `failed (src/a.ts changed during the run, exit 1)`
|
|
761
|
+
(`inputMoved` in `vx last --format json`).
|
|
733
762
|
Either way, every task that ran over the task's outputs, down the graph,
|
|
734
763
|
withholds its save too, and a task of any project that declares the
|
|
735
764
|
moved file as an input withholds its own.
|
|
736
765
|
|
|
737
766
|
Each field is resolved on its own, highest first:
|
|
738
767
|
|
|
739
|
-
1. `
|
|
740
|
-
`
|
|
768
|
+
1. `VX_CACHE_INPUT_STABILITY_STRATEGY` (`immutable`, `mutable`,
|
|
769
|
+
`unchecked`) and `VX_CACHE_INPUT_STABILITY_SEVERITY` (`warn`, `error`), for the whole run
|
|
741
770
|
(a CI job that wants `error` sets it without editing configs); any
|
|
742
771
|
other value is refused before a task runs;
|
|
743
772
|
2. the task's own `cache.inputStability`;
|
|
744
|
-
3. the workspace's `inputStability` (
|
|
773
|
+
3. the workspace's `inputStability` (`.vx/workspace.ts`);
|
|
745
774
|
4. the default, `'immutable'` and `'warn'`.
|
|
746
775
|
|
|
747
776
|
Never folded into a cache key.
|
|
@@ -845,15 +874,15 @@ files: ['app/[id]/**'] // a bracket is literal: the route dir `[id]`
|
|
|
845
874
|
```
|
|
846
875
|
|
|
847
876
|
Empty array is valid — the cache key still incorporates command, env,
|
|
848
|
-
upstream hashes
|
|
849
|
-
|
|
850
|
-
|
|
877
|
+
upstream hashes and any plugin's key material. A task with no _file_
|
|
878
|
+
inputs (e.g. a pure "download-and-verify") still cache-misses on a
|
|
879
|
+
lockfile change when a lockfile plugin (`@vzn/vx-lockfile`) is declared.
|
|
851
880
|
|
|
852
881
|
Always applied to every glob pass (regardless of what you wrote):
|
|
853
882
|
|
|
854
883
|
- **gitignore filter** — workspace-root + project `.gitignore`.
|
|
855
|
-
- **Always-ignored** — `.git/**`, `.vx/**`, `*.tsbuildinfo`,
|
|
856
|
-
`vx
|
|
884
|
+
- **Always-ignored** — `.git/**`, `.vx/cache/**`, `*.tsbuildinfo`,
|
|
885
|
+
`.vx/projects.lock.json`, `vx.lock.json`, `*.bun-build`, and Bun's cross-compile extraction
|
|
857
886
|
directory `.<16 hex>-<8 hex>.tmp/**`, and vx's own restore temp
|
|
858
887
|
`.vx-tmp-*`, at any depth.
|
|
859
888
|
- **Installs** — an untracked file under any `node_modules/` (ignored or
|
|
@@ -907,8 +936,8 @@ backslash separator (`src\**`, `src\*.ts`) or a drive (`C:\src\**`,
|
|
|
907
936
|
`C:/src/**`). A backslash before a bracket, a brace, a `!` or a
|
|
908
937
|
backslash stays an escape.
|
|
909
938
|
|
|
910
|
-
Still applied: the always-ignored set (`.git/**`, `.vx/**`,
|
|
911
|
-
`*.tsbuildinfo`, `vx
|
|
939
|
+
Still applied: the always-ignored set (`.git/**`, `.vx/cache/**`,
|
|
940
|
+
`*.tsbuildinfo`, `.vx/projects.lock.json`, `vx.lock.json`, `*.bun-build`, `.<16 hex>-<8 hex>.tmp/**`, `.vx-tmp-*`),
|
|
912
941
|
untracked files under `node_modules/`, and the task's own declared
|
|
913
942
|
outputs, `outputs.workspaceFiles` and the `outputs.files` its globs reach
|
|
914
943
|
in its own project (a task never invalidates itself).
|
|
@@ -985,8 +1014,8 @@ Semantics:
|
|
|
985
1014
|
`resolveInputs`), exactly like `inputs.env` reads host env values
|
|
986
1015
|
live. `vx lock` freezes the command _strings_ (they're in the
|
|
987
1016
|
resolved config), not their output, so the field stays correct under
|
|
988
|
-
|
|
989
|
-
busts the cache.
|
|
1017
|
+
the lock every run reads — the changed output of a locked command
|
|
1018
|
+
still busts the cache.
|
|
990
1019
|
- A **non-zero exit fails the run** (a hard `UserError` naming the
|
|
991
1020
|
command and exit code) — fail-loud, like a missing git binary. A
|
|
992
1021
|
flaky probe should not silently degrade to a stale hit.
|
|
@@ -1110,6 +1139,7 @@ task's outputs. Typical case: an integration-test task `dependsOn`s
|
|
|
1110
1139
|
interface CacheOutputs {
|
|
1111
1140
|
files: readonly string[] // required
|
|
1112
1141
|
workspaceFiles?: readonly string[] // optional; workspace-root-relative
|
|
1142
|
+
clean?: boolean // optional; false keeps outputs on a miss
|
|
1113
1143
|
}
|
|
1114
1144
|
```
|
|
1115
1145
|
|
|
@@ -1143,7 +1173,7 @@ it (never following it). An output DIRECTORY that is a symlink
|
|
|
1143
1173
|
(`dist -> real-out`) is followed: its target is cleaned, saved and
|
|
1144
1174
|
restored as the output; one that resolves outside the project refuses
|
|
1145
1175
|
the task, naming the link. A link to a directory, a dangling one, or
|
|
1146
|
-
one whose target is outside the project cannot be stored — the save refuses it by name and caches nothing, so
|
|
1176
|
+
one whose target is outside the project or, for a sandboxed task, a file its grants do not let it read cannot be stored — the save refuses it by name and caches nothing, so
|
|
1147
1177
|
the next run executes again. A link to another output of the same task
|
|
1148
1178
|
is stored wherever it is: `gen/latest -> v2.txt` under a
|
|
1149
1179
|
`workspaceFiles` output caches (A-53). The clean also prunes the directories it
|
|
@@ -1154,13 +1184,21 @@ Empty `[]` is valid for tasks that produce no files (e.g. `lint`,
|
|
|
1154
1184
|
`typecheck`, `test`); you still cache the no-op success so the next
|
|
1155
1185
|
run is a no-op too.
|
|
1156
1186
|
|
|
1187
|
+
**`clean: false`** keeps the outputs on a miss instead of wiping them
|
|
1188
|
+
first, for a tool that reads its own last output (tsc's
|
|
1189
|
+
`.tsbuildinfo`). An entry still holds only what a clean run would
|
|
1190
|
+
leave: if the run leaves any old file unchanged, nothing is cached, and
|
|
1191
|
+
the next run executes again. A retry still starts clean, and a hit
|
|
1192
|
+
still restores bit-identically. Omitted → the workspace's
|
|
1193
|
+
`cache.outputs.clean`, else `true`.
|
|
1194
|
+
|
|
1157
1195
|
A **`!` entry takes a path back** from what the positive globs select
|
|
1158
|
-
(A-44; a
|
|
1159
|
-
|
|
1196
|
+
(A-44; a scratch dir under `dist` stays out of the clean this way; a
|
|
1197
|
+
tracked file needs no `!`, below):
|
|
1160
1198
|
|
|
1161
1199
|
```ts
|
|
1162
1200
|
outputs: {
|
|
1163
|
-
files: ['dist/**', '
|
|
1201
|
+
files: ['dist/**', '!**/*.tmp']
|
|
1164
1202
|
}
|
|
1165
1203
|
```
|
|
1166
1204
|
|
|
@@ -1181,10 +1219,12 @@ positive globs, so a task with a `!` entry keeps it (A-46).
|
|
|
1181
1219
|
- Before restore on a cache hit: same wipe, then files are copied
|
|
1182
1220
|
from the cache entry into the project dir. The post-restore tree
|
|
1183
1221
|
is the cached snapshot, byte-for-byte.
|
|
1222
|
+
- A file git tracks is never wiped (2026-10-14). It is still saved and
|
|
1223
|
+
restored, and stays in the task's key, so an edit to it moves the key.
|
|
1184
1224
|
|
|
1185
1225
|
Skipped when `cache.outputs.files` is empty (nothing declared as
|
|
1186
1226
|
output) and when no cache-write axis is enabled (e.g. `--no-cache`,
|
|
1187
|
-
or a read-only `--cache=local:r`) — the user is debugging and
|
|
1227
|
+
or a read-only `--cache-policy=local:r`) — the user is debugging and
|
|
1188
1228
|
managing the tree themselves. `--force` keeps writes on, so it does
|
|
1189
1229
|
clean.
|
|
1190
1230
|
|
|
@@ -1225,7 +1265,7 @@ restore wins. A workspace output is also compared with every other
|
|
|
1225
1265
|
project's `files` outputs, read from the root: `packages/b/dist/a.txt`
|
|
1226
1266
|
collides with `b`'s `dist/**` (item 1088). An overlap
|
|
1227
1267
|
between two tasks one of which depends on the other is refused too,
|
|
1228
|
-
unless the workspace sets `rules
|
|
1268
|
+
unless the workspace sets `run.rules.exclusiveOutputs: false` (X-53);
|
|
1229
1269
|
then the dependant is additive and owns only what its run adds to the
|
|
1230
1270
|
tree (`caching.md` § Additive outputs, item 588). Across the two namespaces
|
|
1231
1271
|
an upstream `files` task is told the dependant's globs in its own
|
|
@@ -1348,7 +1388,7 @@ vx removes the empty file it made (it takes back any placeholder the
|
|
|
1348
1388
|
task never wrote, so an unwritten one is never archived as an output).
|
|
1349
1389
|
A grant that leaves the project through a symlink is refused: the grant
|
|
1350
1390
|
binds the path it names, and vx follows no link out of the project. So
|
|
1351
|
-
is one whose bind would make `.git`, `.vx`, the cache directory or a
|
|
1391
|
+
is one whose bind would make `.git`, `.vx/cache`, the cache directory or a
|
|
1352
1392
|
nested project writable:
|
|
1353
1393
|
a file grant at a single-package workspace's root binds the root. A
|
|
1354
1394
|
directory grant is judged as the directory, made before the judgement
|
|
@@ -1414,7 +1454,16 @@ shares (L-41): `~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.azure`, `~/.kube`,
|
|
|
1414
1454
|
`~/.bunfig.toml`, `~/.config/.bunfig.toml`, `~/.cargo/credentials`,
|
|
1415
1455
|
`~/.cargo/credentials.toml`, `~/.gem/credentials`, `~/.config/hub`,
|
|
1416
1456
|
`~/.config/containers/auth.json`, `~/.terraform.d/credentials.tfrc.json`,
|
|
1417
|
-
`~/.vault-token`, `~/.pgpass
|
|
1457
|
+
`~/.vault-token`, `~/.pgpass`, `~/.config/configstore/firebase-tools.json`,
|
|
1458
|
+
`~/.local/share/com.vercel.cli/auth.json`,
|
|
1459
|
+
`~/Library/Application Support/com.vercel.cli/auth.json`,
|
|
1460
|
+
`~/.netlify/config.json`, `~/.config/netlify/config.json`,
|
|
1461
|
+
`~/Library/Preferences/netlify/config.json`, `~/.wrangler/config`,
|
|
1462
|
+
`~/.config/.wrangler/config`, `~/Library/Preferences/.wrangler/config`,
|
|
1463
|
+
`~/.fly/config.yml`, `~/.supabase/access-token`, `~/.config/doctl`,
|
|
1464
|
+
`~/Library/Application Support/doctl`, `~/.config/stripe/config.toml`,
|
|
1465
|
+
`~/.config/op`, `~/.expo/state.json`, `~/.claude/.credentials.json`,
|
|
1466
|
+
`~/.codex/auth.json`.
|
|
1418
1467
|
What it grants from there is the union of the read grants and, on Linux,
|
|
1419
1468
|
the DIRECTORY holding each file-shaped write grant (above).
|
|
1420
1469
|
Nothing is inherited from `cache` — `cache.inputs` says what INVALIDATES a task, `sandbox.allow`
|
|
@@ -1574,128 +1623,245 @@ The loader rejects:
|
|
|
1574
1623
|
- A task with no `exec`, no `dependsOn` and no `cache` (literal no-op).
|
|
1575
1624
|
- A group whose `cache.outputs` names a file (a group writes nothing).
|
|
1576
1625
|
|
|
1577
|
-
## Workspace config (
|
|
1626
|
+
## Workspace config (`.vx/workspace.ts`)
|
|
1578
1627
|
|
|
1579
|
-
Loaded from
|
|
1580
|
-
|
|
1581
|
-
|
|
1628
|
+
Loaded from `.vx/workspace.{ts,mts,js,mjs,cts,cjs}`: the directory holding
|
|
1629
|
+
`.vx` is the workspace root. `.vx` also holds the lock (`.vx/projects.lock.json`)
|
|
1630
|
+
and `.vx/cache`, the machine-local state (the cache, the workspace id),
|
|
1631
|
+
which is the only part git ignores: `.vx/.gitignore` says `/cache`. Every field falls back to its
|
|
1632
|
+
built-in default.
|
|
1582
1633
|
|
|
1583
1634
|
```ts
|
|
1584
1635
|
import { defineWorkspace } from '@vzn/vx/config'
|
|
1585
1636
|
import { otel } from '@vzn/vx-otel'
|
|
1586
1637
|
|
|
1587
1638
|
export default defineWorkspace({
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1639
|
+
run: {
|
|
1640
|
+
concurrency: 8,
|
|
1641
|
+
timeout: '10m',
|
|
1642
|
+
affectedBase: 'origin/main',
|
|
1643
|
+
schedule: 'most-work',
|
|
1644
|
+
continue: 'deps-ok',
|
|
1645
|
+
killGrace: '2s',
|
|
1646
|
+
teardownTimeout: '3s',
|
|
1647
|
+
readyNotice: '10s',
|
|
1648
|
+
rules: {
|
|
1649
|
+
exclusiveOutputs: true,
|
|
1650
|
+
upfrontKeys: true,
|
|
1651
|
+
dependencies: [{ from: 'tag:lib', deny: ['tag:app'] }],
|
|
1652
|
+
},
|
|
1653
|
+
},
|
|
1654
|
+
cache: {
|
|
1655
|
+
dir: 'build/.vx-cache',
|
|
1656
|
+
download: 'all',
|
|
1657
|
+
retention: { olderThan: '30d', maxSize: '10GB' },
|
|
1658
|
+
policy: 'local:rw,remote:r',
|
|
1659
|
+
scope: 'main',
|
|
1660
|
+
inputStability: { strategy: 'immutable', severity: 'warn' },
|
|
1661
|
+
outputs: { clean: true },
|
|
1662
|
+
},
|
|
1663
|
+
lock: { reviewFiles: true },
|
|
1664
|
+
watch: { poll: false },
|
|
1597
1665
|
plugins: [otel()],
|
|
1598
1666
|
})
|
|
1599
1667
|
```
|
|
1600
1668
|
|
|
1669
|
+
The workspace holds settings in groups (`run`, `cache`, `lock`, `watch`) and the
|
|
1670
|
+
`plugins`. Every setting has one path, and its environment variable and
|
|
1671
|
+
flag derive from it: `run.timeout` is `VX_RUN_TIMEOUT` and
|
|
1672
|
+
`vx run --timeout`. A flag beats its variable, which beats this file,
|
|
1673
|
+
which beats the built-in. A setting a task can set itself
|
|
1674
|
+
(`exec.timeout`, `exec.retries`, `cache.inputStability`) takes the
|
|
1675
|
+
task's value over all of them; the input-stability variables are the one
|
|
1676
|
+
exception, overriding every task for the run. A duration carries its
|
|
1677
|
+
unit (`'500ms'`, `'30s'`, `'10m'`, `'2h'`, `'30d'`), and a bare number is
|
|
1678
|
+
refused naming the forms with units; a size carries one too (`'10GB'`).
|
|
1679
|
+
`debug.timing`, `debug.dumpable`, `config.workerTimeout` and `run.outputLogs` are read
|
|
1680
|
+
before the workspace loads, so they have a variable (and a flag) only.
|
|
1681
|
+
None is folded into a cache key. `vx info` lists every setting not at its
|
|
1682
|
+
default and where it came from.
|
|
1683
|
+
|
|
1684
|
+
### Settings
|
|
1685
|
+
|
|
1686
|
+
<!-- settings table: generated by tests/settings.test.ts -->
|
|
1687
|
+
|
|
1688
|
+
| Setting | Variable | Flag | Value | Default | What it does |
|
|
1689
|
+
| ---------------------------------------------- | ----------------------------------- | -------------------- | ----------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1690
|
+
| `run.concurrency` | `VX_RUN_CONCURRENCY` | `--concurrency` | a positive integer, or a share of the cores like '50%' | the cores this process may use | Tasks at once: a positive integer, or a share of the cores (`'50%'`). |
|
|
1691
|
+
| `run.schedule` | `VX_RUN_SCHEDULE` | `--schedule` | one of 'most-work', 'critical-path', 'direct-dependents', 'ready-order' | most-work | Which ready task starts first when there are more than workers. |
|
|
1692
|
+
| `run.affectedBase` | `VX_RUN_AFFECTED_BASE` | — | a non-blank string | origin/HEAD, then the trunk, else HEAD~1 | The git ref a bare `--affected` compares with. |
|
|
1693
|
+
| `run.branch` (variable or flag only) | `VX_RUN_BRANCH` | `--branch` | a non-blank string | git's branch, then the CI provider's | 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. |
|
|
1694
|
+
| `run.timeout` | `VX_RUN_TIMEOUT` | `--timeout` | a duration like '500ms', '30s' or '5m', at most 24 days | none | A task's time limit, for tasks without their own `exec.timeout` (`'30m'`). A task's own `exec.timeout` beats it. |
|
|
1695
|
+
| `run.retries` | `VX_RUN_RETRIES` | `--retries` | a non-negative integer | 0 | Re-runs of a failed task, for tasks without their own `exec.retries`. A task's own `exec.retries` beats it. |
|
|
1696
|
+
| `run.continue` | `VX_RUN_CONTINUE` | `--continue` | one of 'never', 'deps-ok', 'always' | deps-ok | What a failed task takes down: `never` stops the run, `deps-ok` skips its dependents, `always` runs them too. |
|
|
1697
|
+
| `run.outputLogs` (variable or flag only) | `VX_RUN_OUTPUT_LOGS` | `--output-logs` | one of 'full', 'errors-only', 'hash-only', 'none' | `full` on CI, else by what the run selects | Which task output the run prints. Chosen before the workspace loads, so flag and env only. |
|
|
1698
|
+
| `run.killGrace` | `VX_RUN_KILL_GRACE` | `--kill-grace` | a duration like '500ms', '30s' or '5m', at most 24 days | 2s | How long a task that ignores SIGTERM gets before SIGKILL. |
|
|
1699
|
+
| `run.teardownTimeout` | `VX_RUN_TEARDOWN_TIMEOUT` | `--teardown-timeout` | a duration like '500ms', '30s' or '5m', at most 24 days | 3s | The bound on each plugin's end-of-run flush and teardown. |
|
|
1700
|
+
| `run.readyNotice` | `VX_RUN_READY_NOTICE` | `--ready-notice` | a duration like '500ms', '30s' or '5m', at most 24 days | 10s | How long a persistent task may take to match `readyWhen` before vx says it waits. |
|
|
1701
|
+
| `run.rules.exclusiveOutputs` | `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | — | true or false | true | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
1702
|
+
| `run.rules.upfrontKeys` | `VX_RUN_RULES_UPFRONT_KEYS` | — | true or false | true | Refuse a task whose inputs can match another task's outputs. |
|
|
1703
|
+
| `cache.dir` | `VX_CACHE_DIR` | `--cache-dir` | a non-blank string | .vx/cache, entries in ~/.vx/<repo>/cache | Where the cache lives, relative to the workspace root; named, it holds the whole cache. |
|
|
1704
|
+
| `cache.retention.maxSize` | `VX_CACHE_RETENTION_MAX_SIZE` | — | a size like '500MB' or '10GB' | none | Evict least-recently-used entries at each run's end until the cache is under this (`'10GB'`). |
|
|
1705
|
+
| `cache.retention.olderThan` | `VX_CACHE_RETENTION_OLDER_THAN` | — | a duration like '45s', '12h' or '30d' | none | Evict entries unused for this long at each run's end (`'30d'`). |
|
|
1706
|
+
| `cache.policy` | `VX_CACHE_POLICY` | `--cache-policy` | a non-blank string | every layer reads and writes | Which layers a run reads and writes (`'local:rw,remote:r'`); `--no-cache` and `--force` apply on top. |
|
|
1707
|
+
| `cache.scope` | `VX_CACHE_SCOPE` | `--cache-scope` | a non-blank string | none (the default keys) | Where cache writes land (`'pr-123'`): reads try the default key, then the scope's. |
|
|
1708
|
+
| `cache.download` | `VX_CACHE_DOWNLOAD` | `--cache-download` | one of 'all', 'toplevel', 'none' | all | Which remote hits land on disk: every one, only the requested tasks, or none. |
|
|
1709
|
+
| `cache.inputStability.strategy` | `VX_CACHE_INPUT_STABILITY_STRATEGY` | — | one of 'immutable', 'mutable', 'unchecked' | immutable | What a write to an input during the command means for the save, for tasks without their own. A task's own `cache.inputStability.strategy` beats it. |
|
|
1710
|
+
| `cache.inputStability.severity` | `VX_CACHE_INPUT_STABILITY_SEVERITY` | — | one of 'warn', 'error' | warn | Whether a skipped save warns or fails the task, for tasks without their own. A task's own `cache.inputStability.severity` beats it. |
|
|
1711
|
+
| `cache.outputs.clean` | `VX_CACHE_OUTPUTS_CLEAN` | — | true or false | true | Remove a task's declared outputs before it runs on a miss, for tasks without their own. A task's own `cache.outputs.clean` beats it. |
|
|
1712
|
+
| `lock.reviewFiles` | `VX_LOCK_REVIEW_FILES` | — | true or false | true | `vx lock` writes each project's resolved config, pretty, to `vx.lock.json` beside its `vx.config`, for review; vx never reads it. |
|
|
1713
|
+
| `watch.poll` | `VX_WATCH_POLL` | `vx watch --poll` | true or false | false | Poll for changes from the start instead of the OS watcher. |
|
|
1714
|
+
| `config.workerTimeout` (variable or flag only) | `VX_CONFIG_WORKER_TIMEOUT` | — | a duration like '500ms', '30s' or '5m', at most 24 days | 30s | How long one config evaluation may take. |
|
|
1715
|
+
| `debug.timing` (variable or flag only) | `VX_DEBUG_TIMING` | — | true or false | false | Print the run's stage timings to stderr. |
|
|
1716
|
+
| `debug.dumpable` (variable or flag only) | `VX_DEBUG_DUMPABLE` | — | true or false | false | Linux: keep vx dumpable, so strace or a debugger can read its memory and `/proc/<pid>/fd`, and an unsandboxed task its env. |
|
|
1717
|
+
|
|
1718
|
+
<!-- /settings table -->
|
|
1719
|
+
|
|
1601
1720
|
```ts
|
|
1602
1721
|
interface WorkspaceConfig {
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
/** Default per-task timeout (ms) for tasks without their own exec.timeout. */
|
|
1608
|
-
timeout?: number
|
|
1609
|
-
/** Evict from the local cache at the end of each run (the `vx cache prune` policy). */
|
|
1610
|
-
cacheRetention?: { olderThan?: string; maxSize?: string }
|
|
1611
|
-
/** The git ref a bare `--affected` compares with. */
|
|
1612
|
-
affectedBase?: string
|
|
1613
|
-
/** Where remote writes land: 'trusted' (default), 'read-only', or an untrusted scope name. */
|
|
1614
|
-
cacheScope?: string
|
|
1615
|
-
/** Graph rules checked before anything runs; each on unless set to false. */
|
|
1616
|
-
rules?: WorkspaceRules
|
|
1617
|
-
/** Which ready task starts first with no timings: 'most-work' (default), 'critical-path', 'direct-dependents', 'ready-order'. */
|
|
1618
|
-
schedule?: 'most-work' | 'critical-path' | 'direct-dependents' | 'ready-order'
|
|
1619
|
-
/** Every task's cache.inputStability default: { strategy, severity }. */
|
|
1620
|
-
inputStability?: InputStability
|
|
1722
|
+
run?: RunSettings
|
|
1723
|
+
cache?: CacheSettings
|
|
1724
|
+
lock?: LockSettings
|
|
1725
|
+
watch?: { poll?: boolean }
|
|
1621
1726
|
/** Run-level plugins (cache / executor / telemetry capabilities). */
|
|
1622
1727
|
plugins?: readonly (Plugin | undefined | null | false)[]
|
|
1623
1728
|
}
|
|
1624
1729
|
|
|
1625
|
-
interface
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1730
|
+
interface RunSettings {
|
|
1731
|
+
concurrency?: number | `${number}%`
|
|
1732
|
+
schedule?: 'most-work' | 'critical-path' | 'direct-dependents' | 'ready-order'
|
|
1733
|
+
affectedBase?: string
|
|
1734
|
+
timeout?: Duration
|
|
1735
|
+
retries?: number
|
|
1736
|
+
continue?: 'never' | 'deps-ok' | 'always'
|
|
1737
|
+
killGrace?: Duration
|
|
1738
|
+
teardownTimeout?: Duration
|
|
1739
|
+
readyNotice?: Duration
|
|
1740
|
+
/** Graph rules `vx lock` checks, never a run; each on unless set to false. */
|
|
1741
|
+
rules?: { exclusiveOutputs?: boolean; upfrontKeys?: boolean; dependencies?: DependencyRule[] }
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/** `run.rules.dependencies`, checked by `vx lock` only. */
|
|
1745
|
+
type DependencyRule = { from: string; allow?: string[]; deny?: string[] }
|
|
1746
|
+
|
|
1747
|
+
interface LockSettings {
|
|
1748
|
+
/** A pretty vx.lock.json beside each config, for review; on unless false. */
|
|
1749
|
+
reviewFiles?: boolean
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
interface CacheSettings {
|
|
1753
|
+
dir?: string
|
|
1754
|
+
retention?: { olderThan?: Duration; maxSize?: string }
|
|
1755
|
+
policy?: string
|
|
1756
|
+
scope?: string
|
|
1757
|
+
download?: 'all' | 'toplevel' | 'none'
|
|
1758
|
+
inputStability?: InputStability
|
|
1759
|
+
outputs?: { clean?: boolean }
|
|
1630
1760
|
}
|
|
1631
1761
|
```
|
|
1632
1762
|
|
|
1633
|
-
- **`concurrency`** —
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
- **`
|
|
1763
|
+
- **`run.concurrency`** — the cap on parallel tasks: a positive integer,
|
|
1764
|
+
or a share of the cores this process may use (`'50%'` on eight cores is
|
|
1765
|
+
4, never below 1; over 100% is allowed for I/O-bound work).
|
|
1766
|
+
- **`run.timeout`** — a task's time limit, for any task that declares no
|
|
1767
|
+
`exec.timeout` (which beats it, its flag and its variable). A runaway
|
|
1768
|
+
task's process group is SIGTERMed and the task reported `failed`.
|
|
1769
|
+
Purely a safety net — never folded into a cache key (a timed-out task
|
|
1770
|
+
fails and is never cached).
|
|
1771
|
+
- **`run.retries`** — re-runs of a failed task, for tasks without their
|
|
1772
|
+
own `exec.retries` (which beats it, `retries: 0` included); never a
|
|
1773
|
+
persistent one.
|
|
1774
|
+
- **`run.continue`** — what a failed task takes down: `never` stops
|
|
1775
|
+
dispatch, `deps-ok` (the default) skips its dependents, `always` runs
|
|
1776
|
+
them (`docs/cli.md` § Failure propagation).
|
|
1777
|
+
- **`run.killGrace`**, **`run.teardownTimeout`**, **`run.readyNotice`**
|
|
1778
|
+
— the SIGTERM → SIGKILL grace (2 s), the bound on each plugin's
|
|
1779
|
+
end-of-run flush and teardown (3 s; there is no "no limit"), and how
|
|
1780
|
+
long a persistent task may wait for `readyWhen` before vx says what it
|
|
1781
|
+
waits for (10 s).
|
|
1782
|
+
- **`run.rules`** — checked by `vx lock` and `vx lock --check` only;
|
|
1783
|
+
no run checks them.
|
|
1784
|
+
- **`cache.dir`** — unset, the workspace keeps its index and history in
|
|
1642
1785
|
`.vx/cache` and its entries and artifacts in its repository's shared
|
|
1643
1786
|
store (`~/.vx/<id>/cache`), where every other checkout of the
|
|
1644
1787
|
repository hits them (`docs/caching.md`). Named here, by
|
|
1645
1788
|
`VX_CACHE_DIR`, or by `--cache-dir`, the directory holds the whole
|
|
1646
|
-
cache, shared with no other workspace.
|
|
1789
|
+
cache, shared with no other workspace. `--cache-dir` beats
|
|
1790
|
+
`VX_CACHE_DIR`, which beats this.
|
|
1647
1791
|
Relative paths are resolved against the workspace
|
|
1648
1792
|
root, `~/` against the home directory; absolute paths are used
|
|
1649
|
-
as-is. The home directory itself (`~`) is refused. `vx run`, `vx cache
|
|
1793
|
+
as-is. The home directory itself (`~`) is refused. `vx run`, `vx cache clean`,
|
|
1650
1794
|
and any other reader use the same resolution
|
|
1651
1795
|
(`src/workspace/workspace.ts:resolveCacheDir`). The cache is a
|
|
1652
1796
|
directory of its own: a first index in one that holds a
|
|
1653
|
-
`package.json
|
|
1797
|
+
`package.json`, `pnpm-workspace.yaml`, a `vx.config.*` or a
|
|
1798
|
+
`.vx/workspace.*` (`''` and `'.'` name the
|
|
1654
1799
|
root), whose subdirectory does (`'packages'`), or that holds the
|
|
1655
1800
|
workspace (`'..'`, `'/'`) is refused before anything is written, since its `*`
|
|
1656
|
-
`.gitignore` would hide the sources from git and the cache keys.
|
|
1657
|
-
|
|
1801
|
+
`.gitignore` would hide the sources from git and the cache keys. A
|
|
1802
|
+
directory other than the default `.vx/cache` gets that `*` `.gitignore`;
|
|
1803
|
+
the default is ignored by `.vx/.gitignore` (`/cache`) instead, written
|
|
1804
|
+
when missing and never edited.
|
|
1805
|
+
- **`cache.retention`** — the `vx cache clean` policy, applied at the
|
|
1658
1806
|
end of every run: entries unused for
|
|
1659
1807
|
`olderThan` (`30d`, `12h`, `90m`, `45s`) go first, then the
|
|
1660
|
-
least-recently-used until the cache is under `maxSize` (`
|
|
1808
|
+
least-recently-used until the cache is under `maxSize` (`10GB`,
|
|
1661
1809
|
`500MB`, `64KB`; a bare number is refused as a typo, `10B` is not).
|
|
1662
|
-
Either or both
|
|
1810
|
+
Either or both (each also from `VX_CACHE_RETENTION_OLDER_THAN` /
|
|
1811
|
+
`VX_CACHE_RETENTION_MAX_SIZE`), and neither may be zero: each would evict what every
|
|
1663
1812
|
run just saved (item 969). It runs after the run's saves
|
|
1664
1813
|
and uploads have landed, never on a run a signal or an abort stopped
|
|
1665
1814
|
(one stopped while it waited on the workspace lock never held it), only when something is due (a run with
|
|
1666
1815
|
nothing to evict pays one scan of the index), and says nothing:
|
|
1667
1816
|
housekeeping prints no line (owner, 2026-10-06). Under `olderThan` an entry the run just used is never due, but `maxSize` is least-recently-used first, so a bound below one run's outputs evicts that run's own. The prune's
|
|
1668
1817
|
orphan sweep (artifacts no index row counts, judged by file time —
|
|
1669
|
-
see `vx cache
|
|
1818
|
+
see `vx cache clean`) also runs on its own clock, at most once an
|
|
1670
1819
|
hour, so their bytes go even when nothing the index holds is due;
|
|
1671
1820
|
listing the directory every run would cost 0.5 ms per 1,000
|
|
1672
1821
|
entries. Housekeeping,
|
|
1673
1822
|
not the run's work: a failure is silent, never a failed run. Not
|
|
1674
1823
|
folded into any cache key. Omitted → the cache grows until
|
|
1675
|
-
`vx cache
|
|
1676
|
-
- **`affectedBase`** — the git ref a bare `--affected` compares with
|
|
1824
|
+
`vx cache clean`.
|
|
1825
|
+
- **`run.affectedBase`** — the git ref a bare `--affected` compares with
|
|
1677
1826
|
(`origin/develop`); `--affected=<base>` still wins. Omitted →
|
|
1678
1827
|
`origin/HEAD`, then the first of `origin/main`, `origin/master`, `main`, `master` that is not HEAD (D-93), else `HEAD~1`. A plugin's `config` stage may set it:
|
|
1679
1828
|
`nx()` from `NX_BASE` or nx.json's `defaultBase`, `turbo()` from
|
|
1680
1829
|
`TURBO_SCM_BASE`, else on GitHub Actions from `GITHUB_BASE_REF` or the
|
|
1681
1830
|
push event's `before`, as Turbo does. Not folded into any cache key.
|
|
1682
|
-
- **`
|
|
1683
|
-
`'
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
`
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1831
|
+
- **`cache.policy`** — which layers a run reads and writes, in
|
|
1832
|
+
`--cache-policy`'s spec (`'local:rw,remote:r'`; a layer the spec leaves out
|
|
1833
|
+
stays on; the grammar is in `docs/cli.md` § Cache policy). Omitted,
|
|
1834
|
+
every layer reads and writes on CI, and `vx run` off CI (`CI` unset,
|
|
1835
|
+
`0` or `false`) reads the remote and does not write it: a key holds
|
|
1836
|
+
only declared inputs, so a laptop's write could replay on CI. The
|
|
1837
|
+
policy alone decides whether a layer is written; `cache.scope` never
|
|
1838
|
+
does. `VX_CACHE_POLICY` beats it and
|
|
1839
|
+
`vx run --cache-policy` (or `--cache-policy`) beats both; `--no-cache` and `--force` still apply on top. A spec
|
|
1840
|
+
naming the remote (`remote:rw`) asks to write it, off CI too. Parsed
|
|
1841
|
+
before any task: a bad spec is refused naming `cache.policy`. Whatever
|
|
1842
|
+
the policy, a lookup asks the local cache first, then each remote in
|
|
1843
|
+
the order the plugins declare them. Not folded into any cache key.
|
|
1844
|
+
- **`cache.download`** — which remote hits land on this machine:
|
|
1845
|
+
`all` (the default), only the requested tasks' (`toplevel`), or
|
|
1846
|
+
`none` (`docs/cli.md` § `--cache-download`).
|
|
1847
|
+
- **`cache.scope`** — where this run's cache writes land (2026-10-14).
|
|
1848
|
+
Omitted, a run reads and writes the default keys. A name (`'pr-123'`;
|
|
1849
|
+
letters, digits, `. _ - / @`, at most 128) reads the default key first,
|
|
1850
|
+
then the scope's, and writes only the scope's, so a pull request never
|
|
1851
|
+
writes what the default branch reads, and two scopes never see each
|
|
1852
|
+
other. A default artifact the ingest refuses falls through to the
|
|
1853
|
+
scope's key. A scope's key is derived from the task key, so every
|
|
1854
|
+
remote wire stores it unchanged. The rule holds for the local cache
|
|
1855
|
+
as for every remote, so on one disk an entry a scope saved is a miss
|
|
1856
|
+
for another scope or none, and an unscoped one serves every run. The scope never decides whether a run writes; `cache.policy`
|
|
1857
|
+
does.
|
|
1858
|
+
`github()` sets it on Actions: `pr-<n>`, `ref-<name>` off
|
|
1694
1859
|
the default branch. `VX_CACHE_SCOPE` beats both, and `vx run --cache-scope` beats all three. A client-side convention, not a security
|
|
1695
1860
|
boundary: a run holding a write credential can write any key, so only
|
|
1696
1861
|
a cache server that scopes writes by token can refuse one
|
|
1697
1862
|
(`docs/security.md` § Cache poisoning). Not folded into any cache key.
|
|
1698
|
-
|
|
1863
|
+
The old top-level `cacheScope` is refused, naming `cache: { scope }`.
|
|
1864
|
+
- **`run.schedule`** — which ready task starts first when more are ready
|
|
1699
1865
|
than workers, with no timings. `most-work` (the default): the one the
|
|
1700
1866
|
most tasks wait on, at any depth. `critical-path`: the head of the
|
|
1701
1867
|
longest chain of tasks below it. `direct-dependents`: the one the most
|
|
@@ -1706,19 +1872,20 @@ run` takes `'read-only'` off CI (`CI` unset, `0` or `false`) and
|
|
|
1706
1872
|
their ties. The default costs what it did; another strategy is
|
|
1707
1873
|
computed only when named. Any other value is refused. Never folded
|
|
1708
1874
|
into a cache key.
|
|
1709
|
-
- **`inputStability`** — the `cache.inputStability` of every task that
|
|
1875
|
+
- **`cache.inputStability`** — the `cache.inputStability` of every task that
|
|
1710
1876
|
does not set its own field, same shape (`{ strategy, severity }`, §
|
|
1711
|
-
`cache`).
|
|
1877
|
+
`cache`). `VX_CACHE_INPUT_STABILITY_STRATEGY` and `_SEVERITY` still
|
|
1878
|
+
override every task for the run. Never folded
|
|
1712
1879
|
into a cache key.
|
|
1713
|
-
- **`rules`** — checks on the task graph, each on unless set to
|
|
1880
|
+
- **`run.rules`** — checks on the task graph, each on unless set to
|
|
1714
1881
|
`false`. Turning one off allows a shape vx runs correctly but more
|
|
1715
1882
|
slowly; never folded into a cache key. A value that is not a boolean,
|
|
1716
1883
|
or a rule vx does not know, is refused.
|
|
1717
1884
|
- **`exclusiveOutputs`** (X-53) — two tasks whose declared outputs
|
|
1718
1885
|
overlap are refused even when a `dependsOn` edge orders them:
|
|
1719
1886
|
`<a> and <b> both declare the output "<glob>" … Give each task its
|
|
1720
|
-
own output path, or set rules
|
|
1721
|
-
vx
|
|
1887
|
+
own output path, or set run.rules.exclusiveOutputs: false in
|
|
1888
|
+
.vx/workspace.ts to let a dependant add to its upstream's outputs.`
|
|
1722
1889
|
Off, the dependant adds to its upstream's tree (item 588;
|
|
1723
1890
|
`caching.md` § Additive outputs). Two overlapping tasks with no
|
|
1724
1891
|
edge are refused either way.
|
|
@@ -1730,8 +1897,8 @@ vx.workspace.ts to let a dependant add to its upstream's outputs.`
|
|
|
1730
1897
|
landing in its project (X-135), is refused,
|
|
1731
1898
|
edge or no edge: `<reader> reads "<glob>" in cache.inputs.<field>,
|
|
1732
1899
|
which matches <writer>'s output "<glob>" … Exclude it: add "!<glob>"
|
|
1733
|
-
to <reader>'s cache.inputs.<field>, or set rules
|
|
1734
|
-
false
|
|
1900
|
+
to <reader>'s cache.inputs.<field>, or set run.rules.upfrontKeys:
|
|
1901
|
+
false in .vx/workspace.ts to let it wait for its producer.` Such a key
|
|
1735
1902
|
reads what the producer writes this run, so vx cannot know it before
|
|
1736
1903
|
the producer ran: it is not probed, prefetched or restored ahead of
|
|
1737
1904
|
the schedule (`caching.md` § Local restore tier). The upstream's key
|
|
@@ -1740,17 +1907,33 @@ false } in vx.workspace.ts to let it wait for its producer.` Such a key
|
|
|
1740
1907
|
`!` entries take back no longer holds its key back either. A task's
|
|
1741
1908
|
own outputs, a task with no `cache`, and a group are exempt. Off,
|
|
1742
1909
|
the key waits for its producer, as before.
|
|
1910
|
+
- **`dependencies`** — which workspace projects a project may depend
|
|
1911
|
+
on, checked by `vx lock` and `vx lock --check` only, over each
|
|
1912
|
+
project's direct dependencies (its `package.json` buckets and its
|
|
1913
|
+
tasks' cross-project `dependsOn`). Each rule is
|
|
1914
|
+
`{ from, allow?, deny? }`; a selector is `tag:<tag>` (a project
|
|
1915
|
+
carrying the tag) or a project name, and `*` matches any characters
|
|
1916
|
+
in either (`tag:scope:*`, `@acme/*`). Every rule whose `from`
|
|
1917
|
+
selects a project applies: each dependency must match an `allow`
|
|
1918
|
+
selector when `allow` is given, and none of `deny`, so a deny beats
|
|
1919
|
+
any allow. A project no rule selects is free, and an untagged
|
|
1920
|
+
dependency matches no `tag:` selector. A broken edge refuses the lock
|
|
1921
|
+
with `VX_E_DEPENDENCY_RULE`:
|
|
1922
|
+
`lib depends on app, which run.rules.dependencies[0] (from 'tag:lib') denies`,
|
|
1923
|
+
every broken edge on its own line. A rule with neither `allow` nor
|
|
1924
|
+
`deny` is refused.
|
|
1743
1925
|
- **`plugins`** — the run-level extension points. Optional: core
|
|
1744
1926
|
applies no plugin by default, and the local executor and the local
|
|
1745
|
-
cache are its floor — the tail of every executor list and
|
|
1746
|
-
— so a workspace that declares none runs
|
|
1927
|
+
cache are its floor — the tail of every executor list, and the first
|
|
1928
|
+
layer of every cache chain — so a workspace that declares none runs
|
|
1929
|
+
and caches here.
|
|
1747
1930
|
Declaration order is precedence: every `executor` is consulted in
|
|
1748
1931
|
order per task (first to accept runs it; what all decline runs
|
|
1749
1932
|
locally); every `cache` layer is chained (lookup walks, save reaches
|
|
1750
1933
|
all, the local store last). An `undefined`, `null` or `false` entry
|
|
1751
1934
|
is skipped, as Vite skips one: a factory that declines (`github()` off
|
|
1752
1935
|
Actions) or `cond && plugin()` needs no filter. Each entry is a `VxPlugin` contributing
|
|
1753
|
-
any subset of the
|
|
1936
|
+
any subset of the fifteen hooks, in pipeline order: `config` (edit
|
|
1754
1937
|
the workspace config before anything derives from it), `discover`
|
|
1755
1938
|
(name directories to make projects beyond the members), `project`
|
|
1756
1939
|
(edit a loaded project's tasks — inject, remove, rewrite;
|
|
@@ -1764,19 +1947,20 @@ false } in vx.workspace.ts to let it wait for its producer.` Such a key
|
|
|
1764
1947
|
ready task start now beside what runs here), `executor` (return a
|
|
1765
1948
|
`TaskExecutor` — where one task's command runs — or decline), `cache`
|
|
1766
1949
|
(return a cache layer or decline), `telemetry` (observe-only export:
|
|
1767
|
-
OTel, the GitHub job summary, a custom sink), `
|
|
1950
|
+
OTel, the GitHub job summary, a custom sink), `output` (return a
|
|
1951
|
+
`Logger` that draws the run in place of vx's own, or decline), `setup`
|
|
1952
|
+
and `teardown`
|
|
1768
1953
|
around the run, and CLI `commands` (`{ verb: { description, run } }`,
|
|
1769
1954
|
consulted for a verb core does not know). First-party plugins:
|
|
1770
1955
|
`@vzn/vx-otel`, `@vzn/vx-ci`, `@vzn/vx-reapi`, `@vzn/vx-lockfile`,
|
|
1771
|
-
`@vzn/vx-schedule-history`, `@vzn/vx-
|
|
1956
|
+
`@vzn/vx-schedule-history`, `@vzn/vx-migrate`. A
|
|
1772
1957
|
plugin that declines every capability (`otel()` with no OTLP endpoint
|
|
1773
1958
|
configured) costs nothing — a run with no active plugin is
|
|
1774
1959
|
byte-identical to one with none declared. Plugins observe, route and
|
|
1775
1960
|
execute; they never change what a task is.
|
|
1776
1961
|
|
|
1777
|
-
The loader validates the shape (
|
|
1778
|
-
|
|
1779
|
-
capability) and throws a `UserError` on
|
|
1962
|
+
The loader validates the shape (each setting by its kind, plugin objects
|
|
1963
|
+
with a name and at least one capability) and throws a `UserError` on
|
|
1780
1964
|
malformed input.
|
|
1781
1965
|
|
|
1782
1966
|
There are no workspace-level `globalInputs` / task-default fields:
|
|
@@ -1874,7 +2058,7 @@ export default defineProject({
|
|
|
1874
2058
|
description: 'vite dev server',
|
|
1875
2059
|
exec: {
|
|
1876
2060
|
command: 'vite',
|
|
1877
|
-
timeout:
|
|
2061
|
+
timeout: '30s',
|
|
1878
2062
|
persistent: { readyWhen: 'Local:' },
|
|
1879
2063
|
env: { passThrough: ['VITE_API_URL'] },
|
|
1880
2064
|
},
|
|
@@ -2097,7 +2281,6 @@ a user meets most:
|
|
|
2097
2281
|
| `tasks must be an object keyed by task name` | `tasks` is not an object — an ARRAY included. |
|
|
2098
2282
|
| `<path> is <what> — a config must be JSON data, because the cache key folds its JSON` | A value JSON cannot carry, anywhere in the config: a function, a symbol, a bigint, `NaN` / `±Infinity`, `undefined` in an array, a cycle, a getter or an object that is not plain (`Date`, `Map`, `RegExp`, a class instance). |
|
|
2099
2283
|
| `<level> has unknown field "<key>"` | Typo'd / unsupported key (see below). |
|
|
2100
|
-
| `<level> has field "<key>", which vx <version> removed — use <replacement>` | A field an earlier release accepted (`exec.resources`, removed in 0.0.19). The message names what replaced it (`design/versioning-1.0.md` § Deprecation). |
|
|
2101
2284
|
| `<level> must be an object (fields: <fields>), not an array` | An array where an object goes — `outputs: ['dist/**']` (Turbo's spelling) is `outputs: { files: ['dist/**'] }`, and the message says so. |
|
|
2102
2285
|
| `cannot find '<name>' — no node_modules above the config provides it; install the workspace's dependencies first` | A bare import nothing installed serves — a fresh clone before its install, or a typo. Refused before the config is evaluated, so Bun never auto-installs it from the registry (it would, when no `node_modules` exists above). |
|
|
2103
2286
|
| `cannot find '<name>' — Yarn Plug'n'Play installed the workspace's dependencies into .pnp.cjs, which Bun does not read; set <remedy>` | The same, with a `.pnp.cjs` at or above the config: the install is there and Bun cannot read it, so the remedy is `nodeLinker: node-modules` in `.yarnrc.yml` and `yarn install` (D-109). |
|
|
@@ -2132,10 +2315,10 @@ a user meets most:
|
|
|
2132
2315
|
| `cache.inputs.files: every entry is a negation, which selects NOTHING` | Only `!` globs — nothing to subtract from. |
|
|
2133
2316
|
| `cache.inputs.files: "<file>" is taken back by "!<glob>"` | A literal input a `!` entry covers: the negation subtracts wherever it sits, so the file never entered the key. |
|
|
2134
2317
|
| `cache.outputs.files: every entry is a negation, which selects NOTHING` | Only `!` globs: a `!` entry only takes back what a positive glob selected (A-44). |
|
|
2135
|
-
| `cache.outputs.files: "<glob>" covers the project's own <file>` | An output glob that matches the project's
|
|
2318
|
+
| `cache.outputs.files: "<glob>" covers the project's own <file>` | An output glob that matches the project's own `vx.config.*` (`**`, `*.ts`): the clean before a run would delete it. A `!` entry that takes the file back (`!vx.config.ts`) lets it load. |
|
|
2136
2319
|
| `cache.outputs.files: "<glob>" is under workspace-outputs/` | A project output named into the artifact's namespace for `outputs.workspaceFiles`: every hit read it back as a workspace output. |
|
|
2137
2320
|
| `cache.inputs.files: '!!' is not a double negation` | `!!x` inverts the set — it folds only `x`. |
|
|
2138
|
-
| `exec.timeout
|
|
2321
|
+
| `exec.timeout of <value> exceeds the maximum timer delay` | Past 2^31-1 ms a timer fires at once, not never. |
|
|
2139
2322
|
| `description must be a string` | Non-string description. |
|
|
2140
2323
|
| `tag "<tag>" <why> — --filter tag:<name> could not name it` | A tag with surrounding whitespace or a `*`, or one ending in `...` or `[<ref>]`, which a filter reads as a pattern, a dependency walk or a git range. |
|
|
2141
2324
|
|
|
@@ -2144,7 +2327,7 @@ the project's top level (`tasks`), the task itself, `exec`, `exec.env`,
|
|
|
2144
2327
|
`exec.persistent`, `exec.sandbox` and its `allow` /
|
|
2145
2328
|
`deny` / `ignore` blocks, `cache`, `cache.inputs`, and `cache.outputs`
|
|
2146
2329
|
(`tests/schema-unknown-keys.test.ts` walks every one) — and at the top
|
|
2147
|
-
of
|
|
2330
|
+
of `.vx/workspace.ts`,
|
|
2148
2331
|
where `plugin:` (singular) would otherwise declare no plugins and run
|
|
2149
2332
|
the workspace bare. A silently-dropped
|
|
2150
2333
|
`workspaceFile` (singular) or `timeoutMs` would make the task hash as
|
|
@@ -2168,54 +2351,49 @@ ends `— vx spells it cache.outputs.files` in code quotes. A `cache` that
|
|
|
2168
2351
|
is no object (Turbo's `cache: false`) and a `persistent` that is none
|
|
2169
2352
|
(`true`) name the shape to write.
|
|
2170
2353
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
| Symptom
|
|
2174
|
-
|
|
|
2175
|
-
| `
|
|
2176
|
-
| `<
|
|
2177
|
-
| `<
|
|
2178
|
-
|
|
|
2179
|
-
|
|
|
2180
|
-
|
|
|
2181
|
-
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
| `<file>: <field> entry "<glob>" is an extglob, which vx's glob engine does not read` | A member glob holds `!(…)`, `@(…)`, `+(…)`, `*(…)` or `?(…)`. `Bun.Glob` has no extglob (its scan widened `packages/!(x)` to include x, turborepo#3766); a whole-segment `!(a\|b)` gets its exact rewrite, `["packages/*", "!packages/a", "!packages/b"]`. |
|
|
2354
|
+
Project-discovery errors (`src/workspace/workspace.ts`, `src/orchestrator/projects.ts`):
|
|
2355
|
+
|
|
2356
|
+
| Symptom | Cause |
|
|
2357
|
+
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
2358
|
+
| `two projects are named "<name>": <dir> and <dir> — give one another \`name\` in its vx.config` | Two directories got one name: a config's `name`, a plugin's, or the path. |
|
|
2359
|
+
| `<dir>: a project is named by its path unless named, and this one holds "#" …` | A path no task spec can address: set `name` in its config. |
|
|
2360
|
+
| `<dir>: "//" names the workspace root — set another \`name\`` | A config away from the root says `name: '//'`. |
|
|
2361
|
+
| `.vx/projects.lock.json locks <dir>, but its <file> is gone — run \`vx lock\`` | A locked project's config was deleted or renamed (X-144). |
|
|
2362
|
+
| `.vx/projects.lock.json locks <dir>, but its <file> is not readable by this user (<code>)` | This user may not search the project's directory (D-132). |
|
|
2363
|
+
| `.vx/projects.lock.json has unsupported version <n> (this vx expects 3) — re-run \`vx lock\`` | A lock an earlier vx wrote (version 2 keyed by name). |
|
|
2364
|
+
| `git ls-files failed (exit <n>): <why>` | `vx lock` could not list the configs (a torn index). |
|
|
2365
|
+
|
|
2366
|
+
Core reads no `package.json` or `pnpm-workspace.yaml`; the errors reading them are `@vzn/vx-migrate`'s (its README, "Errors reading the members").
|
|
2185
2367
|
|
|
2186
2368
|
Workspace-config errors:
|
|
2187
2369
|
|
|
2188
|
-
| Symptom
|
|
2189
|
-
|
|
|
2190
|
-
|
|
|
2191
|
-
|
|
|
2192
|
-
|
|
|
2193
|
-
|
|
|
2194
|
-
| `
|
|
2195
|
-
| `
|
|
2196
|
-
| `
|
|
2197
|
-
| `
|
|
2198
|
-
| `
|
|
2199
|
-
| `
|
|
2200
|
-
| `
|
|
2201
|
-
| `
|
|
2202
|
-
| `
|
|
2203
|
-
| `
|
|
2204
|
-
| `
|
|
2205
|
-
| `
|
|
2206
|
-
| `plugins
|
|
2207
|
-
| `plugins[<i>] must be
|
|
2208
|
-
| `plugins[<i>]
|
|
2209
|
-
| `
|
|
2210
|
-
|
|
|
2211
|
-
| `
|
|
2212
|
-
| `
|
|
2213
|
-
| `
|
|
2214
|
-
|
|
|
2215
|
-
| `plugin '<name>'
|
|
2216
|
-
| `
|
|
2217
|
-
| `
|
|
2218
|
-
| `plugins[<i>].fingerprint of plugin '<name>' must be { files: [name, …], affected: function }` | A fingerprint claim without its file list or its `affected` answer. |
|
|
2219
|
-
| `plugin '<name>' claims fingerprint file "<file>", which is not a file name at the workspace root` | A claim names a path, or no name: a claim is a bare file at the root (`vx watch`'s root arm is not recursive). |
|
|
2220
|
-
| `plugins '<a>' and '<b>' both claim fingerprint file '<file>' — a file has one claimant` | Two plugins keying the same lockfile; the key would fold both and `--affected` could ask only one. |
|
|
2221
|
-
| `plugin '<name>' claims fingerprint file '<file>' twice (plugins[<i>] and plugins[<j>]) — a file has one claimant; declare the plugin once` | One plugin package declared twice (`plugins: [bun(), bun()]`); the message named it as two plugins (D-158). |
|
|
2370
|
+
| Symptom | Cause |
|
|
2371
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2372
|
+
| `<setting> must be <what its kind takes> (got <value>)` | A setting of the wrong type or out of range: `run.concurrency: 0`, `cache.dir: 42`, `run.schedule: 'fastest'`. The [settings table](#settings) says what each takes; a count is a number and a switch a boolean, never a string. |
|
|
2373
|
+
| `<setting> takes a duration with its unit, like '<n>ms' or '<n>s' (got <n>)` | A bare number for a duration: `run.timeout: 30000`. Durations carry a unit, so `30000` cannot be read as seconds by mistake. |
|
|
2374
|
+
| `<setting> of <value> exceeds the maximum timer delay` | A timer past 2^31-1 ms fires at once, not never. Omit `run.timeout` for no limit. |
|
|
2375
|
+
| `<group> must be an object (fields: <fields>)` | `run`, `watch`, `run.rules` or `cache.retention` that is not an object. |
|
|
2376
|
+
| `cache must be an object like { dir, policy, scope }; a task caches through its own cache in its project's vx.config` | Not an object: Turbo's and Nx's workspace `cache: true` is a task's own `cache` in vx. |
|
|
2377
|
+
| `cache.dir holds a NUL, which no path can carry` | No path can hold a NUL; the cache directory's error blamed the workspace's permissions. |
|
|
2378
|
+
| `run.affectedBase must be a git ref like 'origin/main'` | Empty, holding whitespace or a NUL, or starting with `-`, which git would read as an option. |
|
|
2379
|
+
| `cache.scope must be a scope name of <rule>` | Not a string, or a scope name holding a character a cache key cannot carry. |
|
|
2380
|
+
| `cache.retention names neither olderThan nor maxSize` | An empty policy would evict nothing and read as one. |
|
|
2381
|
+
| `cache.retention.olderThan of 0 evicts every entry after every run` | Every run would evict what it just saved; `vx cache clean` refuses it too. |
|
|
2382
|
+
| `cache.retention.maxSize of 0 evicts every entry after every run` | The same, for the size bound. |
|
|
2383
|
+
| `cache.retention.maxSize '<n>' reads as <n> bytes — give a unit (e.g. '<n>MB', '<n>GB')` | A bare number is bytes; a cache capped at ten bytes keeps nothing. |
|
|
2384
|
+
| `plugins must be an array of plugin objects` | Wrong shape. |
|
|
2385
|
+
| `plugins[<i>] must be an object` | A non-object entry in `plugins`; a string (Nx's `'@nx/vite/plugin'`) adds that a plugin is what its package's function returns, not a module name (D-49). |
|
|
2386
|
+
| `plugins[<i>] must come from definePlugin(import.meta, { … })` | A plain object where a plugin was expected: a plugin's name is its package name, and only `definePlugin` sets it. |
|
|
2387
|
+
| `plugins[<i>] is a Promise — await the factory that returns the plugin` | An async plugin factory called without `await` (`plugins: [later()]`); the refusal said the plugin did not come from `definePlugin` (D-159). |
|
|
2388
|
+
| `plugins[<i>].name overrides the package name` | A `name` set over `definePlugin`'s result; drop the field. |
|
|
2389
|
+
| `plugins[<i>].<capability> of plugin '<name>' must be a function` | A capability key holding something that is not callable. |
|
|
2390
|
+
| `plugins[<i>] (plugin '<name>') must contribute at least one of config/discover/project/graph/key/fingerprint/schedule/admit/executor/cache/telemetry/output/setup/commands/teardown` | A plugin object with no capability. |
|
|
2391
|
+
| `plugin '<name>' declares '<key>', which is no plugin hook` | A key on a plugin that names no hook (`excutor`, `setUp`): it was never called. The nearest hook is hinted. |
|
|
2392
|
+
| `<file> has unknown field "<key>"` | Typo'd / unsupported top-level key (`plugin`); the hint names the nearest spelling, or where vx keeps a Turbo or Nx key (`pipeline`, `remoteCache`, `parallel`, `cacheDirectory`, `defaultBase`, `tasksRunnerOptions`, `globalPassThroughEnv`; D-38, D-49; pnpm-workspace.yaml's, Lerna's and package.json's `packages`, `workspaces`, `catalog`, `npmClient`, and `ignore`, `cache`, `remote`, `env`, D-98; in a project's vx.config, `targets`, `extends`, `implicitDependencies` and `name`, D-56; `scripts`, `pipeline`, `namedInputs`, `root`, `sourceRoot`, `projectType`, and a task's `dependsOn`, `cache`, `exec`, `inputs`, `outputs`, `env` one level too high, D-99). |
|
|
2393
|
+
| `plugin '<name>' declares command '<verb>', a core verb — core verbs cannot be shadowed` | A plugin verb the dispatcher matches first; it could never run. |
|
|
2394
|
+
| `plugin '<name>' declares command '<verb>', which is not a word — a verb is a letter or digit, then letters, digits, ':', '.', '_' or '-'` | A verb like `--version`, `''` or `a b`: core reads a flag first, an empty word is no verb, and `vx completions` writes verbs unquoted into a shell script. |
|
|
2395
|
+
| `plugins '<a>' and '<b>' both declare command '<verb>' — a verb has one owner` | Two packages on one verb; the first would win and hide the second. Plugins of one package are one owner, and the first declared runs. |
|
|
2396
|
+
| `plugins[<i>].fingerprint of plugin '<name>' must be { files: [name, …], affected: function }` | A fingerprint claim without its file list or its `affected` answer. |
|
|
2397
|
+
| `plugin '<name>' claims fingerprint file "<file>", which is not a file name at the workspace root` | A claim names a path, or no name: a claim is a bare file at the root (`vx watch`'s root arm is not recursive). |
|
|
2398
|
+
| `plugins '<a>' and '<b>' both claim fingerprint file '<file>' — a file has one claimant` | Two plugins keying the same lockfile; the key would fold both and `--affected` could ask only one. |
|
|
2399
|
+
| `plugin '<name>' claims fingerprint file '<file>' twice (plugins[<i>] and plugins[<j>]) — a file has one claimant; declare the plugin once` | One plugin package declared twice (`plugins: [bun(), bun()]`); the message named it as two plugins (D-158). |
|