436a9631fc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
276 lines
12 KiB
JavaScript
276 lines
12 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.ModuleManager = exports.logMessageLevelByKey = exports.LogMessageByKey = void 0;
|
|
const builder_util_1 = require("builder-util");
|
|
const fs = require("fs-extra");
|
|
const path = require("path");
|
|
const semver = require("semver");
|
|
var LogMessageByKey;
|
|
(function (LogMessageByKey) {
|
|
LogMessageByKey["PKG_DUPLICATE_REF"] = "duplicate dependency references";
|
|
LogMessageByKey["PKG_NOT_FOUND"] = "cannot find path for dependency";
|
|
LogMessageByKey["PKG_NOT_ON_DISK"] = "dependency not found on disk";
|
|
LogMessageByKey["PKG_SELF_REF"] = "self-referential dependencies";
|
|
LogMessageByKey["PKG_OPTIONAL_NOT_INSTALLED"] = "missing optional dependencies";
|
|
LogMessageByKey["PKG_COLLECTOR_OUTPUT"] = "collector stderr output";
|
|
})(LogMessageByKey || (exports.LogMessageByKey = LogMessageByKey = {}));
|
|
exports.logMessageLevelByKey = {
|
|
[LogMessageByKey.PKG_DUPLICATE_REF]: "info",
|
|
[LogMessageByKey.PKG_NOT_FOUND]: "warn",
|
|
[LogMessageByKey.PKG_NOT_ON_DISK]: "warn",
|
|
[LogMessageByKey.PKG_SELF_REF]: "debug",
|
|
[LogMessageByKey.PKG_OPTIONAL_NOT_INSTALLED]: "info",
|
|
[LogMessageByKey.PKG_COLLECTOR_OUTPUT]: "warn",
|
|
};
|
|
class ModuleManager {
|
|
constructor() {
|
|
this.jsonMap = new Map();
|
|
this.realPathMap = new Map();
|
|
this.existsMap = new Map();
|
|
this.lstatMap = new Map();
|
|
this.packageDataMap = new Map();
|
|
this.logSummaryMap = new Map();
|
|
this.logSummary = this.createLogSummarySyncProxy();
|
|
this.exists = this.createAsyncProxy(this.existsMap, (p) => (0, builder_util_1.exists)(p));
|
|
this.json = this.createAsyncProxy(this.jsonMap, (p) => fs.readJson(p).catch(() => null));
|
|
this.lstat = this.createAsyncProxy(this.lstatMap, (p) => fs.lstat(p).catch(() => null));
|
|
this.packageData = this.createAsyncProxy(this.packageDataMap, (p) => this.locatePackageVersionFromCacheKey(p).catch(() => null));
|
|
this.realPath = this.createAsyncProxy(this.realPathMap, async (p) => {
|
|
const filePath = path.resolve(p);
|
|
const stat = await this.lstat[filePath];
|
|
return (stat === null || stat === void 0 ? void 0 : stat.isSymbolicLink()) ? fs.realpath(filePath) : filePath;
|
|
});
|
|
}
|
|
createLogSummarySyncProxy() {
|
|
return new Proxy({}, {
|
|
get: (_, key) => {
|
|
if (!this.logSummaryMap.has(key)) {
|
|
this.logSummaryMap.set(key, []);
|
|
}
|
|
return this.logSummaryMap.get(key);
|
|
},
|
|
set: (_, key, value) => {
|
|
this.logSummaryMap.set(key, value);
|
|
return true;
|
|
},
|
|
has: (_, key) => {
|
|
return this.logSummaryMap.has(key);
|
|
},
|
|
// Add these to make Object.entries() work
|
|
ownKeys: _ => {
|
|
return Array.from(this.logSummaryMap.keys());
|
|
},
|
|
getOwnPropertyDescriptor: (_, key) => {
|
|
if (this.logSummaryMap.has(key)) {
|
|
return {
|
|
enumerable: true,
|
|
configurable: true,
|
|
};
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
}
|
|
// this allows dot-notation access while still supporting async retrieval
|
|
// e.g., cache.packageJson[somePath] returns Promise<PackageJson>
|
|
createAsyncProxy(map, compute) {
|
|
return new Proxy({}, {
|
|
async get(_, key) {
|
|
if (map.has(key)) {
|
|
return Promise.resolve(map.get(key));
|
|
}
|
|
return await Promise.resolve(compute(key)).then(value => {
|
|
map.set(key, value);
|
|
return value;
|
|
});
|
|
},
|
|
set(_, key, value) {
|
|
map.set(key, value);
|
|
return true;
|
|
},
|
|
has(_, key) {
|
|
return map.has(key);
|
|
},
|
|
});
|
|
}
|
|
versionedCacheKey(pkg) {
|
|
return [pkg.name, pkg.path, pkg.semver || ""].join("||");
|
|
}
|
|
async locatePackageVersionFromCacheKey(key) {
|
|
const [name, fromDir, semverRange] = key.split("||");
|
|
const result = await this.locatePackageVersion({ parentDir: fromDir, pkgName: name, requiredRange: semverRange });
|
|
if (result == null) {
|
|
return null;
|
|
}
|
|
return { ...result, packageDir: await this.realPath[result.packageDir] };
|
|
}
|
|
async locatePackageVersion({ parentDir, pkgName, requiredRange }) {
|
|
// 1) check direct parent node_modules/pkgName first
|
|
const direct = path.join(path.resolve(parentDir), "node_modules", pkgName, "package.json");
|
|
if (await this.exists[direct]) {
|
|
const json = await this.json[direct];
|
|
if (json && this.semverSatisfies(json.version, requiredRange)) {
|
|
return { packageDir: path.dirname(direct), packageJson: json };
|
|
}
|
|
}
|
|
// 2) upward hoisted search, then 3) downward non-hoisted search
|
|
return (await this.upwardSearch(parentDir, pkgName, requiredRange)) || (await this.downwardSearch(parentDir, pkgName, requiredRange)) || null;
|
|
}
|
|
semverSatisfies(found, range) {
|
|
if ((0, builder_util_1.isEmptyOrSpaces)(range) || range === "*") {
|
|
return true;
|
|
}
|
|
if (range === found) {
|
|
return true;
|
|
}
|
|
if (semver.validRange(range) == null) {
|
|
// ignore, we can't verify non-semver ranges
|
|
// e.g. git urls, file:, patch:, etc. Example:
|
|
// "@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.43#~/.yarn/patches/@ai-sdk-google-npm-2.0.43-689ed559b3.patch"
|
|
builder_util_1.log.debug({ found, range }, "unable to validate semver version range, assuming match");
|
|
return true;
|
|
}
|
|
try {
|
|
return semver.satisfies(found, range);
|
|
}
|
|
catch {
|
|
// fallback: simple equality or basic prefix handling (^, ~)
|
|
if (range.startsWith("^") || range.startsWith("~")) {
|
|
const r = range.slice(1);
|
|
return r === found;
|
|
}
|
|
// if range is like "8.x" or "8.*" match major
|
|
const m = range.match(/^(\d+)[.(*|x)]*/);
|
|
const fm = found.match(/^(\d+)\./);
|
|
if (m && fm) {
|
|
return m[1] === fm[1];
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Upward search (hoisted)
|
|
*/
|
|
async upwardSearch(parentDir, pkgName, requiredRange) {
|
|
let current = path.resolve(parentDir);
|
|
const root = path.parse(current).root;
|
|
while (true) {
|
|
const candidate = path.join(current, "node_modules", pkgName, "package.json");
|
|
if (await this.exists[candidate]) {
|
|
const json = await this.json[candidate];
|
|
if (json && this.semverSatisfies(json.version, requiredRange)) {
|
|
return { packageDir: path.dirname(candidate), packageJson: json };
|
|
}
|
|
// otherwise keep searching upward (we may find a different hoisted version)
|
|
}
|
|
if (current === root) {
|
|
break;
|
|
}
|
|
const parent = path.dirname(current);
|
|
if (parent === current) {
|
|
break;
|
|
}
|
|
current = parent;
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Breadth-first downward search from parentDir/node_modules
|
|
* Looks for node_modules/\*\/node_modules/pkgName (and deeper)
|
|
*/
|
|
async downwardSearch(parentDir, pkgName, requiredRange, maxExplored = 2000, maxDepth = 6) {
|
|
var _a, _b, _c, _d;
|
|
const start = path.join(path.resolve(parentDir), "node_modules");
|
|
if (!(await this.exists[start]) || !((_a = (await this.lstat[start])) === null || _a === void 0 ? void 0 : _a.isDirectory())) {
|
|
return null;
|
|
}
|
|
const visited = new Set();
|
|
const queue = [{ dir: start, depth: 0 }];
|
|
let explored = 0;
|
|
while (queue.length > 0) {
|
|
const { dir, depth } = queue.shift();
|
|
if (explored++ > maxExplored) {
|
|
break;
|
|
}
|
|
if (depth > maxDepth) {
|
|
continue;
|
|
}
|
|
let entries;
|
|
try {
|
|
entries = await fs.readdir(dir);
|
|
}
|
|
catch {
|
|
continue;
|
|
}
|
|
for (const entry of entries) {
|
|
if (entry.startsWith(".")) {
|
|
continue;
|
|
}
|
|
const entryPath = path.join(dir, entry);
|
|
// handle scoped packages @scope/name
|
|
if (entry.startsWith("@")) {
|
|
// queue the scope directory itself to explore its children
|
|
if ((await this.exists[entryPath]) && ((_b = (await this.lstat[entryPath])) === null || _b === void 0 ? void 0 : _b.isDirectory())) {
|
|
const scopeEntries = await fs.readdir(entryPath);
|
|
for (const sc of scopeEntries) {
|
|
const scPath = path.join(entryPath, sc);
|
|
// check scPath/node_modules/pkgName
|
|
const candidatePkgJson = path.join(scPath, "node_modules", pkgName, "package.json");
|
|
if (await this.exists[candidatePkgJson]) {
|
|
const json = await this.json[candidatePkgJson];
|
|
if (json && this.semverSatisfies(json.version, requiredRange)) {
|
|
return { packageDir: path.dirname(candidatePkgJson), packageJson: json };
|
|
}
|
|
}
|
|
// enqueue scPath/node_modules to explore further
|
|
const scNodeModules = path.join(scPath, "node_modules");
|
|
if ((await this.exists[scNodeModules]) && ((_c = (await this.lstat[scNodeModules])) === null || _c === void 0 ? void 0 : _c.isDirectory())) {
|
|
if (!visited.has(scNodeModules)) {
|
|
visited.add(scNodeModules);
|
|
queue.push({ dir: scNodeModules, depth: depth + 1 });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
// check for direct candidate: entry/node_modules/pkgName
|
|
try {
|
|
const stat = await this.lstat[entryPath];
|
|
if (!(stat === null || stat === void 0 ? void 0 : stat.isDirectory())) {
|
|
continue;
|
|
}
|
|
}
|
|
catch {
|
|
continue;
|
|
}
|
|
const candidatePkgJson = path.join(entryPath, "node_modules", pkgName, "package.json");
|
|
if (await this.exists[candidatePkgJson]) {
|
|
const json = await this.json[candidatePkgJson];
|
|
if (json && this.semverSatisfies(json.version, requiredRange)) {
|
|
return { packageDir: path.dirname(candidatePkgJson), packageJson: json };
|
|
}
|
|
}
|
|
// also check entry/node_modules directly for pkgName (some layouts)
|
|
const candidateDirect = path.join(entryPath, pkgName, "package.json");
|
|
if (await this.exists[candidateDirect]) {
|
|
const json = await this.json[candidateDirect];
|
|
if (json && this.semverSatisfies(json.version, requiredRange)) {
|
|
return { packageDir: path.dirname(candidateDirect), packageJson: json };
|
|
}
|
|
}
|
|
// enqueue entry/node_modules for deeper traversal
|
|
const nextNodeModules = path.join(entryPath, "node_modules");
|
|
if ((await this.exists[nextNodeModules]) && ((_d = (await this.lstat[nextNodeModules])) === null || _d === void 0 ? void 0 : _d.isDirectory())) {
|
|
if (!visited.has(nextNodeModules)) {
|
|
visited.add(nextNodeModules);
|
|
queue.push({ dir: nextNodeModules, depth: depth + 1 });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
exports.ModuleManager = ModuleManager;
|
|
//# sourceMappingURL=moduleManager.js.map
|