@vzn/vx 0.0.635 → 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 +47 -19
- package/docs/caching.md +222 -210
- package/docs/cli.md +699 -664
- package/docs/execution.md +67 -75
- package/docs/features.md +44 -40
- package/docs/patterns.md +31 -32
- package/docs/schema.md +418 -202
- 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 +13 -4
- 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 -61
- 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 +123 -144
- 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 +191 -71
- 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 +25 -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 +192 -70
- 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 +43 -28
- 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 +114 -73
- 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 +127 -88
- 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 +251 -241
- 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 +22 -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,54 +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
|
-
'
|
|
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.',
|
|
159
182
|
'',
|
|
160
183
|
'Planning (for run — skips execution):',
|
|
161
|
-
|
|
184
|
+
" --dry[=text|json] Print the task graph and each task's predicted hit",
|
|
185
|
+
' or miss.',
|
|
162
186
|
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
163
187
|
'',
|
|
164
188
|
'Artifacts (for run):',
|
|
165
|
-
' --format <fmt> pretty (default) | json: print the --summarize
|
|
166
|
-
' 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.',
|
|
167
192
|
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
168
193
|
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
169
194
|
' --report[=markdown] Print a markdown run report to stdout after the run.',
|
|
170
|
-
' --report-file <path> Append that report to <path> (
|
|
171
|
-
' --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.',
|
|
172
198
|
'',
|
|
173
199
|
'Extensions (plugins):',
|
|
174
200
|
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
175
201
|
' cache, distributed execution, and telemetry export are provided by',
|
|
176
|
-
' PLUGINS declared in vx
|
|
202
|
+
' PLUGINS declared in .vx/workspace.ts — first-party or your own. Core',
|
|
177
203
|
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
178
204
|
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
179
205
|
'',
|
|
@@ -192,24 +218,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
192
218
|
' Uses the same flags as `vx run` except --dry /',
|
|
193
219
|
' --graph / --summarize / --profile / --report /',
|
|
194
220
|
' --report-file / --verbosity (they plan or report',
|
|
195
|
-
' one run)
|
|
221
|
+
' one run), plus --poll (watch.poll: poll instead of',
|
|
222
|
+
' the OS watcher). Press Ctrl+C to stop. Cache means most',
|
|
196
223
|
' re-runs are near-zero cost (cache hits restore',
|
|
197
224
|
' outputs + replay logs).',
|
|
198
225
|
'',
|
|
199
226
|
'Cache management:',
|
|
200
|
-
' vx cache
|
|
201
|
-
' 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.",
|
|
202
231
|
' --dry-run Say what the policy would reap; delete nothing.',
|
|
203
232
|
' --format <fmt> pretty (default) | json.',
|
|
204
233
|
' --cache-dir <path> The cache a run with the same flag uses (why, last, info too).',
|
|
205
234
|
'',
|
|
206
|
-
' 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).',
|
|
207
236
|
' Cache statistics — directory, entry count, size — are part of `vx info`.',
|
|
208
237
|
'',
|
|
209
238
|
'Introspection:',
|
|
210
239
|
' vx show List every project: name, dir, declared task count.',
|
|
211
|
-
" vx show <project> Print the project's
|
|
212
|
-
'
|
|
240
|
+
" vx show <project> Print the project's resolved config as a run reads",
|
|
241
|
+
' it from the lock, one block per task.',
|
|
213
242
|
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
214
243
|
' vx show <task> That task in every project declaring it.',
|
|
215
244
|
" --filter <pattern> The list (or a task's projects) as `vx run --filter` selects.",
|
|
@@ -232,25 +261,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
232
261
|
' --failed Only failed runs: the latest one, or with --list the list.',
|
|
233
262
|
" --log <task> That task's output in the run (latest that ran it): a failure's",
|
|
234
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).",
|
|
235
267
|
' --format <fmt> pretty (default) | json.',
|
|
236
268
|
' info, why and last:',
|
|
237
269
|
' --cache-dir <path> Read the cache a run with the same flag uses.',
|
|
238
270
|
'',
|
|
239
271
|
'Migration:',
|
|
240
|
-
' vx init
|
|
241
|
-
'
|
|
242
|
-
'
|
|
243
|
-
'
|
|
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).',
|
|
244
276
|
' --dry Print the generated files instead of writing them.',
|
|
245
277
|
' --format json With --dry: the plan as one JSON document (schemas/init.json).',
|
|
246
278
|
' --force Overwrite existing vx.config.* files.',
|
|
247
279
|
' --mjs Write .mjs files (no type import) instead of .ts.',
|
|
248
280
|
' --native Turbo or Nx: write a vx.config.ts per package (no question).',
|
|
249
|
-
' --keep Turbo or Nx: only vx
|
|
281
|
+
' --keep Turbo or Nx: only .vx/workspace.ts, declaring turbo() or nx().',
|
|
250
282
|
' --plugin <seam> Instead: write plugins/<seam>.ts, a runnable plugin, and its',
|
|
251
283
|
' test (executor, cache, telemetry, schedule, admit, commands,',
|
|
252
284
|
' project, graph, key).',
|
|
253
|
-
' From turbo.json or an Nx project graph: `vx init` runs `@vzn/vx-migrate` (its own package).',
|
|
254
285
|
'',
|
|
255
286
|
'Shell completions:',
|
|
256
287
|
' vx completions <shell> Print a completion script for bash, zsh or fish: the',
|
|
@@ -267,14 +298,15 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
267
298
|
' --format json {query, hits: [{page, heading, url, text}]}.',
|
|
268
299
|
'',
|
|
269
300
|
'Config lock:',
|
|
270
|
-
' vx lock Evaluate every vx.config in the current environment
|
|
271
|
-
' freeze the resolved objects
|
|
272
|
-
'
|
|
273
|
-
'
|
|
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.',
|
|
274
306
|
' vx lock --check Audit the lock: file-changed hash report PLUS a full',
|
|
275
|
-
' re-evaluation compared against the frozen objects
|
|
276
|
-
' on drift (e.g. configs that read env
|
|
277
|
-
'
|
|
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',
|
|
278
310
|
' {upToDate, audited, notAudited, problems}.',
|
|
279
311
|
'',
|
|
280
312
|
...(pluginCommands.length > 0 ? ['Plugin commands:', ...pluginCommands, ''] : []),
|
|
@@ -313,7 +345,7 @@ export function refusedWord(arg: string): string {
|
|
|
313
345
|
* `(did you mean --concurrency?)` for a flag within two edits of one `verb`
|
|
314
346
|
* accepts; empty for anything else (a positional included). A third edit
|
|
315
347
|
* is allowed only between flags that share their first five characters,
|
|
316
|
-
* so `--
|
|
348
|
+
* so `--timeouts` reaches `--timeout` while `--zzz` does not reach `--all`.
|
|
317
349
|
*/
|
|
318
350
|
export function flagHint(verb: string, arg: string): string {
|
|
319
351
|
if (!arg.startsWith('-')) return ''
|
|
@@ -341,8 +373,8 @@ export function flagHint(verb: string, arg: string): string {
|
|
|
341
373
|
* The documented `--flags` of one verb, read from the help text's
|
|
342
374
|
* sections headed `… (for <verb>):` so there is no second list to drift —
|
|
343
375
|
* and no hint that names another verb's flag. Only the flag an option line
|
|
344
|
-
* OPENS with is one:
|
|
345
|
-
*
|
|
376
|
+
* OPENS with is one: an option line that named `vx lock --check` once put
|
|
377
|
+
* `--check` in the list, so `vx run --chek`
|
|
346
378
|
* was told "did you mean --check?" and `--check` was then refused.
|
|
347
379
|
*/
|
|
348
380
|
export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
@@ -352,8 +384,9 @@ export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
|
352
384
|
if (/^[A-Z][A-Za-z ]*(?: \(for [a-z]+\))?:$/.test(line))
|
|
353
385
|
inVerb = line.endsWith(`(for ${verb}):`)
|
|
354
386
|
if (!inVerb) continue
|
|
355
|
-
|
|
356
|
-
|
|
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)
|
|
357
390
|
}
|
|
358
391
|
return [...flags]
|
|
359
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 {
|