@vzn/vx 0.0.486 → 0.0.489
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.489",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.489",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.489",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.489",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.489"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/help.ts
CHANGED
|
@@ -1,7 +1,72 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ColorSupport, paint as Paint } from '../orchestrator/index.js'
|
|
2
|
+
import { CORE_VERBS, nearest } from '../util/index.js'
|
|
2
3
|
|
|
3
|
-
export function printHelp(
|
|
4
|
-
|
|
4
|
+
export async function printHelp(
|
|
5
|
+
pluginCommands: readonly string[] = [],
|
|
6
|
+
verb?: string,
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
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
|
+
process.stdout.write(paintHelp(text, helpColors(process.stdout, detectColors), paint))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Help paints on a terminal only. `FORCE_COLOR=1` set for a run's log in
|
|
17
|
+
* CI does not paint it: a script that greps `vx help` (or reads the flag
|
|
18
|
+
* list from it) gets plain text whatever forces colour elsewhere.
|
|
19
|
+
*/
|
|
20
|
+
export function helpColors(
|
|
21
|
+
stream: { isTTY?: boolean },
|
|
22
|
+
detect: (s: NodeJS.WriteStream) => ColorSupport,
|
|
23
|
+
): ColorSupport {
|
|
24
|
+
return { enabled: stream.isTTY === true && detect(stream as NodeJS.WriteStream).enabled }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const VERB = '#06b6d4' // cyan-500 — the orchestrator's accent
|
|
28
|
+
const TERM = '#22d3ee' // cyan-400 — a flag, a selector, an example
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The reference with ANSI accents, line by line and never reflowed, so the
|
|
32
|
+
* painted text strips back to `text` exactly: section headings bold (their
|
|
33
|
+
* `(for <verb>)` dim), `vx <verb>` bold cyan, and the term an option or
|
|
34
|
+
* example line opens with cyan. Prose and continuation lines stay plain.
|
|
35
|
+
*/
|
|
36
|
+
export function paintHelp(text: string, colors: ColorSupport, paint: typeof Paint): string {
|
|
37
|
+
if (!colors.enabled) return text
|
|
38
|
+
const lines = text.split('\n')
|
|
39
|
+
return lines
|
|
40
|
+
.map((line, i) => {
|
|
41
|
+
if (line === '') return line
|
|
42
|
+
if (i === 0) {
|
|
43
|
+
const [name, ...rest] = line.split(' ')
|
|
44
|
+
return [paint(VERB, name!, colors, { bold: true }), ...rest].join(' ')
|
|
45
|
+
}
|
|
46
|
+
const heading = /^([A-Z][^:]*?)( \(for [^)]*\))?:$/.exec(line)
|
|
47
|
+
if (heading !== null) {
|
|
48
|
+
const qualifier =
|
|
49
|
+
heading[2] === undefined ? '' : paint('', heading[2], colors, { dim: true })
|
|
50
|
+
return `${paint('', heading[1]!, colors, { bold: true })}${qualifier}${paint('', ':', colors, { bold: true })}`
|
|
51
|
+
}
|
|
52
|
+
// ` term description`: a term is a flag, a `vx` form, a selector.
|
|
53
|
+
const row = /^(\s+)(\S(?:.*?\S)?)(\s{2,}\S.*)?$/.exec(line)
|
|
54
|
+
if (row === null) return line
|
|
55
|
+
const [, indent, term, description = ''] = row
|
|
56
|
+
if (/^vx \S/.test(term!)) {
|
|
57
|
+
const verb = term!.split(' ')[1]!
|
|
58
|
+
// Prose that opens with `vx` (`vx core runs tasks…`) is not a form.
|
|
59
|
+
if (description === '' && !(CORE_VERBS as readonly string[]).includes(verb)) return line
|
|
60
|
+
const head = `vx ${verb}`
|
|
61
|
+
const tail = term!.slice(head.length)
|
|
62
|
+
return `${indent}${paint(VERB, head, colors, { bold: true })}${tail === '' ? '' : paint(TERM, tail, colors)}${description}`
|
|
63
|
+
}
|
|
64
|
+
if (description === '') return line
|
|
65
|
+
if (/^(-|\(|[\w-]+#)/.test(term!))
|
|
66
|
+
return `${indent}${paint(TERM, term!, colors)}${description}`
|
|
67
|
+
return line
|
|
68
|
+
})
|
|
69
|
+
.join('\n')
|
|
5
70
|
}
|
|
6
71
|
|
|
7
72
|
/**
|
|
@@ -96,11 +161,11 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
96
161
|
' --graph[=<path>] Emit Graphviz DOT (stdout if no path).',
|
|
97
162
|
'',
|
|
98
163
|
'Artifacts (for run):',
|
|
99
|
-
' --summarize[=<path>]
|
|
100
|
-
' --profile[=<path>]
|
|
101
|
-
' --report[=markdown]
|
|
102
|
-
' --report-file <path>
|
|
103
|
-
' --tag <k=v>
|
|
164
|
+
' --summarize[=<path>] Write per-run JSON to <cacheDir>/runs/<run_id>.json.',
|
|
165
|
+
' --profile[=<path>] Write Chrome-trace JSON (default profile.json).',
|
|
166
|
+
' --report[=markdown] Print a markdown run report to stdout after the run.',
|
|
167
|
+
' --report-file <path> Append that report to <path> (for $GITHUB_STEP_SUMMARY).',
|
|
168
|
+
' --tag <k=v> Label this invocation (repeatable); recorded on its history row.',
|
|
104
169
|
'',
|
|
105
170
|
'Extensions (plugins):',
|
|
106
171
|
' vx core runs tasks in-process and nothing else. A dashboard, remote',
|
package/src/cli/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
23
23
|
// which is the one place `--help` is not being asked of vx. Core verbs
|
|
24
24
|
// only — a plugin verb owns its own arguments, `--help` included.
|
|
25
25
|
if (command !== undefined && wantsHelp(command, rest)) {
|
|
26
|
-
printHelp(await (await plugins()).pluginCommandHelp(), command)
|
|
26
|
+
await printHelp(await (await plugins()).pluginCommandHelp(), command)
|
|
27
27
|
return 0
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -39,12 +39,12 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
39
39
|
process.stderr.write(`vx: ${err.message}; \`vx help\` lists the verbs\n`)
|
|
40
40
|
return 1
|
|
41
41
|
}
|
|
42
|
-
printHelp(await (await plugins()).pluginCommandHelp())
|
|
42
|
+
await printHelp(await (await plugins()).pluginCommandHelp())
|
|
43
43
|
return 0
|
|
44
44
|
}
|
|
45
45
|
case '--help':
|
|
46
46
|
case '-h':
|
|
47
|
-
printHelp(await (await plugins()).pluginCommandHelp())
|
|
47
|
+
await printHelp(await (await plugins()).pluginCommandHelp())
|
|
48
48
|
return 0
|
|
49
49
|
case 'help': {
|
|
50
50
|
// `vx help run` is the same question as `vx run --help`. A plugin verb
|
|
@@ -76,7 +76,7 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
76
76
|
return 1
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
printHelp(await (await plugins()).pluginCommandHelp(), core ? verb : undefined)
|
|
79
|
+
await printHelp(await (await plugins()).pluginCommandHelp(), core ? verb : undefined)
|
|
80
80
|
return 0
|
|
81
81
|
}
|
|
82
82
|
case '--version':
|
package/src/exec/env.ts
CHANGED
|
@@ -74,6 +74,11 @@ export function buildIsolatedEnv(opts: BuildEnvOptions): NodeJS.ProcessEnv {
|
|
|
74
74
|
for (const [name, value] of Object.entries(opts.define)) {
|
|
75
75
|
out[name] = value
|
|
76
76
|
}
|
|
77
|
+
// A task's output is a pipe, so a tool asks no TTY and prints plain. vx
|
|
78
|
+
// forces colour, as Nx and Turbo do, and strips it where its own output
|
|
79
|
+
// is plain (`plainOutput`), so the cache holds one form for every
|
|
80
|
+
// machine. Any FORCE_COLOR the task already sees, or a NO_COLOR, wins.
|
|
81
|
+
if (out['FORCE_COLOR'] === undefined && !out['NO_COLOR']) out['FORCE_COLOR'] = '1'
|
|
77
82
|
|
|
78
83
|
// A directory holding the delimiter cannot be named in PATH: split, it
|
|
79
84
|
// became two entries naming nothing, the second RELATIVE, so resolved
|
|
@@ -25,6 +25,7 @@ export { forwardedSignal } from './signals.js'
|
|
|
25
25
|
// `vx cache prune` waits for a run on the workspace before it evicts.
|
|
26
26
|
export { acquireRunLock } from './run-lock.js'
|
|
27
27
|
export { defaultLogger, resolveOutputView } from './logger.js'
|
|
28
|
+
export { detectColors, paint, type ColorSupport } from './colors.js'
|
|
28
29
|
export type { Logger, OutputView } from './logger.js'
|
|
29
30
|
export type { RunPlan, PlannedTask, PlanPrediction, CacheStatus } from './plan.js'
|
|
30
31
|
export { formatDuration } from './summary.js'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { TaskNode } from '../graph/index.js'
|
|
2
|
+
import type { Logger } from './logger.js'
|
|
3
|
+
|
|
4
|
+
// A prefix that may yet complete as a CSI or OSC sequence. An escape cut
|
|
5
|
+
// at a chunk's end is held for the next chunk: stripped alone, its
|
|
6
|
+
// remainder (`1m`) reached the log as text.
|
|
7
|
+
// eslint-disable-next-line no-control-regex -- ESC is the point
|
|
8
|
+
const PARTIAL_ESCAPE = /^\x1b(?:\[[0-?]*[ -/]*|\][^\x07\x1b]*\x1b?)?$/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `sink` with terminal escapes stripped from task output. Tasks run with
|
|
12
|
+
* `FORCE_COLOR` (exec/env.ts), so their bytes carry colour wherever vx's
|
|
13
|
+
* own output is plain: a pipe, CI, `NO_COLOR`.
|
|
14
|
+
*/
|
|
15
|
+
export function plainOutput(sink: Logger): Logger {
|
|
16
|
+
const held = new Map<string, string>()
|
|
17
|
+
const strip = (key: string, chunk: string): string => {
|
|
18
|
+
const prior = held.get(key)
|
|
19
|
+
const text = prior === undefined ? chunk : prior + chunk
|
|
20
|
+
if (prior !== undefined) held.delete(key)
|
|
21
|
+
const esc = text.lastIndexOf('\x1b')
|
|
22
|
+
if (esc === -1) return text
|
|
23
|
+
if (PARTIAL_ESCAPE.test(text.slice(esc))) {
|
|
24
|
+
held.set(key, text.slice(esc))
|
|
25
|
+
return Bun.stripANSI(text.slice(0, esc))
|
|
26
|
+
}
|
|
27
|
+
return Bun.stripANSI(text)
|
|
28
|
+
}
|
|
29
|
+
const flush = (node: TaskNode, key: string, write: (n: TaskNode, c: string) => void): void => {
|
|
30
|
+
const rest = held.get(key)
|
|
31
|
+
if (rest === undefined) return
|
|
32
|
+
held.delete(key)
|
|
33
|
+
const text = Bun.stripANSI(rest)
|
|
34
|
+
if (text !== '') write(node, text)
|
|
35
|
+
}
|
|
36
|
+
// Delegated one by one, never spread: an embedder's logger may be a class
|
|
37
|
+
// whose methods live on its prototype and read `this`.
|
|
38
|
+
return {
|
|
39
|
+
status: (line) => sink.status(line),
|
|
40
|
+
...(sink.runStart && { runStart: (info) => sink.runStart!(info) }),
|
|
41
|
+
...(sink.taskStart && { taskStart: (node) => sink.taskStart!(node) }),
|
|
42
|
+
...(sink.runEnd && { runEnd: () => sink.runEnd!() }),
|
|
43
|
+
taskStdout(node, chunk) {
|
|
44
|
+
const text = strip(`${node.id}\0out`, chunk)
|
|
45
|
+
if (text !== '' || chunk === '') sink.taskStdout(node, text)
|
|
46
|
+
},
|
|
47
|
+
taskStderr(node, chunk) {
|
|
48
|
+
const text = strip(`${node.id}\0err`, chunk)
|
|
49
|
+
if (text !== '' || chunk === '') sink.taskStderr(node, text)
|
|
50
|
+
},
|
|
51
|
+
taskComplete(node, outcome) {
|
|
52
|
+
flush(node, `${node.id}\0out`, (n, c) => sink.taskStdout(n, c))
|
|
53
|
+
flush(node, `${node.id}\0err`, (n, c) => sink.taskStderr(n, c))
|
|
54
|
+
sink.taskComplete(node, outcome)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -59,6 +59,7 @@ import { assembleRunSummary, isPassStatus } from './telemetry.js'
|
|
|
59
59
|
import type { RunContextRecord } from './telemetry.js'
|
|
60
60
|
import { defaultLogger, resolveOutputView, type Logger } from './logger.js'
|
|
61
61
|
import { detectColors, type ColorSupport } from './colors.js'
|
|
62
|
+
import { plainOutput } from './plain-output.js'
|
|
62
63
|
import { formatPersistentList } from './framed-output.js'
|
|
63
64
|
import { LocalHistoryProvider } from './history.js'
|
|
64
65
|
import { plan, type RunPlan } from './plan.js'
|
|
@@ -307,7 +308,12 @@ export async function run(options: RunOptions): Promise<RunSummary> {
|
|
|
307
308
|
// outlives the run, and a renderer left behind reported the next run on
|
|
308
309
|
// it twice (item 635). Otherwise a fresh internal bus.
|
|
309
310
|
const bus = options.bus ?? createEventBus()
|
|
310
|
-
|
|
311
|
+
// Tasks run with colour forced (exec/env.ts); where the terminal prints
|
|
312
|
+
// plain, their escapes are stripped. A custom logger gets the task's
|
|
313
|
+
// bytes as written, as the cache holds them: it formats its own output.
|
|
314
|
+
const unsubscribeTerminal = bus.subscribe(
|
|
315
|
+
terminalSubscriber(terminal === null || colors.enabled ? sink : plainOutput(sink)),
|
|
316
|
+
)
|
|
311
317
|
const detach = (): void => {
|
|
312
318
|
unsubscribeTerminal()
|
|
313
319
|
terminal?.settle()
|