@vzn/vx 0.0.62 → 0.0.64

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.62",
3
+ "version": "0.0.64",
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.62",
21
- "@vzn/vx-linux-arm64": "0.0.62",
22
- "@vzn/vx-darwin-x64": "0.0.62",
23
- "@vzn/vx-darwin-arm64": "0.0.62"
20
+ "@vzn/vx-linux-x64": "0.0.64",
21
+ "@vzn/vx-linux-arm64": "0.0.64",
22
+ "@vzn/vx-darwin-x64": "0.0.64",
23
+ "@vzn/vx-darwin-arm64": "0.0.64"
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
@@ -399,6 +399,9 @@ export async function runGraph(options: ScheduleOptions): Promise<Map<string, Ta
399
399
  const restoreRoom = (): boolean =>
400
400
  serial ? active + activeRestore < 1 : activeRestore < restoreConcurrency
401
401
  let resolved = false
402
+ // A refusal's text → the first task that said it. A corrupt cache index
403
+ // reaches every lookup as one UserError; a repeat names the first.
404
+ const refusedBy = new Map<string, string>()
402
405
 
403
406
  // Admission policy over the count limit (a plugin's `admit`). Inactive
404
407
  // → the tick loop short-circuits before any of this and behaves
@@ -717,10 +720,13 @@ export async function runGraph(options: ScheduleOptions): Promise<Map<string, Ta
717
720
  // UserError is a config/input failure (e.g. a failed
718
721
  // `cache.inputs.runtime` command), not a vx bug — report it
719
722
  // plainly, never as an "internal error".
720
- if (isUserError(err)) {
721
- process.stderr.write(`[vx] ${id}: ${message}\n`)
722
- } else if (isFsRefusal(err)) {
723
- process.stderr.write(`[vx] ${id}: ${message} — ${fsRefusalHint(err)}\n`)
723
+ if (isUserError(err) || isFsRefusal(err)) {
724
+ const text = isUserError(err) ? message : `${message} — ${fsRefusalHint(err)}`
725
+ const first = refusedBy.get(text)
726
+ if (first === undefined) refusedBy.set(text, id)
727
+ process.stderr.write(
728
+ `[vx] ${id}: ${first === undefined ? text : `as ${first} above`}\n`,
729
+ )
724
730
  } else {
725
731
  const named = err instanceof Error && err.name !== 'Error' ? `${err.name}: ` : ''
726
732
  // A wrapped error's cause is the fact the reader needs (a
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
  *