@vzn/vx 0.0.64 → 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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/cache/inputs.ts +37 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.64",
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.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"
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"
@@ -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
- ...OUTPUT_NEVER,
500
- ...boundaryIgnorePatterns(args.projectDir, args.nestedProjectDirs),
501
- ].map(globFor)
502
- const scanned = [...(await scanUnion(asTrees(args.outputs), excludeGlobs, args.projectDir))]
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 boundaryIgnores = boundaryIgnorePatterns(args.projectDir, args.nestedProjectDirs)
895
- const excludeGlobs = [
896
- ...ALWAYS_IGNORE,
897
- ...boundaryIgnores,
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${boundaryIgnores.join('\u0001')}`
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
- function boundaryIgnorePatterns(projectDir: string, nestedDirs: string[]): string[] {
1093
- return nestedDirs.map((d) => {
1094
- const rel = path.relative(projectDir, d).split(path.sep).join('/')
1095
- return `${rel}/**`
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
  }