@vzn/vx 0.0.636 → 0.0.638
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 +246 -213
- package/docs/cli.md +791 -705
- package/docs/execution.md +68 -76
- package/docs/features.md +47 -42
- package/docs/patterns.md +26 -25
- package/docs/schema.md +392 -205
- 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 +4 -4
- package/schemas/show.json +5 -3
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +28 -13
- package/src/bin.ts +30 -9
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +124 -53
- package/src/cache/chained-cache.ts +4 -0
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +15 -1
- package/src/cache/inputs.ts +102 -36
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +15 -18
- package/src/cache/layered-cache.ts +20 -102
- package/src/cache/plugin-cache.ts +248 -0
- package/src/cache/policy.ts +32 -27
- package/src/cache/run-history.ts +104 -14
- package/src/cache/run-store.ts +77 -0
- package/src/cache/schema.ts +58 -31
- package/src/cache/scoped-cache.ts +237 -0
- package/src/cli/cache.ts +173 -34
- package/src/cli/completions.ts +11 -7
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +110 -70
- package/src/cli/index.ts +67 -24
- package/src/cli/info.ts +43 -27
- package/src/cli/init.ts +115 -407
- package/src/cli/last.ts +157 -9
- package/src/cli/lock.ts +356 -122
- package/src/cli/plan-format.ts +7 -5
- package/src/cli/plugin-commands.ts +81 -14
- package/src/cli/plugin-templates.ts +11 -11
- package/src/cli/run.ts +116 -160
- package/src/cli/select.ts +50 -47
- package/src/cli/show.ts +83 -59
- package/src/cli/task-verb.ts +6 -13
- package/src/cli/upgrade.ts +415 -149
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +39 -61
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +18 -4
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +227 -230
- package/src/cli/why.ts +38 -18
- package/src/cli/workspace-config.ts +33 -70
- 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 +27 -7
- package/src/graph/task-graph.ts +76 -24
- package/src/handoff.ts +169 -0
- package/src/index.ts +35 -58
- package/src/orchestrator/affected-tasks.ts +15 -8
- 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 +147 -69
- package/src/orchestrator/failure-mode.ts +19 -10
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +9 -5
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +126 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +20 -5
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +80 -41
- package/src/orchestrator/metrics.ts +78 -9
- 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 +100 -66
- package/src/orchestrator/plugin.ts +173 -67
- package/src/orchestrator/prepare.ts +163 -78
- package/src/orchestrator/projects.ts +105 -99
- package/src/orchestrator/remote-prefetch.ts +11 -6
- 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-records.ts +1 -1
- package/src/orchestrator/run-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +244 -119
- 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 +49 -7
- package/src/orchestrator/summary.ts +115 -106
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +4 -4
- package/src/settings.ts +428 -0
- package/src/{orchestrator → util}/colors.ts +5 -0
- package/src/util/config-frame.ts +31 -2
- package/src/util/errors.ts +33 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +16 -3
- package/src/util/paths.ts +10 -0
- package/src/util/refusal-notice.ts +111 -0
- package/src/util/report.ts +31 -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/ui.ts +186 -0
- package/src/util/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +84 -232
- 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 +65 -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 +307 -215
- 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 +363 -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
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
CORE_VERBS,
|
|
3
|
+
detectColors,
|
|
4
|
+
errorDocument,
|
|
5
|
+
maskedLine,
|
|
6
|
+
nearest,
|
|
7
|
+
paint,
|
|
8
|
+
refusalNotice,
|
|
9
|
+
ui,
|
|
10
|
+
wantsJson,
|
|
11
|
+
type ColorSupport,
|
|
12
|
+
} from '../util/index.js'
|
|
3
13
|
|
|
4
14
|
export async function printHelp(
|
|
5
15
|
pluginCommands: readonly string[] = [],
|
|
6
16
|
verb?: string,
|
|
7
17
|
): Promise<void> {
|
|
8
18
|
const text = verb === undefined ? helpText(pluginCommands) : verbHelpText(verb)
|
|
9
|
-
// The orchestrator is already loaded by the plugin-verb lookup every
|
|
10
|
-
// caller awaits first, so this import costs `vx --version` nothing.
|
|
11
|
-
const { detectColors, paint } = await import('../orchestrator/index.js')
|
|
12
19
|
process.stdout.write(paintHelp(text, helpColors(process.stdout, detectColors), paint))
|
|
13
20
|
}
|
|
14
21
|
|
|
@@ -33,7 +40,7 @@ const TERM = '#22d3ee' // cyan-400 — a flag, a selector, an example
|
|
|
33
40
|
* `(for <verb>)` dim), `vx <verb>` bold cyan, and the term an option or
|
|
34
41
|
* example line opens with cyan. Prose and continuation lines stay plain.
|
|
35
42
|
*/
|
|
36
|
-
export function paintHelp(text: string, colors: ColorSupport, paint:
|
|
43
|
+
export function paintHelp(text: string, colors: ColorSupport, paint: Paint): string {
|
|
37
44
|
if (!colors.enabled) return text
|
|
38
45
|
const lines = text.split('\n')
|
|
39
46
|
return lines
|
|
@@ -102,19 +109,19 @@ export function verbHelpText(verb: string): string {
|
|
|
102
109
|
|
|
103
110
|
export function helpText(pluginCommands: readonly string[] = []): string {
|
|
104
111
|
return [
|
|
105
|
-
'vx —
|
|
112
|
+
'vx — task orchestration you own',
|
|
106
113
|
'',
|
|
107
114
|
'Usage:',
|
|
108
115
|
' vx run [OPTIONS] [TASK | PKG#TASK ...] [-- forwarded-args...]',
|
|
109
|
-
' vx watch [OPTIONS] TASK [-- forwarded-args...]',
|
|
110
|
-
' vx cache
|
|
116
|
+
' vx watch [OPTIONS] [--poll] TASK [-- forwarded-args...]',
|
|
117
|
+
' vx cache clean [--older-than <duration>] [--max-size <size>] [--global] [--dry-run] [--format pretty|json] [--cache-dir <path>]',
|
|
111
118
|
' vx lock [--check] [--format pretty|json]',
|
|
112
119
|
' vx init [--dry [--format pretty|json]] [--force] [--mjs] [--native|--keep] [--plugin <seam>]',
|
|
113
120
|
' vx upgrade [TAG]',
|
|
114
121
|
' vx show [PROJECT[#TASK] | TASK] [--filter <pattern>] [--affected[=<ref>]] [--format pretty|json]',
|
|
115
122
|
' vx info [--format pretty|json] [--cache-dir <path>]',
|
|
116
123
|
' 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>]',
|
|
124
|
+
' vx last [RUNID] [--list[=N]] [--failed] [--log <task>] [--stats [<task>] [--branch <name>]] [--format pretty|json] [--cache-dir <path>]',
|
|
118
125
|
' vx docs <query> [--limit N] [--format pretty|json]',
|
|
119
126
|
' vx completions bash|zsh|fish',
|
|
120
127
|
' vx help [VERB]',
|
|
@@ -126,56 +133,80 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
126
133
|
' --filter <pat> pnpm-style filter (repeatable). Examples:',
|
|
127
134
|
' foo, @scope/*, ./packages/foo, foo..., ...foo,',
|
|
128
135
|
' foo^..., !foo, [<since>]',
|
|
129
|
-
' --affected[=<base>]
|
|
130
|
-
'
|
|
131
|
-
' master,
|
|
136
|
+
' --affected[=<base>] Projects changed since <base>, and dependents',
|
|
137
|
+
' (--filter "...[<base>]"); default run.affectedBase,',
|
|
138
|
+
' else origin/HEAD, main, master, HEAD~1.',
|
|
132
139
|
" pkg#task Run a specific project's task directly.",
|
|
133
140
|
'',
|
|
134
141
|
'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
|
-
'
|
|
142
|
+
' Each setting flag beats its VX_* variable, which beats .vx/workspace',
|
|
143
|
+
' (docs/schema.md § Settings).',
|
|
144
|
+
' --concurrency <n> Max parallel tasks (default: usable cores);',
|
|
145
|
+
' 50% means half of them.',
|
|
146
|
+
' --schedule <rule> most-work (default) | critical-path |',
|
|
147
|
+
' direct-dependents | ready-order.',
|
|
148
|
+
' --branch <name> The branch history records and forecasts read',
|
|
149
|
+
" (default: git's, else the CI provider's).",
|
|
150
|
+
' --exclude-dependencies[=names] Skip dependsOn edges: all with no value,',
|
|
151
|
+
' or a comma list of names.',
|
|
152
|
+
' --no-cache No cache reads or writes; outputs are left',
|
|
153
|
+
' alone.',
|
|
154
|
+
' --force Skip cache reads but still write: every task',
|
|
155
|
+
' runs and refreshes its entry.',
|
|
156
|
+
' --cache-policy <spec> Per-layer read/write: a comma list of',
|
|
157
|
+
' <layer>:<flags> (layer local or remote, flags',
|
|
158
|
+
' from rw). A named layer is set to exactly its',
|
|
159
|
+
' flags; unnamed layers keep theirs. Examples:',
|
|
160
|
+
' local:rw,remote:r (remote read-only), remote:',
|
|
161
|
+
' (remote off), local:r (local read-only).',
|
|
162
|
+
" --cache-scope <name> This run's cache.scope (pr-123): write there,",
|
|
163
|
+
' read the default keys first.',
|
|
164
|
+
' --cache-dir <path> Cache directory (cwd-relative); default',
|
|
165
|
+
' .vx/cache, entries in ~/.vx/<id>/cache.',
|
|
166
|
+
' --cache-download <mode> all (default) | toplevel | none:',
|
|
167
|
+
' where remote outputs land.',
|
|
168
|
+
' --retries <n> Up to <n> more runs of a failed task, for',
|
|
169
|
+
' tasks without their own exec.retries. Not',
|
|
170
|
+
' folded into cache keys.',
|
|
171
|
+
" --timeout <duration> A task's time limit (30s, 10m), for tasks",
|
|
172
|
+
' without their own exec.timeout. A task past it',
|
|
173
|
+
' is killed and fails.',
|
|
174
|
+
' --kill-grace <duration> How long a task that ignores SIGTERM gets',
|
|
175
|
+
' before SIGKILL (default 2s).',
|
|
176
|
+
" --teardown-timeout <duration> The bound on each plugin's end-of-run flush",
|
|
177
|
+
' (default 3s).',
|
|
178
|
+
' --ready-notice <duration> When vx says a persistent task still waits for',
|
|
179
|
+
' readyWhen (default 10s).',
|
|
180
|
+
' --continue[=<mode>] What a failed task takes down: never | deps-ok',
|
|
181
|
+
' (default, skips its dependents) | always (bare',
|
|
182
|
+
' form; dependents run, never save).',
|
|
183
|
+
' --output-logs <mode> full | errors-only | hash-only | none, over',
|
|
184
|
+
' the flow default (focused without',
|
|
185
|
+
' --all/--filter/--affected, broad with, full in',
|
|
186
|
+
' CI).',
|
|
187
|
+
' --verbosity <n> 0 (default), or 1 and up for a per-task',
|
|
188
|
+
' summary table after the run.',
|
|
161
189
|
'',
|
|
162
190
|
'Planning (for run — skips execution):',
|
|
163
|
-
|
|
191
|
+
" --dry[=text|json] Print the task graph and each task's predicted hit",
|
|
192
|
+
' or miss.',
|
|
164
193
|
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
165
194
|
'',
|
|
166
195
|
'Artifacts (for run):',
|
|
167
|
-
' --format <fmt> pretty (default) | json: print the --summarize
|
|
168
|
-
' on stdout when the run ends; every other
|
|
196
|
+
' --format <fmt> pretty (default) | json: print the --summarize',
|
|
197
|
+
' document on stdout when the run ends; every other',
|
|
198
|
+
' line goes to stderr.',
|
|
169
199
|
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
170
200
|
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
171
201
|
' --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
|
|
202
|
+
' --report-file <path> Append that report to <path> ($GITHUB_STEP_SUMMARY).',
|
|
203
|
+
' --tag <k=v> Label this invocation (repeatable); recorded on its',
|
|
204
|
+
' history row.',
|
|
174
205
|
'',
|
|
175
206
|
'Extensions (plugins):',
|
|
176
207
|
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
177
208
|
' cache, distributed execution, and telemetry export are provided by',
|
|
178
|
-
' PLUGINS declared in vx
|
|
209
|
+
' PLUGINS declared in .vx/workspace.ts — first-party or your own. Core',
|
|
179
210
|
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
180
211
|
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
181
212
|
'',
|
|
@@ -194,24 +225,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
194
225
|
' Uses the same flags as `vx run` except --dry /',
|
|
195
226
|
' --graph / --summarize / --profile / --report /',
|
|
196
227
|
' --report-file / --verbosity (they plan or report',
|
|
197
|
-
' one run)
|
|
228
|
+
' one run), plus --poll (watch.poll: poll instead of',
|
|
229
|
+
' the OS watcher). Press Ctrl+C to stop. Cache means most',
|
|
198
230
|
' re-runs are near-zero cost (cache hits restore',
|
|
199
231
|
' outputs + replay logs).',
|
|
200
232
|
'',
|
|
201
233
|
'Cache management:',
|
|
202
|
-
' vx cache
|
|
203
|
-
' vx cache
|
|
234
|
+
' vx cache clean --older-than 30d Evict entries last accessed > 30 days ago.',
|
|
235
|
+
' vx cache clean --max-size 1G Keep total cache under 1 GB (LRU eviction).',
|
|
236
|
+
" vx cache clean Clear .vx/cache and this repo's store ~/.vx/<id>/cache.",
|
|
237
|
+
" --global Clear every repo's store under ~/.vx.",
|
|
204
238
|
' --dry-run Say what the policy would reap; delete nothing.',
|
|
205
239
|
' --format <fmt> pretty (default) | json.',
|
|
206
240
|
' --cache-dir <path> The cache a run with the same flag uses (why, last, info too).',
|
|
207
241
|
'',
|
|
208
|
-
' Duration units: s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
242
|
+
' Duration units: ms, s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
209
243
|
' Cache statistics — directory, entry count, size — are part of `vx info`.',
|
|
210
244
|
'',
|
|
211
245
|
'Introspection:',
|
|
212
246
|
' vx show List every project: name, dir, declared task count.',
|
|
213
|
-
" vx show <project> Print the project's
|
|
214
|
-
'
|
|
247
|
+
" vx show <project> Print the project's resolved config as a run reads",
|
|
248
|
+
' it from the lock, one block per task.',
|
|
215
249
|
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
216
250
|
' vx show <task> That task in every project declaring it.',
|
|
217
251
|
" --filter <pattern> The list (or a task's projects) as `vx run --filter` selects.",
|
|
@@ -234,25 +268,27 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
234
268
|
' --failed Only failed runs: the latest one, or with --list the list.',
|
|
235
269
|
" --log <task> That task's output in the run (latest that ran it): a failure's",
|
|
236
270
|
' or the cached log; none for an uncached pass.',
|
|
271
|
+
' --stats [<task>] Per-task p50/p99, pass and hit rate, failure mode, peak',
|
|
272
|
+
' RSS and CPU over the last 50 runs; a task id or name narrows.',
|
|
273
|
+
" --branch <name> The branch --stats' p50 forecasts from (default: the run's).",
|
|
237
274
|
' --format <fmt> pretty (default) | json.',
|
|
238
275
|
' info, why and last:',
|
|
239
276
|
' --cache-dir <path> Read the cache a run with the same flag uses.',
|
|
240
277
|
'',
|
|
241
278
|
'Migration:',
|
|
242
|
-
' vx init
|
|
243
|
-
'
|
|
244
|
-
'
|
|
245
|
-
'
|
|
279
|
+
' vx init With no package.json: write .vx/workspace.ts and an empty',
|
|
280
|
+
' root vx.config.ts. With one: runs @vzn/vx-migrate, which maps',
|
|
281
|
+
' turbo.json, an Nx graph or package.json scripts to one',
|
|
282
|
+
' vx.config.ts per package (asking native or keep in Turbo/Nx).',
|
|
246
283
|
' --dry Print the generated files instead of writing them.',
|
|
247
284
|
' --format json With --dry: the plan as one JSON document (schemas/init.json).',
|
|
248
285
|
' --force Overwrite existing vx.config.* files.',
|
|
249
286
|
' --mjs Write .mjs files (no type import) instead of .ts.',
|
|
250
287
|
' --native Turbo or Nx: write a vx.config.ts per package (no question).',
|
|
251
|
-
' --keep Turbo or Nx: only vx
|
|
288
|
+
' --keep Turbo or Nx: only .vx/workspace.ts, declaring turbo() or nx().',
|
|
252
289
|
' --plugin <seam> Instead: write plugins/<seam>.ts, a runnable plugin, and its',
|
|
253
290
|
' test (executor, cache, telemetry, schedule, admit, commands,',
|
|
254
291
|
' project, graph, key).',
|
|
255
|
-
' From turbo.json or an Nx project graph: `vx init` runs `@vzn/vx-migrate` (its own package).',
|
|
256
292
|
'',
|
|
257
293
|
'Shell completions:',
|
|
258
294
|
' vx completions <shell> Print a completion script for bash, zsh or fish: the',
|
|
@@ -269,14 +305,15 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
269
305
|
' --format json {query, hits: [{page, heading, url, text}]}.',
|
|
270
306
|
'',
|
|
271
307
|
'Config lock:',
|
|
272
|
-
' vx lock Evaluate every vx.config in the current environment
|
|
273
|
-
' freeze the resolved objects
|
|
274
|
-
'
|
|
275
|
-
'
|
|
308
|
+
' vx lock Evaluate every vx.config in the current environment, check',
|
|
309
|
+
' the graph rules (run.rules) and freeze the resolved objects',
|
|
310
|
+
' into .vx/projects.lock.json, the only place a run reads configs',
|
|
311
|
+
' from. A run never evaluates one, never checks the rules',
|
|
312
|
+
' and never writes the lock: with none it fails.',
|
|
276
313
|
' 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
|
-
'
|
|
314
|
+
' re-evaluation compared against the frozen objects, and the',
|
|
315
|
+
' graph rules. Exits 1 on drift (e.g. configs that read env',
|
|
316
|
+
' vars). Run it before the run in CI. With --format json it prints',
|
|
280
317
|
' {upToDate, audited, notAudited, problems}.',
|
|
281
318
|
'',
|
|
282
319
|
...(pluginCommands.length > 0 ? ['Plugin commands:', ...pluginCommands, ''] : []),
|
|
@@ -315,7 +352,7 @@ export function refusedWord(arg: string): string {
|
|
|
315
352
|
* `(did you mean --concurrency?)` for a flag within two edits of one `verb`
|
|
316
353
|
* accepts; empty for anything else (a positional included). A third edit
|
|
317
354
|
* is allowed only between flags that share their first five characters,
|
|
318
|
-
* so `--
|
|
355
|
+
* so `--timeouts` reaches `--timeout` while `--zzz` does not reach `--all`.
|
|
319
356
|
*/
|
|
320
357
|
export function flagHint(verb: string, arg: string): string {
|
|
321
358
|
if (!arg.startsWith('-')) return ''
|
|
@@ -343,8 +380,8 @@ export function flagHint(verb: string, arg: string): string {
|
|
|
343
380
|
* The documented `--flags` of one verb, read from the help text's
|
|
344
381
|
* sections headed `… (for <verb>):` so there is no second list to drift —
|
|
345
382
|
* and no hint that names another verb's flag. Only the flag an option line
|
|
346
|
-
* OPENS with is one:
|
|
347
|
-
*
|
|
383
|
+
* OPENS with is one: an option line that named `vx lock --check` once put
|
|
384
|
+
* `--check` in the list, so `vx run --chek`
|
|
348
385
|
* was told "did you mean --check?" and `--check` was then refused.
|
|
349
386
|
*/
|
|
350
387
|
export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
@@ -354,8 +391,9 @@ export function documentedFlags(verb: string, text = helpText()): string[] {
|
|
|
354
391
|
if (/^[A-Z][A-Za-z ]*(?: \(for [a-z]+\))?:$/.test(line))
|
|
355
392
|
inVerb = line.endsWith(`(for ${verb}):`)
|
|
356
393
|
if (!inVerb) continue
|
|
357
|
-
|
|
358
|
-
|
|
394
|
+
// `--x, --no-x`: a flag and its negation.
|
|
395
|
+
const own = /^\s+(--[a-zA-Z][a-zA-Z-]*)(?:, (--[a-zA-Z][a-zA-Z-]*))?/.exec(line)
|
|
396
|
+
if (own !== null) for (const f of own.slice(1)) if (f !== undefined) flags.add(f)
|
|
359
397
|
}
|
|
360
398
|
return [...flags]
|
|
361
399
|
}
|
|
@@ -414,7 +452,9 @@ export function refuse(
|
|
|
414
452
|
code: string,
|
|
415
453
|
): number {
|
|
416
454
|
const line = `vx ${verb}: ${message}`
|
|
417
|
-
|
|
455
|
+
ui.print('err', refusalNotice(`vx ${verb}`, message))
|
|
418
456
|
if (wantsJson(args)) process.stdout.write(errorDocument(code, maskedLine(line)))
|
|
419
457
|
return 1
|
|
420
458
|
}
|
|
459
|
+
|
|
460
|
+
type Paint = typeof paint
|
package/src/cli/index.ts
CHANGED
|
@@ -2,14 +2,20 @@
|
|
|
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
|
+
sayNotice,
|
|
12
|
+
isFsRefusal,
|
|
13
|
+
isOutOfFds,
|
|
9
14
|
isUserError,
|
|
10
15
|
maskedLine,
|
|
11
|
-
MOVED_VERBS,
|
|
12
16
|
nearest,
|
|
17
|
+
refusalNotice,
|
|
18
|
+
ui,
|
|
13
19
|
UserError,
|
|
14
20
|
wantsJson,
|
|
15
21
|
} from '../util/index.js'
|
|
@@ -24,6 +30,7 @@ const plugins = () => import('./plugin-commands.js')
|
|
|
24
30
|
|
|
25
31
|
export async function run(argv: readonly string[]): Promise<number> {
|
|
26
32
|
const [command, ...rest] = argv
|
|
33
|
+
await hideEnvFromTasks()
|
|
27
34
|
|
|
28
35
|
// `vx <verb> --help` is the universal reflex, and every verb answered
|
|
29
36
|
// `unknown flag: --help` and exited 1 (walkthrough, 2026-09-04). Only args
|
|
@@ -34,7 +41,33 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
34
41
|
await printHelp(await (await plugins()).pluginCommandHelp(), command)
|
|
35
42
|
return 0
|
|
36
43
|
}
|
|
44
|
+
// Each setting's variable once, before any verb: a renamed one is named,
|
|
45
|
+
// and a deep reader trusts what it reads.
|
|
46
|
+
checkSettingsEnv()
|
|
37
47
|
|
|
48
|
+
try {
|
|
49
|
+
return await dispatch(command, rest)
|
|
50
|
+
} catch (err) {
|
|
51
|
+
throw await brokenLockNamed(err)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A crash from a hand-broken lock entry is that entry's fault: runs validate
|
|
57
|
+
* no entry, so it surfaced as a raw TypeError (M, 2026-10-14). The lock is
|
|
58
|
+
* read only on the crash path, so a normal run pays nothing.
|
|
59
|
+
*/
|
|
60
|
+
async function brokenLockNamed(err: unknown): Promise<unknown> {
|
|
61
|
+
if (isUserError(err) || isFsRefusal(err) || isOutOfFds(err)) return err
|
|
62
|
+
try {
|
|
63
|
+
const { findWorkspaceRoot, LOCK_INVALID, lockEntryBroken } =
|
|
64
|
+
await import('../workspace/index.js')
|
|
65
|
+
if (lockEntryBroken(await findWorkspaceRoot(process.cwd()))) return new UserError(LOCK_INVALID)
|
|
66
|
+
} catch {}
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function dispatch(command: string | undefined, rest: string[]): Promise<number> {
|
|
38
71
|
switch (command) {
|
|
39
72
|
case undefined: {
|
|
40
73
|
// A bare `vx` where no workspace is printed 151 lines of help, none
|
|
@@ -44,7 +77,8 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
44
77
|
await findWorkspaceRoot(process.cwd())
|
|
45
78
|
} catch (err) {
|
|
46
79
|
if (!isUserError(err)) throw err
|
|
47
|
-
|
|
80
|
+
const parts = err.parts ?? { headline: err.message }
|
|
81
|
+
ui.print('err', refusalNotice('vx', err.message, { ...parts, why: 'see vx help' }))
|
|
48
82
|
return 1
|
|
49
83
|
}
|
|
50
84
|
await printHelp(await (await plugins()).pluginCommandHelp())
|
|
@@ -65,21 +99,25 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
65
99
|
// for the whole reference: it printed 145 lines and no hint.
|
|
66
100
|
const resolved = await (await plugins()).resolvePluginCommand(verb)
|
|
67
101
|
const unknown = resolved === null || 'declaredVerbs' in resolved
|
|
68
|
-
const pointer =
|
|
102
|
+
const pointer = FOREIGN_VERBS[verb]
|
|
69
103
|
if (unknown && pointer !== undefined) {
|
|
70
|
-
|
|
104
|
+
sayNotice('info', 'vx', pointer)
|
|
71
105
|
return 1
|
|
72
106
|
}
|
|
73
107
|
if (unknown && verb.startsWith('-')) {
|
|
74
|
-
|
|
75
|
-
|
|
108
|
+
sayNotice(
|
|
109
|
+
'error',
|
|
110
|
+
'vx help',
|
|
111
|
+
`unknown flag: ${verb}${flagHint('help', verb)} (see \`vx help\`)`,
|
|
76
112
|
)
|
|
77
113
|
return 1
|
|
78
114
|
}
|
|
79
115
|
if (unknown) {
|
|
80
116
|
const declared = resolved === null ? [] : resolved.declaredVerbs
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
sayNotice(
|
|
118
|
+
'error',
|
|
119
|
+
'vx help',
|
|
120
|
+
`unknown command: ${verb}${didYouMeanVerb(verb, declared)} (see \`vx help\`)`,
|
|
83
121
|
)
|
|
84
122
|
return 1
|
|
85
123
|
}
|
|
@@ -93,8 +131,10 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
93
131
|
// takes no more of is refused, as every other verb refuses one.
|
|
94
132
|
const extra = rest[0]
|
|
95
133
|
if (extra !== undefined) {
|
|
96
|
-
|
|
97
|
-
|
|
134
|
+
sayNotice(
|
|
135
|
+
'error',
|
|
136
|
+
'vx version',
|
|
137
|
+
`${refusedWord(extra)}: ${extra}${flagHint('version', extra)}${seeHelp('version')}`,
|
|
98
138
|
)
|
|
99
139
|
return 1
|
|
100
140
|
}
|
|
@@ -163,22 +203,25 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
163
203
|
// points at the file that broke it.
|
|
164
204
|
// Every answer below is "no such verb here": under --format json the
|
|
165
205
|
// agent reads it as one code, as a verb's own refusal gives one.
|
|
166
|
-
const unknown = (
|
|
167
|
-
|
|
206
|
+
const unknown = (
|
|
207
|
+
text: string,
|
|
208
|
+
code = 'VX_E_UNKNOWN_COMMAND',
|
|
209
|
+
tone: 'error' | 'info' = 'error',
|
|
210
|
+
): number => {
|
|
211
|
+
sayNotice(tone, 'vx', text.replace(/^vx: /, '').trimEnd())
|
|
168
212
|
if (wantsJson(rest))
|
|
169
213
|
process.stdout.write(errorDocument(code, maskedLine(text.split('\n')[0]!)))
|
|
170
214
|
return 1
|
|
171
215
|
}
|
|
172
216
|
const loadNote =
|
|
173
217
|
resolved !== null && 'loadError' in resolved
|
|
174
|
-
? `\n (plugin verbs could not be looked up: vx
|
|
218
|
+
? `\n (plugin verbs could not be looked up: .vx/workspace failed to load: ${resolved.loadError})`
|
|
175
219
|
: ''
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
const moved = MOVED_VERBS[command] ?? FOREIGN_VERBS[command]
|
|
220
|
+
// Another tool's verb: the pointer, but only after the plugins had
|
|
221
|
+
// their chance — a workspace that declares a plugin verb of that name keeps it.
|
|
222
|
+
const moved = FOREIGN_VERBS[command]
|
|
180
223
|
if (moved !== undefined) {
|
|
181
|
-
return unknown(`${moved}${loadNote}\n
|
|
224
|
+
return unknown(`${moved}${loadNote}\n`, undefined, 'info')
|
|
182
225
|
}
|
|
183
226
|
// A task typed where the verb goes (`turbo build`, `nx build app`),
|
|
184
227
|
// `turbo dev` included: a repo's own `dev` task beats the no-service note.
|
|
@@ -198,7 +241,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
198
241
|
if (command === 'serve' || command === 'dev') {
|
|
199
242
|
// vx core is only a task runner — it has no service layer of its
|
|
200
243
|
// own. A dashboard, remote cache, distributed execution, etc. are
|
|
201
|
-
// provided by PLUGINS (declared in vx
|
|
244
|
+
// provided by PLUGINS (declared in .vx/workspace.ts), never by core.
|
|
202
245
|
// We keep this neutral hint for the common muscle-memory verbs, but
|
|
203
246
|
// core names no specific plugin package: any package can provide
|
|
204
247
|
// these.
|
|
@@ -213,10 +256,10 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
213
256
|
// help after a typo was a hundred lines past the hint that mattered.
|
|
214
257
|
// The verbs this workspace's plugins declare are verbs HERE, so they
|
|
215
258
|
// 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
|
-
//
|
|
259
|
+
// line says where a verb can come from: a typo of a plugin verb read
|
|
260
|
+
// as a plain unknown command, and a plugin verb before the plugin was
|
|
261
|
+
// declared said nothing about the file that would declare it
|
|
262
|
+
// (2026-09-20).
|
|
220
263
|
// A word that opens with a dash is a flag, and vx itself takes only
|
|
221
264
|
// --help and --version: "unknown command: --bogus" named the wrong
|
|
222
265
|
// thing (M-60).
|
|
@@ -268,5 +311,5 @@ function verbSourceNote(resolved: unknown, declaredVerbs: readonly string[]): st
|
|
|
268
311
|
return ` This workspace's plugins declare: ${[...declaredVerbs].sort().join(', ')}.\n`
|
|
269
312
|
}
|
|
270
313
|
const here = resolved === null ? 'there is no workspace here' : 'this workspace declares none'
|
|
271
|
-
return ` A plugin declared in vx
|
|
314
|
+
return ` A plugin declared in .vx/workspace.ts can add verbs; ${here}.\n`
|
|
272
315
|
}
|