@vzn/vx 0.0.16 → 0.0.18
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 +66 -89
- package/index.ts +10 -0
- package/launcher.mjs +10 -11
- package/package.json +19 -5
- package/plugins/local-cache/index.ts +2 -0
- package/plugins/local-executor/index.ts +2 -0
- package/plugins/schedule-history/index.ts +2 -0
- package/src/bin.ts +2 -2
- package/src/cache/archive.ts +557 -0
- package/src/cache/cache.ts +929 -250
- package/src/cache/cas-backend.ts +20 -2
- package/src/cache/chained-cache.ts +202 -0
- package/src/cache/digest.ts +0 -15
- package/src/cache/index.ts +13 -3
- package/src/cache/inputs.ts +445 -41
- package/src/cache/layered-cache.ts +88 -30
- package/src/cache/tar-stream.ts +358 -0
- package/src/cli/cache.ts +36 -10
- package/src/cli/help.ts +167 -128
- package/src/cli/index.ts +74 -43
- package/src/cli/info.ts +30 -0
- package/src/cli/last.ts +140 -0
- package/src/cli/migrate-emit.ts +21 -0
- package/src/cli/migrate-nx.ts +23 -0
- package/src/cli/migrate-scripts.ts +127 -0
- package/src/cli/migrate-turbo.ts +43 -6
- package/src/cli/migrate.ts +119 -33
- package/src/cli/plan-format.ts +58 -3
- package/src/cli/plugin-commands.ts +96 -0
- package/src/cli/prune.ts +291 -0
- package/src/cli/run.ts +272 -80
- package/src/cli/upgrade.ts +7 -6
- package/src/cli/watch.ts +259 -61
- package/src/cli/why.ts +193 -0
- package/src/config.ts +40 -23
- package/src/exec/env.ts +7 -1
- package/src/exec/executor.ts +239 -0
- package/src/exec/index.ts +19 -1
- package/src/exec/runner.ts +116 -29
- package/src/exec/sandbox-runtime.ts +194 -24
- package/src/graph/index.ts +3 -1
- package/src/graph/scheduler.ts +92 -18
- package/src/graph/task-graph.ts +220 -6
- package/src/index.ts +82 -84
- package/src/orchestrator/deferred-outputs.ts +144 -0
- package/src/orchestrator/download-policy.ts +171 -0
- package/src/orchestrator/events.ts +30 -0
- package/src/orchestrator/execute-task.ts +328 -94
- package/src/orchestrator/failure-mode.ts +61 -0
- package/src/orchestrator/framed-output.ts +87 -35
- package/src/orchestrator/history.ts +78 -31
- package/src/orchestrator/index.ts +43 -90
- package/src/orchestrator/local-shortcircuit.ts +22 -0
- package/src/orchestrator/logger.ts +196 -14
- package/src/orchestrator/metrics.ts +57 -1376
- package/src/orchestrator/missing-plugin.ts +9 -0
- package/src/orchestrator/options.ts +22 -4
- package/src/orchestrator/plan.ts +144 -1
- package/src/orchestrator/plugin-host.ts +199 -119
- package/src/orchestrator/plugin.ts +144 -57
- package/src/orchestrator/prepare.ts +220 -69
- package/src/orchestrator/remote-prefetch.ts +12 -8
- package/src/orchestrator/resources.ts +20 -34
- package/src/orchestrator/run-artifacts.ts +41 -16
- package/src/orchestrator/run-context.ts +167 -29
- package/src/orchestrator/run-report.ts +73 -46
- package/src/orchestrator/run.ts +590 -122
- package/src/orchestrator/stable-keys.ts +74 -33
- package/src/orchestrator/status-line.ts +47 -8
- package/src/orchestrator/summary.ts +22 -1
- package/src/orchestrator/tally.ts +78 -35
- package/src/orchestrator/task-hash.ts +105 -11
- package/src/orchestrator/task-log-buffer.ts +291 -0
- package/src/orchestrator/telemetry-host.ts +24 -6
- package/src/orchestrator/telemetry.ts +181 -21
- package/src/orchestrator/upstream.ts +72 -31
- package/src/orchestrator/verify.ts +12 -2
- package/src/plugins/local-cache/index.ts +11 -0
- package/src/plugins/local-executor/index.ts +45 -0
- package/src/plugins/schedule-history/index.ts +109 -0
- package/src/util/edit-distance.ts +21 -0
- package/src/util/errors.ts +15 -1
- package/src/util/index.ts +7 -2
- package/src/util/num.ts +37 -0
- package/src/util/paths.ts +43 -0
- package/src/util/settle.ts +48 -0
- package/src/util/size.ts +5 -1
- package/src/util/tail.ts +66 -0
- package/src/util/timing.ts +63 -0
- package/src/workspace/affected.ts +142 -15
- package/src/workspace/config-cache.ts +304 -0
- package/src/workspace/config-eval.ts +210 -0
- package/src/workspace/config-imports.ts +161 -0
- package/src/workspace/filter.ts +22 -4
- package/src/workspace/fingerprint.ts +16 -1
- package/src/workspace/index.ts +15 -3
- package/src/workspace/lockfile.ts +13 -2
- package/src/workspace/nested-dirs.ts +21 -7
- package/src/workspace/package-graph.ts +23 -20
- package/src/workspace/project-loader.ts +503 -59
- package/src/workspace/workspace.ts +213 -72
- package/src/cache/tar.ts +0 -343
- package/src/cli/backend.ts +0 -23
- package/src/cli/mcp-rpc.ts +0 -292
- package/src/cli/mcp.ts +0 -92
- package/src/orchestrator/devframe-surface.ts +0 -74
- package/src/orchestrator/predict.ts +0 -106
- package/src/orchestrator/protocol.ts +0 -156
- package/src/orchestrator/run-state.ts +0 -145
- package/src/orchestrator/wire-render.ts +0 -92
- package/src/orchestrator/wire.ts +0 -212
package/src/cli/help.ts
CHANGED
|
@@ -1,129 +1,168 @@
|
|
|
1
|
-
export function printHelp(): void {
|
|
2
|
-
process.stdout.write(
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
'
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
'
|
|
127
|
-
|
|
128
|
-
|
|
1
|
+
export function printHelp(pluginCommands: readonly string[] = []): void {
|
|
2
|
+
process.stdout.write(helpText(pluginCommands))
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function helpText(pluginCommands: readonly string[] = []): string {
|
|
6
|
+
return [
|
|
7
|
+
'vx — open, extensible monorepo task runner',
|
|
8
|
+
'',
|
|
9
|
+
'Usage:',
|
|
10
|
+
' vx run [OPTIONS] [TASK | PKG#TASK] [-- forwarded-args...]',
|
|
11
|
+
' vx watch [OPTIONS] TASK [-- forwarded-args...]',
|
|
12
|
+
' vx cache prune [--older-than <duration>] [--max-size <bytes>]',
|
|
13
|
+
' vx lock [--check]',
|
|
14
|
+
' vx init [--dry] [--force]',
|
|
15
|
+
' vx migrate [--from turbo|nx|scripts] [--dry] [--force]',
|
|
16
|
+
' vx upgrade [tag]',
|
|
17
|
+
' vx show [PROJECT[#TASK]] [--format pretty|json]',
|
|
18
|
+
' vx info',
|
|
19
|
+
' vx why [TASK | PKG#TASK] [--run <runId>] [--format pretty|json]',
|
|
20
|
+
' vx last [RUNID] [--list[=N]] [--format pretty|json]',
|
|
21
|
+
' vx prune PROJECT [--out-dir <dir>] [--docker]',
|
|
22
|
+
' vx help',
|
|
23
|
+
' vx version',
|
|
24
|
+
'',
|
|
25
|
+
'Selection (for run):',
|
|
26
|
+
' (default) Run task in the project containing cwd.',
|
|
27
|
+
' --all Run task in every project that declares it.',
|
|
28
|
+
' --filter <pat> pnpm-style filter (repeatable). Examples:',
|
|
29
|
+
' foo, @scope/*, ./packages/foo, foo..., ...foo,',
|
|
30
|
+
' foo^..., !foo, [<since>]',
|
|
31
|
+
' --affected[=<base>] Sugar for --filter "[<base>]". Default base =',
|
|
32
|
+
' origin/HEAD (falls back to HEAD~1).',
|
|
33
|
+
" pkg#task Run a specific project's task directly.",
|
|
34
|
+
'',
|
|
35
|
+
'Execution (for run):',
|
|
36
|
+
' --concurrency <n> Max parallel tasks (default: CPU count).',
|
|
37
|
+
' --excludeDependencies[=names] Skip dependsOn edges. No value = all; comma list = specific names.',
|
|
38
|
+
' --no-cache Disable caching entirely (no reads, no writes, outputs left alone).',
|
|
39
|
+
' --force Re-execute everything (skip reads) but still refresh the cache (writes on).',
|
|
40
|
+
' --cache <spec> Per-layer read/write control. Comma list of <layer>:<flags>',
|
|
41
|
+
' (layer = local|remote, flags ⊆ rw). A named layer is set EXACTLY',
|
|
42
|
+
' to its flags; unnamed layers keep their value. Examples:',
|
|
43
|
+
' local:rw,remote:r (remote read-only), remote: (remote off),',
|
|
44
|
+
' local:r (local read-only).',
|
|
45
|
+
' --cache-dir <path> Cache directory override (cwd-relative). Beats the workspace',
|
|
46
|
+
' cacheDir field + the .vx/cache default.',
|
|
47
|
+
' --retry <n> Re-run a failed task up to <n> more times (default for tasks',
|
|
48
|
+
" without their own exec.retries; explicit config wins). Doesn't",
|
|
49
|
+
' affect cache keys.',
|
|
50
|
+
' --timeout <ms> Default per-task timeout for tasks without their own exec.timeout',
|
|
51
|
+
' (above VX_TASK_TIMEOUT env + workspace timeout). Kills + fails',
|
|
52
|
+
' a runaway task. Per-task exec.timeout always wins.',
|
|
53
|
+
' --memory <size> Memory budget (e.g. 8GB) that exec.resources.memory reservations',
|
|
54
|
+
' pack against. Default: total system RAM — pass this in',
|
|
55
|
+
' cgroup-limited containers (the default reads the HOST RAM).',
|
|
56
|
+
' Reservations are per-task config (exec.resources), not flags.',
|
|
57
|
+
' --verify[=<what>] Prove cache correctness. determinism (default): re-run each',
|
|
58
|
+
' executed cacheable task + content-compare outputs. inputs: sandbox',
|
|
59
|
+
' with the declared-input baseline + flag reads of undeclared inputs.',
|
|
60
|
+
' fingerprint: ship output-tree fingerprints for the cross-machine',
|
|
61
|
+
' diff on a connected serve (~1× exec — no re-run). all: everything.',
|
|
62
|
+
' An unsafe task fails the run with the exact paths.',
|
|
63
|
+
' ~2× exec (determinism); CI-shaped. inputs/all need the OS sandbox.',
|
|
64
|
+
' --verify-allow <pkg#task,…> Exempt known-nondeterministic tasks from failing --verify.',
|
|
65
|
+
' --frozen Load configs from vx-lock.json (CI; pair with vx lock --check).',
|
|
66
|
+
' --output-logs <mode> full | errors-only | hash-only | none — overrides the flow default',
|
|
67
|
+
' --download <mode> all (default) | toplevel | none — where remote outputs land',
|
|
68
|
+
' (focused without --all/--filter/--affected, broad with, full in CI).',
|
|
69
|
+
' --verbosity <n> 0=quiet, 1=verbose summary, 2+=debug (reserved).',
|
|
70
|
+
'',
|
|
71
|
+
'Planning (for run — skips execution):',
|
|
72
|
+
' --dry[=text|json] Print the task graph + cache hit/miss prediction.',
|
|
73
|
+
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
74
|
+
'',
|
|
75
|
+
'Artifacts (for run):',
|
|
76
|
+
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
77
|
+
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
78
|
+
'',
|
|
79
|
+
'Extensions (plugins):',
|
|
80
|
+
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
81
|
+
' cache, distributed execution, and telemetry export are provided by',
|
|
82
|
+
' PLUGINS declared in vx.workspace.ts — first-party or your own. Core',
|
|
83
|
+
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
84
|
+
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
85
|
+
'',
|
|
86
|
+
'Argument forwarding (for run):',
|
|
87
|
+
" Anything after `--` is forwarded (shell-quoted) to the task's exec",
|
|
88
|
+
' command. The forwarded args are folded into the cache key.',
|
|
89
|
+
'',
|
|
90
|
+
'Watch mode:',
|
|
91
|
+
' vx watch <task> Initial run, then re-run on filesystem changes.',
|
|
92
|
+
' Uses the same flags as `vx run` except --dry /',
|
|
93
|
+
' --graph / --summarize / --profile. Press Ctrl+C',
|
|
94
|
+
' to stop. Cache means most re-runs are near-zero',
|
|
95
|
+
' cost (cache hits restore outputs + replay logs).',
|
|
96
|
+
'',
|
|
97
|
+
'Cache management:',
|
|
98
|
+
' vx cache prune --older-than 30d Evict entries last accessed > 30 days ago.',
|
|
99
|
+
' vx cache prune --max-size 1G Keep total cache under 1 GB (LRU eviction).',
|
|
100
|
+
'',
|
|
101
|
+
' Duration units: s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
102
|
+
'',
|
|
103
|
+
'Introspection:',
|
|
104
|
+
' vx show List every project: name, dir, declared task count.',
|
|
105
|
+
" vx show <project> Print the project's LIVE resolved config (fresh",
|
|
106
|
+
' evaluation, not the lock), one block per task.',
|
|
107
|
+
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
108
|
+
' --format <fmt> pretty (default) | json.',
|
|
109
|
+
' vx info Workspace doctor: vx/bun/git versions, project +',
|
|
110
|
+
' task counts, cache dir/entries/size, recent runs,',
|
|
111
|
+
' lock status.',
|
|
112
|
+
' vx stats Deprecated: alias of vx info.',
|
|
113
|
+
" vx why <task> Why did this task re-run? Compares the task's latest",
|
|
114
|
+
' run (or --run <id>) against its previous run: names the',
|
|
115
|
+
' exact changed cache-key components (files / env / runtime',
|
|
116
|
+
' / upstream) from the persisted input fingerprints.',
|
|
117
|
+
' --format <fmt> pretty (default) | json.',
|
|
118
|
+
' vx prune <project> Emit a self-contained workspace SUBSET — the project +',
|
|
119
|
+
' its transitive workspace deps, root manifests, and the',
|
|
120
|
+
' (unpruned) lockfile — for Docker builds. --docker splits',
|
|
121
|
+
' json/ (manifests, the cacheable install layer) from full/.',
|
|
122
|
+
" vx last [runId] Replay a recorded run's summary from the local history —",
|
|
123
|
+
' header (command, when, branch, counts) plus the per-task',
|
|
124
|
+
' table, failures first. No re-execution, no cache probe.',
|
|
125
|
+
' --list[=N] List the N most recent runs (default 10) with run ids.',
|
|
126
|
+
' --format <fmt> pretty (default) | json.',
|
|
127
|
+
'',
|
|
128
|
+
'Migration:',
|
|
129
|
+
' vx init Generate vx.workspace.ts + one vx.config.ts per package',
|
|
130
|
+
' from package.json scripts (= vx migrate --from scripts).',
|
|
131
|
+
' vx migrate Generate vx.config.ts per package from turbo.json or an',
|
|
132
|
+
' Nx project graph (.nx/workspace-data/project-graph.json);',
|
|
133
|
+
' with neither present, from package.json scripts.',
|
|
134
|
+
' Unmappable settings become TODO(vx-migrate) comments;',
|
|
135
|
+
' turbo global fields become a shared vx-preset.ts.',
|
|
136
|
+
' --dry Print the generated files instead of writing them.',
|
|
137
|
+
' --force Overwrite existing vx.config.* / vx-preset.ts files.',
|
|
138
|
+
'',
|
|
139
|
+
'Config lock:',
|
|
140
|
+
' vx lock Evaluate every vx.config in the current environment and',
|
|
141
|
+
' freeze the resolved objects into vx-lock.json. Plain runs',
|
|
142
|
+
' always evaluate live; only `vx run --frozen` consumes the',
|
|
143
|
+
' lock (CI reproducibility, frozen-env semantics).',
|
|
144
|
+
' vx lock --check Audit the lock: file-changed hash report PLUS a full',
|
|
145
|
+
' re-evaluation compared against the frozen objects. Exits 1',
|
|
146
|
+
' on drift (e.g. configs that read env vars). Run it before',
|
|
147
|
+
' `vx run --frozen` in CI.',
|
|
148
|
+
'',
|
|
149
|
+
...(pluginCommands.length > 0 ? ['Plugin commands:', ...pluginCommands, ''] : []),
|
|
150
|
+
].join('\n')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The documented `--flags` of one verb, read from the help text's
|
|
155
|
+
* sections headed `… (for <verb>):` so there is no second list to drift —
|
|
156
|
+
* and no hint that names another verb's flag.
|
|
157
|
+
*/
|
|
158
|
+
export function documentedFlags(verb: string): string[] {
|
|
159
|
+
const flags = new Set<string>()
|
|
160
|
+
let inVerb = false
|
|
161
|
+
for (const line of helpText().split('\n')) {
|
|
162
|
+
if (/^[A-Z][A-Za-z ]*(?: \(for [a-z]+\))?:$/.test(line))
|
|
163
|
+
inVerb = line.endsWith(`(for ${verb}):`)
|
|
164
|
+
if (!inVerb) continue
|
|
165
|
+
for (const f of line.match(/--[a-zA-Z][a-zA-Z-]*/g) ?? []) flags.add(f)
|
|
166
|
+
}
|
|
167
|
+
return [...flags]
|
|
129
168
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import { VERSION } from '../version.js'
|
|
6
6
|
import { runCmd } from './run.js'
|
|
7
|
-
import { watchCmd } from './watch.js'
|
|
8
|
-
import { cacheCmd } from './cache.js'
|
|
9
|
-
import { lockCmd } from './lock.js'
|
|
10
|
-
import { migrateCmd } from './migrate.js'
|
|
11
|
-
import { upgradeCmd } from './upgrade.js'
|
|
12
|
-
import { showCmd } from './show.js'
|
|
13
|
-
import { infoCmd } from './info.js'
|
|
14
|
-
import { mcpCmd } from './mcp.js'
|
|
15
7
|
import { printHelp } from './help.js'
|
|
8
|
+
import { pluginCommandHelp, resolvePluginCommand } from './plugin-commands.js'
|
|
9
|
+
import { UserError } from '../util/index.js'
|
|
10
|
+
|
|
11
|
+
// Every verb but `run` is imported when invoked. `vx run` is the hot path
|
|
12
|
+
// and nearly every invocation; the other verbs' modules are code that
|
|
13
|
+
// process never calls. Measured 2026-09-03: `--version` is 25 ms either
|
|
14
|
+
// way (module loading is not where start-up goes), so this is hygiene, not
|
|
15
|
+
// a speed-up. The specifiers are string literals, so `bun build --compile`
|
|
16
|
+
// still embeds them.
|
|
16
17
|
|
|
17
18
|
export async function run(argv: readonly string[]): Promise<number> {
|
|
18
19
|
const [command, ...rest] = argv
|
|
@@ -22,7 +23,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
22
23
|
case '--help':
|
|
23
24
|
case '-h':
|
|
24
25
|
case 'help':
|
|
25
|
-
printHelp()
|
|
26
|
+
printHelp(await pluginCommandHelp())
|
|
26
27
|
return 0
|
|
27
28
|
case '--version':
|
|
28
29
|
case 'version':
|
|
@@ -31,55 +32,85 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
31
32
|
case 'run':
|
|
32
33
|
return await runCmd(rest)
|
|
33
34
|
case 'watch':
|
|
34
|
-
return await watchCmd(rest)
|
|
35
|
+
return await (await import('./watch.js')).watchCmd(rest)
|
|
35
36
|
case 'cache':
|
|
36
|
-
return await cacheCmd(rest)
|
|
37
|
+
return await (await import('./cache.js')).cacheCmd(rest)
|
|
37
38
|
case 'lock':
|
|
38
|
-
return await lockCmd(rest)
|
|
39
|
+
return await (await import('./lock.js')).lockCmd(rest)
|
|
39
40
|
case 'migrate':
|
|
40
|
-
return await migrateCmd(rest)
|
|
41
|
+
return await (await import('./migrate.js')).migrateCmd(rest)
|
|
42
|
+
case 'init':
|
|
43
|
+
// A workspace from nowhere: package.json scripts are the source.
|
|
44
|
+
return await (
|
|
45
|
+
await import('./migrate.js')
|
|
46
|
+
).migrateCmd(['--from', 'scripts', ...rest], { init: true })
|
|
41
47
|
case 'upgrade':
|
|
42
|
-
return await upgradeCmd(rest)
|
|
48
|
+
return await (await import('./upgrade.js')).upgradeCmd(rest)
|
|
43
49
|
case 'show':
|
|
44
|
-
return await showCmd(rest)
|
|
50
|
+
return await (await import('./show.js')).showCmd(rest)
|
|
45
51
|
case 'info':
|
|
46
52
|
case 'stats': // deprecated alias — `vx info` absorbed `vx stats`
|
|
47
|
-
return await infoCmd(rest)
|
|
48
|
-
case '
|
|
49
|
-
return await
|
|
50
|
-
case '
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
return await (await import('./info.js')).infoCmd(rest)
|
|
54
|
+
case 'why':
|
|
55
|
+
return await (await import('./why.js')).whyCmd(rest)
|
|
56
|
+
case 'last':
|
|
57
|
+
return await (await import('./last.js')).lastCmd(rest)
|
|
58
|
+
case 'prune':
|
|
59
|
+
return await (await import('./prune.js')).pruneWorkspaceCmd(rest)
|
|
60
|
+
default: {
|
|
61
|
+
// Not a core verb: a plugin declared in the workspace around the cwd
|
|
62
|
+
// may own it (`VxPlugin.commands`). Core verbs were matched above, so
|
|
63
|
+
// nothing here can shadow them.
|
|
64
|
+
const resolved = await resolvePluginCommand(command)
|
|
65
|
+
if (resolved !== null && !('loadError' in resolved)) {
|
|
66
|
+
const code = await resolved.command.run(rest, resolved.ctx)
|
|
67
|
+
// A plugin is a boundary: a JS-authored verb that resolves nothing
|
|
68
|
+
// would reach `process.exit(undefined)` and read as SUCCESS. A verb
|
|
69
|
+
// that cannot say whether it succeeded fails, naming its owner.
|
|
70
|
+
if (!Number.isInteger(code)) {
|
|
71
|
+
throw new UserError(
|
|
72
|
+
`plugin '${resolved.plugin.name}': command '${command}' resolved ${JSON.stringify(code)} instead of an exit code`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
return code
|
|
76
|
+
}
|
|
77
|
+
// A broken workspace file cannot say whether the verb exists. Say
|
|
78
|
+
// both things: the verb is unknown HERE, and why the lookup could not
|
|
79
|
+
// finish — a typo still reads as a typo, and a real plugin verb still
|
|
80
|
+
// points at the file that broke it.
|
|
81
|
+
const loadNote =
|
|
82
|
+
resolved !== null && 'loadError' in resolved
|
|
83
|
+
? `\n (plugin verbs could not be looked up: vx.workspace failed to load: ${resolved.loadError})`
|
|
84
|
+
: ''
|
|
85
|
+
if (command === 'serve' || command === 'dev') {
|
|
86
|
+
// vx core is only a task runner — it has no service layer of its
|
|
87
|
+
// own. A dashboard, remote cache, distributed execution, etc. are
|
|
88
|
+
// provided by PLUGINS (declared in vx.workspace.ts), never by core.
|
|
89
|
+
// We keep this neutral hint for the common muscle-memory verbs, but
|
|
90
|
+
// core names no specific plugin package: any package can provide
|
|
91
|
+
// these.
|
|
92
|
+
process.stderr.write(
|
|
93
|
+
`vx: '${command}' is not a vx core command.\n` +
|
|
94
|
+
` vx core runs tasks in-process. A dashboard, remote cache, and\n` +
|
|
95
|
+
` distributed execution come from plugins — not core. See the plugin\n` +
|
|
96
|
+
` guide: https://vznjs.github.io/vx/guides/plugins/\n`,
|
|
97
|
+
)
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
process.stderr.write(`vx: unknown command: ${command}${loadNote}\n`)
|
|
66
101
|
printHelp()
|
|
67
102
|
return 1
|
|
103
|
+
}
|
|
68
104
|
}
|
|
69
105
|
}
|
|
70
106
|
|
|
71
107
|
// Re-exports for tests + programmatic embedders.
|
|
72
|
-
export { detectFlow, parseRunArgs, type RunArgs } from './run.js'
|
|
108
|
+
export { detectFlow, parseRunArgs, resolveRunOptions, type RunArgs } from './run.js'
|
|
73
109
|
export { parsePruneArgs, parseDuration, parseSize } from './cache.js'
|
|
74
110
|
export { parseLockArgs, type LockArgs } from './lock.js'
|
|
75
111
|
export { parseMigrateArgs, type MigrateArgs } from './migrate.js'
|
|
76
112
|
export { parseShowArgs, type ShowArgs } from './show.js'
|
|
77
|
-
export {
|
|
78
|
-
export {
|
|
79
|
-
|
|
80
|
-
listMcpTools,
|
|
81
|
-
setMcpContext,
|
|
82
|
-
type McpContext,
|
|
83
|
-
type McpToolDef,
|
|
84
|
-
} from './mcp-rpc.js'
|
|
113
|
+
export { parseWhyArgs } from './why.js'
|
|
114
|
+
export { parseLastArgs } from './last.js'
|
|
115
|
+
export { parsePruneWorkspaceArgs } from './prune.js'
|
|
85
116
|
export { formatBytes } from './format.js'
|
package/src/cli/info.ts
CHANGED
|
@@ -53,6 +53,11 @@ export async function infoCmd(args: readonly string[]): Promise<number> {
|
|
|
53
53
|
['vx', VERSION],
|
|
54
54
|
['bun', Bun.version],
|
|
55
55
|
['git', gitVersion()],
|
|
56
|
+
// The one `git status` walk per run is the warm path's critical path on
|
|
57
|
+
// a large tree (~55 ms at 1000 projects, measured 2026-09-02). git's
|
|
58
|
+
// own caches make it near-free after the first run, and they are OFF by
|
|
59
|
+
// default — say so, since nothing else in a run would.
|
|
60
|
+
['git status cache', gitStatusCache(root)],
|
|
56
61
|
['workspace root', root],
|
|
57
62
|
['projects', `${metas.length} (${taskCount} task${taskCount === 1 ? '' : 's'})`],
|
|
58
63
|
['cache dir', cacheDir],
|
|
@@ -66,6 +71,31 @@ export async function infoCmd(args: readonly string[]): Promise<number> {
|
|
|
66
71
|
return 0
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
/** Whether git's fsmonitor / untracked cache are on, with the remedy when not. */
|
|
75
|
+
function gitStatusCache(root: string): string {
|
|
76
|
+
try {
|
|
77
|
+
const p = Bun.spawnSync({
|
|
78
|
+
cmd: ['git', 'config', '--get-regexp', '^core\\.(fsmonitor|untrackedcache)$'],
|
|
79
|
+
cwd: root,
|
|
80
|
+
stdout: 'pipe',
|
|
81
|
+
stderr: 'pipe',
|
|
82
|
+
})
|
|
83
|
+
const out = p.exitCode === 0 ? new TextDecoder().decode(p.stdout) : ''
|
|
84
|
+
const on = (key: string): boolean =>
|
|
85
|
+
new RegExp(`^core\\.${key} (true|1|yes|on)$`, 'im').test(out)
|
|
86
|
+
const fsmonitor = on('fsmonitor')
|
|
87
|
+
const untracked = on('untrackedcache')
|
|
88
|
+
if (fsmonitor && untracked) return 'fsmonitor + untrackedCache on'
|
|
89
|
+
const missing = [
|
|
90
|
+
...(fsmonitor ? [] : ['core.fsmonitor']),
|
|
91
|
+
...(untracked ? [] : ['core.untrackedCache']),
|
|
92
|
+
]
|
|
93
|
+
return `${missing.join(', ')} off — \`git config ${missing[0]} true\` makes every run's status walk near-free on a large tree`
|
|
94
|
+
} catch {
|
|
95
|
+
return '(unknown)'
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
69
99
|
function gitVersion(): string {
|
|
70
100
|
try {
|
|
71
101
|
const p = Bun.spawnSync({ cmd: ['git', '--version'], stdout: 'pipe', stderr: 'pipe' })
|