@vzn/vx 0.0.638 → 0.0.640
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 +1 -1
- package/docs/caching.md +152 -95
- package/docs/cli.md +225 -372
- package/docs/execution.md +22 -47
- package/docs/features.md +14 -16
- package/docs/patterns.md +6 -8
- package/docs/schema.md +268 -215
- package/docs/security.md +1 -1
- package/package.json +5 -5
- package/schemas/last.json +7 -4
- package/schemas/show.json +14 -14
- package/skills/vx/SKILL.md +10 -7
- package/src/cache/cache.ts +44 -11
- package/src/cache/file-hashes.ts +7 -12
- package/src/cache/fs-clock.ts +62 -0
- package/src/cache/git-inputs.ts +8 -2
- package/src/cache/index.ts +5 -2
- package/src/cache/inputs.ts +154 -83
- package/src/cache/key-fold.ts +23 -3
- package/src/cache/layer.ts +33 -18
- package/src/cache/layered-cache.ts +108 -315
- package/src/cache/linked-inputs.ts +87 -0
- package/src/cache/plugin-cache.ts +2 -10
- package/src/cache/remote-lane.ts +383 -0
- package/src/cache/run-history.ts +24 -8
- package/src/cache/run-store.ts +4 -4
- package/src/cache/schema.ts +6 -4
- package/src/cache/scoped-cache.ts +7 -0
- package/src/cli/completions.ts +2 -2
- package/src/cli/foreign-flags.ts +11 -23
- package/src/cli/help.ts +6 -31
- package/src/cli/info.ts +2 -1
- package/src/cli/init.ts +23 -11
- package/src/cli/last.ts +30 -4
- package/src/cli/plan-format.ts +5 -58
- package/src/cli/plugin-commands.ts +2 -2
- package/src/cli/plugin-templates.ts +2 -2
- package/src/cli/run.ts +23 -185
- package/src/cli/select.ts +16 -6
- package/src/cli/show.ts +15 -6
- package/src/cli/upgrade.ts +215 -55
- package/src/cli/watch-cycle.ts +2 -1
- package/src/cli/watch-filter.ts +22 -6
- package/src/cli/watch-judge.ts +49 -5
- package/src/cli/watch-set.ts +18 -8
- package/src/cli/watch.ts +103 -16
- package/src/cli/why.ts +9 -7
- package/src/config.ts +19 -21
- package/src/exec/executor.ts +5 -5
- package/src/exec/sandbox-runtime.ts +1 -1
- package/src/exec/sandbox-violations.ts +3 -1
- package/src/graph/dependency-spec.ts +2 -2
- package/src/graph/folds.ts +236 -0
- package/src/graph/index.ts +17 -2
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +45 -31
- package/src/graph/task-graph.ts +190 -58
- package/src/graph/task-status.ts +95 -0
- package/src/{orchestrator → history}/failure-mode.ts +1 -1
- package/src/{orchestrator → history}/history.ts +1 -1
- package/src/history/index.ts +48 -0
- package/src/{orchestrator → history}/metrics.ts +7 -20
- package/src/{orchestrator → history}/miss-reason.ts +1 -1
- package/src/index.ts +11 -29
- package/src/orchestrator/affected-tasks.ts +20 -19
- package/src/orchestrator/doctor.ts +1 -1
- package/src/orchestrator/download-policy.ts +16 -22
- package/src/orchestrator/excluded-keys.ts +4 -5
- package/src/orchestrator/execute-args.ts +80 -0
- package/src/orchestrator/execute-task.ts +50 -411
- package/src/orchestrator/fingerprint-watch.ts +2 -2
- package/src/orchestrator/index.ts +2 -73
- package/src/orchestrator/keyed-projects.ts +12 -51
- package/src/orchestrator/local-shortcircuit.ts +1 -1
- package/src/orchestrator/miss-save.ts +33 -3
- package/src/orchestrator/options.ts +1 -36
- package/src/orchestrator/output-host.ts +4 -3
- package/src/orchestrator/persistent-task.ts +321 -0
- package/src/orchestrator/persistent.ts +130 -3
- package/src/orchestrator/placement.ts +5 -5
- package/src/orchestrator/plan.ts +19 -6
- package/src/orchestrator/plugin-host.ts +129 -69
- package/src/orchestrator/plugin.ts +18 -8
- package/src/orchestrator/prepare.ts +40 -34
- package/src/orchestrator/projects.ts +7 -10
- package/src/orchestrator/remote-prefetch.ts +14 -1
- package/src/orchestrator/run-artifacts.ts +5 -177
- package/src/orchestrator/run-context.ts +55 -0
- package/src/orchestrator/run-failures.ts +15 -3
- package/src/orchestrator/run-records.ts +35 -3
- package/src/orchestrator/run-report.ts +151 -171
- package/src/orchestrator/run-settings.ts +1 -16
- package/src/orchestrator/run-steps.ts +57 -0
- package/src/orchestrator/run-stop.ts +153 -0
- package/src/orchestrator/run.ts +293 -604
- package/src/orchestrator/sandbox-fix.ts +1 -17
- package/src/orchestrator/sandbox-request.ts +2 -2
- package/src/orchestrator/signals.ts +14 -6
- package/src/orchestrator/stable-keys.ts +164 -27
- package/src/orchestrator/steps.ts +70 -0
- package/src/orchestrator/task-hash.ts +34 -25
- package/src/orchestrator/task-log-buffer.ts +1 -1
- package/src/orchestrator/task-process.ts +94 -0
- package/src/orchestrator/telemetry-host.ts +4 -4
- package/src/orchestrator/telemetry.ts +14 -86
- package/src/orchestrator/upstream.ts +4 -181
- package/src/{orchestrator → report}/events.ts +41 -8
- package/src/{orchestrator → report}/forecast.ts +35 -2
- package/src/{orchestrator → report}/framed-output.ts +40 -34
- package/src/report/index.ts +36 -0
- package/src/{orchestrator → report}/logger.ts +158 -20
- package/src/{orchestrator → report}/status-line.ts +88 -7
- package/src/{orchestrator → report}/summary.ts +126 -35
- package/src/report/tally.ts +83 -0
- package/src/settings.ts +12 -39
- package/src/util/colors.ts +44 -4
- package/src/util/git-ignored.ts +143 -0
- package/src/util/index.ts +19 -4
- package/src/util/paths.ts +1 -1
- package/src/util/report.ts +13 -5
- package/src/util/settle.ts +14 -39
- package/src/util/tail.ts +1 -1
- package/src/util/ui.ts +17 -6
- package/src/workspace/affected.ts +81 -2
- package/src/workspace/config-imports.ts +1 -1
- package/src/workspace/config-schema.ts +142 -143
- package/src/workspace/lockfile.ts +1 -1
- package/src/workspace/migration.ts +19 -3
- package/schemas/summary.json +0 -133
- package/src/cache/chained-cache.ts +0 -280
- package/src/orchestrator/tally.ts +0 -112
- /package/src/{orchestrator → report}/path-links.ts +0 -0
- /package/src/{orchestrator → report}/plain-output.ts +0 -0
- /package/src/{orchestrator → report}/terminal-signals.ts +0 -0
package/src/cli/run.ts
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
2
|
-
import path from 'node:path'
|
|
3
1
|
import { isatty } from 'node:tty'
|
|
4
2
|
import { cacheScopeError, findWorkspaceRoot, isCacheScope } from '../workspace/index.js'
|
|
5
3
|
import { translateForeign } from './foreign-flags.js'
|
|
6
|
-
import { flagHint,
|
|
4
|
+
import { flagHint, refuse, seeHelp } from './help.js'
|
|
7
5
|
import {
|
|
8
6
|
planRun,
|
|
9
|
-
formatRunReportMarkdown,
|
|
10
|
-
projectOutcome,
|
|
11
7
|
run as runOrchestrator,
|
|
12
8
|
shellQuote,
|
|
13
9
|
type RunOptions,
|
|
14
10
|
} from '../orchestrator/index.js'
|
|
15
|
-
import type { ContinueMode
|
|
11
|
+
import type { ContinueMode } from '../graph/index.js'
|
|
16
12
|
import {
|
|
17
13
|
applyCacheFlags,
|
|
18
14
|
type CacheFlags,
|
|
@@ -25,7 +21,7 @@ import { affectedFilterFor, findCwdSelection, pickTask, resolveFilters } from '.
|
|
|
25
21
|
import { nxTargetHint, taskNamesHere } from './task-verb.js'
|
|
26
22
|
import { parseDecimalInt, sayNotice, sayLine } from '../util/index.js'
|
|
27
23
|
import { kindHint, parseSettingValue, type Setting, SETTINGS, settingRefusal } from '../settings.js'
|
|
28
|
-
import {
|
|
24
|
+
import { formatPlanJson, formatPlanText } from './plan-format.js'
|
|
29
25
|
|
|
30
26
|
export interface RunArgs {
|
|
31
27
|
/** Failure propagation (`--continue[=never|deps-ok|always]`). */
|
|
@@ -71,10 +67,6 @@ export interface RunArgs {
|
|
|
71
67
|
forwardArgs: string[]
|
|
72
68
|
verbosity: number
|
|
73
69
|
dry: 'text' | 'json' | undefined
|
|
74
|
-
/** `--format json`: the run's result document on stdout, every other line on stderr. */
|
|
75
|
-
format: 'pretty' | 'json'
|
|
76
|
-
graph: string | undefined
|
|
77
|
-
summarize: string | undefined
|
|
78
70
|
profile: string | undefined
|
|
79
71
|
/**
|
|
80
72
|
* `--affected[=<base>]`. When undefined the flag wasn't passed.
|
|
@@ -82,16 +74,6 @@ export interface RunArgs {
|
|
|
82
74
|
* `defaultAffectedBase`). Any other string is an explicit git ref.
|
|
83
75
|
*/
|
|
84
76
|
affected: string | undefined
|
|
85
|
-
/** `--tag k=v` pairs (repeatable). Empty key is a parse error. */
|
|
86
|
-
tags: Record<string, string>
|
|
87
|
-
/** `--report[=markdown]`. Undefined when the flag wasn't passed. */
|
|
88
|
-
report: 'markdown' | undefined
|
|
89
|
-
/**
|
|
90
|
-
* `--report-file=<path>`. Undefined when the flag wasn't passed. Writes
|
|
91
|
-
* the same markdown as `--report`, but to a file — because redirecting
|
|
92
|
-
* vx's stdout captures the whole run log, not just the report.
|
|
93
|
-
*/
|
|
94
|
-
reportFile: string | undefined
|
|
95
77
|
error?: string
|
|
96
78
|
}
|
|
97
79
|
|
|
@@ -130,9 +112,6 @@ const SETTING_OPTIONS = [
|
|
|
130
112
|
['run.timeout', 'timeout'],
|
|
131
113
|
['run.retries', 'retries'],
|
|
132
114
|
['run.outputLogs', 'outputLogs'],
|
|
133
|
-
['run.killGrace', 'killGrace'],
|
|
134
|
-
['run.teardownTimeout', 'teardownTimeout'],
|
|
135
|
-
['run.readyNotice', 'readyNotice'],
|
|
136
115
|
['cache.download', 'download'],
|
|
137
116
|
] as const satisfies readonly (readonly [string, keyof RunOptions])[]
|
|
138
117
|
|
|
@@ -150,14 +129,8 @@ export function parseRunArgs(rawArgs: readonly string[], verb: 'run' | 'watch' =
|
|
|
150
129
|
forwardArgs: [],
|
|
151
130
|
verbosity: 0,
|
|
152
131
|
dry: undefined,
|
|
153
|
-
format: 'pretty',
|
|
154
|
-
graph: undefined,
|
|
155
|
-
summarize: undefined,
|
|
156
132
|
profile: undefined,
|
|
157
133
|
affected: undefined,
|
|
158
|
-
tags: {},
|
|
159
|
-
report: undefined,
|
|
160
|
-
reportFile: undefined,
|
|
161
134
|
}
|
|
162
135
|
|
|
163
136
|
// A Turbo or Nx spelling becomes vx's, or a refusal naming vx's.
|
|
@@ -301,58 +274,18 @@ export function parseRunArgs(rawArgs: readonly string[], verb: 'run' | 'watch' =
|
|
|
301
274
|
return { ...out, error: `--dry must be text or json (got ${fmt})` }
|
|
302
275
|
}
|
|
303
276
|
out.dry = fmt
|
|
304
|
-
} else if (a === '--format' || a?.startsWith('--format=')) {
|
|
305
|
-
const f = formatValue(a === '--format' ? before[++i] : a.slice('--format='.length), verb)
|
|
306
|
-
if (typeof f !== 'string') return { ...out, error: f.error }
|
|
307
|
-
out.format = f
|
|
308
|
-
} else if (a === '--graph') {
|
|
309
|
-
out.graph = ''
|
|
310
|
-
} else if (a?.startsWith('--graph=')) {
|
|
311
|
-
out.graph = a.slice('--graph='.length)
|
|
312
|
-
} else if (a === '--summarize') {
|
|
313
|
-
out.summarize = ''
|
|
314
|
-
} else if (a?.startsWith('--summarize=')) {
|
|
315
|
-
out.summarize = a.slice('--summarize='.length)
|
|
316
277
|
} else if (a === '--profile') {
|
|
317
278
|
out.profile = 'profile.json'
|
|
318
279
|
} else if (a?.startsWith('--profile=')) {
|
|
319
280
|
// An optional-value flag: an empty `=` means "no value given", so it
|
|
320
281
|
// takes the same documented default as the bare form. Resolving ''
|
|
321
282
|
// against cwd would name the cwd DIRECTORY and die with EISDIR after
|
|
322
|
-
// the whole run
|
|
283
|
+
// the whole run.
|
|
323
284
|
out.profile = a.slice('--profile='.length) || 'profile.json'
|
|
324
285
|
} else if (a === '--affected') {
|
|
325
286
|
out.affected = ''
|
|
326
287
|
} else if (a?.startsWith('--affected=')) {
|
|
327
288
|
out.affected = a.slice('--affected='.length)
|
|
328
|
-
} else if (a === '--tag' || a?.startsWith('--tag=')) {
|
|
329
|
-
const raw = a === '--tag' ? before[++i] : a.slice('--tag='.length)
|
|
330
|
-
if (raw === undefined) return { ...out, error: `${a} requires a value (k=v)` }
|
|
331
|
-
// Split on the FIRST `=` so values may contain `=` (e.g. a URL).
|
|
332
|
-
const eq = raw.indexOf('=')
|
|
333
|
-
if (eq <= 0) return { ...out, error: `--tag must be k=v (got ${raw})` }
|
|
334
|
-
out.tags[raw.slice(0, eq)] = raw.slice(eq + 1)
|
|
335
|
-
} else if (a === '--report-file' || a?.startsWith('--report-file=')) {
|
|
336
|
-
const v = a === '--report-file' ? before[++i] : a.slice('--report-file='.length)
|
|
337
|
-
if (v === undefined || v === '') return { ...out, error: `--report-file requires a path` }
|
|
338
|
-
// Same reasoning as --cache-dir: a path is an arbitrary string, so
|
|
339
|
-
// nothing about its shape rejects a swallowed flag. A path starting
|
|
340
|
-
// with `-` needs the `=` form.
|
|
341
|
-
if (a === '--report-file' && v.startsWith('-')) {
|
|
342
|
-
return {
|
|
343
|
-
...out,
|
|
344
|
-
error: `--report-file requires a path (got flag ${v}; a path that starts with - needs --report-file=${v})`,
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
out.reportFile = v
|
|
348
|
-
} else if (a === '--report') {
|
|
349
|
-
out.report = 'markdown'
|
|
350
|
-
} else if (a?.startsWith('--report=')) {
|
|
351
|
-
const fmt = a.slice('--report='.length)
|
|
352
|
-
if (fmt !== 'markdown') {
|
|
353
|
-
return { ...out, error: `--report must be markdown (got ${fmt})` }
|
|
354
|
-
}
|
|
355
|
-
out.report = fmt
|
|
356
289
|
} else if (verb === 'watch' && a === '--poll') {
|
|
357
290
|
out.poll = true
|
|
358
291
|
} else if (a !== undefined && settingFlagOf(a) !== undefined) {
|
|
@@ -400,39 +333,10 @@ export function parseRunArgs(rawArgs: readonly string[], verb: 'run' | 'watch' =
|
|
|
400
333
|
out.cache = applyCacheFlags(cachePolicy, flags)
|
|
401
334
|
if (!cacheFlag && envSpec === undefined) out.cacheFlags = flags
|
|
402
335
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
// planning branch returns before any of them is written, and
|
|
408
|
-
// `--report-file="$GITHUB_STEP_SUMMARY" --dry` exited 0 having written
|
|
409
|
-
// nothing (item 992).
|
|
410
|
-
const needsRun =
|
|
411
|
-
out.summarize !== undefined ||
|
|
412
|
-
out.profile !== undefined ||
|
|
413
|
-
out.report !== undefined ||
|
|
414
|
-
out.reportFile !== undefined
|
|
415
|
-
const runOnly = '--summarize / --profile / --report / --report-file need a real run'
|
|
416
|
-
if (out.dry !== undefined && needsRun) {
|
|
417
|
-
return { ...out, error: `--dry skips execution; ${runOnly}` }
|
|
418
|
-
}
|
|
419
|
-
if (out.graph !== undefined && needsRun) {
|
|
420
|
-
return { ...out, error: `--graph skips execution; ${runOnly}` }
|
|
421
|
-
}
|
|
422
|
-
if (out.format === 'json') {
|
|
423
|
-
// Each of these prints something else on stdout, which the document owns.
|
|
424
|
-
if (out.dry !== undefined)
|
|
425
|
-
return { ...out, error: '--format json runs the tasks; --dry=json prints the plan' }
|
|
426
|
-
if (out.graph === '')
|
|
427
|
-
return {
|
|
428
|
-
...out,
|
|
429
|
-
error: '--format json and --graph both print on stdout; give --graph a file',
|
|
430
|
-
}
|
|
431
|
-
if (out.report === 'markdown')
|
|
432
|
-
return {
|
|
433
|
-
...out,
|
|
434
|
-
error: '--format json and --report=markdown both print on stdout; use --report-file',
|
|
435
|
-
}
|
|
336
|
+
// A profile is about a run: the planning branch returns before it is
|
|
337
|
+
// written (item 992).
|
|
338
|
+
if (out.dry !== undefined && out.profile !== undefined) {
|
|
339
|
+
return { ...out, error: '--dry skips execution; --profile needs a real run' }
|
|
436
340
|
}
|
|
437
341
|
return out
|
|
438
342
|
}
|
|
@@ -619,10 +523,7 @@ export async function resolveRunOptions(
|
|
|
619
523
|
for (const [path, field] of SETTING_OPTIONS) {
|
|
620
524
|
if (parsed.settings[path] !== undefined) Object.assign(opts, { [field]: parsed.settings[path] })
|
|
621
525
|
}
|
|
622
|
-
if (parsed.summarize !== undefined) opts.summarize = parsed.summarize
|
|
623
|
-
if (parsed.format === 'json') opts.json = true
|
|
624
526
|
if (parsed.profile !== undefined) opts.profile = parsed.profile
|
|
625
|
-
if (Object.keys(parsed.tags).length > 0) opts.tags = parsed.tags
|
|
626
527
|
|
|
627
528
|
return opts
|
|
628
529
|
}
|
|
@@ -646,7 +547,7 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
646
547
|
if (tasks.length === 0) {
|
|
647
548
|
// A JSON answer is for a program, which cannot pick from a menu: an
|
|
648
549
|
// agent on a pty waited at the picker forever.
|
|
649
|
-
const forProgram = parsed.
|
|
550
|
+
const forProgram = parsed.dry === 'json'
|
|
650
551
|
if (!process.stdin.isTTY || forProgram) {
|
|
651
552
|
const names = await taskNamesHere(cwd)
|
|
652
553
|
const shown = (names ?? []).slice(0, TASKS_SHOWN)
|
|
@@ -710,17 +611,17 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
710
611
|
...args.map(shellQuote),
|
|
711
612
|
].join(' ')
|
|
712
613
|
|
|
713
|
-
// Planning
|
|
714
|
-
|
|
715
|
-
if (parsed.dry !== undefined || parsed.graph !== undefined) {
|
|
614
|
+
// Planning short-circuits execution: the full task graph + a cache probe.
|
|
615
|
+
if (parsed.dry !== undefined) {
|
|
716
616
|
const plan = await planRun(opts)
|
|
617
|
+
// Refused as a run refuses: `--dry=json` gets the error document.
|
|
717
618
|
if (plan.unresolvedTasks !== undefined && plan.unresolvedTasks.length > 0) {
|
|
718
|
-
|
|
719
|
-
'
|
|
720
|
-
|
|
619
|
+
return refuse(
|
|
620
|
+
'run',
|
|
621
|
+
args,
|
|
721
622
|
`no projects declare task(s): ${plan.unresolvedTasks.join(', ')}.${plan.unresolvedHint ?? ''}`,
|
|
623
|
+
'VX_E_UNKNOWN_TASK',
|
|
722
624
|
)
|
|
723
|
-
return 1
|
|
724
625
|
}
|
|
725
626
|
if (plan.tasks.length === 0) {
|
|
726
627
|
// The run's own line: a dry run that guessed from the scope alone
|
|
@@ -733,27 +634,14 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
733
634
|
sayNotice('info', 'vx run', `no affected project declares task(s): ${tasks.join(', ')}`)
|
|
734
635
|
return 0
|
|
735
636
|
}
|
|
736
|
-
|
|
737
|
-
|
|
637
|
+
return refuse(
|
|
638
|
+
'run',
|
|
639
|
+
args,
|
|
640
|
+
`no projects declare task(s): ${tasks.join(', ')}`,
|
|
641
|
+
'VX_E_UNKNOWN_TASK',
|
|
642
|
+
)
|
|
738
643
|
}
|
|
739
|
-
if (parsed.
|
|
740
|
-
const out = formatGraphDot(plan)
|
|
741
|
-
if (parsed.graph === '') {
|
|
742
|
-
process.stdout.write(out)
|
|
743
|
-
} else {
|
|
744
|
-
// The graph is the command's one product, so a path it cannot write
|
|
745
|
-
// is exit 1, said in one line as the other output paths say theirs:
|
|
746
|
-
// a directory or an unreachable path printed a raw stack (item 993).
|
|
747
|
-
const target = path.resolve(cwd, parsed.graph)
|
|
748
|
-
try {
|
|
749
|
-
await Bun.write(target, out)
|
|
750
|
-
} catch (err) {
|
|
751
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
752
|
-
sayNotice('error', 'vx run', `failed to write graph to ${target}: ${message}`)
|
|
753
|
-
return 1
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
} else if (parsed.dry === 'json') {
|
|
644
|
+
if (parsed.dry === 'json') {
|
|
757
645
|
process.stdout.write(formatPlanJson(plan, scopeOut.affectedBase))
|
|
758
646
|
} else {
|
|
759
647
|
process.stdout.write(formatPlanText(plan))
|
|
@@ -761,25 +649,6 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
761
649
|
return 0
|
|
762
650
|
}
|
|
763
651
|
|
|
764
|
-
// The report prints above the footer: nothing prints below it (owner).
|
|
765
|
-
// Rendered once, however many sinks asked for it; a kept server's crash
|
|
766
|
-
// after the footer re-renders the file's copy (C-53).
|
|
767
|
-
const wantsReport = parsed.report === 'markdown' || parsed.reportFile !== undefined
|
|
768
|
-
let reported: { outcomes: readonly TaskOutcome[]; md: string } | undefined
|
|
769
|
-
if (wantsReport) {
|
|
770
|
-
opts.beforeFooter = (outcomes, ok) => {
|
|
771
|
-
reported = {
|
|
772
|
-
outcomes,
|
|
773
|
-
md: formatRunReportMarkdown({ ok, outcomes: outcomes.map(projectOutcome) }),
|
|
774
|
-
}
|
|
775
|
-
// `--report` writes stdout. The report ITSELF is machine-clean, but
|
|
776
|
-
// stdout is not vx's alone — the status logger writes there too, so
|
|
777
|
-
// `--report=markdown >> "$GITHUB_STEP_SUMMARY"` captures every frame,
|
|
778
|
-
// meter bar and `::group::` command above the table. `--report-file`
|
|
779
|
-
// is the redirect-free form.
|
|
780
|
-
return parsed.report === 'markdown' ? reported.md : ''
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
652
|
// A run executes in THIS process, always. Where an individual task's
|
|
784
653
|
// command runs is the `executor` capability's business (per task, with
|
|
785
654
|
// the scheduler, cache, retries and telemetry unchanged above it); there
|
|
@@ -789,37 +658,6 @@ export async function runCmd(args: readonly string[]): Promise<number> {
|
|
|
789
658
|
const unknown = summary.refused.startsWith('no projects declare')
|
|
790
659
|
return refuse('run', args, summary.refused, unknown ? 'VX_E_UNKNOWN_TASK' : 'VX_E_REFUSED')
|
|
791
660
|
}
|
|
792
|
-
if (wantsReport) {
|
|
793
|
-
const md =
|
|
794
|
-
reported?.outcomes === summary.outcomes
|
|
795
|
-
? reported.md
|
|
796
|
-
: formatRunReportMarkdown({
|
|
797
|
-
ok: summary.ok,
|
|
798
|
-
outcomes: summary.outcomes.map(projectOutcome),
|
|
799
|
-
})
|
|
800
|
-
if (parsed.reportFile !== undefined) {
|
|
801
|
-
const target = path.resolve(cwd, parsed.reportFile)
|
|
802
|
-
try {
|
|
803
|
-
// APPEND, not overwrite: the flag exists for `$GITHUB_STEP_SUMMARY`,
|
|
804
|
-
// which GitHub documents as append-only and which other steps in the
|
|
805
|
-
// same job also write to. Truncating it would silently destroy their
|
|
806
|
-
// content, and would make replacing the documented `>>` recipe a
|
|
807
|
-
// behaviour change rather than a drop-in.
|
|
808
|
-
// Its directory made first, as `--summarize`, `--profile` and
|
|
809
|
-
// `--graph` make theirs: `nr/r.md` failed alone of the four (item 993).
|
|
810
|
-
// Sync: on a Ctrl-C the signal path exits once stdout drains, and
|
|
811
|
-
// an awaited write lost the race every time (E-23).
|
|
812
|
-
mkdirSync(path.dirname(target), { recursive: true })
|
|
813
|
-
appendFileSync(target, md)
|
|
814
|
-
} catch (err) {
|
|
815
|
-
// Same contract as --summarize / --profile: the run already
|
|
816
|
-
// happened, so a write failure is reported, not fatal.
|
|
817
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
818
|
-
sayNotice('warn', 'vx run', `failed to write report to ${target}: ${message}`)
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
if (summary.json !== undefined) process.stdout.write(`${JSON.stringify(summary.json)}\n`)
|
|
823
661
|
return summary.ok ? 0 : 1
|
|
824
662
|
}
|
|
825
663
|
|
package/src/cli/select.ts
CHANGED
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
import { resolveSetting } from '../settings.js'
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
* The projects whose tasks declare
|
|
54
|
+
* The projects whose tasks declare an `inputs.workspaceFiles` glob
|
|
55
55
|
* matching a changed path, one inside another project included (item
|
|
56
56
|
* 954): `--affected` selects them, since the glob is that task's input. Through the run
|
|
57
57
|
* path's staged load, so a glob a `project` plugin gave a config-less
|
|
@@ -71,10 +71,8 @@ export async function workspaceGlobOwners(
|
|
|
71
71
|
): Promise<string[]> {
|
|
72
72
|
const declaresMatch = (config: ProjectConfig): boolean => {
|
|
73
73
|
for (const task of Object.values(config.tasks ?? {})) {
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
if (changed.some((rel) => declaresInput(cache, null, rel))) return true
|
|
77
|
-
if (nested.some((dir) => workspaceFilesReachInto(cache, dir))) return true
|
|
74
|
+
if (changed.some((rel) => declaresInput(task, null, rel))) return true
|
|
75
|
+
if (nested.some((dir) => workspaceFilesReachInto(task, dir))) return true
|
|
78
76
|
}
|
|
79
77
|
return false
|
|
80
78
|
}
|
|
@@ -335,7 +333,19 @@ export async function resolveFilters(
|
|
|
335
333
|
workspaceGlobOwners: (changed: readonly string[], nested: readonly string[]) =>
|
|
336
334
|
workspaceGlobOwners(root, projects, changed, load, stagedOnce, nested),
|
|
337
335
|
fingerprintClaims: () => workspaceFingerprintClaims(root, projects, load),
|
|
338
|
-
...(git !== undefined
|
|
336
|
+
...(git !== undefined
|
|
337
|
+
? {
|
|
338
|
+
untracked: async () => (await git.start()).untracked,
|
|
339
|
+
// A scoped enumeration lists only its projects: null asks git.
|
|
340
|
+
links: async () => {
|
|
341
|
+
const g = await git.start()
|
|
342
|
+
if (!g.scopes.includes('.')) return null
|
|
343
|
+
const out: string[] = []
|
|
344
|
+
for (const [rel, oid] of g.trusted) if (oid.startsWith('120000:')) out.push(rel)
|
|
345
|
+
return out
|
|
346
|
+
},
|
|
347
|
+
}
|
|
348
|
+
: {}),
|
|
339
349
|
}
|
|
340
350
|
let names: Set<string>
|
|
341
351
|
if (f.raw === affected) {
|
package/src/cli/show.ts
CHANGED
|
@@ -169,6 +169,12 @@ export async function showCmd(args: readonly string[]): Promise<number> {
|
|
|
169
169
|
const projects = await loadCliProjects(root, metas, scope, { closure: true })
|
|
170
170
|
|
|
171
171
|
if (parsed.target === undefined) {
|
|
172
|
+
if (metas.length === 0)
|
|
173
|
+
sayNotice(
|
|
174
|
+
'info',
|
|
175
|
+
'vx show',
|
|
176
|
+
'no projects: no directory holds a vx.config — run `vx init` to write them, then `vx lock`',
|
|
177
|
+
)
|
|
172
178
|
const shown = metas.filter((m) => inSelection(m.name))
|
|
173
179
|
process.stdout.write(renderList(root, shown, projects, parsed.format, colorsOut()))
|
|
174
180
|
return 0
|
|
@@ -460,14 +466,17 @@ function taskRows(raw: TaskConfig): ui.FieldRow[] {
|
|
|
460
466
|
)
|
|
461
467
|
}
|
|
462
468
|
add('interactive', exec?.interactive === undefined ? undefined : exec.interactive ? 'yes' : 'no')
|
|
469
|
+
const inputs = task.inputs
|
|
470
|
+
if (inputs !== undefined) {
|
|
471
|
+
add('inputs.files', list(inputs.files))
|
|
472
|
+
add('inputs.workspaceFiles', list(inputs.workspaceFiles))
|
|
473
|
+
add('inputs.env', list(inputs.env))
|
|
474
|
+
add('inputs.tasks', list(inputs.tasks))
|
|
475
|
+
add('inputs.runtime', list(inputs.runtime))
|
|
476
|
+
add('inputs.workspaceRuntime', list(inputs.workspaceRuntime))
|
|
477
|
+
}
|
|
463
478
|
const cache = task.cache
|
|
464
479
|
if (cache !== undefined) {
|
|
465
|
-
add('inputs.files', list(cache.inputs.files))
|
|
466
|
-
add('inputs.workspaceFiles', list(cache.inputs.workspaceFiles))
|
|
467
|
-
add('inputs.env', list(cache.inputs.env))
|
|
468
|
-
add('inputs.tasks', list(cache.inputs.tasks))
|
|
469
|
-
add('inputs.runtime', list(cache.inputs.runtime))
|
|
470
|
-
add('inputs.workspaceRuntime', list(cache.inputs.workspaceRuntime))
|
|
471
480
|
add('outputs.files', list(cache.outputs.files))
|
|
472
481
|
add('outputs.workspaceFiles', list(cache.outputs.workspaceFiles))
|
|
473
482
|
}
|