app-builder-lib 26.3.4 → 26.3.6

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.
@@ -1,24 +1,27 @@
1
- import * as fs from "fs";
2
1
  import { PackageJson } from "./types";
3
- /**
4
- * Unified cache for all file system and module resolution operations
5
- */
6
- export type ModuleCache = {
2
+ import * as fs from "fs-extra";
3
+ type PackageJsonCache = Record<string, Promise<PackageJson>>;
4
+ type RealPathCache = Record<string, Promise<string>>;
5
+ type ExistsCache = Record<string, Promise<boolean>>;
6
+ type LstatCache = Record<string, Promise<fs.Stats>>;
7
+ type RequireResolveCache = Record<string, Promise<string | null>>;
8
+ export declare class ModuleCache {
7
9
  /** Cache for package.json contents (readJson/require) */
8
- packageJson: Map<string, PackageJson>;
10
+ readonly packageJson: PackageJsonCache;
9
11
  /** Cache for resolved real paths (realpath) */
10
- realPath: Map<string, string>;
12
+ readonly realPath: RealPathCache;
11
13
  /** Cache for file/directory existence checks */
12
- exists: Map<string, boolean>;
14
+ readonly exists: ExistsCache;
13
15
  /** Cache for lstat results */
14
- lstat: Map<string, fs.Stats>;
16
+ readonly lstat: LstatCache;
15
17
  /** Cache for require.resolve results (key: "packageName::fromDir") */
16
- requireResolve: Map<string, {
17
- entry: string;
18
- packageDir: string;
19
- } | null>;
20
- };
21
- /**
22
- * Creates a new empty ModuleCache instance
23
- */
24
- export declare function createModuleCache(): ModuleCache;
18
+ readonly requireResolve: RequireResolveCache;
19
+ private readonly packageJsonMap;
20
+ private readonly realPathMap;
21
+ private readonly existsMap;
22
+ private readonly lstatMap;
23
+ private readonly requireResolveMap;
24
+ constructor();
25
+ private createAsyncProxy;
26
+ }
27
+ export {};
@@ -1,16 +1,57 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createModuleCache = createModuleCache;
4
- /**
5
- * Creates a new empty ModuleCache instance
6
- */
7
- function createModuleCache() {
8
- return {
9
- packageJson: new Map(),
10
- realPath: new Map(),
11
- exists: new Map(),
12
- lstat: new Map(),
13
- requireResolve: new Map(),
14
- };
3
+ exports.ModuleCache = void 0;
4
+ const builder_util_1 = require("builder-util");
5
+ const fs = require("fs-extra");
6
+ const path_1 = require("path");
7
+ class ModuleCache {
8
+ constructor() {
9
+ this.packageJsonMap = new Map();
10
+ this.realPathMap = new Map();
11
+ this.existsMap = new Map();
12
+ this.lstatMap = new Map();
13
+ this.requireResolveMap = new Map();
14
+ this.packageJson = this.createAsyncProxy(this.packageJsonMap, (path) => fs.readJson(path));
15
+ this.exists = this.createAsyncProxy(this.existsMap, (path) => (0, builder_util_1.exists)(path));
16
+ this.lstat = this.createAsyncProxy(this.lstatMap, (path) => fs.lstat(path));
17
+ this.requireResolve = this.createAsyncProxy(this.requireResolveMap, (path) => require.resolve(path));
18
+ this.realPath = this.createAsyncProxy(this.realPathMap, async (path) => {
19
+ const p = (0, path_1.resolve)(path);
20
+ try {
21
+ const stats = await this.lstat[p];
22
+ if (stats.isSymbolicLink()) {
23
+ return await fs.realpath(p);
24
+ }
25
+ return p;
26
+ }
27
+ catch (error) {
28
+ builder_util_1.log.debug({ filePath: p, message: error.message || error.stack }, "error resolving path");
29
+ }
30
+ return p;
31
+ });
32
+ }
33
+ // this allows dot-notation access while still supporting async retrieval
34
+ // e.g., cache.packageJson[somePath] returns Promise<PackageJson>
35
+ createAsyncProxy(map, compute) {
36
+ return new Proxy({}, {
37
+ async get(_, key) {
38
+ if (map.has(key)) {
39
+ return Promise.resolve(map.get(key));
40
+ }
41
+ return await Promise.resolve(compute(key)).then(value => {
42
+ map.set(key, value);
43
+ return value;
44
+ });
45
+ },
46
+ set(_, key, value) {
47
+ map.set(key, value);
48
+ return true;
49
+ },
50
+ has(_, key) {
51
+ return map.has(key);
52
+ },
53
+ });
54
+ }
15
55
  }
56
+ exports.ModuleCache = ModuleCache;
16
57
  //# sourceMappingURL=moduleCache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"moduleCache.js","sourceRoot":"","sources":["../../src/node-module-collector/moduleCache.ts"],"names":[],"mappings":";;AAsBA,8CAQC;AAXD;;GAEG;AACH,SAAgB,iBAAiB;IAC/B,OAAO;QACL,WAAW,EAAE,IAAI,GAAG,EAAE;QACtB,QAAQ,EAAE,IAAI,GAAG,EAAE;QACnB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,cAAc,EAAE,IAAI,GAAG,EAAE;KAC1B,CAAA;AACH,CAAC","sourcesContent":["import * as fs from \"fs\"\nimport { PackageJson } from \"./types\"\n\n/**\n * Unified cache for all file system and module resolution operations\n */\nexport type ModuleCache = {\n /** Cache for package.json contents (readJson/require) */\n packageJson: Map<string, PackageJson>\n /** Cache for resolved real paths (realpath) */\n realPath: Map<string, string>\n /** Cache for file/directory existence checks */\n exists: Map<string, boolean>\n /** Cache for lstat results */\n lstat: Map<string, fs.Stats>\n /** Cache for require.resolve results (key: \"packageName::fromDir\") */\n requireResolve: Map<string, { entry: string; packageDir: string } | null>\n}\n\n/**\n * Creates a new empty ModuleCache instance\n */\nexport function createModuleCache(): ModuleCache {\n return {\n packageJson: new Map(),\n realPath: new Map(),\n exists: new Map(),\n lstat: new Map(),\n requireResolve: new Map(),\n }\n}\n"]}
1
+ {"version":3,"file":"moduleCache.js","sourceRoot":"","sources":["../../src/node-module-collector/moduleCache.ts"],"names":[],"mappings":"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","sourcesContent":["import { exists, log } from \"builder-util\"\nimport { PackageJson } from \"./types\"\nimport * as fs from \"fs-extra\"\nimport { resolve } from \"path\"\n\n// Type aliases for clarity\ntype PackageJsonCache = Record<string, Promise<PackageJson>>\ntype RealPathCache = Record<string, Promise<string>>\ntype ExistsCache = Record<string, Promise<boolean>>\ntype LstatCache = Record<string, Promise<fs.Stats>>\ntype RequireResolveCache = Record<string, Promise<string | null>>\n\nexport class ModuleCache {\n /** Cache for package.json contents (readJson/require) */\n readonly packageJson: PackageJsonCache\n /** Cache for resolved real paths (realpath) */\n readonly realPath: RealPathCache\n /** Cache for file/directory existence checks */\n readonly exists: ExistsCache\n /** Cache for lstat results */\n readonly lstat: LstatCache\n /** Cache for require.resolve results (key: \"packageName::fromDir\") */\n readonly requireResolve: RequireResolveCache\n\n private readonly packageJsonMap: Map<string, PackageJson> = new Map()\n private readonly realPathMap: Map<string, string> = new Map()\n private readonly existsMap: Map<string, boolean> = new Map()\n private readonly lstatMap: Map<string, fs.Stats> = new Map()\n private readonly requireResolveMap: Map<string, string | null> = new Map()\n\n constructor() {\n this.packageJson = this.createAsyncProxy(this.packageJsonMap, (path: string) => fs.readJson(path))\n this.exists = this.createAsyncProxy(this.existsMap, (path: string) => exists(path))\n this.lstat = this.createAsyncProxy(this.lstatMap, (path: string) => fs.lstat(path))\n this.requireResolve = this.createAsyncProxy(this.requireResolveMap, (path: string) => require.resolve(path))\n this.realPath = this.createAsyncProxy(this.realPathMap, async (path: string) => {\n const p = resolve(path)\n try {\n const stats = await this.lstat[p]\n if (stats.isSymbolicLink()) {\n return await fs.realpath(p)\n }\n return p\n } catch (error: any) {\n log.debug({ filePath: p, message: error.message || error.stack }, \"error resolving path\")\n }\n return p\n })\n }\n\n // this allows dot-notation access while still supporting async retrieval\n // e.g., cache.packageJson[somePath] returns Promise<PackageJson>\n private createAsyncProxy<T>(map: Map<string, T>, compute: (key: string) => T | Promise<T>): Record<string, Promise<T>> {\n return new Proxy({} as Record<string, Promise<T>>, {\n async get(_, key: string) {\n if (map.has(key)) {\n return Promise.resolve(map.get(key)!)\n }\n return await Promise.resolve(compute(key)).then(value => {\n map.set(key, value)\n return value\n })\n },\n set(_, key: string, value: T) {\n map.set(key, value)\n return true\n },\n has(_, key: string) {\n return map.has(key)\n },\n })\n }\n}\n"]}
@@ -1,18 +1,21 @@
1
1
  import { TmpDir } from "builder-util";
2
2
  import { CancellationToken } from "builder-util-runtime";
3
- import * as fs from "fs-extra";
4
3
  import { Lazy } from "lazy-val";
4
+ import { ModuleCache } from "./moduleCache";
5
5
  import { PM } from "./packageManager";
6
- import type { Dependency, DependencyGraph, NodeModuleInfo, PackageJson } from "./types";
6
+ import type { Dependency, DependencyGraph, NodeModuleInfo } from "./types";
7
+ type Result = {
8
+ packageDir: string;
9
+ version: string;
10
+ } | null;
7
11
  export declare abstract class NodeModulesCollector<ProdDepType extends Dependency<ProdDepType, OptionalDepType>, OptionalDepType> {
8
12
  protected readonly rootDir: string;
9
13
  private readonly tempDirManager;
10
14
  private readonly nodeModules;
11
15
  protected readonly allDependencies: Map<string, ProdDepType>;
12
16
  protected readonly productionGraph: DependencyGraph;
13
- private readonly cache;
14
- protected readonly isHoisted: Lazy<boolean>;
15
- protected appPkgJson: Lazy<PackageJson>;
17
+ protected readonly cache: ModuleCache;
18
+ protected isHoisted: Lazy<boolean>;
16
19
  constructor(rootDir: string, tempDirManager: TmpDir);
17
20
  getNodeModules({ cancellationToken, packageName }: {
18
21
  cancellationToken: CancellationToken;
@@ -27,23 +30,6 @@ export declare abstract class NodeModulesCollector<ProdDepType extends Dependenc
27
30
  protected abstract extractProductionDependencyGraph(tree: Dependency<ProdDepType, OptionalDepType>, dependencyId: string): Promise<void>;
28
31
  protected abstract collectAllDependencies(tree: Dependency<ProdDepType, OptionalDepType>, appPackageName: string): Promise<void>;
29
32
  protected getDependenciesTree(pm: PM): Promise<ProdDepType>;
30
- protected existsMemoized(filePath: string): Promise<boolean>;
31
- protected readJsonMemoized(filePath: string): Promise<PackageJson>;
32
- protected lstatMemoized(filePath: string): Promise<fs.Stats>;
33
- protected realpathMemoized(filePath: string): Promise<string>;
34
- protected requireMemoized(pkgPath: string): PackageJson;
35
- protected existsSyncMemoized(filePath: string): boolean;
36
- protected resolvePath(filePath: string): Promise<string>;
37
- /**
38
- * Resolve a package directory purely from the filesystem.
39
- * Does NOT attempt to load the module or resolve an "exports" entrypoint.
40
- * Good for Yarn 4 because a package may not be resolvable as a module,
41
- * but still exists on disk.
42
- */
43
- protected resolvePackage(packageName: string, fromDir: string): Promise<{
44
- entry: string;
45
- packageDir: string;
46
- } | null>;
47
33
  protected cacheKey(pkg: ProdDepType): string;
48
34
  protected packageVersionString(pkg: ProdDepType): string;
49
35
  /**
@@ -61,4 +47,17 @@ export declare abstract class NodeModulesCollector<ProdDepType extends Dependenc
61
47
  stderr: string | undefined;
62
48
  }>;
63
49
  streamCollectorCommandToFile(command: string, args: string[], cwd: string, tempOutputFile: string): Promise<void>;
50
+ protected locatePackageVersion(parentDir: string, pkgName: string, requiredRange?: string): Promise<Result | null>;
51
+ protected readPackageVersion(pkgJsonPath: string): Promise<string | null>;
52
+ protected semverSatisfies(found: string, range?: string): boolean;
53
+ /**
54
+ * Upward search (hoisted)
55
+ */
56
+ private upwardSearch;
57
+ /**
58
+ * Breadth-first downward search from parentDir/node_modules
59
+ * Looks for node_modules/\*\/node_modules/pkgName (and deeper)
60
+ */
61
+ private downwardSearch;
64
62
  }
63
+ export {};
@@ -7,6 +7,7 @@ const fs = require("fs-extra");
7
7
  const fs_extra_1 = require("fs-extra");
8
8
  const lazy_val_1 = require("lazy-val");
9
9
  const path = require("path");
10
+ const semver = require("semver");
10
11
  const hoist_1 = require("./hoist");
11
12
  const moduleCache_1 = require("./moduleCache");
12
13
  const packageManager_1 = require("./packageManager");
@@ -17,14 +18,12 @@ class NodeModulesCollector {
17
18
  this.nodeModules = [];
18
19
  this.allDependencies = new Map();
19
20
  this.productionGraph = {};
20
- // Unified cache for all file system and module operations
21
- this.cache = (0, moduleCache_1.createModuleCache)();
21
+ this.cache = new moduleCache_1.ModuleCache();
22
22
  this.isHoisted = new lazy_val_1.Lazy(async () => {
23
- var _a;
24
23
  const { manager } = this.installOptions;
25
24
  const command = (0, packageManager_1.getPackageManagerCommand)(manager);
26
- const config = (_a = (await this.asyncExec(command, ["config", "list"])).stdout) === null || _a === void 0 ? void 0 : _a.trim();
27
- if ((0, builder_util_1.isEmptyOrSpaces)(config)) {
25
+ const config = (await this.asyncExec(command, ["config", "list"])).stdout;
26
+ if (config == null) {
28
27
  builder_util_1.log.debug({ manager }, "unable to determine if node_modules are hoisted: no config output. falling back to hoisted mode");
29
28
  return false;
30
29
  }
@@ -35,10 +34,6 @@ class NodeModulesCollector {
35
34
  }
36
35
  return false;
37
36
  });
38
- this.appPkgJson = new lazy_val_1.Lazy(async () => {
39
- const appPkgPath = path.join(this.rootDir, "package.json");
40
- return this.readJsonMemoized(appPkgPath);
41
- });
42
37
  }
43
38
  async getNodeModules({ cancellationToken, packageName }) {
44
39
  const tree = await this.getDependenciesTree(this.installOptions.manager);
@@ -76,7 +71,7 @@ class NodeModulesCollector {
76
71
  shouldRetry: async (error) => {
77
72
  var _a;
78
73
  const logFields = { error: error.message, tempOutputFile, cwd: this.rootDir };
79
- if (!(await this.existsMemoized(tempOutputFile))) {
74
+ if (!(await this.cache.exists[tempOutputFile])) {
80
75
  builder_util_1.log.debug(logFields, "dependency tree output file missing, retrying");
81
76
  return true;
82
77
  }
@@ -95,109 +90,6 @@ class NodeModulesCollector {
95
90
  },
96
91
  });
97
92
  }
98
- async existsMemoized(filePath) {
99
- if (!this.cache.exists.has(filePath)) {
100
- this.cache.exists.set(filePath, await (0, builder_util_1.exists)(filePath));
101
- }
102
- return this.cache.exists.get(filePath);
103
- }
104
- async readJsonMemoized(filePath) {
105
- if (!this.cache.packageJson.has(filePath)) {
106
- this.cache.packageJson.set(filePath, await (0, fs_extra_1.readJson)(filePath));
107
- }
108
- return this.cache.packageJson.get(filePath);
109
- }
110
- async lstatMemoized(filePath) {
111
- if (!this.cache.lstat.has(filePath)) {
112
- this.cache.lstat.set(filePath, await fs.lstat(filePath));
113
- }
114
- return this.cache.lstat.get(filePath);
115
- }
116
- async realpathMemoized(filePath) {
117
- if (!this.cache.realPath.has(filePath)) {
118
- this.cache.realPath.set(filePath, await fs.realpath(filePath));
119
- }
120
- return this.cache.realPath.get(filePath);
121
- }
122
- requireMemoized(pkgPath) {
123
- if (!this.cache.packageJson.has(pkgPath)) {
124
- this.cache.packageJson.set(pkgPath, require(pkgPath));
125
- }
126
- return this.cache.packageJson.get(pkgPath);
127
- }
128
- existsSyncMemoized(filePath) {
129
- if (!this.cache.exists.has(filePath)) {
130
- this.cache.exists.set(filePath, fs.existsSync(filePath));
131
- }
132
- return this.cache.exists.get(filePath);
133
- }
134
- async resolvePath(filePath) {
135
- // Check if we've already resolved this path
136
- if (this.cache.realPath.has(filePath)) {
137
- return this.cache.realPath.get(filePath);
138
- }
139
- try {
140
- const stats = await this.lstatMemoized(filePath);
141
- if (stats.isSymbolicLink()) {
142
- const resolved = await this.realpathMemoized(filePath);
143
- this.cache.realPath.set(filePath, resolved);
144
- return resolved;
145
- }
146
- else {
147
- this.cache.realPath.set(filePath, filePath);
148
- return filePath;
149
- }
150
- }
151
- catch (error) {
152
- builder_util_1.log.debug({ filePath, message: error.message || error.stack }, "error resolving path");
153
- this.cache.realPath.set(filePath, filePath);
154
- return filePath;
155
- }
156
- }
157
- /**
158
- * Resolve a package directory purely from the filesystem.
159
- * Does NOT attempt to load the module or resolve an "exports" entrypoint.
160
- * Good for Yarn 4 because a package may not be resolvable as a module,
161
- * but still exists on disk.
162
- */
163
- async resolvePackage(packageName, fromDir) {
164
- const cacheKey = `${packageName}::${fromDir}`;
165
- if (this.cache.requireResolve.has(cacheKey)) {
166
- return this.cache.requireResolve.get(cacheKey);
167
- }
168
- // 1. NESTED under fromDir/node_modules/<name>
169
- let candidate = path.join(fromDir, "node_modules", packageName);
170
- let pkgJson = path.join(candidate, "package.json");
171
- if (await this.existsMemoized(pkgJson)) {
172
- this.cache.requireResolve.set(cacheKey, { entry: pkgJson, packageDir: candidate });
173
- return { entry: pkgJson, packageDir: candidate };
174
- }
175
- // 2. HOISTED under rootDir/node_modules/<name>
176
- candidate = path.join(this.rootDir, "node_modules", packageName);
177
- pkgJson = path.join(candidate, "package.json");
178
- if (await this.existsMemoized(pkgJson)) {
179
- this.cache.requireResolve.set(cacheKey, { entry: pkgJson, packageDir: candidate });
180
- return { entry: pkgJson, packageDir: candidate };
181
- }
182
- // 3. FALLBACK: try parent directories BFS (classic Node-style search)
183
- let current = fromDir;
184
- while (true) {
185
- const nm = path.join(current, "node_modules", packageName);
186
- const pkg = path.join(nm, "package.json");
187
- if (await this.existsMemoized(pkg)) {
188
- this.cache.requireResolve.set(cacheKey, { entry: pkg, packageDir: nm });
189
- return { entry: pkg, packageDir: nm };
190
- }
191
- const parent = path.dirname(current);
192
- if (parent === current) {
193
- break;
194
- }
195
- current = parent;
196
- }
197
- // 4. LAST RESORT: DO NOT throw — just return null
198
- this.cache.requireResolve.set(cacheKey, null);
199
- return null;
200
- }
201
93
  cacheKey(pkg) {
202
94
  const rel = path.relative(this.rootDir, pkg.path);
203
95
  return `${pkg.name}::${pkg.version}::${rel !== null && rel !== void 0 ? rel : "."}`;
@@ -225,7 +117,7 @@ class NodeModulesCollector {
225
117
  }
226
118
  if ((_a = tree.dependencies) === null || _a === void 0 ? void 0 : _a[packageName]) {
227
119
  const { name, path, dependencies } = tree.dependencies[packageName];
228
- builder_util_1.log.debug({ name, path, dependencies: JSON.stringify(dependencies) }, "pruning root app/self reference from workspace tree, merging dependencies uptree");
120
+ builder_util_1.log.debug({ name, path, dependencies: JSON.stringify(dependencies) }, "pruning root app/self reference from workspace tree");
229
121
  for (const [name, pkg] of Object.entries(dependencies !== null && dependencies !== void 0 ? dependencies : {})) {
230
122
  tree.dependencies[name] = pkg;
231
123
  this.allDependencies.set(this.packageVersionString(pkg), pkg);
@@ -263,19 +155,19 @@ class NodeModulesCollector {
263
155
  const reference = [...d.references][0];
264
156
  const p = (_a = this.allDependencies.get(`${d.name}@${reference}`)) === null || _a === void 0 ? void 0 : _a.path;
265
157
  if (p === undefined) {
266
- builder_util_1.log.debug({ name: d.name, reference }, "cannot find path for dependency");
158
+ builder_util_1.log.warn({ name: d.name, reference }, "cannot find path for dependency");
267
159
  continue;
268
160
  }
269
161
  // fix npm list issue
270
162
  // https://github.com/npm/cli/issues/8535
271
- if (!(await (0, builder_util_1.exists)(p))) {
163
+ if (!(await this.cache.exists[p])) {
272
164
  builder_util_1.log.debug({ name: d.name, reference, p }, "dependency path does not exist");
273
165
  continue;
274
166
  }
275
167
  const node = {
276
168
  name: d.name,
277
169
  version: reference,
278
- dir: await this.resolvePath(p),
170
+ dir: await this.cache.realPath[p],
279
171
  };
280
172
  result.push(node);
281
173
  if (d.dependencies.size > 0) {
@@ -314,7 +206,6 @@ class NodeModulesCollector {
314
206
  command = "cmd.exe";
315
207
  args = ["/c", tempBatFile, ...args];
316
208
  }
317
- builder_util_1.log.debug({ command, args, cwd, tempOutputFile }, "spawning node module collector process");
318
209
  await new Promise((resolve, reject) => {
319
210
  const outStream = (0, fs_extra_1.createWriteStream)(tempOutputFile);
320
211
  const child = childProcess.spawn(command, args, {
@@ -344,6 +235,175 @@ class NodeModulesCollector {
344
235
  });
345
236
  });
346
237
  }
238
+ async locatePackageVersion(parentDir, pkgName, requiredRange) {
239
+ // 1) check direct parent node_modules/pkgName first
240
+ const direct = path.join(path.resolve(parentDir), "node_modules", pkgName, "package.json");
241
+ if (await this.cache.exists[direct]) {
242
+ const ver = await this.readPackageVersion(direct);
243
+ if (ver && this.semverSatisfies(ver, requiredRange)) {
244
+ return { packageDir: path.dirname(direct), version: ver };
245
+ }
246
+ }
247
+ // 2) upward hoisted search, then 3) downward non-hoisted search
248
+ return (await this.upwardSearch(parentDir, pkgName, requiredRange)) || (await this.downwardSearch(parentDir, pkgName, requiredRange)) || null;
249
+ }
250
+ async readPackageVersion(pkgJsonPath) {
251
+ return await this.cache.packageJson[pkgJsonPath].then(pkg => pkg.version).catch(() => null);
252
+ }
253
+ semverSatisfies(found, range) {
254
+ if (!range || range === "*" || range === "") {
255
+ return true;
256
+ }
257
+ if (range === found) {
258
+ return true;
259
+ }
260
+ if (semver.validRange(range) == null) {
261
+ // ignore, we can't verify non-semver ranges
262
+ // e.g. git urls, file:, patch:, etc. Example:
263
+ // "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch"
264
+ builder_util_1.log.debug({ found, range }, "unable to validate semver version range, assuming match");
265
+ return true;
266
+ }
267
+ try {
268
+ return semver.satisfies(found, range);
269
+ }
270
+ catch {
271
+ // fallback: simple equality or basic prefix handling (^, ~)
272
+ if (range.startsWith("^") || range.startsWith("~")) {
273
+ const r = range.slice(1);
274
+ return r === found;
275
+ }
276
+ // if range is like "8.x" or "8.*" match major
277
+ const m = range.match(/^(\d+)[.(*|x)]*/);
278
+ const fm = found.match(/^(\d+)\./);
279
+ if (m && fm) {
280
+ return m[1] === fm[1];
281
+ }
282
+ return false;
283
+ }
284
+ }
285
+ /**
286
+ * Upward search (hoisted)
287
+ */
288
+ async upwardSearch(parentDir, pkgName, requiredRange) {
289
+ let current = path.resolve(parentDir);
290
+ const root = path.parse(current).root;
291
+ while (true) {
292
+ const candidate = path.join(current, "node_modules", pkgName, "package.json");
293
+ if (await this.cache.exists[candidate]) {
294
+ const ver = await this.readPackageVersion(candidate);
295
+ if (ver && this.semverSatisfies(ver, requiredRange)) {
296
+ return { packageDir: path.dirname(candidate), version: ver };
297
+ }
298
+ // otherwise keep searching upward (we may find a different hoisted version)
299
+ }
300
+ if (current === root) {
301
+ break;
302
+ }
303
+ const parent = path.dirname(current);
304
+ if (parent === current) {
305
+ break;
306
+ }
307
+ current = parent;
308
+ }
309
+ return null;
310
+ }
311
+ /**
312
+ * Breadth-first downward search from parentDir/node_modules
313
+ * Looks for node_modules/\*\/node_modules/pkgName (and deeper)
314
+ */
315
+ async downwardSearch(parentDir, pkgName, requiredRange, maxExplored = 2000, maxDepth = 6) {
316
+ const start = path.join(path.resolve(parentDir), "node_modules");
317
+ if (!(await this.cache.exists[start]) || !(await this.cache.lstat[start]).isDirectory()) {
318
+ return null;
319
+ }
320
+ const visited = new Set();
321
+ const queue = [{ dir: start, depth: 0 }];
322
+ let explored = 0;
323
+ while (queue.length > 0) {
324
+ const { dir, depth } = queue.shift();
325
+ if (explored++ > maxExplored) {
326
+ break;
327
+ }
328
+ if (depth > maxDepth) {
329
+ continue;
330
+ }
331
+ let entries;
332
+ try {
333
+ entries = await fs.readdir(dir);
334
+ }
335
+ catch (e) {
336
+ continue;
337
+ }
338
+ for (const entry of entries) {
339
+ if (entry.startsWith(".")) {
340
+ continue;
341
+ }
342
+ const entryPath = path.join(dir, entry);
343
+ // handle scoped packages @scope/name
344
+ if (entry.startsWith("@")) {
345
+ // queue the scope directory itself to explore its children
346
+ if ((await this.cache.exists[entryPath]) && (await this.cache.lstat[entryPath]).isDirectory()) {
347
+ const scopeEntries = await fs.readdir(entryPath);
348
+ for (const sc of scopeEntries) {
349
+ const scPath = path.join(entryPath, sc);
350
+ // check scPath/node_modules/pkgName
351
+ const candidatePkgJson = path.join(scPath, "node_modules", pkgName, "package.json");
352
+ if (await this.cache.exists[candidatePkgJson]) {
353
+ const ver = await this.readPackageVersion(candidatePkgJson);
354
+ if (ver && this.semverSatisfies(ver, requiredRange)) {
355
+ return { packageDir: path.dirname(candidatePkgJson), version: ver };
356
+ }
357
+ }
358
+ // enqueue scPath/node_modules to explore further
359
+ const scNodeModules = path.join(scPath, "node_modules");
360
+ if ((await this.cache.exists[scNodeModules]) && (await this.cache.lstat[scNodeModules]).isDirectory()) {
361
+ if (!visited.has(scNodeModules)) {
362
+ visited.add(scNodeModules);
363
+ queue.push({ dir: scNodeModules, depth: depth + 1 });
364
+ }
365
+ }
366
+ }
367
+ }
368
+ continue;
369
+ }
370
+ // check for direct candidate: entry/node_modules/pkgName
371
+ try {
372
+ const stat = await this.cache.lstat[entryPath];
373
+ if (!stat.isDirectory()) {
374
+ continue;
375
+ }
376
+ }
377
+ catch {
378
+ continue;
379
+ }
380
+ const candidatePkgJson = path.join(entryPath, "node_modules", pkgName, "package.json");
381
+ if (await this.cache.exists[candidatePkgJson]) {
382
+ const ver = await this.readPackageVersion(candidatePkgJson);
383
+ if (ver && this.semverSatisfies(ver, requiredRange)) {
384
+ return { packageDir: path.dirname(candidatePkgJson), version: ver };
385
+ }
386
+ }
387
+ // also check entry/node_modules directly for pkgName (some layouts)
388
+ const candidateDirect = path.join(entryPath, pkgName, "package.json");
389
+ if (await this.cache.exists[candidateDirect]) {
390
+ const ver = await this.readPackageVersion(candidateDirect);
391
+ if (ver && this.semverSatisfies(ver, requiredRange)) {
392
+ return { packageDir: path.dirname(candidateDirect), version: ver };
393
+ }
394
+ }
395
+ // enqueue entry/node_modules for deeper traversal
396
+ const nextNodeModules = path.join(entryPath, "node_modules");
397
+ if ((await this.cache.exists[nextNodeModules]) && (await this.cache.lstat[nextNodeModules]).isDirectory()) {
398
+ if (!visited.has(nextNodeModules)) {
399
+ visited.add(nextNodeModules);
400
+ queue.push({ dir: nextNodeModules, depth: depth + 1 });
401
+ }
402
+ }
403
+ }
404
+ }
405
+ return null;
406
+ }
347
407
  }
348
408
  exports.NodeModulesCollector = NodeModulesCollector;
349
409
  //# sourceMappingURL=nodeModulesCollector.js.map