@vzn/vx 0.0.61 → 0.0.63
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 +5 -5
- package/src/cache/inputs.ts +5 -1
- package/src/orchestrator/run-artifacts.ts +5 -2
- package/src/orchestrator/run.ts +12 -5
- package/src/util/index.ts +1 -0
- package/src/util/paths.ts +34 -0
- package/src/workspace/workspace.ts +7 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.63",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.63",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.63",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.63",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.63"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/inputs.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
isExecutableMissing,
|
|
26
26
|
isLiteralPattern,
|
|
27
27
|
normalizeGlob,
|
|
28
|
+
slashBraceExpansions,
|
|
28
29
|
taskGlob,
|
|
29
30
|
UserError,
|
|
30
31
|
} from '../util/index.js'
|
|
@@ -1062,7 +1063,10 @@ async function scanUnion(
|
|
|
1062
1063
|
cwd: string,
|
|
1063
1064
|
): Promise<Set<string>> {
|
|
1064
1065
|
const matches = new Set<string>()
|
|
1065
|
-
for
|
|
1066
|
+
// `Bun.Glob`'s scan finds nothing for a brace whose alternatives hold a
|
|
1067
|
+
// `/`: `{dist,lib/esm}/**` saved an empty artifact, and a hit restored
|
|
1068
|
+
// nothing over a cleaned tree (A-10). Expanded here, as discovery does.
|
|
1069
|
+
for (const pattern of positive.flatMap(slashBraceExpansions)) {
|
|
1066
1070
|
const glob = globFor(pattern)
|
|
1067
1071
|
for (const rel of glob.scanSync({ cwd, onlyFiles: false, followSymlinks: false, dot: true })) {
|
|
1068
1072
|
if (excludeGlobs.some((g) => g.match(rel))) continue
|
|
@@ -29,6 +29,8 @@ export interface SummarizeArgs {
|
|
|
29
29
|
totalMs: number
|
|
30
30
|
/** The run's verdict — the same value the CLI turns into the exit code. */
|
|
31
31
|
ok: boolean
|
|
32
|
+
/** The process's exit code where it is not `ok ? 0 : 1`: a stopping signal's. */
|
|
33
|
+
exitCode?: number
|
|
32
34
|
outcomes: readonly TaskOutcome[]
|
|
33
35
|
/** What `detectFlaky` found this run; the footer's Flaky section, typed. */
|
|
34
36
|
flaky?: readonly FlakyFinding[]
|
|
@@ -96,12 +98,13 @@ export async function writeRunSummary(args: SummarizeArgs): Promise<string> {
|
|
|
96
98
|
// parser must not be told less than a human is.
|
|
97
99
|
const aborted = args.outcomes.filter((o) => !isGroupTask(o.node) && o.status === 'aborted')
|
|
98
100
|
const flakyById = new Map((args.flaky ?? []).map((f) => [f.taskId, f]))
|
|
101
|
+
const exitCode = args.exitCode ?? (args.ok ? 0 : 1)
|
|
99
102
|
const payload = {
|
|
100
103
|
runId: args.runId,
|
|
101
104
|
// The run-level verdict, first: a consumer gating on this artifact must
|
|
102
105
|
// not have to re-derive it by re-implementing the bucket rules.
|
|
103
|
-
ok:
|
|
104
|
-
exitCode
|
|
106
|
+
ok: exitCode === 0,
|
|
107
|
+
exitCode,
|
|
105
108
|
startedAt: new Date(args.startedAtMs).toISOString(),
|
|
106
109
|
endedAt: new Date(args.endedAtMs).toISOString(),
|
|
107
110
|
totalMs: args.totalMs,
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -58,6 +58,7 @@ import { prepareRun, type PreparedRun } from './prepare.js'
|
|
|
58
58
|
import { acquireRunLock } from './run-lock.js'
|
|
59
59
|
import {
|
|
60
60
|
forwardedSignal,
|
|
61
|
+
type StopSignal,
|
|
61
62
|
forwardSignals,
|
|
62
63
|
SIGNAL_SHUTDOWN_GRACE_MS,
|
|
63
64
|
terminateChildren,
|
|
@@ -456,7 +457,7 @@ async function runOnBus(
|
|
|
456
457
|
// handler exited before any outcome landed, and a Ctrl-C'd run then read
|
|
457
458
|
// in `vx last` as FAILED with 0 tasks (item 854). An embedder's abort is
|
|
458
459
|
// its own call and keeps its record.
|
|
459
|
-
let
|
|
460
|
+
let stoppedBy: StopSignal | undefined
|
|
460
461
|
let leftRun = (): void => {}
|
|
461
462
|
const runLeft = new Promise<void>((resolve) => {
|
|
462
463
|
leftRun = resolve
|
|
@@ -468,7 +469,7 @@ async function runOnBus(
|
|
|
468
469
|
liveChildren,
|
|
469
470
|
persistentRegistry,
|
|
470
471
|
stop: (signal) => {
|
|
471
|
-
|
|
472
|
+
stoppedBy = signal
|
|
472
473
|
stopRun.abort(signal)
|
|
473
474
|
},
|
|
474
475
|
done: runLeft,
|
|
@@ -903,7 +904,10 @@ async function runOnBus(
|
|
|
903
904
|
|
|
904
905
|
// Optional artifacts. Errors are surfaced to the user but don't
|
|
905
906
|
// change the run's exit code — the run already happened.
|
|
906
|
-
|
|
907
|
+
// Written again after the keep-alive wait: a kept server's crash or a
|
|
908
|
+
// Ctrl-C there is the process's exit, and the first write said ok.
|
|
909
|
+
const summarize = async (runOk: boolean): Promise<void> => {
|
|
910
|
+
if (options.summarize === undefined) return
|
|
907
911
|
try {
|
|
908
912
|
const wrote = await writeRunSummary({
|
|
909
913
|
target: options.summarize,
|
|
@@ -913,7 +917,8 @@ async function runOnBus(
|
|
|
913
917
|
startedAtMs: endedAtMsAtStart,
|
|
914
918
|
endedAtMs,
|
|
915
919
|
totalMs,
|
|
916
|
-
ok,
|
|
920
|
+
ok: runOk,
|
|
921
|
+
...(stoppedBy !== undefined && { exitCode: signalExitCode(stoppedBy) }),
|
|
917
922
|
outcomes: list,
|
|
918
923
|
flaky,
|
|
919
924
|
})
|
|
@@ -923,6 +928,7 @@ async function runOnBus(
|
|
|
923
928
|
log.status(`vx: failed to write summary: ${msg}`)
|
|
924
929
|
}
|
|
925
930
|
}
|
|
931
|
+
await summarize(ok)
|
|
926
932
|
if (options.profile !== undefined) {
|
|
927
933
|
try {
|
|
928
934
|
const wrote = await writeRunProfile({
|
|
@@ -964,7 +970,7 @@ async function runOnBus(
|
|
|
964
970
|
withTelemetry: telemetry !== undefined,
|
|
965
971
|
})
|
|
966
972
|
try {
|
|
967
|
-
if (
|
|
973
|
+
if (stoppedBy === undefined) cache.recordRunBundle(records)
|
|
968
974
|
} catch (err) {
|
|
969
975
|
// History is observability: a full cache disk at the very end must
|
|
970
976
|
// not turn a finished run's verdict into a stack and exit 1 (seen as
|
|
@@ -1105,6 +1111,7 @@ async function runOnBus(
|
|
|
1105
1111
|
)
|
|
1106
1112
|
}
|
|
1107
1113
|
await terminateChildren(() => keepAlive.children)
|
|
1114
|
+
await summarize(ok && first.code === 0)
|
|
1108
1115
|
return { ok: ok && first.code === 0, outcomes: list }
|
|
1109
1116
|
}
|
|
1110
1117
|
|
package/src/util/index.ts
CHANGED
package/src/util/paths.ts
CHANGED
|
@@ -227,3 +227,37 @@ export function asTrees(patterns: readonly string[]): string[] {
|
|
|
227
227
|
}
|
|
228
228
|
return out
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* `pattern` with its first brace group expanded, recursively, while that
|
|
233
|
+
* group holds a `/`; any other pattern as itself, since `Bun.Glob`
|
|
234
|
+
* expands a slash-free brace on its own. An unbalanced or escaped brace
|
|
235
|
+
* is left to the glob.
|
|
236
|
+
*/
|
|
237
|
+
export function slashBraceExpansions(pattern: string): string[] {
|
|
238
|
+
if (pattern.includes('\\')) return [pattern]
|
|
239
|
+
for (let open = pattern.indexOf('{'); open !== -1; open = pattern.indexOf('{', open + 1)) {
|
|
240
|
+
let depth = 0
|
|
241
|
+
const cuts: number[] = []
|
|
242
|
+
let close = -1
|
|
243
|
+
for (let i = open; i < pattern.length && close === -1; i++) {
|
|
244
|
+
const c = pattern[i]
|
|
245
|
+
if (c === '{') depth++
|
|
246
|
+
else if (c === '}' && --depth === 0) close = i
|
|
247
|
+
else if (c === ',' && depth === 1) cuts.push(i)
|
|
248
|
+
}
|
|
249
|
+
if (close === -1) return [pattern]
|
|
250
|
+
const body = pattern.slice(open + 1, close)
|
|
251
|
+
if (cuts.length === 0 || !body.includes('/')) continue
|
|
252
|
+
const head = pattern.slice(0, open)
|
|
253
|
+
const tail = pattern.slice(close + 1)
|
|
254
|
+
const bounds = [open, ...cuts, close]
|
|
255
|
+
const out: string[] = []
|
|
256
|
+
for (let k = 0; k + 1 < bounds.length; k++) {
|
|
257
|
+
const alt = pattern.slice(bounds[k]! + 1, bounds[k + 1])
|
|
258
|
+
out.push(...slashBraceExpansions(head + alt + tail))
|
|
259
|
+
}
|
|
260
|
+
return [...new Set(out)]
|
|
261
|
+
}
|
|
262
|
+
return [pattern]
|
|
263
|
+
}
|
|
@@ -2,7 +2,13 @@ import type { Dirent } from 'node:fs'
|
|
|
2
2
|
import { readdir, stat } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import type { ProjectConfig, WorkspaceConfig } from '../config.js'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
BUN_GLOB_WILDCARDS,
|
|
7
|
+
relPosix,
|
|
8
|
+
slashBraceExpansions,
|
|
9
|
+
UserError,
|
|
10
|
+
normalizeBunGlob,
|
|
11
|
+
} from '../util/index.js'
|
|
6
12
|
import { type LoadReads, readOnce } from './load-reads.js'
|
|
7
13
|
|
|
8
14
|
export interface PackageJson {
|
|
@@ -441,40 +447,6 @@ async function memberDirs(root: string, pattern: string): Promise<string[]> {
|
|
|
441
447
|
return dirs
|
|
442
448
|
}
|
|
443
449
|
|
|
444
|
-
/**
|
|
445
|
-
* `pattern` with its first brace group expanded, recursively, while that
|
|
446
|
-
* group holds a `/`; any other pattern as itself, since `Bun.Glob`
|
|
447
|
-
* expands a slash-free brace on its own. An unbalanced or escaped brace
|
|
448
|
-
* is left to the glob.
|
|
449
|
-
*/
|
|
450
|
-
function slashBraceExpansions(pattern: string): string[] {
|
|
451
|
-
if (pattern.includes('\\')) return [pattern]
|
|
452
|
-
for (let open = pattern.indexOf('{'); open !== -1; open = pattern.indexOf('{', open + 1)) {
|
|
453
|
-
let depth = 0
|
|
454
|
-
const cuts: number[] = []
|
|
455
|
-
let close = -1
|
|
456
|
-
for (let i = open; i < pattern.length && close === -1; i++) {
|
|
457
|
-
const c = pattern[i]
|
|
458
|
-
if (c === '{') depth++
|
|
459
|
-
else if (c === '}' && --depth === 0) close = i
|
|
460
|
-
else if (c === ',' && depth === 1) cuts.push(i)
|
|
461
|
-
}
|
|
462
|
-
if (close === -1) return [pattern]
|
|
463
|
-
const body = pattern.slice(open + 1, close)
|
|
464
|
-
if (cuts.length === 0 || !body.includes('/')) continue
|
|
465
|
-
const head = pattern.slice(0, open)
|
|
466
|
-
const tail = pattern.slice(close + 1)
|
|
467
|
-
const bounds = [open, ...cuts, close]
|
|
468
|
-
const out: string[] = []
|
|
469
|
-
for (let k = 0; k + 1 < bounds.length; k++) {
|
|
470
|
-
const alt = pattern.slice(bounds[k]! + 1, bounds[k + 1])
|
|
471
|
-
out.push(...slashBraceExpansions(head + alt + tail))
|
|
472
|
-
}
|
|
473
|
-
return [...new Set(out)]
|
|
474
|
-
}
|
|
475
|
-
return [pattern]
|
|
476
|
-
}
|
|
477
|
-
|
|
478
450
|
/**
|
|
479
451
|
* The project's config file, by `PROJECT_CONFIG_FILENAMES` precedence, or null.
|
|
480
452
|
*
|