forked from electron/rebuild
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodule-walker.ts
149 lines (122 loc) · 4.64 KB
/
module-walker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import debug from 'debug';
import fs from 'fs-extra';
import path from 'path';
import { readPackageJson } from './read-package-json';
import { searchForModule, searchForNodeModules } from './search-module';
const d = debug('electron-rebuild');
export type ModuleType = 'prod' | 'dev' | 'optional';
export class ModuleWalker {
buildPath: string;
modulesToRebuild: string[];
onlyModules: string[] | null;
prodDeps: Set<string>;
projectRootPath?: string;
realModulePaths: Set<string>;
realNodeModulesPaths: Set<string>;
types: ModuleType[];
constructor(buildPath: string, projectRootPath: string | undefined, types: ModuleType[], prodDeps: Set<string>, onlyModules: string[] | null) {
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(): Promise<string[]> {
return searchForNodeModules(
this.buildPath,
this.projectRootPath
);
}
async walkModules(): Promise<void> {
const rootPackageJson = await readPackageJson(this.buildPath);
const markWaiters: Promise<void>[] = [];
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[key] = true;
const modulePaths: string[] = 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: string, fromDir: string, foundFn: ((p: string) => Promise<void>)): Promise<void[]> {
const testPaths = await searchForModule(
fromDir,
moduleName,
this.projectRootPath
);
const foundFns = testPaths.map(testPath => foundFn(testPath));
return Promise.all(foundFns);
}
async markChildrenAsProdDeps(modulePath: string): Promise<void> {
if (!await fs.pathExists(modulePath)) {
return;
}
d('exploring', modulePath);
let childPackageJson;
try {
childPackageJson = await readPackageJson(modulePath, true);
} catch (err) {
return;
}
const moduleWait: Promise<void[]>[] = [];
const callback = this.markChildrenAsProdDeps.bind(this);
for (const key of Object.keys(childPackageJson.dependencies || {}).concat(Object.keys(childPackageJson.optionalDependencies || {}))) {
if (this.prodDeps[key]) {
continue;
}
this.prodDeps[key] = true;
moduleWait.push(this.findModule(key, modulePath, callback));
}
await Promise.all(moduleWait);
}
async findAllModulesIn(nodeModulesPath: string, prefix = ''): Promise<void> {
// 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.realpath(nodeModulesPath);
if (this.realNodeModulesPaths.has(realNodeModulesPath)) {
return;
}
this.realNodeModulesPaths.add(realNodeModulesPath);
d('scanning:', realNodeModulesPath);
for (const modulePath of await fs.readdir(realNodeModulesPath)) {
// Ignore the magical .bin directory
if (modulePath === '.bin') continue;
// Ensure that we don't mark modules as needing to be rebuilt more than once
// by ignoring / resolving symlinks
const realPath = await fs.realpath(path.resolve(nodeModulesPath, modulePath));
if (this.realModulePaths.has(realPath)) {
continue;
}
this.realModulePaths.add(realPath);
if (this.prodDeps[`${prefix}${modulePath}`] && (!this.onlyModules || this.onlyModules.includes(modulePath))) {
this.modulesToRebuild.push(realPath);
}
if (modulePath.startsWith('@')) {
await this.findAllModulesIn(realPath, `${modulePath}/`);
}
if (await fs.pathExists(path.resolve(nodeModulesPath, modulePath, 'node_modules'))) {
await this.findAllModulesIn(path.resolve(realPath, 'node_modules'));
}
}
}
}