@vzn/vx 0.0.638 → 0.0.640
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 +1 -1
- package/docs/caching.md +152 -95
- package/docs/cli.md +225 -372
- package/docs/execution.md +22 -47
- package/docs/features.md +14 -16
- package/docs/patterns.md +6 -8
- package/docs/schema.md +268 -215
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/last.json +7 -4
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +10 -7
- package/src/cache/cache.ts +44 -11
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +8 -2
- package/src/cache/index.ts +5 -2
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +33 -18
- package/src/cache/layered-cache.ts +108 -315
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cache/plugin-cache.ts +2 -10
- package/src/cache/remote-lane.ts +383 -0
- package/src/cache/run-history.ts +24 -8
- package/src/cache/run-store.ts +4 -4
- package/src/cache/schema.ts +6 -4
- package/src/cache/scoped-cache.ts +7 -0
- package/src/cli/completions.ts +2 -2
- package/src/cli/foreign-flags.ts +11 -23
- package/src/cli/help.ts +6 -31
- package/src/cli/info.ts +2 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +30 -4
- package/src/cli/plan-format.ts +5 -58
- package/src/cli/plugin-commands.ts +2 -2
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +23 -185
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +15 -6
- package/src/cli/upgrade.ts +215 -55
- package/src/cli/watch-cycle.ts +2 -1
- package/src/cli/watch-filter.ts +22 -6
- package/src/cli/watch-judge.ts +49 -5
- package/src/cli/watch-set.ts +18 -8
- package/src/cli/watch.ts +103 -16
- package/src/cli/why.ts +9 -7
- package/src/config.ts +19 -21
- package/src/exec/executor.ts +5 -5
- package/src/exec/sandbox-runtime.ts +1 -1
- package/src/exec/sandbox-violations.ts +3 -1
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +17 -2
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +45 -31
- package/src/graph/task-graph.ts +190 -58
- package/src/graph/task-status.ts +95 -0
- package/src/{orchestrator → history}/failure-mode.ts +1 -1
- package/src/{orchestrator → history}/history.ts +1 -1
- package/src/history/index.ts +48 -0
- package/src/{orchestrator → history}/metrics.ts +7 -20
- package/src/{orchestrator → history}/miss-reason.ts +1 -1
- package/src/index.ts +11 -29
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-args.ts +80 -0
- package/src/orchestrator/execute-task.ts +50 -411
- package/src/orchestrator/fingerprint-watch.ts +2 -2
- package/src/orchestrator/index.ts +2 -73
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/miss-save.ts +33 -3
- package/src/orchestrator/options.ts +1 -36
- package/src/orchestrator/output-host.ts +4 -3
- package/src/orchestrator/persistent-task.ts +321 -0
- package/src/orchestrator/persistent.ts +130 -3
- package/src/orchestrator/placement.ts +5 -5
- package/src/orchestrator/plan.ts +19 -6
- package/src/orchestrator/plugin-host.ts +129 -69
- package/src/orchestrator/plugin.ts +18 -8
- package/src/orchestrator/prepare.ts +40 -34
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/remote-prefetch.ts +14 -1
- package/src/orchestrator/run-artifacts.ts +5 -177
- package/src/orchestrator/run-context.ts +55 -0
- package/src/orchestrator/run-failures.ts +15 -3
- package/src/orchestrator/run-records.ts +35 -3
- package/src/orchestrator/run-report.ts +151 -171
- package/src/orchestrator/run-settings.ts +1 -16
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run-stop.ts +153 -0
- package/src/orchestrator/run.ts +293 -604
- package/src/orchestrator/sandbox-fix.ts +1 -17
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/signals.ts +14 -6
- package/src/orchestrator/stable-keys.ts +164 -27
- package/src/orchestrator/steps.ts +70 -0
- package/src/orchestrator/task-hash.ts +34 -25
- package/src/orchestrator/task-log-buffer.ts +1 -1
- package/src/orchestrator/task-process.ts +94 -0
- package/src/orchestrator/telemetry-host.ts +4 -4
- package/src/orchestrator/telemetry.ts +14 -86
- package/src/orchestrator/upstream.ts +4 -181
- package/src/{orchestrator → report}/events.ts +41 -8
- package/src/{orchestrator → report}/forecast.ts +35 -2
- package/src/{orchestrator → report}/framed-output.ts +40 -34
- package/src/report/index.ts +36 -0
- package/src/{orchestrator → report}/logger.ts +158 -20
- package/src/{orchestrator → report}/status-line.ts +88 -7
- package/src/{orchestrator → report}/summary.ts +126 -35
- package/src/report/tally.ts +83 -0
- package/src/settings.ts +12 -39
- package/src/util/colors.ts +44 -4
- package/src/util/git-ignored.ts +143 -0
- package/src/util/index.ts +19 -4
- package/src/util/paths.ts +1 -1
- package/src/util/report.ts +13 -5
- package/src/util/settle.ts +14 -39
- package/src/util/tail.ts +1 -1
- package/src/util/ui.ts +17 -6
- package/src/workspace/affected.ts +81 -2
- package/src/workspace/config-imports.ts +1 -1
- package/src/workspace/config-schema.ts +142 -143
- package/src/workspace/lockfile.ts +1 -1
- package/src/workspace/migration.ts +19 -3
- package/schemas/summary.json +0 -133
- package/src/cache/chained-cache.ts +0 -280
- package/src/orchestrator/tally.ts +0 -112
- /package/src/{orchestrator → report}/path-links.ts +0 -0
- /package/src/{orchestrator → report}/plain-output.ts +0 -0
- /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/docs/schema.md
CHANGED
|
@@ -87,7 +87,7 @@ absent, which is what a conditional spread
|
|
|
87
87
|
held to this: its plugins are objects of functions.
|
|
88
88
|
|
|
89
89
|
`tasks` is a `Record<string, TaskConfig>`. Task names are strings
|
|
90
|
-
referenced by `dependsOn`, by `
|
|
90
|
+
referenced by `dependsOn`, by `inputs.tasks`, and by the CLI
|
|
91
91
|
(`vx run <taskName>` or `vx run <pkg>#<taskName>`), so a name those
|
|
92
92
|
could not spell is refused at load: an empty one, one with surrounding
|
|
93
93
|
whitespace, one holding `#` (the project separator) or `*` (a pattern),
|
|
@@ -109,12 +109,13 @@ interface TaskConfig {
|
|
|
109
109
|
description?: string // one-line blurb for the picker / --dry view
|
|
110
110
|
exec?: ExecConfig // omit to declare a group task
|
|
111
111
|
dependsOn?: readonly string[] // Turbo/Nx micro-syntax
|
|
112
|
-
|
|
112
|
+
inputs?: InputsConfig // what the task reads: its key, cached or not
|
|
113
|
+
cache?: CacheConfig // caching is opt-in; needs `exec` and `inputs`
|
|
113
114
|
}
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
A task either has an `exec` (it does work) or omits `exec` and declares
|
|
117
|
-
`dependsOn` or `
|
|
118
|
+
`dependsOn` or `inputs` (it's a **group task**: an aggregator, or a key).
|
|
118
119
|
The loader rejects a task that has none of them — a no-op standalone
|
|
119
120
|
task is almost always a config mistake. `dependsOn: []` is the deliberate form: an
|
|
120
121
|
explicit empty group, for a package that wants a task by that name to
|
|
@@ -122,7 +123,7 @@ exist and do nothing.
|
|
|
122
123
|
|
|
123
124
|
A project whose config and plugins declare no `build` gets one (owner,
|
|
124
125
|
2026-10-04): a group with `dependsOn: ['^build']` keyed on every file of
|
|
125
|
-
the project (`
|
|
126
|
+
the project (`inputs.files: ['**']`, no outputs). It runs nothing,
|
|
126
127
|
but a dependant behind `^build` folds its key, so a package consumed as
|
|
127
128
|
source moves its dependants' keys and reaches them under `--affected`.
|
|
128
129
|
A config can declare a keyed group too (Group tasks, below). A
|
|
@@ -156,8 +157,8 @@ places:
|
|
|
156
157
|
test: {
|
|
157
158
|
description: 'bun test against the tests/ tree',
|
|
158
159
|
exec: { command: 'bun test' },
|
|
160
|
+
inputs: { files: ['src/**', 'tests/**'] },
|
|
159
161
|
cache: {
|
|
160
|
-
inputs: { files: ['src/**', 'tests/**'] },
|
|
161
162
|
outputs: { files: [] },
|
|
162
163
|
},
|
|
163
164
|
}
|
|
@@ -319,7 +320,7 @@ daemon, a Docker socket, a device, a VPN-only host.
|
|
|
319
320
|
declares — a worker cannot reach a port served on the submitter. So is
|
|
320
321
|
a task with a `sandbox` block, and its dependants: the sandbox is this
|
|
321
322
|
machine's machinery, and a boundary a worker does not enforce would
|
|
322
|
-
pass vacuously. So is a task whose key folds `
|
|
323
|
+
pass vacuously. So is a task whose key folds `inputs.runtime` or
|
|
323
324
|
`workspaceRuntime`: the key holds this machine's answer, which a worker
|
|
324
325
|
cannot prove it shares. Its dependants are not pinned.
|
|
325
326
|
- **Placement is decided once per task**, before scheduling, so the
|
|
@@ -341,8 +342,8 @@ daemon, a Docker socket, a device, a VPN-only host.
|
|
|
341
342
|
```ts
|
|
342
343
|
install: {
|
|
343
344
|
exec: { command: 'pnpm install --frozen-lockfile', remote: 'only' },
|
|
345
|
+
inputs: { files: ['package.json', 'pnpm-lock.yaml'] },
|
|
344
346
|
cache: {
|
|
345
|
-
inputs: { files: ['package.json', 'pnpm-lock.yaml'] },
|
|
346
347
|
outputs: { files: ['node_modules/**'] },
|
|
347
348
|
},
|
|
348
349
|
},
|
|
@@ -376,7 +377,7 @@ for it to exit. Instead it considers the task "ready":
|
|
|
376
377
|
The trailing partial line is tested too, so prompt-style banners
|
|
377
378
|
without a newline (`printf 'Listening on :3000'`) count, and so does
|
|
378
379
|
output read up to 250 ms after the shell exits. A server
|
|
379
|
-
not ready after 10 s
|
|
380
|
+
not ready after 10 s is said once, naming
|
|
380
381
|
the pattern it waits for, and whether `exec.timeout` bounds the wait.
|
|
381
382
|
|
|
382
383
|
```ts
|
|
@@ -539,11 +540,11 @@ highest priority:
|
|
|
539
540
|
build; if one does, say so explicitly:
|
|
540
541
|
|
|
541
542
|
```ts
|
|
542
|
-
|
|
543
|
+
inputs: { files: ['src/**'], env: ['NODE_OPTIONS'] }, cache: { outputs: { files: [] } }
|
|
543
544
|
```
|
|
544
545
|
|
|
545
546
|
That folds its value into the key. The two axes are orthogonal on
|
|
546
|
-
purpose: `
|
|
547
|
+
purpose: `inputs.env` decides what the key _sees_,
|
|
547
548
|
`exec.env.passThrough` decides what the child _gets_.
|
|
548
549
|
|
|
549
550
|
2. **`passThrough`** names → value taken from host `process.env` at
|
|
@@ -564,14 +565,14 @@ exec: {
|
|
|
564
565
|
}
|
|
565
566
|
```
|
|
566
567
|
|
|
567
|
-
Every name in these lists (and in `
|
|
568
|
+
Every name in these lists (and in `inputs.env`) must be one an
|
|
568
569
|
environment can hold: non-empty, with no `=` and no NUL, and a `define`
|
|
569
570
|
value holds no NUL. Such a name is refused at load; it used to reach the
|
|
570
571
|
child split at its `=` or not at all. A `passThrough` or `define` name is
|
|
571
572
|
also a shell variable name, `[A-Za-z_][A-Za-z0-9_]*`: the task runs under
|
|
572
573
|
`sh -c`, and dash (Linux's `sh`) drops any other name from the environment
|
|
573
574
|
it hands the command, so `my.var` reached a task on macOS and never on
|
|
574
|
-
Linux. `
|
|
575
|
+
Linux. `inputs.env` takes any such name: the key reads it from vx's
|
|
575
576
|
own environment.
|
|
576
577
|
|
|
577
578
|
vx also sets `npm_execpath` to the workspace's package manager (the root
|
|
@@ -608,8 +609,8 @@ shows it: the task's output and the line vx adds under a shell's 127 or
|
|
|
608
609
|
replays, the command a cache entry stores (what `vx why` prints and a
|
|
609
610
|
remote cache receives), the `$ command` line, telemetry records,
|
|
610
611
|
`vx show`, the hashes `vx why` gives for such a variable in
|
|
611
|
-
`
|
|
612
|
-
secret passed after `--`)
|
|
612
|
+
`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 `inputs` runtime probe's error (its command and output), and the run's own invocation line that `vx last` prints (a
|
|
613
|
+
secret passed after `--`). So are every string of a
|
|
613
614
|
task `vx show` prints (a runtime probe, `readyWhen`, the description),
|
|
614
615
|
the description `--dry` and the task picker print, a runtime probe's
|
|
615
616
|
command as the run history stores it and `vx why` names its row (two
|
|
@@ -676,8 +677,8 @@ Semantics:
|
|
|
676
677
|
edge from a `package.json`) and the cross-project `pkg#task` edges.
|
|
677
678
|
With no plugin returning `deps` the workspace has no edge source and
|
|
678
679
|
a `'^name'` is refused (`VX_E_DEPENDS_ON`: "`^build` needs project
|
|
679
|
-
edges; add pnpm()/bun()/npm()/yarn()
|
|
680
|
-
`deps`"): it would resolve to nothing and fold no upstream into the
|
|
680
|
+
edges; add pnpm()/bun()/npm()/yarn() and install so its lockfile
|
|
681
|
+
exists, or a plugin that returns `deps`"): it would resolve to nothing and fold no upstream into the
|
|
681
682
|
key. With one, a project with no deps is a leaf; each path stops at the first dep
|
|
682
683
|
that declares the task and an edge is added to it (Turbo/Nx
|
|
683
684
|
direct-deps parity). The holder's own `dependsOn` is responsible
|
|
@@ -701,7 +702,7 @@ Semantics:
|
|
|
701
702
|
nothing anywhere stays legal, as `'name.*'` does.
|
|
702
703
|
- **`'pkg#name'`** — missing pkg or task is a hard error (you named
|
|
703
704
|
them explicitly). `'//#name'`, Turbo's spelling of the root package,
|
|
704
|
-
names the root project (D-39) here and in `
|
|
705
|
+
names the root project (D-39) here and in `inputs.tasks`; with no
|
|
705
706
|
root project it is refused as such.
|
|
706
707
|
- **Patterns (`'name.*'` / `'^name.*'`)** — `*` matches any run of
|
|
707
708
|
characters in a task NAME; everything else is literal (the dotted
|
|
@@ -714,102 +715,37 @@ Semantics:
|
|
|
714
715
|
ALL its matches and stops the walk. Patterns are not supported in
|
|
715
716
|
the `'pkg#task'` form. (Nx 19.5 `build-*` parity.)
|
|
716
717
|
- **No bare wildcards or negation here.** `'*'` / `'^*'` / `'!form'`
|
|
717
|
-
belong in `
|
|
718
|
+
belong in `inputs.tasks` (filtering which upstream hashes
|
|
718
719
|
participate in this task's cache key), not in `dependsOn` (declaring
|
|
719
720
|
graph edges). The micro-syntax parser rejects them in this position.
|
|
720
721
|
- **Cycle detection** runs across the resolved graph at the end of
|
|
721
722
|
`buildTaskGraph`. Cycles throw with a path-formatted message.
|
|
722
723
|
|
|
723
|
-
### `
|
|
724
|
-
|
|
725
|
-
```ts
|
|
726
|
-
interface CacheConfig {
|
|
727
|
-
inputs: CacheInputs // required when `cache` is provided
|
|
728
|
-
outputs: CacheOutputs // required when `cache` is provided
|
|
729
|
-
inputStability?: InputStability // optional
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
interface InputStability {
|
|
733
|
-
strategy?: 'immutable' | 'mutable' | 'unchecked' // default 'immutable'
|
|
734
|
-
severity?: 'warn' | 'error' // default 'warn'
|
|
735
|
-
}
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
`inputStability` says what a write to an input while the command runs
|
|
739
|
-
means. `strategy` says which writes count:
|
|
740
|
-
|
|
741
|
-
- `'immutable'` (the default, on CI too): any write. An edit undone
|
|
742
|
-
mid-run, or another task changing the file and putting it back, cannot
|
|
743
|
-
be told from no edit.
|
|
744
|
-
- `'mutable'`: a file rewritten to the bytes the key folded counts as
|
|
745
|
-
unchanged, for a task that rewrites its own input identically
|
|
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.
|
|
752
|
-
- `'unchecked'`: none. The entry is saved under the key taken before the
|
|
753
|
-
command, whatever the command did to its inputs; `severity` is ignored.
|
|
754
|
-
A key that no longer held before the command started still withholds
|
|
755
|
-
it.
|
|
756
|
-
|
|
757
|
-
A write that counts withholds the entry, and `severity` says what else
|
|
758
|
-
happens: `'warn'` (the default) keeps the result, says why on the status
|
|
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`).
|
|
762
|
-
Either way, every task that ran over the task's outputs, down the graph,
|
|
763
|
-
withholds its save too, and a task of any project that declares the
|
|
764
|
-
moved file as an input withholds its own.
|
|
765
|
-
|
|
766
|
-
Each field is resolved on its own, highest first:
|
|
767
|
-
|
|
768
|
-
1. `VX_CACHE_INPUT_STABILITY_STRATEGY` (`immutable`, `mutable`,
|
|
769
|
-
`unchecked`) and `VX_CACHE_INPUT_STABILITY_SEVERITY` (`warn`, `error`), for the whole run
|
|
770
|
-
(a CI job that wants `error` sets it without editing configs); any
|
|
771
|
-
other value is refused before a task runs;
|
|
772
|
-
2. the task's own `cache.inputStability`;
|
|
773
|
-
3. the workspace's `inputStability` (`.vx/workspace.ts`);
|
|
774
|
-
4. the default, `'immutable'` and `'warn'`.
|
|
775
|
-
|
|
776
|
-
Never folded into a cache key.
|
|
724
|
+
### `inputs` (optional)
|
|
777
725
|
|
|
778
726
|
```ts
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
export default defineProject({
|
|
782
|
-
tasks: {
|
|
783
|
-
build: {
|
|
784
|
-
// tsdown rewrites package.json with the same bytes on every build;
|
|
785
|
-
// different bytes fail the run rather than rebuild next time.
|
|
786
|
-
exec: { command: 'tsdown' },
|
|
787
|
-
cache: {
|
|
788
|
-
inputs: { files: ['src/**', 'package.json'] },
|
|
789
|
-
outputs: { files: ['dist/**'] },
|
|
790
|
-
inputStability: { strategy: 'mutable', severity: 'error' },
|
|
791
|
-
},
|
|
792
|
-
},
|
|
793
|
-
},
|
|
794
|
-
})
|
|
727
|
+
inputs?: InputsConfig
|
|
795
728
|
```
|
|
796
729
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
730
|
+
What the task reads, and so what its key folds. A task is keyed on its
|
|
731
|
+
inputs whether it caches or not, as Turbo and Nx hash every task: a
|
|
732
|
+
dependant folds that key, so an edit to an uncached generator's inputs
|
|
733
|
+
re-runs the cached task behind it. A group with `inputs` is a keyed
|
|
734
|
+
group (Group tasks, below). The default `build` is
|
|
735
|
+
`{ dependsOn: ['^build'], inputs: { files: ['**'] } }`.
|
|
802
736
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
737
|
+
A task that runs a command and declares no `inputs` has no key: nothing
|
|
738
|
+
says what it read. A key that folds it is this run's alone, so every
|
|
739
|
+
task behind it misses and saves nothing, never a stale hit. Declare
|
|
740
|
+
`inputs` on an uncached task a cached one depends on;
|
|
741
|
+
`inputs: { files: [] }` is a declaration too: it keys the task on its
|
|
742
|
+
config and its dependencies' keys, and a task behind it can hit. An
|
|
743
|
+
absent `inputs` and an empty one differ.
|
|
808
744
|
|
|
809
|
-
#### `
|
|
745
|
+
#### `InputsConfig`
|
|
810
746
|
|
|
811
747
|
```ts
|
|
812
|
-
interface
|
|
748
|
+
interface InputsConfig {
|
|
813
749
|
files: readonly string[] // required
|
|
814
750
|
workspaceFiles?: readonly string[] // optional; workspace-root-relative
|
|
815
751
|
env?: readonly string[] // optional
|
|
@@ -880,7 +816,18 @@ lockfile change when a lockfile plugin (`@vzn/vx-lockfile`) is declared.
|
|
|
880
816
|
|
|
881
817
|
Always applied to every glob pass (regardless of what you wrote):
|
|
882
818
|
|
|
883
|
-
- **gitignore filter** — workspace-root + project `.gitignore
|
|
819
|
+
- **gitignore filter** — workspace-root + project `.gitignore`, unless
|
|
820
|
+
an entry names the ignored path outright: a literal (`.env.local`), or
|
|
821
|
+
a glob whose fixed prefix git ignores (`dist/**`, `dist/*.js`,
|
|
822
|
+
`.cache/gen/**`), which is then walked on disk under that prefix only.
|
|
823
|
+
Such a file is hashed from disk and never stored or restored; a
|
|
824
|
+
missing one is not an input until it exists. A broad glob (`**`,
|
|
825
|
+
`src/**` over a listed `src`) still skips ignored files, so
|
|
826
|
+
`node_modules` is never walked, and an install stays out either way.
|
|
827
|
+
Its bytes differ per machine, so machines with different files miss
|
|
828
|
+
each other's shared entries. An ignored file never selects a task
|
|
829
|
+
under `--affected` on its own, as in Nx (only what git sees does); `vx
|
|
830
|
+
watch` re-runs it on an edit there.
|
|
884
831
|
- **Always-ignored** — `.git/**`, `.vx/cache/**`,
|
|
885
832
|
`.vx/projects.lock.json`, `vx.lock.json`, `*.bun-build`, and Bun's cross-compile extraction
|
|
886
833
|
directory `.<16 hex>-<8 hex>.tmp/**`, and vx's own restore temp
|
|
@@ -892,8 +839,10 @@ Always applied to every glob pass (regardless of what you wrote):
|
|
|
892
839
|
itself via its own output. A path an output `!` entry takes back is
|
|
893
840
|
no output, so it stays an input (A-44). Another task's outputs are
|
|
894
841
|
not excluded for you: an input glob that can match them is refused
|
|
895
|
-
|
|
896
|
-
|
|
842
|
+
(X-54), unless git ignores them whole, or the producer is a
|
|
843
|
+
dependency the key does not fold (see Inputs over outputs in §
|
|
844
|
+
Settings). Write the exclusion,
|
|
845
|
+
`['**/*', '!dist/**']`, or ignore `dist/`. An input entry the task's OWN outputs take
|
|
897
846
|
back whole is always refused (X-55): vx removes outputs before a run,
|
|
898
847
|
so a formatter declaring `src/**` as both would delete its sources. A
|
|
899
848
|
task that rewrites files in place declares no outputs.
|
|
@@ -908,8 +857,9 @@ Workspace-root-relative globs — the Turbo `$TURBO_ROOT$` / Nx
|
|
|
908
857
|
`{workspaceRoot}` equivalent, for inputs that live outside the project
|
|
909
858
|
dir (a root `tsconfig.base.json`, shared codegen output, …). Same
|
|
910
859
|
syntax as `files` (`!` negation subtracts, in any order), same git-aware
|
|
911
|
-
resolution (tracked + untracked-not-ignored; gitignored
|
|
912
|
-
|
|
860
|
+
resolution (tracked + untracked-not-ignored; a gitignored file only when
|
|
861
|
+
named outright, as in `files`), and the resolved paths join the same
|
|
862
|
+
cache-key file list.
|
|
913
863
|
|
|
914
864
|
```ts
|
|
915
865
|
inputs: {
|
|
@@ -1133,6 +1083,92 @@ task's outputs. Typical case: an integration-test task `dependsOn`s
|
|
|
1133
1083
|
`build` for ordering, but its inputs are only the test source — so
|
|
1134
1084
|
`inputs.tasks: []` to keep `build` from invalidating it.
|
|
1135
1085
|
|
|
1086
|
+
### `cache` (optional)
|
|
1087
|
+
|
|
1088
|
+
```ts
|
|
1089
|
+
interface CacheConfig {
|
|
1090
|
+
outputs: CacheOutputs // required when `cache` is provided
|
|
1091
|
+
inputStability?: InputStability // optional
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
interface InputStability {
|
|
1095
|
+
strategy?: 'immutable' | 'mutable' | 'unchecked' // default 'immutable'
|
|
1096
|
+
severity?: 'warn' | 'error' // default 'warn'
|
|
1097
|
+
}
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
`inputStability` says what a write to an input while the command runs
|
|
1101
|
+
means. `strategy` says which writes count:
|
|
1102
|
+
|
|
1103
|
+
- `'immutable'` (the default, on CI too): any write. An edit undone
|
|
1104
|
+
mid-run, or another task changing the file and putting it back, cannot
|
|
1105
|
+
be told from no edit.
|
|
1106
|
+
- `'mutable'`: a file rewritten to the bytes the key folded counts as
|
|
1107
|
+
unchanged, for a task that rewrites its own input identically
|
|
1108
|
+
(tsdown's `package.json`); different bytes still count, and so does
|
|
1109
|
+
any write while another task that may write the file was running (one
|
|
1110
|
+
of the same project, or one whose sandbox grants writes elsewhere in
|
|
1111
|
+
the workspace; a server `vx watch` keeps from an earlier run counts),
|
|
1112
|
+
since it may have changed the file and put it back.
|
|
1113
|
+
The line names that task; order the two with `dependsOn` to save.
|
|
1114
|
+
- `'unchecked'`: none. The entry is saved under the key taken before the
|
|
1115
|
+
command, whatever the command did to its inputs; `severity` is ignored.
|
|
1116
|
+
A key that no longer held before the command started still withholds
|
|
1117
|
+
it.
|
|
1118
|
+
|
|
1119
|
+
A write that counts withholds the entry, and `severity` says what else
|
|
1120
|
+
happens: `'warn'` (the default) keeps the result, says why on the status
|
|
1121
|
+
line, and the run goes on; `'error'` fails the task, so the run fails,
|
|
1122
|
+
and its label names the file: `failed (src/a.ts changed during the run, exit 1)`
|
|
1123
|
+
(`inputMoved` in `vx last --format json`).
|
|
1124
|
+
Either way, every task that ran over the task's outputs, down the graph,
|
|
1125
|
+
withholds its save too, and a task of any project that declares the
|
|
1126
|
+
moved file as an input withholds its own.
|
|
1127
|
+
|
|
1128
|
+
Each field is resolved on its own, highest first:
|
|
1129
|
+
|
|
1130
|
+
1. `VX_CACHE_INPUT_STABILITY_STRATEGY` (`immutable`, `mutable`,
|
|
1131
|
+
`unchecked`) and `VX_CACHE_INPUT_STABILITY_SEVERITY` (`warn`, `error`), for the whole run
|
|
1132
|
+
(a CI job that wants `error` sets it without editing configs); any
|
|
1133
|
+
other value is refused before a task runs;
|
|
1134
|
+
2. the task's own `cache.inputStability`;
|
|
1135
|
+
3. the workspace's `inputStability` (`.vx/workspace.ts`);
|
|
1136
|
+
4. the default, `'immutable'` and `'warn'`.
|
|
1137
|
+
|
|
1138
|
+
Never folded into a cache key.
|
|
1139
|
+
|
|
1140
|
+
```ts
|
|
1141
|
+
import { defineProject } from '@vzn/vx/config'
|
|
1142
|
+
|
|
1143
|
+
export default defineProject({
|
|
1144
|
+
tasks: {
|
|
1145
|
+
build: {
|
|
1146
|
+
// tsdown rewrites package.json with the same bytes on every build;
|
|
1147
|
+
// different bytes fail the run rather than rebuild next time.
|
|
1148
|
+
exec: { command: 'tsdown' },
|
|
1149
|
+
inputs: { files: ['src/**', 'package.json'] },
|
|
1150
|
+
cache: {
|
|
1151
|
+
outputs: { files: ['dist/**'] },
|
|
1152
|
+
inputStability: { strategy: 'mutable', severity: 'error' },
|
|
1153
|
+
},
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
})
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
**Caching is opt-in.** Omit `cache` and the task always runs (no read,
|
|
1160
|
+
no write). Provide it and caching is active for that task. The
|
|
1161
|
+
project loader requires `exec`, the task's `inputs` and `cache.outputs`
|
|
1162
|
+
when `cache` is set: declaring "what does this read?" and "what does
|
|
1163
|
+
it produce?" is a deliberate, conscious choice, and a task with no
|
|
1164
|
+
command produces nothing to cache.
|
|
1165
|
+
|
|
1166
|
+
**Why opt-in?** Defaulting caching to ON with implicit "all files / no
|
|
1167
|
+
outputs" silently produces stale builds the moment a user forgets
|
|
1168
|
+
to revisit the config. The cost of a single forgotten cache miss
|
|
1169
|
+
(re-run a task) is much less than the cost of a stale cache hit
|
|
1170
|
+
(ship broken artifacts).
|
|
1171
|
+
|
|
1136
1172
|
#### `CacheOutputs`
|
|
1137
1173
|
|
|
1138
1174
|
```ts
|
|
@@ -1261,7 +1297,9 @@ uses, so `dist` collides with `dist/app.js` (item 442). A whole subtree
|
|
|
1261
1297
|
collides with any glob whose literal head lies inside it: `dist/**` with
|
|
1262
1298
|
`dist/extra/**` or `dist/*.js` (item 941). Two globs a path built from
|
|
1263
1299
|
either one matches are one too: `**/*` and `**/dist/**` both match
|
|
1264
|
-
`x/dist/x`.
|
|
1300
|
+
`x/dist/x`. A pair is no overlap where one task's `!` output entry
|
|
1301
|
+
takes the other's glob back whole: `['**/dist', '!types/**']` beside
|
|
1302
|
+
`types/**` (a `!` entry is never cleaned or captured). Globs that only _might_
|
|
1265
1303
|
overlap (`dist/*` and `dist/sub/**`) are let through; there, last
|
|
1266
1304
|
restore wins. A workspace output is also compared with every other
|
|
1267
1305
|
project's `files` outputs, read from the root: `packages/b/dist/a.txt`
|
|
@@ -1468,7 +1506,7 @@ shares (L-41): `~/.ssh`, `~/.gnupg`, `~/.aws`, `~/.azure`, `~/.kube`,
|
|
|
1468
1506
|
`~/.codex/auth.json`.
|
|
1469
1507
|
What it grants from there is the union of the read grants and, on Linux,
|
|
1470
1508
|
the DIRECTORY holding each file-shaped write grant (above).
|
|
1471
|
-
Nothing is inherited from `cache` — `
|
|
1509
|
+
Nothing is inherited from `cache` — `inputs` says what INVALIDATES a task, `sandbox.allow`
|
|
1472
1510
|
says what it may TOUCH, and deriving one from the other made a
|
|
1473
1511
|
declaration added for caching silently widen the sandbox. The one grant
|
|
1474
1512
|
vx makes for you is dependencies: `node_modules` and, through it, the
|
|
@@ -1482,7 +1520,7 @@ past its `allow.read`.
|
|
|
1482
1520
|
|
|
1483
1521
|
A task that declares `cache` gets a linked workspace package only when
|
|
1484
1522
|
its key already answers for it: some task of that package is folded into
|
|
1485
|
-
the key through `dependsOn` (as `
|
|
1523
|
+
the key through `dependsOn` (as `inputs.tasks` selects it, groups
|
|
1486
1524
|
passing everything through). Every other link inside the workspace root
|
|
1487
1525
|
is withheld, and a read through one fails the task with the denial plus
|
|
1488
1526
|
one line naming the package and the edge that would key it. Declaring
|
|
@@ -1574,17 +1612,16 @@ dependant's `^build`, by `vx run build --all` — and runs nothing. A
|
|
|
1574
1612
|
project with no `build` gets a keyed one (above), which a bare name
|
|
1575
1613
|
does not select.
|
|
1576
1614
|
|
|
1577
|
-
A group with `
|
|
1615
|
+
A group with `inputs` is a **keyed group**: its `inputs` are a key
|
|
1578
1616
|
and nothing runs. A task that depends on it folds that key, so an edit
|
|
1579
|
-
to those inputs re-runs the dependant, and `--affected` reaches it.
|
|
1580
|
-
`cache
|
|
1581
|
-
needs no `dependsOn`. It spawns nothing, writes no cache entry, and the
|
|
1617
|
+
to those inputs re-runs the dependant, and `--affected` reaches it. It
|
|
1618
|
+
takes no `cache` (a group writes nothing), and it needs no `dependsOn`. It spawns nothing, writes no cache entry, and the
|
|
1582
1619
|
run summary does not count it. `@vzn/vx-migrate` writes one for each
|
|
1583
1620
|
Nx `^production`-style input and each Turbo transit node.
|
|
1584
1621
|
|
|
1585
1622
|
```ts
|
|
1586
1623
|
// `test` re-runs when a file under fixtures/ changes; nothing spawns for it
|
|
1587
|
-
fixtures: {
|
|
1624
|
+
fixtures: { inputs: { files: ['fixtures/**'] } },
|
|
1588
1625
|
test: { exec: { command: 'bun test' }, dependsOn: ['fixtures'], cache: { /* … */ } },
|
|
1589
1626
|
```
|
|
1590
1627
|
|
|
@@ -1622,8 +1659,8 @@ Group tasks:
|
|
|
1622
1659
|
|
|
1623
1660
|
The loader rejects:
|
|
1624
1661
|
|
|
1625
|
-
- A task with no `exec`, no `dependsOn` and no `
|
|
1626
|
-
- A group
|
|
1662
|
+
- A task with no `exec`, no `dependsOn` and no `inputs` (literal no-op).
|
|
1663
|
+
- A group with `cache` (a group writes nothing).
|
|
1627
1664
|
|
|
1628
1665
|
## Workspace config (`.vx/workspace.ts`)
|
|
1629
1666
|
|
|
@@ -1644,12 +1681,8 @@ export default defineWorkspace({
|
|
|
1644
1681
|
affectedBase: 'origin/main',
|
|
1645
1682
|
schedule: 'most-work',
|
|
1646
1683
|
continue: 'deps-ok',
|
|
1647
|
-
killGrace: '2s',
|
|
1648
|
-
teardownTimeout: '3s',
|
|
1649
|
-
readyNotice: '10s',
|
|
1650
1684
|
rules: {
|
|
1651
1685
|
exclusiveOutputs: true,
|
|
1652
|
-
upfrontKeys: true,
|
|
1653
1686
|
dependencies: [{ from: 'tag:lib', deny: ['tag:app'] }],
|
|
1654
1687
|
},
|
|
1655
1688
|
},
|
|
@@ -1661,6 +1694,7 @@ export default defineWorkspace({
|
|
|
1661
1694
|
scope: 'main',
|
|
1662
1695
|
inputStability: { strategy: 'immutable', severity: 'warn' },
|
|
1663
1696
|
outputs: { clean: true },
|
|
1697
|
+
upload: { failures: 'ci' },
|
|
1664
1698
|
},
|
|
1665
1699
|
lock: { reviewFiles: true },
|
|
1666
1700
|
watch: { poll: false },
|
|
@@ -1687,36 +1721,33 @@ default and where it came from.
|
|
|
1687
1721
|
|
|
1688
1722
|
<!-- settings table: generated by tests/settings.test.ts -->
|
|
1689
1723
|
|
|
1690
|
-
| Setting | Variable | Flag
|
|
1691
|
-
| ---------------------------------------------- | ----------------------------------- |
|
|
1692
|
-
| `run.concurrency` | `VX_RUN_CONCURRENCY` | `--concurrency`
|
|
1693
|
-
| `run.schedule` | `VX_RUN_SCHEDULE` | `--schedule`
|
|
1694
|
-
| `run.affectedBase` | `VX_RUN_AFFECTED_BASE` | —
|
|
1695
|
-
| `run.branch` (variable or flag only) | `VX_RUN_BRANCH` | `--branch`
|
|
1696
|
-
| `run.timeout` | `VX_RUN_TIMEOUT` | `--timeout`
|
|
1697
|
-
| `run.retries` | `VX_RUN_RETRIES` | `--retries`
|
|
1698
|
-
| `run.continue` | `VX_RUN_CONTINUE` | `--continue`
|
|
1699
|
-
| `run.outputLogs` (variable or flag only) | `VX_RUN_OUTPUT_LOGS` | `--output-logs`
|
|
1700
|
-
| `run.
|
|
1701
|
-
| `
|
|
1702
|
-
| `
|
|
1703
|
-
| `
|
|
1704
|
-
| `
|
|
1705
|
-
| `cache.
|
|
1706
|
-
| `cache.
|
|
1707
|
-
| `cache.
|
|
1708
|
-
| `cache.
|
|
1709
|
-
| `cache.
|
|
1710
|
-
| `cache.
|
|
1711
|
-
| `
|
|
1712
|
-
| `
|
|
1713
|
-
| `
|
|
1714
|
-
| `
|
|
1715
|
-
| `watch
|
|
1716
|
-
| `
|
|
1717
|
-
| `debug.timing` (variable or flag only) | `VX_DEBUG_TIMING` | — | true or false | false | Print the run's stage timings to stderr. |
|
|
1718
|
-
| `debug.watch` (variable or flag only) | `VX_DEBUG_WATCH` | — | true or false | false | `vx watch`: say on stderr each time the loop arms, with the projects it watches. |
|
|
1719
|
-
| `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. |
|
|
1724
|
+
| Setting | Variable | Flag | Value | Default | What it does |
|
|
1725
|
+
| ---------------------------------------------- | ----------------------------------- | ------------------ | ----------------------------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1726
|
+
| `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%'`). |
|
|
1727
|
+
| `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. |
|
|
1728
|
+
| `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. |
|
|
1729
|
+
| `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. |
|
|
1730
|
+
| `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. |
|
|
1731
|
+
| `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. |
|
|
1732
|
+
| `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. |
|
|
1733
|
+
| `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. |
|
|
1734
|
+
| `run.rules.exclusiveOutputs` | `VX_RUN_RULES_EXCLUSIVE_OUTPUTS` | — | true or false | true | Refuse two tasks whose outputs overlap even when an edge orders them. |
|
|
1735
|
+
| `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. |
|
|
1736
|
+
| `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'`). |
|
|
1737
|
+
| `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'`). |
|
|
1738
|
+
| `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. |
|
|
1739
|
+
| `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. |
|
|
1740
|
+
| `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. |
|
|
1741
|
+
| `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. |
|
|
1742
|
+
| `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. |
|
|
1743
|
+
| `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. |
|
|
1744
|
+
| `cache.upload.failures` | — | — | one of 'ci', 'local', 'always' | ci | Where a failed task's log and outputs are kept in the remote: CI runs, local runs, or both. |
|
|
1745
|
+
| `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. |
|
|
1746
|
+
| `watch.poll` | `VX_WATCH_POLL` | `vx watch --poll` | true or false | false | Poll for changes from the start instead of the OS watcher. |
|
|
1747
|
+
| `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. |
|
|
1748
|
+
| `debug.timing` (variable or flag only) | `VX_DEBUG_TIMING` | — | true or false | false | Print the run's stage timings to stderr. |
|
|
1749
|
+
| `debug.watch` (variable or flag only) | `VX_DEBUG_WATCH` | — | true or false | false | `vx watch`: say on stderr each time the loop arms, with the projects it watches. |
|
|
1750
|
+
| `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. |
|
|
1720
1751
|
|
|
1721
1752
|
<!-- /settings table -->
|
|
1722
1753
|
|
|
@@ -1737,11 +1768,8 @@ interface RunSettings {
|
|
|
1737
1768
|
timeout?: Duration
|
|
1738
1769
|
retries?: number
|
|
1739
1770
|
continue?: 'never' | 'deps-ok' | 'always'
|
|
1740
|
-
killGrace?: Duration
|
|
1741
|
-
teardownTimeout?: Duration
|
|
1742
|
-
readyNotice?: Duration
|
|
1743
1771
|
/** Graph rules `vx lock` checks, never a run; each on unless set to false. */
|
|
1744
|
-
rules?: { exclusiveOutputs?: boolean;
|
|
1772
|
+
rules?: { exclusiveOutputs?: boolean; dependencies?: DependencyRule[] }
|
|
1745
1773
|
}
|
|
1746
1774
|
|
|
1747
1775
|
/** `run.rules.dependencies`, checked by `vx lock` only. */
|
|
@@ -1760,6 +1788,7 @@ interface CacheSettings {
|
|
|
1760
1788
|
download?: 'all' | 'toplevel' | 'none'
|
|
1761
1789
|
inputStability?: InputStability
|
|
1762
1790
|
outputs?: { clean?: boolean }
|
|
1791
|
+
upload?: { failures?: 'ci' | 'local' | 'always' }
|
|
1763
1792
|
}
|
|
1764
1793
|
```
|
|
1765
1794
|
|
|
@@ -1777,11 +1806,6 @@ interface CacheSettings {
|
|
|
1777
1806
|
- **`run.continue`** — what a failed task takes down: `never` stops
|
|
1778
1807
|
dispatch, `deps-ok` (the default) skips its dependents, `always` runs
|
|
1779
1808
|
them (`docs/cli.md` § Failure propagation).
|
|
1780
|
-
- **`run.killGrace`**, **`run.teardownTimeout`**, **`run.readyNotice`**
|
|
1781
|
-
— the SIGTERM → SIGKILL grace (2 s), the bound on each plugin's
|
|
1782
|
-
end-of-run flush and teardown (3 s; there is no "no limit"), and how
|
|
1783
|
-
long a persistent task may wait for `readyWhen` before vx says what it
|
|
1784
|
-
waits for (10 s).
|
|
1785
1809
|
- **`run.rules`** — checked by `vx lock` and `vx lock --check` only;
|
|
1786
1810
|
no run checks them.
|
|
1787
1811
|
- **`cache.dir`** — unset, the workspace keeps its index and history in
|
|
@@ -1847,6 +1871,13 @@ interface CacheSettings {
|
|
|
1847
1871
|
- **`cache.download`** — which remote hits land on this machine:
|
|
1848
1872
|
`all` (the default), only the requested tasks' (`toplevel`), or
|
|
1849
1873
|
`none` (`docs/cli.md` § `--cache-download`).
|
|
1874
|
+
- **`cache.upload.failures`** — which runs keep a failed task's log and
|
|
1875
|
+
the files its outputs left in the remote: CI runs (`'ci'`, the
|
|
1876
|
+
default), runs off CI (`'local'`), or both (`'always'`). Only a run
|
|
1877
|
+
whose policy writes a remote keeps any, and it always keeps the log of
|
|
1878
|
+
a task that saved no entry. `ctx.cache.getRun` reads them
|
|
1879
|
+
(`docs/modules/plugin.md` § Run report and run bytes).
|
|
1880
|
+
|
|
1850
1881
|
- **`cache.scope`** — where this run's cache writes land (2026-10-14).
|
|
1851
1882
|
Omitted, a run reads and writes the default keys. A name (`'pr-123'`;
|
|
1852
1883
|
letters, digits, `. _ - / @`, at most 128) reads the default key first,
|
|
@@ -1892,24 +1923,6 @@ own output path, or set run.rules.exclusiveOutputs: false in
|
|
|
1892
1923
|
Off, the dependant adds to its upstream's tree (item 588;
|
|
1893
1924
|
`caching.md` § Additive outputs). Two overlapping tasks with no
|
|
1894
1925
|
edge are refused either way.
|
|
1895
|
-
- **`upfrontKeys`** (X-54) — a cached task whose `inputs.files` can
|
|
1896
|
-
match a same-project task's `outputs.files`, or whose
|
|
1897
|
-
`inputs.workspaceFiles` can match any task's `outputs.workspaceFiles`
|
|
1898
|
-
or another project's `outputs.files` read from the root, or whose
|
|
1899
|
-
`inputs.files` can match another task's `outputs.workspaceFiles`
|
|
1900
|
-
landing in its project (X-135), is refused,
|
|
1901
|
-
edge or no edge: `<reader> reads "<glob>" in cache.inputs.<field>,
|
|
1902
|
-
which matches <writer>'s output "<glob>" … Exclude it: add "!<glob>"
|
|
1903
|
-
to <reader>'s cache.inputs.<field>, or set run.rules.upfrontKeys:
|
|
1904
|
-
false in .vx/workspace.ts to let it wait for its producer.` Such a key
|
|
1905
|
-
reads what the producer writes this run, so vx cannot know it before
|
|
1906
|
-
the producer ran: it is not probed, prefetched or restored ahead of
|
|
1907
|
-
the schedule (`caching.md` § Local restore tier). The upstream's key
|
|
1908
|
-
already reaches the reader through `dependsOn`. A `!` entry that
|
|
1909
|
-
takes the whole output back clears it, and an output the reader's
|
|
1910
|
-
`!` entries take back no longer holds its key back either. A task's
|
|
1911
|
-
own outputs, a task with no `cache`, and a group are exempt. Off,
|
|
1912
|
-
the key waits for its producer, as before.
|
|
1913
1926
|
- **`dependencies`** — which workspace projects a project may depend
|
|
1914
1927
|
on, checked by `vx lock` and `vx lock --check` only, over each
|
|
1915
1928
|
project's direct dependencies (its `package.json` buckets and its
|
|
@@ -1925,6 +1938,41 @@ false in .vx/workspace.ts to let it wait for its producer.` Such a key
|
|
|
1925
1938
|
`lib depends on app, which run.rules.dependencies[0] (from 'tag:lib') denies`,
|
|
1926
1939
|
every broken edge on its own line. A rule with neither `allow` nor
|
|
1927
1940
|
`deny` is refused.
|
|
1941
|
+
- **Inputs over outputs** (X-54, always checked, no setting) — a cached task whose `inputs.files` can
|
|
1942
|
+
match a same-project task's `outputs.files`, or whose
|
|
1943
|
+
`inputs.workspaceFiles` can match any task's `outputs.workspaceFiles`
|
|
1944
|
+
or another project's `outputs.files` read from the root, or whose
|
|
1945
|
+
`inputs.files` can match another task's `outputs.workspaceFiles`
|
|
1946
|
+
landing in its project (X-135), is refused by `vx lock` unless one of
|
|
1947
|
+
the exceptions below applies: `<reader> reads "<glob>" in inputs.<field>,
|
|
1948
|
+
which matches <writer>'s output "<glob>" … Exclude it: add "!<glob>"
|
|
1949
|
+
to <reader>'s inputs.<field>, or depend on <writer> and leave it out of
|
|
1950
|
+
<reader>'s inputs.tasks, so its key hashes those bytes instead.` Such a key
|
|
1951
|
+
reads what the producer writes this run, so vx cannot know it before
|
|
1952
|
+
the producer ran: it is not probed, prefetched or restored ahead of
|
|
1953
|
+
the schedule (`caching.md` § Local restore tier). The upstream's key
|
|
1954
|
+
already reaches the reader through `dependsOn`. A `!` entry that
|
|
1955
|
+
takes the whole output back clears it, and an output the reader's
|
|
1956
|
+
`!` entries take back no longer holds its key back either. Nor does
|
|
1957
|
+
an output git ignores whole: a key reads only what git lists, so
|
|
1958
|
+
`**/*` beside a gitignored `**/dist/**` passes and keys up front,
|
|
1959
|
+
unless the reader's entry starts in the ignored path (`dist/**`):
|
|
1960
|
+
naming it reads it, so the check applies. A reader may read the
|
|
1961
|
+
outputs of a task in its `dependsOn` closure that its key does not
|
|
1962
|
+
fold: it sets `inputs.tasks` and leaves the producer out, also
|
|
1963
|
+
through the tasks it does fold (a producer folded by a folded task
|
|
1964
|
+
counts as folded). Its key then waits for the producer and hashes
|
|
1965
|
+
the bytes it wrote, so a producer rerun with identical output still
|
|
1966
|
+
hits. `vx lock` checks all of it; a run reads only the lock. Git
|
|
1967
|
+
is asked, never a `.gitignore` read: `git check-ignore` on the
|
|
1968
|
+
output's directory (for `**/dist/**`, `dist/` and a fresh
|
|
1969
|
+
`__vx_probe__/dist/`), and no tracked or listed file may match it,
|
|
1970
|
+
so a tracked output is still refused. A glob no few paths stand for
|
|
1971
|
+
(`*.js`) is refused as before. `vx lock` asks once for every
|
|
1972
|
+
overlapped output; a run asks once beside its git walk for the
|
|
1973
|
+
stability gate, so a rule changed after the lock keys the reader
|
|
1974
|
+
late until the next `vx lock` refuses it. A task's
|
|
1975
|
+
own outputs, a task with no `cache`, and a group are exempt.
|
|
1928
1976
|
- **`plugins`** — the run-level extension points. Optional: core
|
|
1929
1977
|
applies no plugin by default, and the local executor and the local
|
|
1930
1978
|
cache are its floor — the tail of every executor list, and the first
|
|
@@ -1968,8 +2016,8 @@ malformed input.
|
|
|
1968
2016
|
|
|
1969
2017
|
There are no workspace-level `globalInputs` / task-default fields:
|
|
1970
2018
|
root-anchored file inputs are declared per task via
|
|
1971
|
-
`
|
|
1972
|
-
`
|
|
2019
|
+
`inputs.workspaceFiles`, root-anchored probes via
|
|
2020
|
+
`inputs.workspaceRuntime`, and shared task shapes compose
|
|
1973
2021
|
through plain TypeScript (import a preset and spread it) — the
|
|
1974
2022
|
language replaces Nx-style `namedInputs` / `targetDefaults` schema
|
|
1975
2023
|
machinery by design.
|
|
@@ -2019,11 +2067,11 @@ export default defineProject({
|
|
|
2019
2067
|
// too — the exec env is isolated, not inherited.
|
|
2020
2068
|
exec: { command: 'tsc -b', env: { passThrough: ['NODE_ENV'] } },
|
|
2021
2069
|
dependsOn: ['^build'],
|
|
2070
|
+
inputs: {
|
|
2071
|
+
files: ['src/**', '!**/*.test.ts', 'tsconfig.json'],
|
|
2072
|
+
env: ['NODE_ENV'],
|
|
2073
|
+
},
|
|
2022
2074
|
cache: {
|
|
2023
|
-
inputs: {
|
|
2024
|
-
files: ['src/**', '!**/*.test.ts', 'tsconfig.json'],
|
|
2025
|
-
env: ['NODE_ENV'],
|
|
2026
|
-
},
|
|
2027
2075
|
outputs: { files: ['dist/**'] },
|
|
2028
2076
|
},
|
|
2029
2077
|
},
|
|
@@ -2034,8 +2082,8 @@ export default defineProject({
|
|
|
2034
2082
|
description: 'run unit tests',
|
|
2035
2083
|
exec: { command: 'bun test', env: { passThrough: ['CI'] } },
|
|
2036
2084
|
dependsOn: ['build'],
|
|
2085
|
+
inputs: { files: ['src/**', 'tests/**'] },
|
|
2037
2086
|
cache: {
|
|
2038
|
-
inputs: { files: ['src/**', 'tests/**'] },
|
|
2039
2087
|
outputs: { files: [] },
|
|
2040
2088
|
},
|
|
2041
2089
|
},
|
|
@@ -2046,11 +2094,11 @@ export default defineProject({
|
|
|
2046
2094
|
description: 'pack the npm tarball',
|
|
2047
2095
|
exec: { command: 'rm -rf pkg && npm pack --pack-destination ./pkg' },
|
|
2048
2096
|
dependsOn: ['build', 'test', '^build'],
|
|
2097
|
+
inputs: {
|
|
2098
|
+
files: ['package.json'],
|
|
2099
|
+
tasks: ['build', '^build'], // hash these upstream too
|
|
2100
|
+
},
|
|
2049
2101
|
cache: {
|
|
2050
|
-
inputs: {
|
|
2051
|
-
files: ['package.json'],
|
|
2052
|
-
tasks: ['build', '^build'], // hash these upstream too
|
|
2053
|
-
},
|
|
2054
2102
|
outputs: { files: ['pkg/*.tgz'] },
|
|
2055
2103
|
},
|
|
2056
2104
|
},
|
|
@@ -2080,12 +2128,14 @@ export default defineProject({
|
|
|
2080
2128
|
lint: {
|
|
2081
2129
|
description: 'oxlint',
|
|
2082
2130
|
exec: { command: 'oxlint .' },
|
|
2083
|
-
|
|
2131
|
+
inputs: { files: ['src/**', '.oxlintrc.json'] },
|
|
2132
|
+
cache: { outputs: { files: [] } },
|
|
2084
2133
|
},
|
|
2085
2134
|
'format-check': {
|
|
2086
2135
|
description: 'oxfmt --check',
|
|
2087
2136
|
exec: { command: 'oxfmt --check .' },
|
|
2088
|
-
|
|
2137
|
+
inputs: { files: ['src/**', '.oxfmtrc.json'] },
|
|
2138
|
+
cache: { outputs: { files: [] } },
|
|
2089
2139
|
},
|
|
2090
2140
|
|
|
2091
2141
|
// Pure group task — `vx run ci` fans out, the group itself is
|
|
@@ -2134,12 +2184,12 @@ export default defineProject({
|
|
|
2134
2184
|
weakerWhenNested: false,
|
|
2135
2185
|
},
|
|
2136
2186
|
},
|
|
2187
|
+
inputs: {
|
|
2188
|
+
files: ['e2e/**'],
|
|
2189
|
+
runtime: ['npx playwright --version'],
|
|
2190
|
+
workspaceRuntime: ['node -v'],
|
|
2191
|
+
},
|
|
2137
2192
|
cache: {
|
|
2138
|
-
inputs: {
|
|
2139
|
-
files: ['e2e/**'],
|
|
2140
|
-
runtime: ['npx playwright --version'],
|
|
2141
|
-
workspaceRuntime: ['node -v'],
|
|
2142
|
-
},
|
|
2143
2193
|
outputs: { files: ['test-results/**'] },
|
|
2144
2194
|
},
|
|
2145
2195
|
},
|
|
@@ -2164,11 +2214,13 @@ export default defineProject({
|
|
|
2164
2214
|
tasks: {
|
|
2165
2215
|
build: {
|
|
2166
2216
|
exec: { command: 'tsc' },
|
|
2167
|
-
|
|
2217
|
+
inputs: { files: srcInputs },
|
|
2218
|
+
cache: { outputs: { files: ['dist/**'] } },
|
|
2168
2219
|
},
|
|
2169
2220
|
test: {
|
|
2170
2221
|
exec: { command: 'bun test' },
|
|
2171
|
-
|
|
2222
|
+
inputs: { files: [...srcInputs, 'tests/**'] },
|
|
2223
|
+
cache: { outputs: { files: [] } },
|
|
2172
2224
|
},
|
|
2173
2225
|
},
|
|
2174
2226
|
})
|
|
@@ -2186,8 +2238,8 @@ export function tsBuild(opts?: { tsconfig?: string }): TaskConfig {
|
|
|
2186
2238
|
return {
|
|
2187
2239
|
description: 'compile TypeScript',
|
|
2188
2240
|
exec: { command: `tsc -b ${opts?.tsconfig ?? ''}`.trim() },
|
|
2241
|
+
inputs: { files: ['src/**', opts?.tsconfig ?? 'tsconfig.json'] },
|
|
2189
2242
|
cache: {
|
|
2190
|
-
inputs: { files: ['src/**', opts?.tsconfig ?? 'tsconfig.json'] },
|
|
2191
2243
|
outputs: { files: ['dist/**'] },
|
|
2192
2244
|
},
|
|
2193
2245
|
}
|
|
@@ -2205,7 +2257,8 @@ export default defineProject({
|
|
|
2205
2257
|
test: {
|
|
2206
2258
|
exec: { command: 'bun test' },
|
|
2207
2259
|
dependsOn: ['build'],
|
|
2208
|
-
|
|
2260
|
+
inputs: { files: ['src/**'] },
|
|
2261
|
+
cache: { outputs: { files: [] } },
|
|
2209
2262
|
},
|
|
2210
2263
|
},
|
|
2211
2264
|
})
|
|
@@ -2216,7 +2269,7 @@ to `.ts` source — see
|
|
|
2216
2269
|
[`architecture.md` § Config-time imports](./architecture.md#config-time-imports--the-bootstrap-problem)
|
|
2217
2270
|
for the bootstrap-cycle tradeoff.
|
|
2218
2271
|
|
|
2219
|
-
### When to use `
|
|
2272
|
+
### When to use `inputs.tasks: []`
|
|
2220
2273
|
|
|
2221
2274
|
When a `dependsOn` exists for ordering only, and the upstream's
|
|
2222
2275
|
output doesn't influence this task's output. Common case: an
|
|
@@ -2227,11 +2280,11 @@ own inputs are just the test source.
|
|
|
2227
2280
|
e2e: {
|
|
2228
2281
|
dependsOn: ['dev'],
|
|
2229
2282
|
exec: { command: 'playwright test' },
|
|
2283
|
+
inputs: {
|
|
2284
|
+
files: ['e2e/**'],
|
|
2285
|
+
tasks: [], // dev's hash does not affect e2e's cache identity
|
|
2286
|
+
},
|
|
2230
2287
|
cache: {
|
|
2231
|
-
inputs: {
|
|
2232
|
-
files: ['e2e/**'],
|
|
2233
|
-
tasks: [], // dev's hash does not affect e2e's cache identity
|
|
2234
|
-
},
|
|
2235
2288
|
outputs: { files: ['playwright-report/**'] },
|
|
2236
2289
|
},
|
|
2237
2290
|
}
|
|
@@ -2306,27 +2359,27 @@ a user meets most:
|
|
|
2306
2359
|
| `sandbox is not allowed on an interactive task` | interactive + sandbox combined. |
|
|
2307
2360
|
| `persistent.readyWhen is not allowed on an interactive task` | vx reads none of an interactive task's output. |
|
|
2308
2361
|
| `a task with no exec must declare dependsOn` | Group task with no edges. |
|
|
2309
|
-
| `a task with no
|
|
2362
|
+
| `cache needs exec — a task with no command produces nothing to cache` | `cache` on a group: a task with no command writes nothing to cache. |
|
|
2310
2363
|
| `dependsOn must be an array of strings` | Wrong shape. |
|
|
2311
|
-
| `cache
|
|
2312
|
-
| `
|
|
2313
|
-
| `
|
|
2314
|
-
| `
|
|
2315
|
-
| `
|
|
2316
|
-
| `
|
|
2317
|
-
| `
|
|
2364
|
+
| `cache needs inputs — the key is the task's inputs` | Forgot `inputs` on a cached task. |
|
|
2365
|
+
| `inputs must be an object` | Present but not an object: a string (`outputs: 'dist'`). An array is the row above. |
|
|
2366
|
+
| `inputs.files must be an array` | Wrong shape. |
|
|
2367
|
+
| `inputs.runtime must be an array of non-empty shell command strings with no NUL` | Non-string / blank entry (whitespace alone runs as a no-op), or one holding a NUL. |
|
|
2368
|
+
| `inputs.workspaceRuntime must be an array of non-empty shell command strings with no NUL` | Non-string / blank entry (whitespace alone runs as a no-op), or one holding a NUL. |
|
|
2369
|
+
| `inputs.tasks must be an array of non-empty strings` | Non-string / empty entry, or a bare string. |
|
|
2370
|
+
| `inputs.tasks: "<name>" names no task in <task>.dependsOn` | An exact entry no `dependsOn` entry of its form names. |
|
|
2318
2371
|
| `cache.outputs is required when cache is set` | Forgot `outputs`. |
|
|
2319
2372
|
| `cache.inputStability must be an object` | A bare string (`inputStability: 'mutable'`). |
|
|
2320
2373
|
| `cache.inputStability.strategy must be 'immutable', 'mutable' or 'unchecked'` | Any other value (`'content'`, `false`). |
|
|
2321
2374
|
| `cache.inputStability.severity must be 'warn' or 'error'` | Any other value (`'fail'`, `true`). |
|
|
2322
2375
|
| `cache.outputs must be an object` | Present but not an object: a string (`outputs: 'dist'`). An array is the row above. |
|
|
2323
2376
|
| `cache.outputs.files must be an array` | Wrong shape. |
|
|
2324
|
-
| `
|
|
2325
|
-
| `
|
|
2377
|
+
| `inputs.files: every entry is a negation, which selects NOTHING` | Only `!` globs — nothing to subtract from. |
|
|
2378
|
+
| `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. |
|
|
2326
2379
|
| `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). |
|
|
2327
2380
|
| `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. |
|
|
2328
2381
|
| `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. |
|
|
2329
|
-
| `
|
|
2382
|
+
| `inputs.files: '!!' is not a double negation` | `!!x` inverts the set — it folds only `x`. |
|
|
2330
2383
|
| `exec.timeout of <value> exceeds the maximum timer delay` | Past 2^31-1 ms a timer fires at once, not never. |
|
|
2331
2384
|
| `description must be a string` | Non-string description. |
|
|
2332
2385
|
| `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. |
|
|
@@ -2334,7 +2387,7 @@ a user meets most:
|
|
|
2334
2387
|
**Unknown fields are rejected**, not ignored, at every object level —
|
|
2335
2388
|
the project's top level (`tasks`), the task itself, `exec`, `exec.env`,
|
|
2336
2389
|
`exec.persistent`, `exec.sandbox` and its `allow` /
|
|
2337
|
-
`deny` / `ignore` blocks, `cache`, `
|
|
2390
|
+
`deny` / `ignore` blocks, `cache`, `inputs`, and `cache.outputs`
|
|
2338
2391
|
(`tests/schema-unknown-keys.test.ts` walks every one) — and at the top
|
|
2339
2392
|
of `.vx/workspace.ts`,
|
|
2340
2393
|
where `plugin:` (singular) would otherwise declare no plugins and run
|