@vzn/vx 0.0.16 → 0.0.21
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/LICENSE +1 -1
- package/README.md +135 -124
- package/index.ts +10 -0
- package/launcher.mjs +10 -11
- package/package.json +11 -7
- package/src/bin.ts +14 -4
- package/src/cache/archive.ts +659 -0
- package/src/cache/cache.ts +769 -1101
- package/src/cache/chained-cache.ts +194 -0
- package/src/cache/config-evals.ts +89 -0
- package/src/cache/file-hashes.ts +262 -0
- package/src/cache/git-inputs.ts +637 -0
- package/src/cache/index.ts +24 -5
- package/src/cache/inputs.ts +373 -456
- package/src/cache/layer.ts +604 -0
- package/src/cache/layered-cache.ts +96 -43
- package/src/cache/output-index.ts +224 -0
- package/src/cache/policy.ts +84 -0
- package/src/cache/run-history.ts +147 -0
- package/src/cache/tar-stream.ts +358 -0
- package/src/cache/zstd.ts +160 -0
- package/src/cli/cache.ts +75 -21
- package/src/cli/completions.ts +104 -0
- package/src/cli/core-alias.ts +25 -0
- package/src/cli/help.ts +195 -128
- package/src/cli/index.ts +116 -46
- package/src/cli/info.ts +122 -63
- package/src/cli/init.ts +75 -0
- package/src/cli/last.ts +172 -0
- package/src/cli/lock.ts +2 -1
- package/src/cli/plan-format.ts +72 -6
- package/src/cli/plugin-commands.ts +103 -0
- package/src/cli/run.ts +245 -271
- package/src/cli/select.ts +332 -0
- package/src/cli/show.ts +144 -69
- package/src/cli/upgrade.ts +9 -7
- package/src/cli/watch.ts +858 -110
- package/src/cli/why.ts +223 -0
- package/src/cli/workspace-config.ts +126 -0
- package/src/config.ts +187 -150
- package/src/exec/env.ts +18 -1
- package/src/exec/executor.ts +224 -0
- package/src/exec/index.ts +24 -2
- package/src/exec/local-executor.ts +41 -0
- package/src/exec/runner.ts +166 -40
- package/src/exec/sandbox-binds.ts +182 -0
- package/src/exec/sandbox-paths.ts +50 -0
- package/src/exec/sandbox-runtime.ts +642 -260
- package/src/exec/sandbox-violations.ts +280 -0
- package/src/graph/index.ts +4 -10
- package/src/graph/scheduler.ts +232 -203
- package/src/graph/task-graph.ts +222 -8
- package/src/index.ts +146 -153
- package/src/orchestrator/admission.ts +137 -0
- package/src/orchestrator/deferred-outputs.ts +147 -0
- package/src/orchestrator/doctor.ts +255 -0
- package/src/orchestrator/download-policy.ts +171 -0
- package/src/orchestrator/events.ts +72 -8
- package/src/orchestrator/execute-task.ts +374 -549
- package/src/orchestrator/failure-mode.ts +202 -0
- package/src/orchestrator/framed-output.ts +126 -69
- package/src/orchestrator/history.ts +126 -59
- package/src/orchestrator/hit-restore.ts +221 -0
- package/src/orchestrator/index.ts +77 -93
- package/src/orchestrator/local-shortcircuit.ts +22 -0
- package/src/orchestrator/lockfile-claim.ts +281 -0
- package/src/orchestrator/logger.ts +208 -35
- package/src/orchestrator/metrics.ts +102 -1388
- package/src/orchestrator/miss-save.ts +169 -0
- package/src/orchestrator/options.ts +55 -34
- package/src/orchestrator/persistent.ts +79 -0
- package/src/orchestrator/placement.ts +182 -0
- package/src/orchestrator/plan.ts +144 -1
- package/src/orchestrator/plugin-host.ts +357 -120
- package/src/orchestrator/plugin.ts +298 -58
- package/src/orchestrator/prepare.ts +202 -106
- package/src/orchestrator/projects.ts +259 -0
- package/src/orchestrator/remote-prefetch.ts +12 -8
- package/src/orchestrator/run-artifacts.ts +63 -16
- package/src/orchestrator/run-context.ts +167 -29
- package/src/orchestrator/run-records.ts +149 -0
- package/src/orchestrator/run-report.ts +74 -47
- package/src/orchestrator/run.ts +534 -417
- package/src/orchestrator/sandbox-request.ts +243 -0
- package/src/orchestrator/save-lane.ts +47 -0
- package/src/orchestrator/signals.ts +111 -0
- package/src/orchestrator/stable-keys.ts +129 -31
- package/src/orchestrator/status-line.ts +49 -9
- package/src/orchestrator/summary.ts +90 -26
- package/src/orchestrator/tally.ts +78 -35
- package/src/orchestrator/task-hash.ts +111 -15
- package/src/orchestrator/task-log-buffer.ts +288 -0
- package/src/orchestrator/telemetry-host.ts +34 -6
- package/src/orchestrator/telemetry.ts +182 -36
- package/src/orchestrator/upstream.ts +72 -31
- package/src/util/cgroup.ts +160 -0
- package/src/util/edit-distance.ts +70 -0
- package/src/util/errors.ts +15 -1
- package/src/util/index.ts +15 -2
- package/src/util/num.ts +37 -0
- package/src/util/paths.ts +64 -0
- package/src/util/settle.ts +67 -0
- package/src/util/size.ts +5 -1
- package/src/util/tail.ts +66 -0
- package/src/util/timing.ts +63 -0
- package/src/util/verbs.ts +39 -0
- package/src/workspace/affected.ts +250 -23
- package/src/workspace/config-cache.ts +313 -0
- package/src/workspace/config-eval.ts +217 -0
- package/src/workspace/config-imports.ts +162 -0
- package/src/workspace/config-schema.ts +837 -0
- package/src/workspace/filter.ts +65 -10
- package/src/workspace/fingerprint.ts +53 -4
- package/src/workspace/index.ts +42 -3
- package/src/workspace/lockfile.ts +25 -9
- package/src/workspace/migrate-scripts.ts +134 -0
- package/src/workspace/migration.ts +314 -0
- package/src/workspace/nested-dirs.ts +21 -7
- package/src/workspace/package-graph.ts +114 -41
- package/src/workspace/project-loader.ts +224 -450
- package/src/workspace/workspace.ts +298 -72
- package/src/cache/cas-backend.ts +0 -97
- package/src/cache/digest.ts +0 -47
- 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/cli/migrate-nx.ts +0 -450
- package/src/cli/migrate-turbo.ts +0 -364
- package/src/cli/migrate.ts +0 -219
- 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/resources.ts +0 -59
- package/src/orchestrator/run-state.ts +0 -145
- package/src/orchestrator/verify.ts +0 -164
- package/src/orchestrator/wire-render.ts +0 -92
- package/src/orchestrator/wire.ts +0 -212
package/src/cli/cache.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { Cache } from '../cache/index.js'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Cache, noteSchemaReset } from '../cache/index.js'
|
|
2
|
+
import { seeHelp } from './help.js'
|
|
3
|
+
import { parseDecimalInt, parseSize } from '../util/index.js'
|
|
4
|
+
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
5
|
+
import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
|
|
4
6
|
import { formatBytes } from './format.js'
|
|
5
7
|
|
|
6
8
|
// parseSize moved to `util` (the orchestrator's resource resolver needs it
|
|
@@ -16,7 +18,7 @@ export async function cacheCmd(args: readonly string[]): Promise<number> {
|
|
|
16
18
|
process.stderr.write('vx cache: missing subcommand. Try `vx cache prune`.\n')
|
|
17
19
|
return 1
|
|
18
20
|
default:
|
|
19
|
-
process.stderr.write(`vx cache: unknown subcommand: ${sub}\n`)
|
|
21
|
+
process.stderr.write(`vx cache: unknown subcommand: ${sub}${seeHelp('cache')}\n`)
|
|
20
22
|
return 1
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -24,6 +26,9 @@ export async function cacheCmd(args: readonly string[]): Promise<number> {
|
|
|
24
26
|
interface PruneArgs {
|
|
25
27
|
olderThanMs?: number
|
|
26
28
|
maxBytes?: number
|
|
29
|
+
dryRun?: boolean
|
|
30
|
+
/** `--cache-dir`: prune the cache a run with the same flag uses. */
|
|
31
|
+
cacheDir?: string
|
|
27
32
|
error?: string
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -31,20 +36,54 @@ export function parsePruneArgs(args: readonly string[]): PruneArgs {
|
|
|
31
36
|
const out: PruneArgs = {}
|
|
32
37
|
for (let i = 0; i < args.length; i++) {
|
|
33
38
|
const a = args[i]
|
|
34
|
-
if (a === '--older-than') {
|
|
35
|
-
const v = args[++i]
|
|
36
|
-
if (v === undefined
|
|
39
|
+
if (a === '--older-than' || a?.startsWith('--older-than=')) {
|
|
40
|
+
const v = a === '--older-than' ? args[++i] : a.slice('--older-than='.length)
|
|
41
|
+
if (v === undefined || v === '') {
|
|
42
|
+
return { error: `--older-than requires a value (e.g. 30d, 24h, 60m)` }
|
|
43
|
+
}
|
|
37
44
|
const ms = parseDuration(v)
|
|
38
45
|
if (ms === null) return { error: `invalid duration: ${v}` }
|
|
46
|
+
// `0d` evicts everything ever cached. That is far more often a
|
|
47
|
+
// computed-to-zero retention than an intent, and no other flag
|
|
48
|
+
// combination expresses "wipe the cache" — so refuse it and name
|
|
49
|
+
// the deliberate way to do it.
|
|
50
|
+
if (ms === 0) {
|
|
51
|
+
return {
|
|
52
|
+
error: `--older-than 0 would evict every entry — delete the cache directory instead`,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
39
55
|
out.olderThanMs = Date.now() - ms
|
|
40
|
-
} else if (a === '--max-size') {
|
|
41
|
-
const v = args[++i]
|
|
42
|
-
if (v === undefined
|
|
56
|
+
} else if (a === '--max-size' || a?.startsWith('--max-size=')) {
|
|
57
|
+
const v = a === '--max-size' ? args[++i] : a.slice('--max-size='.length)
|
|
58
|
+
if (v === undefined || v === '') {
|
|
59
|
+
return { error: `--max-size requires a value (e.g. 500M, 1G)` }
|
|
60
|
+
}
|
|
43
61
|
const bytes = parseSize(v)
|
|
44
62
|
if (bytes === null) return { error: `invalid size: ${v}` }
|
|
63
|
+
if (bytes === 0) {
|
|
64
|
+
return {
|
|
65
|
+
error: `--max-size 0 would evict every entry — delete the cache directory instead`,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// A bare number is bytes to `parseSize` (a computed size needs that),
|
|
69
|
+
// but nobody caps a cache at 10 bytes: `--max-size 10`
|
|
70
|
+
// meant 10G or 10M and would evict everything, the same class of
|
|
71
|
+
// destructive typo the zero bound above refuses. An explicit `10B`
|
|
72
|
+
// still passes.
|
|
73
|
+
if (/^\d+$/.test(v)) {
|
|
74
|
+
return {
|
|
75
|
+
error: `--max-size ${v} would read as ${v} bytes and evict nearly everything — give a unit (e.g. ${v}M, ${v}G)`,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
45
78
|
out.maxBytes = bytes
|
|
79
|
+
} else if (a === '--dry-run') {
|
|
80
|
+
out.dryRun = true
|
|
46
81
|
} else {
|
|
47
|
-
|
|
82
|
+
const cd = parseCacheDirFlag(args, i)
|
|
83
|
+
if (cd === null) return { error: `unknown argument: ${a}${seeHelp('cache')}` }
|
|
84
|
+
if ('error' in cd) return { error: cd.error }
|
|
85
|
+
out.cacheDir = cd.cacheDir
|
|
86
|
+
i = cd.next
|
|
48
87
|
}
|
|
49
88
|
}
|
|
50
89
|
if (out.olderThanMs === undefined && out.maxBytes === undefined) {
|
|
@@ -67,18 +106,25 @@ async function pruneCmd(args: readonly string[]): Promise<number> {
|
|
|
67
106
|
process.stderr.write(`vx cache prune: ${(err as Error).message}\n`)
|
|
68
107
|
return 1
|
|
69
108
|
}
|
|
70
|
-
// Honor `defineWorkspace({ cacheDir: '...' })`
|
|
71
|
-
// `
|
|
72
|
-
// silently no-ops against the
|
|
73
|
-
|
|
74
|
-
const cache = new Cache(
|
|
109
|
+
// Honor `--cache-dir`, `defineWorkspace({ cacheDir: '...' })` and a
|
|
110
|
+
// `config` plugin's edit of it — `vx run` and `vx cache prune` must
|
|
111
|
+
// operate on the same directory or prune silently no-ops against the
|
|
112
|
+
// wrong path.
|
|
113
|
+
const cache = new Cache(await cliCacheDir(root, parsed.cacheDir))
|
|
114
|
+
noteSchemaReset(cache, warnToStderr)
|
|
75
115
|
try {
|
|
76
|
-
const opts: { olderThanMs?: number; maxBytes?: number } = {}
|
|
116
|
+
const opts: { olderThanMs?: number; maxBytes?: number; dryRun?: boolean } = {}
|
|
77
117
|
if (parsed.olderThanMs !== undefined) opts.olderThanMs = parsed.olderThanMs
|
|
78
118
|
if (parsed.maxBytes !== undefined) opts.maxBytes = parsed.maxBytes
|
|
119
|
+
if (parsed.dryRun) opts.dryRun = true
|
|
79
120
|
const result = await cache.prune(opts)
|
|
121
|
+
const dry = parsed.dryRun === true
|
|
122
|
+
const orphans =
|
|
123
|
+
result.orphans > 0
|
|
124
|
+
? `, ${dry ? 'would reap' : 'reaped'} ${result.orphans} orphaned artifact${result.orphans === 1 ? '' : 's'} (${formatBytes(result.orphanBytes)})`
|
|
125
|
+
: ''
|
|
80
126
|
process.stdout.write(
|
|
81
|
-
|
|
127
|
+
`${dry ? 'Would prune' : 'Pruned'} ${result.evicted} entr${result.evicted === 1 ? 'y' : 'ies'} (${formatBytes(result.bytesFreed)}${dry ? '' : ' freed'})${orphans}\n`,
|
|
82
128
|
)
|
|
83
129
|
} finally {
|
|
84
130
|
cache.close()
|
|
@@ -86,11 +132,19 @@ async function pruneCmd(args: readonly string[]): Promise<number> {
|
|
|
86
132
|
return 0
|
|
87
133
|
}
|
|
88
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Parse `<n><unit>` where unit is s/m/h/d, case-insensitively (`parseSize`
|
|
137
|
+
* has always been case-insensitive; this matched only lowercase, so `30D`
|
|
138
|
+
* was rejected while `1GB` and `1gb` both worked).
|
|
139
|
+
*/
|
|
89
140
|
export function parseDuration(input: string): number | null {
|
|
90
|
-
const m = input.match(/^(\d+)([smhd])$/)
|
|
141
|
+
const m = input.match(/^(\d+)([smhd])$/i)
|
|
91
142
|
if (!m) return null
|
|
92
|
-
const n =
|
|
93
|
-
|
|
143
|
+
const n = parseDecimalInt(m[1]!)
|
|
144
|
+
if (n === null) return null
|
|
145
|
+
// Lowercase before the switch: with the /i flag an uppercase `M` would
|
|
146
|
+
// otherwise fall through to the days branch.
|
|
147
|
+
const unit = m[2]!.toLowerCase()
|
|
94
148
|
const mult = unit === 's' ? 1000 : unit === 'm' ? 60_000 : unit === 'h' ? 3_600_000 : 86_400_000
|
|
95
149
|
return n * mult
|
|
96
150
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// `vx completions <shell>` — a bash / zsh / fish script over the verb table
|
|
2
|
+
// and each verb's help cut, so the verbs and every flag of each complete
|
|
3
|
+
// from the one text `vx <verb> --help` prints. Plugin verbs of the
|
|
4
|
+
// workspace around the cwd are included at generation time; task and
|
|
5
|
+
// project names are not (a completion that evaluates configs on every
|
|
6
|
+
// Tab is the wrong price).
|
|
7
|
+
|
|
8
|
+
import { CORE_VERBS, seeHelp, verbHelpText } from './help.js'
|
|
9
|
+
|
|
10
|
+
export type CompletionShell = 'bash' | 'zsh' | 'fish'
|
|
11
|
+
const SHELLS: readonly CompletionShell[] = ['bash', 'zsh', 'fish']
|
|
12
|
+
|
|
13
|
+
/** Every `--flag` the verb's help cut names, plus `--help`; deduped, in order of appearance. */
|
|
14
|
+
export function verbFlags(verb: string): string[] {
|
|
15
|
+
const flags = new Set<string>(['--help'])
|
|
16
|
+
for (const f of verbHelpText(verb).match(/--[a-zA-Z][a-zA-Z-]*/g) ?? []) flags.add(f)
|
|
17
|
+
return [...flags]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The words that follow `vx <verb>` besides flags: subcommands and fixed choices. */
|
|
21
|
+
const VERB_WORDS: Readonly<Record<string, readonly string[]>> = {
|
|
22
|
+
cache: ['prune'],
|
|
23
|
+
completions: [...SHELLS],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function completionScript(shell: CompletionShell, verbs: readonly string[]): string {
|
|
27
|
+
const table = verbs.map((v) => ({
|
|
28
|
+
verb: v,
|
|
29
|
+
// A plugin verb owns its own arguments; only `--help` is known here.
|
|
30
|
+
words: [
|
|
31
|
+
...(VERB_WORDS[v] ?? []),
|
|
32
|
+
...((CORE_VERBS as readonly string[]).includes(v) ? verbFlags(v) : ['--help']),
|
|
33
|
+
],
|
|
34
|
+
}))
|
|
35
|
+
switch (shell) {
|
|
36
|
+
case 'bash':
|
|
37
|
+
return [
|
|
38
|
+
'# vx completions — generated by `vx completions bash`',
|
|
39
|
+
'_vx() {',
|
|
40
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
41
|
+
' local verb="${COMP_WORDS[1]}"',
|
|
42
|
+
' if [ "$COMP_CWORD" -eq 1 ]; then',
|
|
43
|
+
` COMPREPLY=($(compgen -W "${verbs.join(' ')}" -- "$cur"))`,
|
|
44
|
+
' return',
|
|
45
|
+
' fi',
|
|
46
|
+
' case "$verb" in',
|
|
47
|
+
...table.map(
|
|
48
|
+
(t) => ` ${t.verb}) COMPREPLY=($(compgen -W "${t.words.join(' ')}" -- "$cur")) ;;`,
|
|
49
|
+
),
|
|
50
|
+
' *) COMPREPLY=() ;;',
|
|
51
|
+
' esac',
|
|
52
|
+
'}',
|
|
53
|
+
'complete -F _vx vx',
|
|
54
|
+
'',
|
|
55
|
+
].join('\n')
|
|
56
|
+
case 'zsh':
|
|
57
|
+
return [
|
|
58
|
+
'#compdef vx',
|
|
59
|
+
'# vx completions — generated by `vx completions zsh`',
|
|
60
|
+
'_vx() {',
|
|
61
|
+
' if (( CURRENT == 2 )); then',
|
|
62
|
+
` compadd -- ${verbs.join(' ')}`,
|
|
63
|
+
' return',
|
|
64
|
+
' fi',
|
|
65
|
+
' case "${words[2]}" in',
|
|
66
|
+
...table.map((t) => ` ${t.verb}) compadd -- ${t.words.join(' ')} ;;`),
|
|
67
|
+
' esac',
|
|
68
|
+
'}',
|
|
69
|
+
'compdef _vx vx',
|
|
70
|
+
'',
|
|
71
|
+
].join('\n')
|
|
72
|
+
case 'fish':
|
|
73
|
+
return [
|
|
74
|
+
'# vx completions — generated by `vx completions fish`',
|
|
75
|
+
'complete -c vx -f',
|
|
76
|
+
...verbs.map((v) => `complete -c vx -n __fish_use_subcommand -a ${v}`),
|
|
77
|
+
...table.flatMap((t) =>
|
|
78
|
+
t.words.map((w) =>
|
|
79
|
+
w.startsWith('--')
|
|
80
|
+
? `complete -c vx -n "__fish_seen_subcommand_from ${t.verb}" -l ${w.slice(2)}`
|
|
81
|
+
: `complete -c vx -n "__fish_seen_subcommand_from ${t.verb}" -a ${w}`,
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
'',
|
|
85
|
+
].join('\n')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function completionsCmd(
|
|
90
|
+
args: readonly string[],
|
|
91
|
+
pluginVerbs: readonly string[],
|
|
92
|
+
): Promise<number> {
|
|
93
|
+
const shell = args[0]
|
|
94
|
+
if (args.length !== 1 || !(SHELLS as readonly string[]).includes(shell ?? '')) {
|
|
95
|
+
process.stderr.write(
|
|
96
|
+
`vx completions: expected one shell — bash, zsh or fish${shell !== undefined && args.length === 1 ? ` (got ${shell})` : ''}${seeHelp('completions')}\n`,
|
|
97
|
+
)
|
|
98
|
+
return 1
|
|
99
|
+
}
|
|
100
|
+
const verbs = [...CORE_VERBS.filter((v) => v !== 'help' && v !== 'version'), 'help', 'version']
|
|
101
|
+
for (const v of pluginVerbs) if (!verbs.includes(v)) verbs.push(v)
|
|
102
|
+
process.stdout.write(completionScript(shell as CompletionShell, verbs))
|
|
103
|
+
return 0
|
|
104
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One core per process. A plugin package, a `vx.workspace.ts`, a
|
|
3
|
+
* `vx.config.ts` all `import … from '@vzn/vx'`, and that specifier
|
|
4
|
+
* resolves through node_modules — to a SECOND copy of core when the host
|
|
5
|
+
* is the compiled binary (whose own copy lives under the bunfs root) or
|
|
6
|
+
* an install with its own nested `@vzn/vx`. The second copy is core's
|
|
7
|
+
* whole source transpiled again, per process: ~20 ms per plugin package
|
|
8
|
+
* in the binary (measured 2026-09-10, `@vzn/vx-otel`: 20–25 ms → 2–3 ms),
|
|
9
|
+
* and two module states — two `definePlugin` memos, two of everything
|
|
10
|
+
* keyed by identity rather than by registry symbol.
|
|
11
|
+
*
|
|
12
|
+
* The alias is a Bun virtual module for the exact specifier `@vzn/vx`,
|
|
13
|
+
* served from the host's own façade, loaded lazily on the first import
|
|
14
|
+
* so a verb that never loads a plugin never loads the façade. The
|
|
15
|
+
* source form (`bun src/bin.ts`) resolves the specifier to the same file
|
|
16
|
+
* anyway; the alias makes that a guarantee rather than a layout fact.
|
|
17
|
+
*/
|
|
18
|
+
export function registerCoreAlias(load: () => Promise<Record<string, unknown>>): void {
|
|
19
|
+
Bun.plugin({
|
|
20
|
+
name: 'vx-core-alias',
|
|
21
|
+
setup(build) {
|
|
22
|
+
build.module('@vzn/vx', async () => ({ exports: await load(), loader: 'object' }))
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
package/src/cli/help.ts
CHANGED
|
@@ -1,129 +1,196 @@
|
|
|
1
|
-
export function printHelp(): void {
|
|
2
|
-
process.stdout.write(
|
|
3
|
-
[
|
|
4
|
-
'vx — open, extensible monorepo task runner',
|
|
5
|
-
'',
|
|
6
|
-
'Usage:',
|
|
7
|
-
' vx run [OPTIONS] [TASK | PKG#TASK] [-- forwarded-args...]',
|
|
8
|
-
' vx watch [OPTIONS] TASK [-- forwarded-args...]',
|
|
9
|
-
' vx cache prune [--older-than <duration>] [--max-size <bytes>]',
|
|
10
|
-
' vx lock [--check]',
|
|
11
|
-
' vx migrate [--from turbo|nx] [--dry] [--force]',
|
|
12
|
-
' vx upgrade [tag]',
|
|
13
|
-
' vx show [PROJECT[#TASK]] [--format pretty|json]',
|
|
14
|
-
' vx info',
|
|
15
|
-
' vx mcp [--stdio]',
|
|
16
|
-
' vx help',
|
|
17
|
-
' vx version',
|
|
18
|
-
'',
|
|
19
|
-
'Selection (for run):',
|
|
20
|
-
' (default) Run task in the project containing cwd.',
|
|
21
|
-
' --all Run task in every project that declares it.',
|
|
22
|
-
' --filter <pat> pnpm-style filter (repeatable). Examples:',
|
|
23
|
-
' foo, @scope/*, ./packages/foo, foo..., ...foo,',
|
|
24
|
-
' foo^..., !foo, [<since>]',
|
|
25
|
-
' --affected[=<base>] Sugar for --filter "[<base>]". Default base =',
|
|
26
|
-
' origin/HEAD (falls back to HEAD~1).',
|
|
27
|
-
" pkg#task Run a specific project's task directly.",
|
|
28
|
-
'',
|
|
29
|
-
'Execution (for run):',
|
|
30
|
-
' --concurrency <n> Max parallel tasks (default: CPU count).',
|
|
31
|
-
' --excludeDependencies[=names] Skip dependsOn edges. No value = all; comma list = specific names.',
|
|
32
|
-
' --no-cache Disable caching entirely (no reads, no writes, outputs left alone).',
|
|
33
|
-
' --force Re-execute everything (skip reads) but still refresh the cache (writes on).',
|
|
34
|
-
' --cache <spec> Per-layer read/write control. Comma list of <layer>:<flags>',
|
|
35
|
-
' (layer = local|remote, flags ⊆ rw). A named layer is set EXACTLY',
|
|
36
|
-
' to its flags; unnamed layers keep their value. Examples:',
|
|
37
|
-
' local:rw,remote:r (remote read-only), remote: (remote off),',
|
|
38
|
-
' local:r (local read-only).',
|
|
39
|
-
' --cache-dir <path> Cache directory override (cwd-relative). Beats the workspace',
|
|
40
|
-
' cacheDir field + the .vx/cache default.',
|
|
41
|
-
' --retry <n> Re-run a failed task up to <n> more times (default for tasks',
|
|
42
|
-
" without their own exec.retries; explicit config wins). Doesn't",
|
|
43
|
-
' affect cache keys.',
|
|
44
|
-
' --timeout <ms> Default per-task timeout for tasks without their own exec.timeout',
|
|
45
|
-
' (above VX_TASK_TIMEOUT env + workspace timeout). Kills + fails',
|
|
46
|
-
' a runaway task. Per-task exec.timeout always wins.',
|
|
47
|
-
' --memory <size> Memory budget (e.g. 8GB) that exec.resources.memory reservations',
|
|
48
|
-
' pack against. Default: total system RAM — pass this in',
|
|
49
|
-
' cgroup-limited containers (the default reads the HOST RAM).',
|
|
50
|
-
' Reservations are per-task config (exec.resources), not flags.',
|
|
51
|
-
' --verify[=<what>] Prove cache correctness. determinism (default): re-run each',
|
|
52
|
-
' executed cacheable task + content-compare outputs. inputs: sandbox',
|
|
53
|
-
' with the declared-input baseline + flag reads of undeclared inputs.',
|
|
54
|
-
' fingerprint: ship output-tree fingerprints for the cross-machine',
|
|
55
|
-
' diff on a connected serve (~1× exec — no re-run). all: everything.',
|
|
56
|
-
' An unsafe task fails the run with the exact paths.',
|
|
57
|
-
' ~2× exec (determinism); CI-shaped. inputs/all need the OS sandbox.',
|
|
58
|
-
' --verify-allow <pkg#task,…> Exempt known-nondeterministic tasks from failing --verify.',
|
|
59
|
-
' --frozen Load configs from vx-lock.json (CI; pair with vx lock --check).',
|
|
60
|
-
' --output-logs <mode> full | errors-only | none — overrides the flow default',
|
|
61
|
-
' (focused without --all/--filter/--affected, broad with, full in CI).',
|
|
62
|
-
' --verbosity <n> 0=quiet, 1=verbose summary, 2+=debug (reserved).',
|
|
63
|
-
'',
|
|
64
|
-
'Planning (for run — skips execution):',
|
|
65
|
-
' --dry[=text|json] Print the task graph + cache hit/miss prediction.',
|
|
66
|
-
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
67
|
-
'',
|
|
68
|
-
'Artifacts (for run):',
|
|
69
|
-
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
70
|
-
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
71
|
-
'',
|
|
72
|
-
'Extensions (plugins):',
|
|
73
|
-
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
74
|
-
' cache, distributed execution, and telemetry export are provided by',
|
|
75
|
-
' PLUGINS declared in vx.workspace.ts — first-party or your own. Core',
|
|
76
|
-
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
77
|
-
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
78
|
-
'',
|
|
79
|
-
'Argument forwarding (for run):',
|
|
80
|
-
" Anything after `--` is forwarded (shell-quoted) to the task's exec",
|
|
81
|
-
' command. The forwarded args are folded into the cache key.',
|
|
82
|
-
'',
|
|
83
|
-
'Watch mode:',
|
|
84
|
-
' vx watch <task> Initial run, then re-run on filesystem changes.',
|
|
85
|
-
' Uses the same flags as `vx run` except --dry /',
|
|
86
|
-
' --graph / --summarize / --profile. Press Ctrl+C',
|
|
87
|
-
' to stop. Cache means most re-runs are near-zero',
|
|
88
|
-
' cost (cache hits restore outputs + replay logs).',
|
|
89
|
-
'',
|
|
90
|
-
'Cache management:',
|
|
91
|
-
' vx cache prune --older-than 30d Evict entries last accessed > 30 days ago.',
|
|
92
|
-
' vx cache prune --max-size 1G Keep total cache under 1 GB (LRU eviction).',
|
|
93
|
-
'',
|
|
94
|
-
' Duration units: s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
95
|
-
'',
|
|
96
|
-
'Introspection:',
|
|
97
|
-
' vx show List every project: name, dir, declared task count.',
|
|
98
|
-
" vx show <project> Print the project's LIVE resolved config (fresh",
|
|
99
|
-
' evaluation, not the lock), one block per task.',
|
|
100
|
-
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
101
|
-
' --format <fmt> pretty (default) | json.',
|
|
102
|
-
' vx info Workspace doctor: vx/bun/git versions, project +',
|
|
103
|
-
' task counts, cache dir/entries/size, recent runs,',
|
|
104
|
-
' lock status.',
|
|
105
|
-
' vx stats Deprecated: alias of vx info.',
|
|
106
|
-
' vx mcp Model Context Protocol server (stdio) — exposes inspector',
|
|
107
|
-
' RPCs as MCP tools for AI agents (Claude Code, Cursor, …).',
|
|
108
|
-
'',
|
|
109
|
-
'Migration:',
|
|
110
|
-
' vx migrate Generate vx.config.ts per package from turbo.json or an',
|
|
111
|
-
' Nx project graph (.nx/workspace-data/project-graph.json).',
|
|
112
|
-
' Unmappable settings become TODO(vx-migrate) comments;',
|
|
113
|
-
' turbo global fields become a shared vx-preset.ts.',
|
|
114
|
-
' --dry Print the generated files instead of writing them.',
|
|
115
|
-
' --force Overwrite existing vx.config.* / vx-preset.ts files.',
|
|
116
|
-
'',
|
|
117
|
-
'Config lock:',
|
|
118
|
-
' vx lock Evaluate every vx.config in the current environment and',
|
|
119
|
-
' freeze the resolved objects into vx-lock.json. Plain runs',
|
|
120
|
-
' always evaluate live; only `vx run --frozen` consumes the',
|
|
121
|
-
' lock (CI reproducibility, frozen-env semantics).',
|
|
122
|
-
' vx lock --check Audit the lock: file-changed hash report PLUS a full',
|
|
123
|
-
' re-evaluation compared against the frozen objects. Exits 1',
|
|
124
|
-
' on drift (e.g. configs that read env vars). Run it before',
|
|
125
|
-
' `vx run --frozen` in CI.',
|
|
126
|
-
'',
|
|
127
|
-
].join('\n'),
|
|
128
|
-
)
|
|
1
|
+
export function printHelp(pluginCommands: readonly string[] = [], verb?: string): void {
|
|
2
|
+
process.stdout.write(verb === undefined ? helpText(pluginCommands) : verbHelpText(verb))
|
|
129
3
|
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `vx <verb> --help`: the reference cut to one verb — the title, the Usage
|
|
7
|
+
* lines that name it, and every section that is `(for <verb>)` or lists a
|
|
8
|
+
* `vx <verb>` form — read from the same text, so nothing can drift. A verb
|
|
9
|
+
* the reference does not know gets the whole thing.
|
|
10
|
+
*/
|
|
11
|
+
export function verbHelpText(verb: string): string {
|
|
12
|
+
const blocks = helpText().split('\n\n')
|
|
13
|
+
const title = blocks[0]!
|
|
14
|
+
const names = (line: string): boolean =>
|
|
15
|
+
new RegExp(`^\\s*vx ${verb}( |$)`).test(line) ||
|
|
16
|
+
line.includes(`(for ${verb})`) ||
|
|
17
|
+
line.includes(`(for ${verb} `)
|
|
18
|
+
const kept: string[] = []
|
|
19
|
+
for (const block of blocks.slice(1)) {
|
|
20
|
+
const lines = block.split('\n')
|
|
21
|
+
if (lines[0] === 'Usage:') {
|
|
22
|
+
const own = lines.slice(1).filter(names)
|
|
23
|
+
if (own.length > 0) kept.push(['Usage:', ...own].join('\n'))
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
if (lines.some(names)) kept.push(block)
|
|
27
|
+
}
|
|
28
|
+
if (kept.length === 0) return helpText()
|
|
29
|
+
return [title, ...kept, 'Full reference: vx help', ''].join('\n\n')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function helpText(pluginCommands: readonly string[] = []): string {
|
|
33
|
+
return [
|
|
34
|
+
'vx — open, extensible monorepo task runner',
|
|
35
|
+
'',
|
|
36
|
+
'Usage:',
|
|
37
|
+
' vx run [OPTIONS] [TASK | PKG#TASK] [-- forwarded-args...]',
|
|
38
|
+
' vx watch [OPTIONS] TASK [-- forwarded-args...]',
|
|
39
|
+
' vx cache prune [--older-than <duration>] [--max-size <size>] [--dry-run] [--cache-dir <path>]',
|
|
40
|
+
' vx lock [--check]',
|
|
41
|
+
' vx init [--dry] [--force] [--mjs]',
|
|
42
|
+
' vx upgrade [tag]',
|
|
43
|
+
' vx show [PROJECT[#TASK] | TASK] [--format pretty|json]',
|
|
44
|
+
' vx info [--format pretty|json] [--cache-dir <path>]',
|
|
45
|
+
' vx why [TASK | PKG#TASK] [--run <runId>] [--format pretty|json] [--cache-dir <path>]',
|
|
46
|
+
' vx last [RUNID] [--list[=N]] [--format pretty|json] [--cache-dir <path>]',
|
|
47
|
+
' vx completions bash|zsh|fish',
|
|
48
|
+
' vx help',
|
|
49
|
+
' vx version',
|
|
50
|
+
'',
|
|
51
|
+
'Selection (for run):',
|
|
52
|
+
' (default) Run task in the project containing cwd.',
|
|
53
|
+
' --all Run task in every project that declares it.',
|
|
54
|
+
' --filter <pat> pnpm-style filter (repeatable). Examples:',
|
|
55
|
+
' foo, @scope/*, ./packages/foo, foo..., ...foo,',
|
|
56
|
+
' foo^..., !foo, [<since>]',
|
|
57
|
+
' --affected[=<base>] Sugar for --filter "[<base>]". Default base =',
|
|
58
|
+
' origin/HEAD (falls back to HEAD~1).',
|
|
59
|
+
" pkg#task Run a specific project's task directly.",
|
|
60
|
+
'',
|
|
61
|
+
'Execution (for run):',
|
|
62
|
+
' --concurrency <n> Max parallel tasks (default: the cores this process may use); `50%` = half of them.',
|
|
63
|
+
' --exclude-dependencies[=names] Skip dependsOn edges. No value = all; comma list = specific names.',
|
|
64
|
+
' --no-cache Disable caching entirely (no reads, no writes, outputs left alone).',
|
|
65
|
+
' --force Re-execute everything (skip reads) but still refresh the cache (writes on).',
|
|
66
|
+
' --cache <spec> Per-layer read/write control. Comma list of <layer>:<flags>',
|
|
67
|
+
' (layer = local|remote, flags ⊆ rw). A named layer is set EXACTLY',
|
|
68
|
+
' to its flags; unnamed layers keep their value. Examples:',
|
|
69
|
+
' local:rw,remote:r (remote read-only), remote: (remote off),',
|
|
70
|
+
' local:r (local read-only).',
|
|
71
|
+
' --cache-dir <path> Cache directory override (cwd-relative). Beats the workspace',
|
|
72
|
+
' cacheDir field + the .vx/cache default.',
|
|
73
|
+
' --retry <n> Re-run a failed task up to <n> more times (default for tasks',
|
|
74
|
+
" without their own exec.retries; explicit config wins). Doesn't",
|
|
75
|
+
' affect cache keys.',
|
|
76
|
+
' --timeout <ms> Default per-task timeout for tasks without their own exec.timeout',
|
|
77
|
+
' (above VX_TASK_TIMEOUT env + workspace timeout). Kills + fails',
|
|
78
|
+
' a runaway task. Per-task exec.timeout always wins.',
|
|
79
|
+
' --frozen Load configs from vx-lock.json (CI; pair with vx lock --check).',
|
|
80
|
+
' --output-logs <mode> full | errors-only | hash-only | none — overrides the flow default',
|
|
81
|
+
' --download <mode> all (default) | toplevel | none — where remote outputs land',
|
|
82
|
+
' (focused without --all/--filter/--affected, broad with, full in CI).',
|
|
83
|
+
' --verbosity <n> 0 (default) or 1+: print a per-task summary table after the run.',
|
|
84
|
+
'',
|
|
85
|
+
'Planning (for run — skips execution):',
|
|
86
|
+
' --dry[=text|json] Print the task graph + cache hit/miss prediction.',
|
|
87
|
+
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
88
|
+
'',
|
|
89
|
+
'Artifacts (for run):',
|
|
90
|
+
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
91
|
+
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
92
|
+
'',
|
|
93
|
+
'Extensions (plugins):',
|
|
94
|
+
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
|
95
|
+
' cache, distributed execution, and telemetry export are provided by',
|
|
96
|
+
' PLUGINS declared in vx.workspace.ts — first-party or your own. Core',
|
|
97
|
+
' depends on none of them; a plain `vx run` never requires a plugin.',
|
|
98
|
+
' See the plugin guide: https://vznjs.github.io/vx/guides/plugins/',
|
|
99
|
+
'',
|
|
100
|
+
'Argument forwarding (for run):',
|
|
101
|
+
" Anything after `--` is forwarded (shell-quoted) to the task's exec",
|
|
102
|
+
' command. The forwarded args are folded into the cache key.',
|
|
103
|
+
'',
|
|
104
|
+
'Watch mode:',
|
|
105
|
+
' vx watch <task> Initial run, then re-run on filesystem changes.',
|
|
106
|
+
' Uses the same flags as `vx run` except --dry /',
|
|
107
|
+
' --graph / --summarize / --profile. Press Ctrl+C',
|
|
108
|
+
' to stop. Cache means most re-runs are near-zero',
|
|
109
|
+
' cost (cache hits restore outputs + replay logs).',
|
|
110
|
+
'',
|
|
111
|
+
'Cache management:',
|
|
112
|
+
' vx cache prune --older-than 30d Evict entries last accessed > 30 days ago.',
|
|
113
|
+
' vx cache prune --max-size 1G Keep total cache under 1 GB (LRU eviction).',
|
|
114
|
+
' --dry-run Say what the policy would reap; delete nothing.',
|
|
115
|
+
' --cache-dir <path> The cache a run with the same flag uses (why, last, info too).',
|
|
116
|
+
'',
|
|
117
|
+
' Duration units: s, m, h, d. Size units: K, M, G, T (powers of 1024).',
|
|
118
|
+
'',
|
|
119
|
+
'Introspection:',
|
|
120
|
+
' vx show List every project: name, dir, declared task count.',
|
|
121
|
+
" vx show <project> Print the project's LIVE resolved config (fresh",
|
|
122
|
+
' evaluation, not the lock), one block per task.',
|
|
123
|
+
" vx show <pkg>#<task> Print a single task's resolved config.",
|
|
124
|
+
' vx show <task> That task in every project declaring it.',
|
|
125
|
+
' --format <fmt> pretty (default) | json.',
|
|
126
|
+
' vx info Workspace doctor: vx/bun/git versions, project +',
|
|
127
|
+
' task counts, cache dir/entries/size, recent runs,',
|
|
128
|
+
' lock status.',
|
|
129
|
+
' --format <fmt> pretty (default) | json.',
|
|
130
|
+
' vx stats Deprecated: alias of vx info.',
|
|
131
|
+
" vx why <task> Why did this task re-run? Compares the task's latest",
|
|
132
|
+
' run (or --run <id>) against its previous run: names the',
|
|
133
|
+
' exact changed cache-key components (files / env / runtime',
|
|
134
|
+
' / upstream) from the persisted input fingerprints.',
|
|
135
|
+
' --format <fmt> pretty (default) | json.',
|
|
136
|
+
" vx last [runId] Replay a recorded run's summary from the local history —",
|
|
137
|
+
' header (command, when, branch, counts) plus the per-task',
|
|
138
|
+
' table, failures first. No re-execution, no cache probe.',
|
|
139
|
+
' --list[=N] List the N most recent runs (default 10) with run ids.',
|
|
140
|
+
' --format <fmt> pretty (default) | json.',
|
|
141
|
+
'',
|
|
142
|
+
'Migration:',
|
|
143
|
+
' vx init Generate vx.workspace.ts + one vx.config.ts per package',
|
|
144
|
+
' from package.json scripts. Unmappable settings become',
|
|
145
|
+
' TODO(vx-migrate) comments.',
|
|
146
|
+
' --dry Print the generated files instead of writing them.',
|
|
147
|
+
' --force Overwrite existing vx.config.* files.',
|
|
148
|
+
' From turbo.json or an Nx project graph: `bunx @vzn/vx-migrate` (its own package).',
|
|
149
|
+
'',
|
|
150
|
+
'Shell completions:',
|
|
151
|
+
' vx completions <shell> Print a completion script for bash, zsh or fish: the',
|
|
152
|
+
' verbs (plugin verbs of this workspace included) and',
|
|
153
|
+
' every flag of each. `eval "$(vx completions bash)"`,',
|
|
154
|
+
' or write it where the shell loads completions from.',
|
|
155
|
+
'',
|
|
156
|
+
'Config lock:',
|
|
157
|
+
' vx lock Evaluate every vx.config in the current environment and',
|
|
158
|
+
' freeze the resolved objects into vx-lock.json. Plain runs',
|
|
159
|
+
' always evaluate live; only `vx run --frozen` consumes the',
|
|
160
|
+
' lock (CI reproducibility, frozen-env semantics).',
|
|
161
|
+
' vx lock --check Audit the lock: file-changed hash report PLUS a full',
|
|
162
|
+
' re-evaluation compared against the frozen objects. Exits 1',
|
|
163
|
+
' on drift (e.g. configs that read env vars). Run it before',
|
|
164
|
+
' `vx run --frozen` in CI.',
|
|
165
|
+
'',
|
|
166
|
+
...(pluginCommands.length > 0 ? ['Plugin commands:', ...pluginCommands, ''] : []),
|
|
167
|
+
].join('\n')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Every argument error ends with this. `vx <verb> --help` prints the
|
|
172
|
+
* reference (2026-09-04); before that there was nowhere to send a user who
|
|
173
|
+
* got a flag wrong, so the errors just said no.
|
|
174
|
+
*/
|
|
175
|
+
export function seeHelp(verb: string): string {
|
|
176
|
+
return ` (see \`vx ${verb} --help\`)`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The documented `--flags` of one verb, read from the help text's
|
|
181
|
+
* sections headed `… (for <verb>):` so there is no second list to drift —
|
|
182
|
+
* and no hint that names another verb's flag.
|
|
183
|
+
*/
|
|
184
|
+
export function documentedFlags(verb: string): string[] {
|
|
185
|
+
const flags = new Set<string>()
|
|
186
|
+
let inVerb = false
|
|
187
|
+
for (const line of helpText().split('\n')) {
|
|
188
|
+
if (/^[A-Z][A-Za-z ]*(?: \(for [a-z]+\))?:$/.test(line))
|
|
189
|
+
inVerb = line.endsWith(`(for ${verb}):`)
|
|
190
|
+
if (!inVerb) continue
|
|
191
|
+
for (const f of line.match(/--[a-zA-Z][a-zA-Z-]*/g) ?? []) flags.add(f)
|
|
192
|
+
}
|
|
193
|
+
return [...flags]
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { CORE_VERBS } from '../util/index.js'
|