@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.61",
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.61",
21
- "@vzn/vx-linux-arm64": "0.0.61",
22
- "@vzn/vx-darwin-x64": "0.0.61",
23
- "@vzn/vx-darwin-arm64": "0.0.61"
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"
@@ -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 (const pattern of positive) {
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: args.ok,
104
- exitCode: args.ok ? 0 : 1,
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,
@@ -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 stoppedBySignal = false
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
- stoppedBySignal = true
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
- if (options.summarize !== undefined) {
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 (!stoppedBySignal) cache.recordRunBundle(records)
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
@@ -30,6 +30,7 @@ export {
30
30
  staticPrefix,
31
31
  taskGlob,
32
32
  wholeSubtreePrefixes,
33
+ slashBraceExpansions,
33
34
  } from './paths.js'
34
35
  export { killGraceMs, settleWithin, teardownTimeoutMs } from './settle.js'
35
36
  export { formatBytes, parseDuration, parseSize } from './size.js'
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 { BUN_GLOB_WILDCARDS, relPosix, UserError, normalizeBunGlob } from '../util/index.js'
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
  *