Files
dinlo 436a9631fc Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:44:04 +08:00

129 lines
5.2 KiB
JavaScript

import debug from 'debug';
import fs from 'graceful-fs';
import path from 'node:path';
import { readPackageJson } from './read-package-json.js';
import { searchForModule, searchForNodeModules } from './search-module.js';
import { promisifiedGracefulFs } from './promisifiedGracefulFs.js';
const d = debug('electron-rebuild');
export class ModuleWalker {
buildPath;
modulesToRebuild;
onlyModules;
prodDeps;
projectRootPath;
realModulePaths;
realNodeModulesPaths;
types;
constructor(buildPath, projectRootPath, types, prodDeps, onlyModules) {
this.buildPath = buildPath;
this.modulesToRebuild = [];
this.projectRootPath = projectRootPath;
this.types = types;
this.prodDeps = prodDeps;
this.onlyModules = onlyModules;
this.realModulePaths = new Set();
this.realNodeModulesPaths = new Set();
}
get nodeModulesPaths() {
return searchForNodeModules(this.buildPath, this.projectRootPath);
}
async walkModules() {
const rootPackageJson = await readPackageJson(this.buildPath);
const markWaiters = [];
const depKeys = [];
if (this.types.includes('prod') || this.onlyModules) {
depKeys.push(...Object.keys(rootPackageJson.dependencies || {}));
}
if (this.types.includes('optional') || this.onlyModules) {
depKeys.push(...Object.keys(rootPackageJson.optionalDependencies || {}));
}
if (this.types.includes('dev') || this.onlyModules) {
depKeys.push(...Object.keys(rootPackageJson.devDependencies || {}));
}
for (const key of depKeys) {
this.prodDeps.add(key);
const modulePaths = await searchForModule(this.buildPath, key, this.projectRootPath);
for (const modulePath of modulePaths) {
markWaiters.push(this.markChildrenAsProdDeps(modulePath));
}
}
await Promise.all(markWaiters);
d('identified prod deps:', this.prodDeps);
}
async findModule(moduleName, fromDir, foundFn) {
const testPaths = await searchForModule(fromDir, moduleName, this.projectRootPath);
const foundFns = testPaths.map(testPath => foundFn(testPath));
return Promise.all(foundFns);
}
async markChildrenAsProdDeps(modulePath) {
if (!fs.existsSync(modulePath)) {
return;
}
d('exploring', modulePath);
let childPackageJson;
try {
childPackageJson = await readPackageJson(modulePath, true);
}
catch (err) {
return;
}
const moduleWait = [];
const callback = this.markChildrenAsProdDeps.bind(this);
for (const key of Object.keys(childPackageJson.dependencies || {}).concat(Object.keys(childPackageJson.optionalDependencies || {}))) {
if (this.prodDeps.has(key)) {
continue;
}
this.prodDeps.add(key);
moduleWait.push(this.findModule(key, modulePath, callback));
}
await Promise.all(moduleWait);
}
async findAllModulesIn(nodeModulesPath, prefix = '') {
// Some package managers use symbolic links when installing node modules
// we need to be sure we've never tested the a package before by resolving
// all symlinks in the path and testing against a set
const realNodeModulesPath = await fs.promises.realpath(nodeModulesPath);
if (this.realNodeModulesPaths.has(realNodeModulesPath)) {
return;
}
this.realNodeModulesPaths.add(realNodeModulesPath);
d('scanning:', realNodeModulesPath);
for (const modulePath of await promisifiedGracefulFs.readdir(realNodeModulesPath)) {
// Ignore the magical .bin directory
if (modulePath === '.bin')
continue;
const subPath = path.resolve(nodeModulesPath, modulePath);
// Ensure that we don't mark modules as needing to be rebuilt more than once
// by ignoring / resolving symlinks
let realPath;
try {
realPath = await fs.promises.realpath(subPath);
}
catch (error) {
// pnpm leaves dangling symlinks when modules are removed
if (error.code === 'ENOENT') {
const stat = await fs.promises.lstat(subPath);
if (stat.isSymbolicLink()) {
continue;
}
}
throw error;
}
if (this.realModulePaths.has(realPath)) {
continue;
}
this.realModulePaths.add(realPath);
const moduleName = `${prefix}${modulePath}`;
if (this.prodDeps.has(moduleName) && (!this.onlyModules || this.onlyModules.includes(moduleName))) {
this.modulesToRebuild.push(realPath);
}
if (modulePath.startsWith('@')) {
await this.findAllModulesIn(realPath, `${modulePath}/`);
}
if (fs.existsSync(path.resolve(nodeModulesPath, modulePath, 'node_modules'))) {
await this.findAllModulesIn(path.resolve(realPath, 'node_modules'));
}
}
}
}
//# sourceMappingURL=module-walker.js.map