Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TraversalNodeModulesCollector = void 0;
|
||||
const builder_util_1 = require("builder-util");
|
||||
const path = require("path");
|
||||
const moduleManager_1 = require("./moduleManager");
|
||||
const nodeModulesCollector_1 = require("./nodeModulesCollector");
|
||||
const packageManager_js_1 = require("./packageManager.js");
|
||||
// manual traversal of node_modules for package managers without CLI support for dependency tree extraction (e.g., bun) OR as a fallback (e.g. corepack enabled w/ strict mode)
|
||||
class TraversalNodeModulesCollector extends nodeModulesCollector_1.NodeModulesCollector {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.installOptions = {
|
||||
manager: packageManager_js_1.PM.TRAVERSAL,
|
||||
lockfile: "none",
|
||||
};
|
||||
}
|
||||
getArgs() {
|
||||
return [];
|
||||
}
|
||||
getDependenciesTree(_pm) {
|
||||
builder_util_1.log.info(null, "using manual traversal of node_modules to build dependency tree");
|
||||
return this.buildNodeModulesTreeManually(this.rootDir, undefined);
|
||||
}
|
||||
async collectAllDependencies(tree, appPackageName) {
|
||||
for (const [packageKey, value] of Object.entries({ ...tree.dependencies, ...tree.optionalDependencies })) {
|
||||
const normalizedDep = this.normalizePackageVersion(packageKey, value);
|
||||
this.allDependencies.set(normalizedDep.id, normalizedDep.pkgOverride);
|
||||
await this.collectAllDependencies(value, appPackageName);
|
||||
}
|
||||
}
|
||||
// we don't need to check optional dependencies here because they're pre-processed in `buildNodeModulesTreeManually`
|
||||
async extractProductionDependencyGraph(tree, dependencyId) {
|
||||
if (this.productionGraph[dependencyId]) {
|
||||
return;
|
||||
}
|
||||
this.productionGraph[dependencyId] = { dependencies: [] };
|
||||
const prodDependencies = { ...(tree.dependencies || {}), ...(tree.optionalDependencies || {}) };
|
||||
const collectedDependencies = [];
|
||||
for (const packageName in prodDependencies) {
|
||||
const dependency = prodDependencies[packageName];
|
||||
const { id: childDependencyId, pkgOverride } = this.normalizePackageVersion(packageName, dependency);
|
||||
await this.extractProductionDependencyGraph(pkgOverride, childDependencyId);
|
||||
collectedDependencies.push(childDependencyId);
|
||||
}
|
||||
this.productionGraph[dependencyId] = { dependencies: collectedDependencies };
|
||||
}
|
||||
/**
|
||||
* Builds a dependency tree using only package.json dependencies and optionalDependencies.
|
||||
* This skips devDependencies and uses Node.js module resolution (require.resolve).
|
||||
*/
|
||||
async buildNodeModulesTreeManually(baseDir, aliasName) {
|
||||
// Track visited packages by their resolved path to prevent infinite loops
|
||||
const visited = new Set();
|
||||
const resolvedBaseDir = await this.cache.realPath[baseDir];
|
||||
/**
|
||||
* Recursively builds dependency tree starting from a package directory.
|
||||
* @param packageDir - The directory of the package to process
|
||||
* @param aliasName - Optional alias name for npm aliased dependencies (e.g., "foo": "npm:@scope/bar@1.0.0")
|
||||
* When provided, this name is used instead of the package.json name for the module name,
|
||||
* ensuring the package is copied to the correct location in node_modules.
|
||||
*/
|
||||
const buildFromPackage = async (packageDir, aliasName) => {
|
||||
const pkgPath = path.join(packageDir, "package.json");
|
||||
if (!(await this.cache.exists[pkgPath])) {
|
||||
throw new Error(`package.json not found at ${pkgPath}`);
|
||||
}
|
||||
const pkg = (await this.cache.json[pkgPath]);
|
||||
const resolvedPackageDir = await this.cache.realPath[packageDir];
|
||||
// Use the alias name if provided, otherwise fall back to the package.json name
|
||||
// This ensures npm aliased packages are copied to the correct location
|
||||
const moduleName = aliasName !== null && aliasName !== void 0 ? aliasName : pkg.name;
|
||||
// Use resolved path as the unique identifier to prevent circular dependencies
|
||||
if (visited.has(resolvedPackageDir)) {
|
||||
builder_util_1.log.debug({ name: moduleName, version: pkg.version, path: resolvedPackageDir }, "skipping already visited package");
|
||||
return {
|
||||
name: moduleName,
|
||||
version: pkg.version,
|
||||
path: resolvedPackageDir,
|
||||
};
|
||||
}
|
||||
visited.add(resolvedPackageDir);
|
||||
const buildPackage = async (dependencies, nullHandler) => {
|
||||
const builtPackages = {};
|
||||
for (const [depName, depVersion] of Object.entries(dependencies || {})) {
|
||||
const pkg = await this.locatePackageWithVersion({ name: depName, version: depVersion, path: resolvedPackageDir });
|
||||
const logFields = { parent: moduleName, dependency: depName, version: depVersion };
|
||||
if (pkg == null) {
|
||||
nullHandler(depName, depVersion);
|
||||
continue;
|
||||
}
|
||||
// Skip if this dependency resolves to the base directory or any parent we're already processing
|
||||
if (pkg.packageDir === resolvedPackageDir || pkg.packageDir === resolvedBaseDir) {
|
||||
this.cache.logSummary[moduleManager_1.LogMessageByKey.PKG_SELF_REF].push(`${depName}@${depVersion}`);
|
||||
continue;
|
||||
}
|
||||
builder_util_1.log.debug(logFields, "processing production dependency");
|
||||
builtPackages[depName] = await buildFromPackage(pkg.packageDir, depName);
|
||||
}
|
||||
return builtPackages;
|
||||
};
|
||||
const prodDeps = await buildPackage(pkg.dependencies, (depName, version) => {
|
||||
builder_util_1.log.error({ parent: moduleName, dependency: depName, version }, "production dependency not found");
|
||||
throw new Error(`Production dependency ${depName} not found for package ${moduleName}`);
|
||||
});
|
||||
const optionalDeps = await buildPackage(pkg.optionalDependencies, (depName, version) => {
|
||||
builder_util_1.log.debug({ parent: moduleName, dependency: depName }, "optional dependency not installed, skipping");
|
||||
this.cache.logSummary[moduleManager_1.LogMessageByKey.PKG_OPTIONAL_NOT_INSTALLED].push(`${depName}@${version}`);
|
||||
});
|
||||
return {
|
||||
name: moduleName,
|
||||
version: pkg.version,
|
||||
path: resolvedPackageDir,
|
||||
dependencies: Object.keys(prodDeps).length > 0 ? prodDeps : undefined,
|
||||
optionalDependencies: Object.keys(optionalDeps).length > 0 ? optionalDeps : undefined,
|
||||
};
|
||||
};
|
||||
return buildFromPackage(baseDir, aliasName);
|
||||
}
|
||||
}
|
||||
exports.TraversalNodeModulesCollector = TraversalNodeModulesCollector;
|
||||
//# sourceMappingURL=traversalNodeModulesCollector.js.map
|
||||
Reference in New Issue
Block a user