@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/src/cli/help.ts
CHANGED
|
@@ -102,19 +102,19 @@ export function verbHelpText(verb: string): string {
|
|
|
102
102
|
|
|
103
103
|
export function helpText(pluginCommands: readonly string[] = []): string {
|
|
104
104
|
return [
|
|
105
|
-
'vx —
|
|
105
|
+
'vx — task orchestration you own',
|
|
106
106
|
'',
|
|
107
107
|
'Usage:',
|
|
108
108
|
' vx run [OPTIONS] [TASK | PKG#TASK ...] [-- forwarded-args...]',
|
|
109
|
-
' vx watch [OPTIONS] TASK [-- forwarded-args...]',
|
|
110
|
-
' vx cache
|
|
109
|
+
' vx watch [OPTIONS] [--poll] TASK [-- forwarded-args...]',
|
|
110
|
+
' vx cache clean [--older-than <duration>] [--max-size <size>] [--global] [--dry-run] [--format pretty|json] [--cache-dir <path>]',
|
|
111
111
|
' vx lock [--check] [--format pretty|json]',
|
|
112
112
|
' vx init [--dry [--format pretty|json]] [--force] [--mjs] [--native|--keep] [--plugin <seam>]',
|
|
113
113
|
' vx upgrade [TAG]',
|
|
114
114
|
' vx show [PROJECT[#TASK] | TASK] [--filter <pattern>] [--affected[=<ref>]] [--format pretty|json]',
|
|
115
115
|
' vx info [--format pretty|json] [--cache-dir <path>]',
|
|
116
116
|
' vx why (TASK | PKG#TASK) [--run RUNID] [--format pretty|json] [--cache-dir <path>]',
|
|
117
|
-
' vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--format pretty|json] [--cache-dir <path>]',
|
|
117
|
+
' vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]',
|
|
118
118
|
' vx docs <query> [--limit N] [--format pretty|json]',
|
|
119
119
|
' vx completions bash|zsh|fish',
|
|
120
120
|
' vx help [VERB]',
|
|
@@ -126,56 +126,80 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
126
126
|
' --filter <pat> pnpm-style filter (repeatable). Examples:',
|
|
127
127
|
' foo, @scope/*, ./packages/foo, foo..., ...foo,',
|
|
128
128
|
' foo^..., !foo, [<since>]',
|
|
129
|
-
' --affected[=<base>]
|
|
130
|
-
'
|
|
131
|
-
' master,
|
|
129
|
+
' --affected[=<base>] Projects changed since <base>, and dependents',
|
|
130
|
+
' (--filter "...[<base>]"); default run.affectedBase,',
|
|
131
|
+
' else origin/HEAD, main, master, HEAD~1.',
|
|
132
132
|
" pkg#task Run a specific project's task directly.",
|
|
133
133
|
'',
|
|
134
134
|
'Execution (for run):',
|
|
135
|
-
'
|
|
136
|
-
'
|
|
137
|
-
' --
|
|
138
|
-
'
|
|
139
|
-
' --
|
|
140
|
-
'
|
|
141
|
-
'
|
|
142
|
-
|
|
143
|
-
'
|
|
144
|
-
|
|
145
|
-
'
|
|
146
|
-
'
|
|
147
|
-
'
|
|
148
|
-
'
|
|
149
|
-
|
|
150
|
-
'
|
|
151
|
-
'
|
|
152
|
-
'
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
|
|
156
|
-
'
|
|
157
|
-
' --
|
|
158
|
-
'
|
|
159
|
-
' --download <mode>
|
|
160
|
-
'
|
|
135
|
+
' Each setting flag beats its VX_* variable, which beats .vx/workspace',
|
|
136
|
+
' (docs/schema.md § Settings).',
|
|
137
|
+
' --concurrency <n> Max parallel tasks (default: usable cores);',
|
|
138
|
+
' 50% means half of them.',
|
|
139
|
+
' --schedule <rule> most-work (default) | critical-path |',
|
|
140
|
+
' direct-dependents | ready-order.',
|
|
141
|
+
' --branch <name> The branch history records and forecasts read',
|
|
142
|
+
" (default: git's, else the CI provider's).",
|
|
143
|
+
' --exclude-dependencies[=names] Skip dependsOn edges: all with no value,',
|
|
144
|
+
' or a comma list of names.',
|
|
145
|
+
' --no-cache No cache reads or writes; outputs are left',
|
|
146
|
+
' alone.',
|
|
147
|
+
' --force Skip cache reads but still write: every task',
|
|
148
|
+
' runs and refreshes its entry.',
|
|
149
|
+
' --cache-policy <spec> Per-layer read/write: a comma list of',
|
|
150
|
+
' <layer>:<flags> (layer local or remote, flags',
|
|
151
|
+
' from rw). A named layer is set to exactly its',
|
|
152
|
+
' flags; unnamed layers keep theirs. Examples:',
|
|
153
|
+
' local:rw,remote:r (remote read-only), remote:',
|
|
154
|
+
' (remote off), local:r (local read-only).',
|
|
155
|
+
" --cache-scope <name> This run's cache.scope (pr-123): write there,",
|
|
156
|
+
' read the default keys first.',
|
|
157
|
+
' --cache-dir <path> Cache directory (cwd-relative); default',
|
|
158
|
+
' .vx/cache, entries in ~/.vx/<id>/cache.',
|
|
159
|
+
' --cache-download <mode> all (default) | toplevel | none:',
|
|
160
|
+
' where remote outputs land.',
|
|
161
|
+
' --retries <n> Up to <n> more runs of a failed task, for',
|
|
162
|
+
' tasks without their own exec.retries. Not',
|
|
163
|
+
' folded into cache keys.',
|
|
164
|
+
" --timeout <duration> A task's time limit (30s, 10m), for tasks",
|
|
165
|
+
' without their own exec.timeout. A task past it',
|
|
166
|
+
' is killed and fails.',
|
|
167
|
+
' --kill-grace <duration> How long a task that ignores SIGTERM gets',
|
|
168
|
+
' before SIGKILL (default 2s).',
|
|
169
|
+
" --teardown-timeout <duration> The bound on each plugin's end-of-run flush",
|
|
170
|
+
' (default 3s).',
|
|
171
|
+
' --ready-notice <duration> When vx says a persistent task still waits for',
|
|
172
|
+
' readyWhen (default 10s).',
|
|
173
|
+
' --continue[=<mode>] What a failed task takes down: never | deps-ok',
|
|
174
|
+
' (default, skips its dependents) | always (bare',
|
|
175
|
+
' form; dependents run, never save).',
|
|
176
|
+
' --output-logs <mode> full | errors-only | hash-only | none, over',
|
|
177
|
+
' the flow default (focused without',
|
|
178
|
+
' --all/--filter/--affected, broad with, full in',
|
|
179
|
+
' CI).',
|
|
180
|
+
' --verbosity <n> 0 (default), or 1 and up for a per-task',
|
|
181
|
+
' summary table after the run.',
|
|
161
182
|
'',
|
|
162
183
|
'Planning (for run — skips execution):',
|
|
163
|
-
|
|
184
|
+
" --dry[=text|json] Print the task graph and each task's predicted hit",
|
|
185
|
+
' or miss.',
|
|
164
186
|
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
165
187
|
'',
|
|
166
188
|
'Artifacts (for run):',
|
|
167
|
-
' --format <fmt> pretty (default) | json: print the --summarize
|
|
168
|
-
' on stdout when the run ends; every other
|
|
189
|
+
' --format <fmt> pretty (default) | json: print the --summarize',
|
|
190
|
+
' document on stdout when the run ends; every other',
|
|
191
|
+
' line goes to stderr.',
|
|
169
192
|
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
170
193
|
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
171
194
|
' --report[=markdown] Print a markdown run report to stdout after the run.',
|
|
172
|
-
' --report-file <path> Append that report to <path> (
|
|
173
|
-
' --tag <k=v> Label this invocation (repeatable); recorded on its
|
|
195
|
+
' --report-file <path> Append that report to <path> ($GITHUB_STEP_SUMMARY).',
|
|
196
|
+
' --tag <k=v> Label this invocation (repeatable); recorded on its',
|
|
197
|
+
' history row.',
|
|
174
198
|
'',
|
|
175
199
|
'Extensions (plugins):',
|
|
176
200
|
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
177
201
|
' cache, distributed execution, and telemetry export are provided by',
|
|
178
|
-
' PLUGINS declared in vx
|
|
202
|
+
' PLUGINS declared in .vx/workspace.ts — first-party or your own. Core',
|
|
179
203
|
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
180
204
|
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
181
205
|
'',
|
|
@@ -194,24 +218,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
194
218
|
' Uses the same flags as `vx run` except --dry /',
|
|
195
219
|
' --graph / --summarize / --profile / --report /',
|
|
196
220
|
' --report-file / --verbosity (they plan or report',
|
|
197
|
-
' one run)
|
|
221
|
+
' one run), plus --poll (watch.poll: poll instead of',
|
|
222
|
+
' the OS watcher). Press Ctrl+C to stop. Cache means most',
|
|
198
223
|
' re-runs are near-zero cost (cache hits restore',
|
|
199
224
|
' outputs + replay logs).',
|
|
200
225
|
'',
|
|
201
226
|
'Cache management:',
|
|
202
|
-
' vx cache
|
|
203
|
-
' vx cache
|
|
227
|
+
' vx cache clean --older-than 30d Evict entries last accessed > 30 days ago.',
|
|
228
|
+
' vx cache clean --max-size 1G Keep total cache under 1 GB (LRU eviction).',
|
|
229
|
+
" vx cache clean Clear .vx/cache and this repo's store ~/.vx/<id>/cache.",
|
|
230
|
+
" --global Clear every repo's store under ~/.vx.",
|
|
204
231
|
' --dry-run Say what the policy would reap; delete nothing.',
|
|
205
232
|
' --format <fmt> pretty (default) | json.',
|
|
206
233
|
' --cache-dir <path> The cache a run with the same flag uses (why, last, info too).',
|
|
207
234
|
'',
|
|
208
|
-
' Duration units: s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
235
|
+
' Duration units: ms, s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
209
236
|
' Cache statistics — directory, entry count, size — are part of `vx info`.',
|
|
210
237
|
'',
|
|
211
238
|
'Introspection:',
|
|
212
239
|
' vx show List every project: name, dir, declared task count.',
|
|
213
|
-
" vx show <project> Print the project's
|
|
214
|
-
'
|
|
240
|
+
" vx show <project> Print the project's resolved config as a run reads",
|
|
241
|
+
' it from the lock, one block per task.',
|
|
215
242
|
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
216
243
|
' vx show <task> That task in every project declaring it.',
|
|
217
244
|
" --filter <pattern> The list (or a task's projects) as `vx run --filter` selects.",
|
|
@@ -234,25 +261,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
234
261
|
' --failed Only failed runs: the latest one, or with --list the list.',
|
|
235
262
|
" --log <task> That task's output in the run (latest that ran it): a failure's",
|
|
236
263
|
' or the cached log; none for an uncached pass.',
|
|
264
|
+
' --stats [<task>] Per-task p50/p99, pass and hit rate, failure mode, peak',
|
|
265
|
+
' RSS and CPU over the last 50 runs; a task id or name narrows.',
|
|
266
|
+
" --branch <name> The branch --stats' p50 forecasts from (default: the run's).",
|
|
237
267
|
' --format <fmt> pretty (default) | json.',
|
|
238
268
|
' info, why and last:',
|
|
239
269
|
' --cache-dir <path> Read the cache a run with the same flag uses.',
|
|
240
270
|
'',
|
|
241
271
|
'Migration:',
|
|
242
|
-
' vx init
|
|
243
|
-
'
|
|
244
|
-
'
|
|
245
|
-
'
|
|
272
|
+
' vx init With no package.json: write .vx/workspace.ts and an empty',
|
|
273
|
+
' root vx.config.ts. With one: runs @vzn/vx-migrate, which maps',
|
|
274
|
+
' turbo.json, an Nx graph or package.json scripts to one',
|
|
275
|
+
' vx.config.ts per package (asking native or keep in Turbo/Nx).',
|
|
246
276
|
' --dry Print the generated files instead of writing them.',
|
|
247
277
|
' --format json With --dry: the plan as one JSON document (schemas/init.json).',
|
|
248
278
|
' --force Overwrite existing vx.config.* files.',
|
|
249
279
|
' --mjs Write .mjs files (no type import) instead of .ts.',
|
|
250
280
|
' --native Turbo or Nx: write a vx.config.ts per package (no question).',
|
|
251
|
-
' --keep Turbo or Nx: only vx
|
|
281
|
+
' --keep Turbo or Nx: only .vx/workspace.ts, declaring turbo() or nx().',
|
|
252
282
|
' --plugin <seam> Instead: write plugins/<seam>.ts, a runnable plugin, and its',
|
|
253
283
|
' test (executor, cache, telemetry, schedule, admit, commands,',
|
|
254
284
|
' project, graph, key).',
|
|
255
|
-
' From turbo.json or an Nx project graph: `vx init` runs `@vzn/vx-migrate` (its own package).',
|
|
256
285
|
'',
|
|
257
286
|
'Shell completions:',
|
|
258
287
|
' vx completions <shell> Print a completion script for bash, zsh or fish: the',
|
|
@@ -269,14 +298,15 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
269
298
|
' --format json {query, hits: [{page, heading, url, text}]}.',
|
|
270
299
|
'',
|
|
271
300
|
'Config lock:',
|
|
272
|
-
' vx lock Evaluate every vx.config in the current environment
|
|
273
|
-
' freeze the resolved objects
|
|
274
|
-
'
|
|
275
|
-
'
|
|
301
|
+
' vx lock Evaluate every vx.config in the current environment, check',
|
|
302
|
+
' the graph rules (run.rules) and freeze the resolved objects',
|
|
303
|
+
' into .vx/projects.lock.json, the only place a run reads configs',
|
|
304
|
+
' from. A run never evaluates one, never checks the rules',
|
|
305
|
+
' and never writes the lock: with none it fails.',
|
|
276
306
|
' vx lock --check Audit the lock: file-changed hash report PLUS a full',
|
|
277
|
-
' re-evaluation compared against the frozen objects
|
|
278
|
-
' on drift (e.g. configs that read env
|
|
279
|
-
'
|
|
307
|
+
' re-evaluation compared against the frozen objects, and the',
|
|
308
|
+
' graph rules. Exits 1 on drift (e.g. configs that read env',
|
|
309
|
+
' vars). Run it before the run in CI. With --format json it prints',
|
|
280
310
|
' {upToDate, audited, notAudited, problems}.',
|
|
281
311
|
'',
|
|
282
312
|
...(pluginCommands.length > 0 ? ['Plugin commands:', ...pluginCommands, ''] : []),
|
|
@@ -315,7 +345,7 @@ export function refusedWord(arg: string): string {
|
|
|
315
345
|
* `(did you mean --concurrency?)` for a flag within two edits of one `verb`
|
|
316
346
|
* accepts; empty for anything else (a positional included). A third edit
|
|
317
347
|
* is allowed only between flags that share their first five characters,
|
|
318
|
-
* so `--
|
|
348
|
+
* so `--timeouts` reaches `--timeout` while `--zzz` does not reach `--all`.
|
|
319
349
|
*/
|
|
320
350
|
export function flagHint(verb: string, arg: string): string {
|
|
321
351
|
if (!arg.startsWith('-')) return ''
|
|
@@ -343,8 +373,8 @@ export function flagHint(verb: string, arg: string): string {
|
|
|
343
373
|
* The documented `--flags` of one verb, read from the help text's
|
|
344
374
|
* sections headed `… (for <verb>):` so there is no second list to drift —
|
|
345
375
|
* and no hint that names another verb's flag. Only the flag an option line
|
|
346
|
-
* OPENS with is one:
|
|
347
|
-
*
|
|
376
|
+
* OPENS with is one: an option line that named `vx lock --check` once put
|
|
377
|
+
* `--check` in the list, so `vx run --chek`
|
|
348
378
|
* was told "did you mean --check?" and `--check` was then refused.
|
|
349
379
|
*/
|
|
350
380
|
export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
@@ -354,8 +384,9 @@ export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
|
354
384
|
if (/^[A-Z][A-Za-z ]*(?: \(for [a-z]+\))?:$/.test(line))
|
|
355
385
|
inVerb = line.endsWith(`(for ${verb}):`)
|
|
356
386
|
if (!inVerb) continue
|
|
357
|
-
|
|
358
|
-
|
|
387
|
+
// `--x, --no-x`: a flag and its negation.
|
|
388
|
+
const own = /^\s+(--[a-zA-Z][a-zA-Z-]*)(?:, (--[a-zA-Z][a-zA-Z-]*))?/.exec(line)
|
|
389
|
+
if (own !== null) for (const f of own.slice(1)) if (f !== undefined) flags.add(f)
|
|
359
390
|
}
|
|
360
391
|
return [...flags]
|
|
361
392
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// handler lives in a sibling `<name>.ts`; tests import its parsers there.
|
|
3
3
|
|
|
4
4
|
import { VERSION } from '../version.js'
|
|
5
|
+
import { checkSettingsEnv } from '../settings.js'
|
|
5
6
|
import { CORE_VERBS, flagHint, printHelp, refusedWord, seeHelp } from './help.js'
|
|
6
7
|
import { FOREIGN_VERBS } from './foreign-flags.js'
|
|
7
8
|
import {
|
|
8
9
|
errorDocument,
|
|
10
|
+
hideEnvFromTasks,
|
|
11
|
+
isFsRefusal,
|
|
12
|
+
isOutOfFds,
|
|
9
13
|
isUserError,
|
|
10
14
|
maskedLine,
|
|
11
|
-
MOVED_VERBS,
|
|
12
15
|
nearest,
|
|
13
16
|
UserError,
|
|
14
17
|
wantsJson,
|
|
@@ -24,6 +27,7 @@ const plugins = () => import('./plugin-commands.js')
|
|
|
24
27
|
|
|
25
28
|
export async function run(argv: readonly string[]): Promise<number> {
|
|
26
29
|
const [command, ...rest] = argv
|
|
30
|
+
await hideEnvFromTasks()
|
|
27
31
|
|
|
28
32
|
// `vx <verb> --help` is the universal reflex, and every verb answered
|
|
29
33
|
// `unknown flag: --help` and exited 1 (walkthrough, 2026-09-04). Only args
|
|
@@ -34,7 +38,33 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
34
38
|
await printHelp(await (await plugins()).pluginCommandHelp(), command)
|
|
35
39
|
return 0
|
|
36
40
|
}
|
|
41
|
+
// Each setting's variable once, before any verb: a renamed one is named,
|
|
42
|
+
// and a deep reader trusts what it reads.
|
|
43
|
+
checkSettingsEnv()
|
|
37
44
|
|
|
45
|
+
try {
|
|
46
|
+
return await dispatch(command, rest)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw await brokenLockNamed(err)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A crash from a hand-broken lock entry is that entry's fault: runs validate
|
|
54
|
+
* no entry, so it surfaced as a raw TypeError (M, 2026-10-14). The lock is
|
|
55
|
+
* read only on the crash path, so a normal run pays nothing.
|
|
56
|
+
*/
|
|
57
|
+
async function brokenLockNamed(err: unknown): Promise<unknown> {
|
|
58
|
+
if (isUserError(err) || isFsRefusal(err) || isOutOfFds(err)) return err
|
|
59
|
+
try {
|
|
60
|
+
const { findWorkspaceRoot, LOCK_INVALID, lockEntryBroken } =
|
|
61
|
+
await import('../workspace/index.js')
|
|
62
|
+
if (lockEntryBroken(await findWorkspaceRoot(process.cwd()))) return new UserError(LOCK_INVALID)
|
|
63
|
+
} catch {}
|
|
64
|
+
return err
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function dispatch(command: string | undefined, rest: string[]): Promise<number> {
|
|
38
68
|
switch (command) {
|
|
39
69
|
case undefined: {
|
|
40
70
|
// A bare `vx` where no workspace is printed 151 lines of help, none
|
|
@@ -65,7 +95,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
65
95
|
// for the whole reference: it printed 145 lines and no hint.
|
|
66
96
|
const resolved = await (await plugins()).resolvePluginCommand(verb)
|
|
67
97
|
const unknown = resolved === null || 'declaredVerbs' in resolved
|
|
68
|
-
const pointer =
|
|
98
|
+
const pointer = FOREIGN_VERBS[verb]
|
|
69
99
|
if (unknown && pointer !== undefined) {
|
|
70
100
|
process.stderr.write(`${pointer}\n`)
|
|
71
101
|
return 1
|
|
@@ -171,12 +201,11 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
171
201
|
}
|
|
172
202
|
const loadNote =
|
|
173
203
|
resolved !== null && 'loadError' in resolved
|
|
174
|
-
? `\n (plugin verbs could not be looked up: vx
|
|
204
|
+
? `\n (plugin verbs could not be looked up: .vx/workspace failed to load: ${resolved.loadError})`
|
|
175
205
|
: ''
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
const moved = MOVED_VERBS[command] ?? FOREIGN_VERBS[command]
|
|
206
|
+
// Another tool's verb: the pointer, but only after the plugins had
|
|
207
|
+
// their chance — a workspace that declares a plugin verb of that name keeps it.
|
|
208
|
+
const moved = FOREIGN_VERBS[command]
|
|
180
209
|
if (moved !== undefined) {
|
|
181
210
|
return unknown(`${moved}${loadNote}\n`)
|
|
182
211
|
}
|
|
@@ -198,7 +227,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
198
227
|
if (command === 'serve' || command === 'dev') {
|
|
199
228
|
// vx core is only a task runner — it has no service layer of its
|
|
200
229
|
// own. A dashboard, remote cache, distributed execution, etc. are
|
|
201
|
-
// provided by PLUGINS (declared in vx
|
|
230
|
+
// provided by PLUGINS (declared in .vx/workspace.ts), never by core.
|
|
202
231
|
// We keep this neutral hint for the common muscle-memory verbs, but
|
|
203
232
|
// core names no specific plugin package: any package can provide
|
|
204
233
|
// these.
|
|
@@ -213,10 +242,10 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
213
242
|
// help after a typo was a hundred lines past the hint that mattered.
|
|
214
243
|
// The verbs this workspace's plugins declare are verbs HERE, so they
|
|
215
244
|
// join the "did you mean" set, and when nothing is close the second
|
|
216
|
-
// line says where a verb can come from:
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
245
|
+
// line says where a verb can come from: a typo of a plugin verb read
|
|
246
|
+
// as a plain unknown command, and a plugin verb before the plugin was
|
|
247
|
+
// declared said nothing about the file that would declare it
|
|
248
|
+
// (2026-09-20).
|
|
220
249
|
// A word that opens with a dash is a flag, and vx itself takes only
|
|
221
250
|
// --help and --version: "unknown command: --bogus" named the wrong
|
|
222
251
|
// thing (M-60).
|
|
@@ -268,5 +297,5 @@ function verbSourceNote(resolved: unknown, declaredVerbs: readonly string[]): st
|
|
|
268
297
|
return ` This workspace's plugins declare: ${[...declaredVerbs].sort().join(', ')}.\n`
|
|
269
298
|
}
|
|
270
299
|
const here = resolved === null ? 'there is no workspace here' : 'this workspace declares none'
|
|
271
|
-
return ` A plugin declared in vx
|
|
300
|
+
return ` A plugin declared in .vx/workspace.ts can add verbs; ${here}.\n`
|
|
272
301
|
}
|
package/src/cli/info.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// `vx info` — workspace doctor printout. One screen of environment +
|
|
2
2
|
// workspace + cache facts for bug reports and quick sanity checks.
|
|
3
3
|
// The facts come
|
|
4
|
-
// from the orchestrator's `collectInfo` (doctor.ts)
|
|
5
|
-
// too; this file parses the flags and renders the rows.
|
|
4
|
+
// from the orchestrator's `collectInfo` (doctor.ts); this file parses the flags and renders the rows.
|
|
6
5
|
|
|
7
6
|
import os from 'node:os'
|
|
8
7
|
import { collectInfo, type FlakyTask, type InfoFacts } from '../orchestrator/index.js'
|
|
@@ -10,6 +9,7 @@ import { flagHint, formatValue, refuse, refusedWord, seeHelp } from './help.js'
|
|
|
10
9
|
import { namedCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
|
|
11
10
|
import { formatBytes } from './format.js'
|
|
12
11
|
import { MIN_BUN } from '../util/index.js'
|
|
12
|
+
import { envName } from '../settings.js'
|
|
13
13
|
|
|
14
14
|
interface InfoArgs {
|
|
15
15
|
format: 'pretty' | 'json'
|
|
@@ -46,7 +46,7 @@ export function parseInfoArgs(args: readonly string[]): InfoArgs {
|
|
|
46
46
|
export async function infoCmd(args: readonly string[]): Promise<number> {
|
|
47
47
|
const parsed = parseInfoArgs(args)
|
|
48
48
|
if (parsed.error) return refuse('info', args, parsed.error, 'VX_E_USAGE')
|
|
49
|
-
// `collectInfo` takes a resolved directory
|
|
49
|
+
// `collectInfo` takes a resolved directory, where one
|
|
50
50
|
// not made yet is a workspace that never ran; the flag is the user's,
|
|
51
51
|
// and a directory it names that is not there is a typo (item 900).
|
|
52
52
|
if (parsed.cacheDir !== undefined) namedCacheDir(parsed.cacheDir)
|
|
@@ -55,14 +55,17 @@ export async function infoCmd(args: readonly string[]): Promise<number> {
|
|
|
55
55
|
warn: warnToStderr,
|
|
56
56
|
})
|
|
57
57
|
process.stdout.write(
|
|
58
|
-
parsed.format === 'json' ? `${JSON.stringify(facts
|
|
58
|
+
parsed.format === 'json' ? `${JSON.stringify(facts)}\n` : `${renderInfo(facts)}\n`,
|
|
59
59
|
)
|
|
60
60
|
return 0
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export function renderInfo(f: InfoFacts): string {
|
|
64
64
|
const rows: [string, string][] = [
|
|
65
|
-
[
|
|
65
|
+
[
|
|
66
|
+
'vx',
|
|
67
|
+
`${f.vx} (${tildePath(f.vxPath)}${f.handedOffFrom === null ? '' : `, handed off from ${tildePath(f.handedOffFrom)}`})`,
|
|
68
|
+
],
|
|
66
69
|
// The prose stays in the rendered row; `f.bun` itself is the bare
|
|
67
70
|
// version a script reads (doctor.ts says why).
|
|
68
71
|
[
|
|
@@ -75,24 +78,25 @@ export function renderInfo(f: InfoFacts): string {
|
|
|
75
78
|
['git status cache', renderGitStatusCache(f.gitStatusCache)],
|
|
76
79
|
['workspace root', tildePath(f.workspaceRoot)],
|
|
77
80
|
['projects', describeProjects(f)],
|
|
78
|
-
// Only when a config did not load: the count above says "zero" for
|
|
79
|
-
// it, and this row says which and why.
|
|
80
|
-
...(f.configErrors.length > 0
|
|
81
|
-
? ([['config errors', f.configErrors.map((e) => `${e.path}: ${e.message}`).join('; ')]] as [
|
|
82
|
-
string,
|
|
83
|
-
string,
|
|
84
|
-
][])
|
|
85
|
-
: []),
|
|
86
81
|
['plugins', describePlugins(f.plugins)],
|
|
87
82
|
['workers', describeWorkers(f.workers)],
|
|
88
83
|
['memory', describeMemory(f.memory)],
|
|
89
|
-
[
|
|
84
|
+
[
|
|
85
|
+
'cache dir',
|
|
86
|
+
`${tildePath(f.cacheDir)} (${formatBytes(f.cacheDirBytes)}, ${f.cacheStore === null ? 'the entries and ' : ''}this workspace's run history)`,
|
|
87
|
+
],
|
|
90
88
|
[
|
|
91
89
|
'cache store',
|
|
92
|
-
f.cacheStore === null
|
|
90
|
+
f.cacheStore === null
|
|
91
|
+
? 'none: the cache dir holds the entries'
|
|
92
|
+
: `${tildePath(f.cacheStore)} (${formatBytes(f.cacheStoreBytes ?? 0)}, shared by this repo's checkouts)`,
|
|
93
93
|
],
|
|
94
94
|
['cache versions', `keys ${f.cacheVersion} · index schema ${f.schemaVersion}`],
|
|
95
95
|
['cache entries', describeEntries(f)],
|
|
96
|
+
// Only the settings a variable or the workspace changed.
|
|
97
|
+
...(Object.values(f.settings).some((s) => s.source !== 'default')
|
|
98
|
+
? ([['settings', describeSettings(f.settings)]] as [string, string][])
|
|
99
|
+
: []),
|
|
96
100
|
...(f.cacheRetention !== null
|
|
97
101
|
? ([['cache retention', describeRetention(f.cacheRetention)]] as [string, string][])
|
|
98
102
|
: []),
|
|
@@ -101,7 +105,7 @@ export function renderInfo(f: InfoFacts): string {
|
|
|
101
105
|
? ([
|
|
102
106
|
[
|
|
103
107
|
'orphans',
|
|
104
|
-
`${f.orphans.artifacts} artifact${f.orphans.artifacts === 1 ? '' : 's'} (${formatBytes(f.orphans.bytes)}) the index does not know — \`vx cache
|
|
108
|
+
`${f.orphans.artifacts} artifact${f.orphans.artifacts === 1 ? '' : 's'} (${formatBytes(f.orphans.bytes)}) the index does not know — \`vx cache clean\` reaps them`,
|
|
105
109
|
],
|
|
106
110
|
] as [string, string][])
|
|
107
111
|
: []),
|
|
@@ -116,7 +120,7 @@ export function renderInfo(f: InfoFacts): string {
|
|
|
116
120
|
],
|
|
117
121
|
['flaky tasks', describeFlakyTasks(f.flakyTasks)],
|
|
118
122
|
['sandbox', describeSandbox(f.sandbox)],
|
|
119
|
-
['
|
|
123
|
+
['projects lock', f.lockfile ? 'yes' : 'no'],
|
|
120
124
|
]
|
|
121
125
|
const labelW = Math.max(...rows.map(([label]) => label.length))
|
|
122
126
|
return rows.map(([label, value]) => `${`${label}:`.padEnd(labelW + 1)} ${value}`).join('\n')
|
|
@@ -132,6 +136,20 @@ function describeEntries(f: InfoFacts): string {
|
|
|
132
136
|
return `${f.cacheEntries} (${size})`
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The settings not at their default, with where each came from:
|
|
141
|
+
* `run.timeout 30m (VX_RUN_TIMEOUT), cache.policy local:rw (.vx/workspace)`.
|
|
142
|
+
*/
|
|
143
|
+
function describeSettings(settings: InfoFacts['settings']): string {
|
|
144
|
+
return Object.entries(settings)
|
|
145
|
+
.filter(([, s]) => s.source !== 'default')
|
|
146
|
+
.map(
|
|
147
|
+
([path, s]) =>
|
|
148
|
+
`${path} ${String(s.value)} (${s.source === 'env' ? envName(path) : '.vx/workspace'})`,
|
|
149
|
+
)
|
|
150
|
+
.join(', ')
|
|
151
|
+
}
|
|
152
|
+
|
|
135
153
|
/** What a run's end evicts: `entries unused for 30d, and the oldest entries past 10G`. */
|
|
136
154
|
function describeRetention(r: NonNullable<InfoFacts['cacheRetention']>): string {
|
|
137
155
|
const parts = [
|
|
@@ -163,10 +181,10 @@ function describeSandbox(s: InfoFacts['sandbox']): string {
|
|
|
163
181
|
return `unavailable — ${s.reason}; ${declared}${s.declared > 0 ? ' and will fail' : ''}`
|
|
164
182
|
}
|
|
165
183
|
|
|
166
|
-
/** `4 — the CPU count`, `2 — cgroup CPU quota 2 of 8 cores`, `8 — vx
|
|
184
|
+
/** `4 — the CPU count`, `2 — cgroup CPU quota 2 of 8 cores`, `8 — .vx/workspace.ts (4 cores)`. */
|
|
167
185
|
export function describeWorkers(w: InfoFacts['workers']): string {
|
|
168
186
|
const quota = w.cpuQuota === null ? '' : `, cgroup CPU quota ${trimCores(w.cpuQuota)}`
|
|
169
|
-
if (w.source === 'workspace') return `${w.count} — vx
|
|
187
|
+
if (w.source === 'workspace') return `${w.count} — .vx/workspace.ts (${w.cores} cores${quota})`
|
|
170
188
|
if (w.source === 'cgroup')
|
|
171
189
|
return `${w.count} — cgroup CPU quota ${trimCores(w.cpuQuota ?? w.count)} of ${w.cores} cores`
|
|
172
190
|
return `${w.count} — the CPU count${quota}`
|
|
@@ -186,10 +204,7 @@ function trimCores(n: number): string {
|
|
|
186
204
|
|
|
187
205
|
/** `2 — app#test (3 of 7 runs failed on unchanged inputs); api#e2e (1 of 4)`, or `none`. */
|
|
188
206
|
function describeProjects(f: InfoFacts): string {
|
|
189
|
-
|
|
190
|
-
const n = f.configErrors.length
|
|
191
|
-
if (n === 0) return `${f.projects} (${tasks})`
|
|
192
|
-
return `${f.projects} (${tasks} · ${n} config${n === 1 ? '' : 's'} did not load)`
|
|
207
|
+
return `${f.projects} (${f.tasks} task${f.tasks === 1 ? '' : 's'})`
|
|
193
208
|
}
|
|
194
209
|
|
|
195
210
|
function describeFlakyTasks(tasks: readonly FlakyTask[]): string {
|