@vzn/vx 0.0.639 → 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 +111 -83
- package/docs/cli.md +70 -40
- package/docs/execution.md +4 -4
- package/docs/features.md +9 -8
- package/docs/patterns.md +1 -1
- package/docs/schema.md +235 -180
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +4 -3
- package/src/cache/cache.ts +4 -1
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +2 -2
- package/src/cache/index.ts +3 -0
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +8 -7
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cli/foreign-flags.ts +1 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +24 -1
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +9 -6
- package/src/cli/upgrade.ts +95 -19
- 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 +74 -2
- package/src/cli/why.ts +5 -3
- package/src/config.ts +19 -15
- package/src/exec/executor.ts +4 -4
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +8 -1
- package/src/graph/scheduler.ts +41 -15
- package/src/graph/task-graph.ts +135 -73
- package/src/index.ts +1 -1
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-task.ts +29 -25
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/persistent-task.ts +3 -3
- package/src/orchestrator/placement.ts +2 -2
- package/src/orchestrator/plan.ts +1 -1
- package/src/orchestrator/plugin-host.ts +62 -22
- package/src/orchestrator/plugin.ts +12 -2
- package/src/orchestrator/prepare.ts +12 -5
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/run-report.ts +124 -46
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run.ts +51 -21
- package/src/orchestrator/sandbox-fix.ts +1 -1
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/stable-keys.ts +59 -26
- package/src/orchestrator/steps.ts +26 -6
- package/src/orchestrator/task-hash.ts +31 -9
- package/src/orchestrator/telemetry-host.ts +3 -3
- package/src/orchestrator/telemetry.ts +9 -4
- package/src/orchestrator/upstream.ts +4 -181
- package/src/report/framed-output.ts +1 -1
- package/src/report/status-line.ts +7 -3
- package/src/settings.ts +11 -17
- package/src/util/paths.ts +1 -1
- 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/README.md
CHANGED
|
@@ -65,8 +65,8 @@ export default defineProject({
|
|
|
65
65
|
build: {
|
|
66
66
|
exec: { command: 'tsc -b' },
|
|
67
67
|
dependsOn: ['^build'], // dependencies build first
|
|
68
|
+
inputs: { files: ['src/**', 'tsconfig.json'] },
|
|
68
69
|
cache: {
|
|
69
|
-
inputs: { files: ['src/**', 'tsconfig.json'] },
|
|
70
70
|
outputs: { files: ['dist/**'] },
|
|
71
71
|
},
|
|
72
72
|
},
|
package/docs/caching.md
CHANGED
|
@@ -8,8 +8,10 @@ invalidation_, _what's actually stored_, and _why_.
|
|
|
8
8
|
## Why caching is opt-in
|
|
9
9
|
|
|
10
10
|
A task is cached iff its `TaskConfig` provides a `cache` block, with
|
|
11
|
-
|
|
12
|
-
task always runs; no read, no write.
|
|
11
|
+
`cache.outputs.files` and the task's `inputs.files`. Omit `cache` and
|
|
12
|
+
the task always runs; no read, no write. `inputs` stands alone: a task
|
|
13
|
+
is keyed on it whether it caches or not, and its dependents fold that
|
|
14
|
+
key.
|
|
13
15
|
|
|
14
16
|
The reasoning:
|
|
15
17
|
|
|
@@ -55,9 +57,9 @@ over (in order):
|
|
|
55
57
|
`exec.remote` is dropped, because it says WHERE a task runs and not
|
|
56
58
|
what it does (§ What's NOT in the key). Captures:
|
|
57
59
|
- `exec` block (command, env declarations, timeout, persistent).
|
|
58
|
-
- `dependsOn` and `
|
|
59
|
-
- `cache.outputs.files`, `
|
|
60
|
-
`
|
|
60
|
+
- `dependsOn` and `inputs.tasks` declarations.
|
|
61
|
+
- `cache.outputs.files`, `inputs.files`, `inputs.env`,
|
|
62
|
+
`inputs.runtime` / `workspaceRuntime` declarations (the
|
|
61
63
|
strings themselves; their resolved file content / env values /
|
|
62
64
|
command output contribute separately).
|
|
63
65
|
- `description` (because it's part of the resolved object — even
|
|
@@ -73,14 +75,14 @@ over (in order):
|
|
|
73
75
|
pulled deps don't see them (their cache identity stays clean) — and
|
|
74
76
|
to a command: a requested default `build` runs none and folds none
|
|
75
77
|
(X-119).
|
|
76
|
-
5. **`
|
|
78
|
+
5. **`inputs.env` resolved values** — `[name, value]` pairs
|
|
77
79
|
read from host `process.env` at hash time (delimited `name\0value`
|
|
78
80
|
so boundaries are unambiguous). Listed names get their current
|
|
79
81
|
values; an unset name folds its bare name, with no `\0`, so unset
|
|
80
82
|
and set-to-empty are two keys (the child tells them apart:
|
|
81
83
|
`passThrough` leaves an unset name out). A name holding a NUL is
|
|
82
84
|
refused at load.
|
|
83
|
-
6. **`
|
|
85
|
+
6. **`inputs.runtime` resolved output** — `[command, output]`
|
|
84
86
|
pairs, where `output` is the trimmed stdout of each command run via
|
|
85
87
|
`sh -c` in the **project dir** at hash time; a command with stderr
|
|
86
88
|
(or a NUL in stdout) folds `\0<stdout length>\0<stdout><stderr>`,
|
|
@@ -88,12 +90,12 @@ over (in order):
|
|
|
88
90
|
The runtime-output analog of step 5: the command _strings_ are in
|
|
89
91
|
the resolved config (step 3), their _output_ is resolved live every
|
|
90
92
|
run. Folded with the command count + each `command\0output` pair.
|
|
91
|
-
7. **`
|
|
93
|
+
7. **`inputs.workspaceRuntime` resolved output** — same as step
|
|
92
94
|
8 but commands run at the **workspace root**, and the pairs fold
|
|
93
95
|
into a **distinct namespace** (`ws-runtime-values:`) so an identical
|
|
94
96
|
`(command, output)` never aliases the project-cwd `runtime` values.
|
|
95
97
|
8. **Filtered upstream task cache hashes** — every upstream task's
|
|
96
|
-
own cache key, filtered by `
|
|
98
|
+
own cache key, filtered by `inputs.tasks` (default: all of
|
|
97
99
|
them). Sorted by hash before folding so the ordering of `dependsOn`
|
|
98
100
|
doesn't change the key. This is the cascade mechanism: if anything
|
|
99
101
|
beneath you changes, your hash changes too. The set is `dependsOn`'s,
|
|
@@ -103,7 +105,7 @@ over (in order):
|
|
|
103
105
|
whatever is on disk, which nothing proves current, so a task whose
|
|
104
106
|
key folds one, and everything built on it, may hit but does not
|
|
105
107
|
save (`orchestrator/excluded-keys.ts`); a task with
|
|
106
|
-
`
|
|
108
|
+
`inputs.tasks: []` folds none and saves as usual.
|
|
107
109
|
9. **Plugin key material** — the `{ name: value }` pairs a plugin's
|
|
108
110
|
`key(task, ctx)` stage returned for this task, stored on the node as
|
|
109
111
|
sorted `plugin/name` pairs and folded after the upstream keys and BEFORE the input files,
|
|
@@ -147,7 +149,7 @@ over (in order):
|
|
|
147
149
|
[`modules/fingerprint-watch.md`](./modules/fingerprint-watch.md)).
|
|
148
150
|
With nothing claimed, no root file is read or watched.
|
|
149
151
|
|
|
150
|
-
10. **Input files' content hashes** — `
|
|
152
|
+
10. **Input files' content hashes** — `inputs.files` resolved to
|
|
151
153
|
a concrete list of project-relative paths (gitignore-aware,
|
|
152
154
|
declared-outputs-excluded, nested-projects-excluded), each file
|
|
153
155
|
contributing its **git blob OID** (v20). On a clean tree the OID
|
|
@@ -216,19 +218,23 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
216
218
|
same shape — git reports the nested repository as one changed path,
|
|
217
219
|
and every project under it is selected.
|
|
218
220
|
|
|
219
|
-
A task with **no `cache` block** derives a key too
|
|
220
|
-
fold it (step 8)
|
|
221
|
-
its
|
|
222
|
-
and
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
221
|
+
A task with **no `cache` block** derives a key too, from its
|
|
222
|
+
`inputs` — its dependents fold it (step 8), as Turbo and Nx hash
|
|
223
|
+
every task. That key is for its dependents only: the task always
|
|
224
|
+
runs, and no cache, local or remote, is asked about or handed
|
|
225
|
+
anything under it; its log goes to the run's own store
|
|
226
|
+
(`run/<id>/<task>/log`). A lookup follows `cache`, never a key
|
|
227
|
+
(`tests/uncached-no-lookup.test.ts`). One that also declares no
|
|
228
|
+
`inputs` is **keyless**: no
|
|
229
|
+
key can stand for what it read, so a key that folds it is this
|
|
230
|
+
run's alone, and every task behind it misses and saves nothing.
|
|
231
|
+
A task reading `**` with no `cache` folds its own outputs too,
|
|
232
|
+
since it declared none: its key moves whenever it writes a file
|
|
233
|
+
git does not ignore, so a cached dependent misses once more after
|
|
234
|
+
the upstream's first run. Ignore the outputs in git, narrow its
|
|
235
|
+
`inputs`, or declare the block (`tests/uncached-upstream-key.test.ts`).
|
|
236
|
+
|
|
237
|
+
Declaring no outputs, such a task may also have **written** anywhere
|
|
232
238
|
in its project, so what the run learned about that project at its
|
|
233
239
|
start — the git listing and the index OIDs — is dropped once its command exits, pass or fail, and a later key
|
|
234
240
|
re-lists and hashes by content. For the same reason a task after it
|
|
@@ -260,20 +266,19 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
260
266
|
dropping out of the key or the artifact without a word
|
|
261
267
|
(turborepo#9345). Rename it, or exclude it with a negated glob.
|
|
262
268
|
|
|
263
|
-
Your globs are a **filter over the set git reports**, so a
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
a
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
under it, folds nothing, and is refused (item 576).
|
|
269
|
+
Your globs are a **filter over the set git reports**, so a broad
|
|
270
|
+
glob never reaches a gitignored file. An entry that names one
|
|
271
|
+
outright does: a literal (`.env.local`, `gen`), or a glob whose fixed
|
|
272
|
+
prefix git ignores (`dist/**`), walked on disk under that prefix
|
|
273
|
+
only (owner, 2026-10-16). Such a file is hashed from disk with the
|
|
274
|
+
blob formula an untracked file gets, and is never stored or
|
|
275
|
+
restored. `--affected` reflects only what git sees, so an edit to
|
|
276
|
+
one never selects a task on its own (as in Nx); `vx watch` re-runs
|
|
277
|
+
it on an edit there.
|
|
278
|
+
A **glob** matching nothing stays silent, and so does a literal
|
|
279
|
+
naming a path that does not exist, until it does. An ignored
|
|
280
|
+
directory named outright folds every file under it; an empty one
|
|
281
|
+
folds nothing.
|
|
277
282
|
|
|
278
283
|
A **bracket is literal** in a task glob, and there are no character
|
|
279
284
|
classes (item 667): `app/[id]/**` folds the route directory
|
|
@@ -315,7 +320,15 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
315
320
|
second by two, since FAT32 keeps even seconds; a rewrite later in
|
|
316
321
|
the same stamp keeps it (`racyWindowMs`; until 2026-09-27, A-2 and
|
|
317
322
|
A-38, such a rewrite of the same size was a hit on the first bytes'
|
|
318
|
-
output).
|
|
323
|
+
output). Every such window compares a file's stamp, on its file
|
|
324
|
+
system's clock, with a moment vx read from its own, so a run first
|
|
325
|
+
measures how far the file system runs behind (one file written and
|
|
326
|
+
removed in the cache dir) and widens every window by a lag past the
|
|
327
|
+
kernel's tick (20 ms): a WSL2 VM drifted from the Windows host that
|
|
328
|
+
stamps `/mnt/c`, a Docker Desktop bind mount or an NFS server dated an
|
|
329
|
+
edit made while a command ran before the command, and the entry
|
|
330
|
+
filed the edit's output under the old bytes' key (until 2026-10-16).
|
|
331
|
+
That is the same value the index holds whenever no filter applies,
|
|
319
332
|
so a file's contribution doesn't flip across dirty↔clean
|
|
320
333
|
transitions. Folded as `(relPath, identity)` pairs, sorted for
|
|
321
334
|
stability across OSes and walk orders. The identity is the OID
|
|
@@ -330,19 +343,21 @@ add` under a clean filter (`core.autocrlf=true`, a `text` rule)
|
|
|
330
343
|
there a clean file's mode comes from an lstat too, and its OID
|
|
331
344
|
from the index (item 1076).
|
|
332
345
|
|
|
333
|
-
A **symlink** folds as git folds it
|
|
334
|
-
_string_ (its mode-120000 index OID),
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
A link
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
A **symlink** folds as git folds it, the blob of its target
|
|
347
|
+
_string_ (its mode-120000 index OID), and then the content behind
|
|
348
|
+
it, as Nx reads a linked file where it points: a file's identity, or
|
|
349
|
+
for a directory every file beneath it at any depth, tracked or not
|
|
350
|
+
(`.git`, `node_modules`, `.vx/cache` and `.yarn/cache` aside, as Nx's walker skips them), a link
|
|
351
|
+
among them followed in turn. The target may be anywhere: in the
|
|
352
|
+
project, elsewhere in the workspace, outside it. Retargeting a link
|
|
353
|
+
changes the key, to equal bytes too; an edit behind it changes the
|
|
354
|
+
key. A dangling link, or a loop of links, keys its target string
|
|
355
|
+
alone. `--affected` selects a link whose target changed (a file
|
|
356
|
+
the diff names, or one under a linked directory) as a changed path of
|
|
357
|
+
its project, and a target git cannot diff (outside the workspace,
|
|
358
|
+
git-ignored) on every run; `vx watch` arms each link's target and
|
|
359
|
+
names the link when it changes (`linked-inputs.ts`). Nx does not
|
|
360
|
+
enter a linked directory; vx does, since the task reads it.
|
|
346
361
|
|
|
347
362
|
The composition is seed-chained (`xxh3(part, prevDigest)`) with a
|
|
348
363
|
label prefix per field, so two different field layouts can't collide.
|
|
@@ -452,10 +467,12 @@ root-anchored output can land in any project's directory — and every
|
|
|
452
467
|
transitive dependant of one with it (their up-front keys fold a
|
|
453
468
|
preliminary key); a glob with no literal prefix reaches every project.
|
|
454
469
|
Before item 584 one such declaration emptied the tier graph-wide.
|
|
455
|
-
`
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
470
|
+
`vx lock` refuses a task whose input globs can match another task's
|
|
471
|
+
declared outputs (X-54, no opt-out), unless git ignores those outputs
|
|
472
|
+
whole (and the entry does not start inside them) or the producer is a
|
|
473
|
+
dependency its key does not fold, whose bytes it then hashes after the
|
|
474
|
+
producer ran, so the clauses on declared outputs fire only for such a
|
|
475
|
+
dependency read or a pair the check cannot decide. An output git ignores whole is no reach either:
|
|
459
476
|
a key reads only what git lists, so a `**/*` reader beside a gitignored
|
|
460
477
|
`**/dist/**` producer keys up front, the same key before and after the
|
|
461
478
|
build. The run asks git once, beside its walk and only when a reader
|
|
@@ -550,9 +567,8 @@ Hard invariants of the remote prefetch:
|
|
|
550
567
|
stat pass.
|
|
551
568
|
- **Stable keys only.** A task with a _preliminary_ key (§ Local
|
|
552
569
|
restore tier's stability gate: undeclared writes, an
|
|
553
|
-
`outputs.workspaceFiles` producer upstream, or
|
|
554
|
-
|
|
555
|
-
declared outputs) is not prefetched: its key would name the wrong
|
|
570
|
+
`outputs.workspaceFiles` producer upstream, or input globs that match
|
|
571
|
+
the outputs of a dependency the key does not fold) is not prefetched: its key would name the wrong
|
|
556
572
|
artifact, and it resolves via the lazy read-through in `execute-task`. Instability propagates: a task that folds an
|
|
557
573
|
unstable upstream is itself unstable. When in doubt, skip.
|
|
558
574
|
- **At most once.** The `LayeredCache` keeps an in-flight map keyed by
|
|
@@ -628,12 +644,14 @@ get/save and the file-hash memo's writes (never `recordRun` / `stats` / `prune`
|
|
|
628
644
|
read-through (`remoteRead`), upload (`remoteWrite`), and prefetch
|
|
629
645
|
(`remoteRead`). The orchestrator derives two booleans per task:
|
|
630
646
|
|
|
631
|
-
- `willRead = task has a cache block AND it is not remote-only AND (localRead || remoteRead)`
|
|
647
|
+
- `willRead = task has a cache block AND it is not remote-only AND it folds no keyless task AND (localRead || remoteRead)`
|
|
632
648
|
- `willWrite = task has a cache block AND it is not remote-only AND (localWrite || remoteWrite)`
|
|
633
649
|
|
|
634
650
|
A task reads the cache only when `willRead`, saves only when
|
|
635
|
-
`willWrite
|
|
636
|
-
|
|
651
|
+
`willWrite` and it folds no keyless task, and cleans its declared outputs
|
|
652
|
+
before exec only when `willWrite`. A keyless task runs a command and
|
|
653
|
+
declares no `inputs`: its key is unknown, so a task that folds it
|
|
654
|
+
(through `dependsOn`, directly or through a group) always misses. Remote-only is an `exec.remote: 'only'` task placed on a
|
|
637
655
|
remote executor: it never touches this machine's disk (no probe, no
|
|
638
656
|
restore, no output clean, no local save), and its result lives in the
|
|
639
657
|
remote executor's own store.
|
|
@@ -784,7 +802,7 @@ from the snapshot's index OID for an output (or for an input the task
|
|
|
784
802
|
rewrote) restored the bytes from before the command.
|
|
785
803
|
|
|
786
804
|
A declared set that resolves to **nothing** is said on the run's
|
|
787
|
-
status line. `
|
|
805
|
+
status line. `inputs matched no files (lib/**)` — the key would
|
|
788
806
|
not change when the source does — is said on every miss of a cacheable
|
|
789
807
|
task, right after `describeTaskInputs` and before the command, whether
|
|
790
808
|
or not it saves. `cache.outputs matched no files (build/**)` — an empty
|
|
@@ -948,13 +966,13 @@ A task's cache becomes invalid when any of these change:
|
|
|
948
966
|
| Any package manager updates a lockfile (`pnpm`, `npm`, `yarn`, `bun`) | step 9 — the `key` material of the plugin that claims it (`@vzn/vx-lockfile`: only the projects whose dependency closure moved); **NOT a trigger** with no such plugin, since core keys no package file |
|
|
949
967
|
| Edit an install setting (`.npmrc`, `bunfig.toml`, `pnpm-workspace.yaml`, `.yarnrc.yml`) | step 9 — the claiming lockfile plugin folds it whole into every task; **NOT a trigger** without one |
|
|
950
968
|
| Edit `package.json`'s `workspaces` field | membership: a project that joins or leaves changes which projects exist and which nested dirs its parent's globs exclude (step 10) |
|
|
951
|
-
| Edit the project's `package.json` (dep / version / scripts change) | step 10 — an input file of every cached task when `@vzn/vx-lockfile` is declared (it adds `package.json` to `
|
|
969
|
+
| Edit the project's `package.json` (dep / version / scripts change) | step 10 — an input file of every cached task when `@vzn/vx-lockfile` is declared (it adds `package.json` to `inputs.files`), or when a task lists it; **NOT a trigger** otherwise |
|
|
952
970
|
| Edit the task's `vx.config.ts` | step 3 (task config hash) |
|
|
953
971
|
| Edit a config file that the task config imports | step 3 (configHash sees the resolved object after Bun evaluates imports) |
|
|
954
972
|
| Change CLI `forwardArgs` (after `--`) | step 4 |
|
|
955
|
-
| Change a `
|
|
956
|
-
| Change the combined stdout+stderr of a `
|
|
957
|
-
| Change the combined stdout+stderr of a `
|
|
973
|
+
| Change a `inputs.env` host value | step 5 |
|
|
974
|
+
| Change the combined stdout+stderr of a `inputs.runtime` command (resolved at hash time) | step 6 |
|
|
975
|
+
| Change the combined stdout+stderr of a `inputs.workspaceRuntime` command (resolved at hash time) | step 7 |
|
|
958
976
|
| Upstream task's cache key changes (because its inputs changed) | step 8 |
|
|
959
977
|
| Bump `CACHE_VERSION` | step 1 — orphans every entry |
|
|
960
978
|
| Change `exec.env.passThrough` _values_ alone | **NOT a trigger** by design — passThrough values are host-specific |
|
|
@@ -968,7 +986,7 @@ re-runs automatically.
|
|
|
968
986
|
|
|
969
987
|
## Cross-project boundaries
|
|
970
988
|
|
|
971
|
-
A project's `
|
|
989
|
+
A project's `inputs.files` globs **never** reach into another
|
|
972
990
|
project's directory, even if a `**/*` pattern would otherwise match.
|
|
973
991
|
|
|
974
992
|
`workspace/nested-dirs.ts` computes the set of nested project
|
|
@@ -985,21 +1003,22 @@ globs is `dependsOn` + upstream-hash propagation (step 8).
|
|
|
985
1003
|
For a task with `exec.sandbox` and `cache`, the sandbox enforces that
|
|
986
1004
|
"only". A workspace package reached through a `node_modules` link is
|
|
987
1005
|
granted only when the task's key folds a task of that package, walking
|
|
988
|
-
the same selection `
|
|
989
|
-
(`orchestrator/keyed-projects.ts`, over `
|
|
1006
|
+
the same selection `inputs.tasks` applies at hash time
|
|
1007
|
+
(`orchestrator/keyed-projects.ts`, over `graph/folds.ts`'s
|
|
990
1008
|
`selectFoldedDeps`); every other link target inside the root is
|
|
991
1009
|
withheld, so an import of a sibling the key never sees fails and is
|
|
992
1010
|
reported instead of caching a result an edit there would not re-run.
|
|
993
|
-
Any exec task counts, cached or not:
|
|
994
|
-
`inputs
|
|
995
|
-
|
|
996
|
-
e2e behind a dev server re-runs when
|
|
1011
|
+
Any exec task counts, cached or not: its key folds its project's files
|
|
1012
|
+
through its `inputs`, and one with no `inputs` is keyless, so a key that
|
|
1013
|
+
folds it is never stored or read. A persistent task counts the same way,
|
|
1014
|
+
so a cached e2e behind a dev server keyed on its sources re-runs when
|
|
1015
|
+
they change.
|
|
997
1016
|
The coverage is per package, not per file: an edge to `ui#source`
|
|
998
1017
|
(inputs `src/**`) also admits a read of `ui/README.md`, whose edit moves
|
|
999
1018
|
no key — the same limit a grant wider than a task's own inputs already
|
|
1000
1019
|
has. A task with no `cache` keeps every link, having no key to be stale.
|
|
1001
1020
|
|
|
1002
|
-
**Exception:** `
|
|
1021
|
+
**Exception:** `inputs.workspaceFiles` /
|
|
1003
1022
|
`cache.outputs.workspaceFiles` are workspace-root-anchored and apply
|
|
1004
1023
|
NO boundary rule — a deliberate escape hatch (owner call: "they don't
|
|
1005
1024
|
care about boundaries; it is bad practice but is there"). Prefer
|
|
@@ -1061,8 +1080,8 @@ the gate exists to avoid paying needlessly.
|
|
|
1061
1080
|
|
|
1062
1081
|
## Runtime inputs and the lock (the env parallel)
|
|
1063
1082
|
|
|
1064
|
-
`
|
|
1065
|
-
exactly on `
|
|
1083
|
+
`inputs.runtime` / `inputs.workspaceRuntime` are modeled
|
|
1084
|
+
exactly on `inputs.env`, and they share its asymmetry with the
|
|
1066
1085
|
lockfile:
|
|
1067
1086
|
|
|
1068
1087
|
- **The command _strings_ live in the resolved config**, so `vx lock`
|
|
@@ -1214,7 +1233,7 @@ by this user, or point cacheDir / --cache-dir at one that is.` (A-40).
|
|
|
1214
1233
|
An input the key must hash that this user cannot read fails its task
|
|
1215
1234
|
naming the read: `<path> is not readable by this user (EACCES), and vx
|
|
1216
1235
|
reads it to derive a cache key. Make it readable, or, for a task input,
|
|
1217
|
-
take it out of
|
|
1236
|
+
take it out of inputs.files.` (A-50).
|
|
1218
1237
|
|
|
1219
1238
|
By default the entries and their artifacts live in a **shared store**
|
|
1220
1239
|
in `~/.vx/<id>/cache`, and each workspace keeps only its own index in
|
|
@@ -1850,10 +1869,19 @@ moat.
|
|
|
1850
1869
|
passthrough still bumps the key for affected tasks.
|
|
1851
1870
|
- **Files outside the project directory that aren't declared.**
|
|
1852
1871
|
Workspace-root configs (`tsconfig.base.json`, etc.) are not
|
|
1853
|
-
auto-included — declare them via `
|
|
1872
|
+
auto-included — declare them via `inputs.workspaceFiles`
|
|
1854
1873
|
(root-anchored globs).
|
|
1874
|
+
- **Gitignored files no entry names.** Vite and Next load `.env.local`
|
|
1875
|
+
on their own; a broad glob skips it, so editing it replays the old
|
|
1876
|
+
build. Name it: `files: ['src/**', '.env.local']`. Its bytes are
|
|
1877
|
+
hashed, never stored, and a missing file is simply not an input.
|
|
1878
|
+
Machines with different files compute different keys, so they miss
|
|
1879
|
+
each other's shared entries. `--affected` ignores it (only what git
|
|
1880
|
+
sees selects a task), and `vx watch` re-runs on its edit. A probe
|
|
1881
|
+
(`runtime: ['cat .env.local 2>/dev/null | cksum']`) still works where
|
|
1882
|
+
a command, not a path, decides what to read.
|
|
1855
1883
|
- **Node / Bun / OS / build-tool versions — unless you declare them.**
|
|
1856
|
-
The canonical mechanism is `
|
|
1884
|
+
The canonical mechanism is `inputs.runtime` /
|
|
1857
1885
|
`workspaceRuntime` (e.g. `workspaceRuntime: ['node -v']`): the
|
|
1858
1886
|
command output is resolved live at hash time, so it stays correct
|
|
1859
1887
|
under the lock. Avoid baking versions via
|
|
@@ -2187,11 +2215,11 @@ breaking footer).
|
|
|
2187
2215
|
committed, so git enumerates it, but it's vx's own frozen-config
|
|
2188
2216
|
metadata — never a task input. Without this, a `vx lock` re-write
|
|
2189
2217
|
busts every key on a project that globs the root lockfile (a broad
|
|
2190
|
-
`**/*` on the root project). Tasks whose `
|
|
2218
|
+
`**/*` on the root project). Tasks whose `inputs.files` never
|
|
2191
2219
|
matched it derive byte-identical keys. No SCHEMA bump — only the
|
|
2192
2220
|
hashed file set changed, not the key layout or on-disk format.
|
|
2193
2221
|
|
|
2194
|
-
- **v22 → v23**: fold `
|
|
2222
|
+
- **v22 → v23**: fold `inputs.runtime` / `workspaceRuntime`
|
|
2195
2223
|
command output into the key (two namespaced sections after
|
|
2196
2224
|
env-values). The command _strings_ stay in the config hash (step 3);
|
|
2197
2225
|
their combined trimmed stdout+stderr is resolved live at hash time
|
|
@@ -2236,7 +2264,7 @@ breaking footer).
|
|
|
2236
2264
|
- **v11 → v12** (PR #42): project's `package.json` bytes folded into
|
|
2237
2265
|
every task's cache key implicitly. Matches Turbo / Nx "implicit
|
|
2238
2266
|
dependencies" behavior — a `package.json` dep change invalidates
|
|
2239
|
-
the project's tasks even when `
|
|
2267
|
+
the project's tasks even when `inputs.files` is narrow and
|
|
2240
2268
|
doesn't cover the file.
|
|
2241
2269
|
- **v12 → v13** (PR #65): per-entry on-disk layout unified. Outputs
|
|
2242
2270
|
moved from `<hash>/<rel>` (mixed with metadata) to
|