@vzn/vx 0.0.63 → 0.0.65
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 +37 -20
- package/src/graph/scheduler.ts +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.65",
|
|
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.65",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.65",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.65",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.65"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/inputs.ts
CHANGED
|
@@ -495,11 +495,15 @@ export async function resolveOutputs(args: {
|
|
|
495
495
|
nestedProjectDirs: string[]
|
|
496
496
|
}): Promise<string[]> {
|
|
497
497
|
if (args.outputs.length === 0) return []
|
|
498
|
-
const excludeGlobs =
|
|
499
|
-
|
|
500
|
-
...
|
|
501
|
-
|
|
502
|
-
|
|
498
|
+
const excludeGlobs = OUTPUT_NEVER.map(globFor)
|
|
499
|
+
const scanned = [
|
|
500
|
+
...(await scanUnion(
|
|
501
|
+
asTrees(args.outputs),
|
|
502
|
+
excludeGlobs,
|
|
503
|
+
args.projectDir,
|
|
504
|
+
inNestedProject(args.projectDir, args.nestedProjectDirs),
|
|
505
|
+
)),
|
|
506
|
+
]
|
|
503
507
|
// Containment, enforced HERE and not only at the loader. `cleanOutputs`
|
|
504
508
|
// DELETES whatever this returns, and `Bun.Glob.scan` happily walks `..` out
|
|
505
509
|
// of its cwd — so the loader's `..`/absolute rejection alone was a single
|
|
@@ -891,13 +895,10 @@ async function resolveFiles(args: ResolveFilesArgs): Promise<string[]> {
|
|
|
891
895
|
|
|
892
896
|
if (positive.length === 0) return []
|
|
893
897
|
|
|
894
|
-
const
|
|
895
|
-
const excludeGlobs = [
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
...asTrees(args.ownOutputs),
|
|
899
|
-
...asTrees(negative),
|
|
900
|
-
].map(globFor)
|
|
898
|
+
const nested = inNestedProject(args.projectDir, args.nestedProjectDirs)
|
|
899
|
+
const excludeGlobs = [...ALWAYS_IGNORE, ...asTrees(args.ownOutputs), ...asTrees(negative)].map(
|
|
900
|
+
globFor,
|
|
901
|
+
)
|
|
901
902
|
|
|
902
903
|
// Defer to git for the file set (Turbo / Nx parity). Nested .gitignore
|
|
903
904
|
// files, .git/info/exclude, and global excludes all participate
|
|
@@ -910,7 +911,7 @@ async function resolveFiles(args: ResolveFilesArgs): Promise<string[]> {
|
|
|
910
911
|
const positiveGlobs = asTrees(positive).map(globFor)
|
|
911
912
|
// Everything below the snapshot that decides the result: the project, what
|
|
912
913
|
// it declares, what it excludes as its own outputs, and the boundaries.
|
|
913
|
-
const memoKey = `${args.projectDir}\0${positive.join('\u0001')}\0${negative.join('\u0001')}\0${args.ownOutputs.join('\u0001')}\0${
|
|
914
|
+
const memoKey = `${args.projectDir}\0${positive.join('\u0001')}\0${negative.join('\u0001')}\0${args.ownOutputs.join('\u0001')}\0${args.nestedProjectDirs.join('\u0001')}`
|
|
914
915
|
let gitFiles = args.gitFilesCache?.snapshotFor(args.projectDir, positiveGlobs)
|
|
915
916
|
let undecodable = args.gitFilesCache?.undecodableNames
|
|
916
917
|
if (gitFiles !== undefined) {
|
|
@@ -962,7 +963,7 @@ async function resolveFiles(args: ResolveFilesArgs): Promise<string[]> {
|
|
|
962
963
|
}
|
|
963
964
|
}
|
|
964
965
|
if (!matched) continue
|
|
965
|
-
if (excludeGlobs.some((g) => g.match(rel))) continue
|
|
966
|
+
if (nested(rel) || excludeGlobs.some((g) => g.match(rel))) continue
|
|
966
967
|
candidates.push(path.resolve(args.projectDir, rel))
|
|
967
968
|
}
|
|
968
969
|
if (unmatchedLiterals.size > 0) {
|
|
@@ -1061,6 +1062,7 @@ async function scanUnion(
|
|
|
1061
1062
|
positive: readonly string[],
|
|
1062
1063
|
excludeGlobs: readonly Bun.Glob[],
|
|
1063
1064
|
cwd: string,
|
|
1065
|
+
nested: (rel: string) => boolean = () => false,
|
|
1064
1066
|
): Promise<Set<string>> {
|
|
1065
1067
|
const matches = new Set<string>()
|
|
1066
1068
|
// `Bun.Glob`'s scan finds nothing for a brace whose alternatives hold a
|
|
@@ -1069,7 +1071,7 @@ async function scanUnion(
|
|
|
1069
1071
|
for (const pattern of positive.flatMap(slashBraceExpansions)) {
|
|
1070
1072
|
const glob = globFor(pattern)
|
|
1071
1073
|
for (const rel of glob.scanSync({ cwd, onlyFiles: false, followSymlinks: false, dot: true })) {
|
|
1072
|
-
if (excludeGlobs.some((g) => g.match(rel))) continue
|
|
1074
|
+
if (nested(rel) || excludeGlobs.some((g) => g.match(rel))) continue
|
|
1073
1075
|
const abs = path.resolve(cwd, rel)
|
|
1074
1076
|
const st = lstatSync(abs, { throwIfNoEntry: false })
|
|
1075
1077
|
if (st !== undefined && (st.isFile() || st.isSymbolicLink())) matches.add(abs)
|
|
@@ -1089,9 +1091,24 @@ function globFor(pattern: string): Bun.Glob {
|
|
|
1089
1091
|
return g
|
|
1090
1092
|
}
|
|
1091
1093
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1094
|
+
/**
|
|
1095
|
+
* Whether a project-relative path lies inside a nested project: its
|
|
1096
|
+
* ancestor directories looked up in a set, O(depth). One `<nested>/**` glob
|
|
1097
|
+
* per nested project matched every file against every one, O(files ×
|
|
1098
|
+
* nested): 320 ms of astro's 834 ms warm no-op, 4,003 files against 330
|
|
1099
|
+
* fixture projects (I's log, A-11). A glob also read `*`, `?` or `{` in a
|
|
1100
|
+
* nested directory's NAME as syntax, so a project at `pkg*` took its
|
|
1101
|
+
* sibling `pkg-b`'s files out of the key; a name is a name.
|
|
1102
|
+
*/
|
|
1103
|
+
function inNestedProject(projectDir: string, nestedDirs: string[]): (rel: string) => boolean {
|
|
1104
|
+
if (nestedDirs.length === 0) return () => false
|
|
1105
|
+
const dirs = new Set(
|
|
1106
|
+
nestedDirs.map((d) => path.relative(projectDir, d).split(path.sep).join('/')),
|
|
1107
|
+
)
|
|
1108
|
+
return (rel) => {
|
|
1109
|
+
for (let i = rel.indexOf('/'); i !== -1; i = rel.indexOf('/', i + 1)) {
|
|
1110
|
+
if (dirs.has(rel.slice(0, i))) return true
|
|
1111
|
+
}
|
|
1112
|
+
return false
|
|
1113
|
+
}
|
|
1097
1114
|
}
|
package/src/graph/scheduler.ts
CHANGED
|
@@ -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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|