@yarnpkg/nm 2.4.1-rc.4 → 3.0.0-rc.11
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.
- package/lib/buildNodeModulesTree.d.ts +1 -0
- package/lib/buildNodeModulesTree.js +130 -80
- package/lib/hoist.d.ts +2 -0
- package/lib/hoist.js +87 -31
- package/package.json +5 -5
|
@@ -39,6 +39,7 @@ export interface NodeModulesTreeOptions {
|
|
|
39
39
|
pnpifyFs?: boolean;
|
|
40
40
|
validateExternalSoftLinks?: boolean;
|
|
41
41
|
hoistingLimitsByCwd?: Map<PortablePath, NodeModulesHoistingLimits>;
|
|
42
|
+
selfReferencesByCwd?: Map<PortablePath, Boolean>;
|
|
42
43
|
project?: Project;
|
|
43
44
|
}
|
|
44
45
|
/** Package locator key for usage inside maps */
|
|
@@ -49,7 +49,7 @@ const buildNodeModulesTree = (pnp, options) => {
|
|
|
49
49
|
const { packageTree, hoistingLimits, errors, preserveSymlinksRequired } = buildPackageTree(pnp, options);
|
|
50
50
|
let tree = null;
|
|
51
51
|
if (errors.length === 0) {
|
|
52
|
-
const hoistedTree = hoist_1.hoist(packageTree, { hoistingLimits });
|
|
52
|
+
const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
|
|
53
53
|
tree = populateNodeModulesTree(pnp, hoistedTree, options);
|
|
54
54
|
}
|
|
55
55
|
return { tree, errors, preserveSymlinksRequired };
|
|
@@ -84,6 +84,83 @@ const areRealLocatorsEqual = (a, b) => {
|
|
|
84
84
|
const realB = core_1.structUtils.isVirtualLocator(b) ? core_1.structUtils.devirtualizeLocator(b) : b;
|
|
85
85
|
return core_1.structUtils.areLocatorsEqual(realA, realB);
|
|
86
86
|
};
|
|
87
|
+
const isExternalSoftLink = (pkg, locator, pnp, topPkgPortableLocation) => {
|
|
88
|
+
if (pkg.linkType !== LinkType.SOFT)
|
|
89
|
+
return false;
|
|
90
|
+
const realSoftLinkPath = fslib_1.npath.toPortablePath(pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`) ? pnp.resolveVirtual(pkg.packageLocation) : pkg.packageLocation);
|
|
91
|
+
return fslib_1.ppath.contains(topPkgPortableLocation, realSoftLinkPath) === null;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Builds a map representing layout of nested workspaces and internal portals on the file system.
|
|
95
|
+
*/
|
|
96
|
+
const buildWorkspaceMap = (pnp) => {
|
|
97
|
+
const topPkg = pnp.getPackageInformation(pnp.topLevel);
|
|
98
|
+
if (topPkg === null)
|
|
99
|
+
throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
|
|
100
|
+
const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
|
|
101
|
+
if (topLocator === null)
|
|
102
|
+
throw new Error(`Assertion failed: Expected the top-level package to have a physical locator`);
|
|
103
|
+
const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation.slice(0, -1));
|
|
104
|
+
const workspaceMap = new Map();
|
|
105
|
+
const workspaceTree = { children: new Map() };
|
|
106
|
+
const pnpRoots = pnp.getDependencyTreeRoots();
|
|
107
|
+
// Workspace and internal portal locations to locators map
|
|
108
|
+
const workspaceLikeLocators = new Map();
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
const visit = (locator) => {
|
|
111
|
+
const locatorKey = stringifyLocator(locator);
|
|
112
|
+
if (seen.has(locatorKey))
|
|
113
|
+
return;
|
|
114
|
+
seen.add(locatorKey);
|
|
115
|
+
const pkg = pnp.getPackageInformation(locator);
|
|
116
|
+
if (pkg) {
|
|
117
|
+
if (pkg.linkType === LinkType.SOFT && !isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation))
|
|
118
|
+
workspaceLikeLocators.set(getRealPackageLocation(pkg, locator, pnp), locator);
|
|
119
|
+
for (const [name, referencish] of pkg.packageDependencies) {
|
|
120
|
+
if (referencish !== null) {
|
|
121
|
+
if (!pkg.packagePeers.has(name)) {
|
|
122
|
+
visit(pnp.getLocator(name, referencish));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
for (const locator of pnpRoots)
|
|
129
|
+
visit(locator);
|
|
130
|
+
const cwdSegments = topPkgPortableLocation.split(fslib_1.ppath.sep);
|
|
131
|
+
for (const locator of workspaceLikeLocators.values()) {
|
|
132
|
+
const pkg = pnp.getPackageInformation(locator);
|
|
133
|
+
const location = fslib_1.npath.toPortablePath(pkg.packageLocation.slice(0, -1));
|
|
134
|
+
const segments = location.split(fslib_1.ppath.sep).slice(cwdSegments.length);
|
|
135
|
+
let node = workspaceTree;
|
|
136
|
+
for (const segment of segments) {
|
|
137
|
+
let nextNode = node.children.get(segment);
|
|
138
|
+
if (!nextNode) {
|
|
139
|
+
nextNode = { children: new Map() };
|
|
140
|
+
node.children.set(segment, nextNode);
|
|
141
|
+
}
|
|
142
|
+
node = nextNode;
|
|
143
|
+
}
|
|
144
|
+
node.workspaceLocator = locator;
|
|
145
|
+
}
|
|
146
|
+
const addWorkspace = (node, parentWorkspaceLocator) => {
|
|
147
|
+
if (node.workspaceLocator) {
|
|
148
|
+
const parentLocatorKey = stringifyLocator(parentWorkspaceLocator);
|
|
149
|
+
let dependencies = workspaceMap.get(parentLocatorKey);
|
|
150
|
+
if (!dependencies) {
|
|
151
|
+
dependencies = new Set();
|
|
152
|
+
workspaceMap.set(parentLocatorKey, dependencies);
|
|
153
|
+
}
|
|
154
|
+
dependencies.add(node.workspaceLocator);
|
|
155
|
+
}
|
|
156
|
+
for (const child of node.children.values()) {
|
|
157
|
+
addWorkspace(child, node.workspaceLocator || parentWorkspaceLocator);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
for (const child of workspaceTree.children.values())
|
|
161
|
+
addWorkspace(child, workspaceTree.workspaceLocator);
|
|
162
|
+
return workspaceMap;
|
|
163
|
+
};
|
|
87
164
|
/**
|
|
88
165
|
* Traverses PnP tree and produces input for the `RawHoister`
|
|
89
166
|
*
|
|
@@ -92,81 +169,28 @@ const areRealLocatorsEqual = (a, b) => {
|
|
|
92
169
|
* @returns package tree, packages info and locators
|
|
93
170
|
*/
|
|
94
171
|
const buildPackageTree = (pnp, options) => {
|
|
95
|
-
const pnpRoots = pnp.getDependencyTreeRoots();
|
|
96
172
|
const errors = [];
|
|
97
173
|
let preserveSymlinksRequired = false;
|
|
98
174
|
const hoistingLimits = new Map();
|
|
99
|
-
const
|
|
175
|
+
const workspaceMap = buildWorkspaceMap(pnp);
|
|
100
176
|
const topPkg = pnp.getPackageInformation(pnp.topLevel);
|
|
101
177
|
if (topPkg === null)
|
|
102
178
|
throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
|
|
103
179
|
const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
|
|
104
180
|
if (topLocator === null)
|
|
105
181
|
throw new Error(`Assertion failed: Expected the top-level package to have a physical locator`);
|
|
106
|
-
const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation);
|
|
107
|
-
const topLocatorKey = stringifyLocator(topLocator);
|
|
108
|
-
if (options.project) {
|
|
109
|
-
const workspaceTree = { children: new Map() };
|
|
110
|
-
const cwdSegments = options.project.cwd.split(fslib_1.ppath.sep);
|
|
111
|
-
for (const [cwd, workspace] of options.project.workspacesByCwd) {
|
|
112
|
-
const segments = cwd.split(fslib_1.ppath.sep).slice(cwdSegments.length);
|
|
113
|
-
let node = workspaceTree;
|
|
114
|
-
for (const segment of segments) {
|
|
115
|
-
let nextNode = node.children.get(segment);
|
|
116
|
-
if (!nextNode) {
|
|
117
|
-
nextNode = { children: new Map() };
|
|
118
|
-
node.children.set(segment, nextNode);
|
|
119
|
-
}
|
|
120
|
-
node = nextNode;
|
|
121
|
-
}
|
|
122
|
-
node.workspaceLocator = { name: core_1.structUtils.stringifyIdent(workspace.anchoredLocator), reference: workspace.anchoredLocator.reference };
|
|
123
|
-
}
|
|
124
|
-
const addWorkspace = (node, parentWorkspaceLocator) => {
|
|
125
|
-
if (node.workspaceLocator) {
|
|
126
|
-
const parentLocatorKey = stringifyLocator(parentWorkspaceLocator);
|
|
127
|
-
let dependencies = workspaceDependenciesMap.get(parentLocatorKey);
|
|
128
|
-
if (!dependencies) {
|
|
129
|
-
dependencies = new Set();
|
|
130
|
-
workspaceDependenciesMap.set(parentLocatorKey, dependencies);
|
|
131
|
-
}
|
|
132
|
-
dependencies.add(node.workspaceLocator);
|
|
133
|
-
}
|
|
134
|
-
for (const child of node.children.values()) {
|
|
135
|
-
addWorkspace(child, node.workspaceLocator || parentWorkspaceLocator);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
for (const child of workspaceTree.children.values()) {
|
|
139
|
-
addWorkspace(child, workspaceTree.workspaceLocator);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
for (const locator of pnpRoots) {
|
|
144
|
-
if (locator.name !== topLocator.name || locator.reference !== topLocator.reference) {
|
|
145
|
-
let dependencies = workspaceDependenciesMap.get(topLocatorKey);
|
|
146
|
-
if (!dependencies) {
|
|
147
|
-
dependencies = new Set();
|
|
148
|
-
workspaceDependenciesMap.set(topLocatorKey, dependencies);
|
|
149
|
-
}
|
|
150
|
-
dependencies.add(locator);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
182
|
+
const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation.slice(0, -1));
|
|
154
183
|
const packageTree = {
|
|
155
184
|
name: topLocator.name,
|
|
156
185
|
identName: topLocator.name,
|
|
157
186
|
reference: topLocator.reference,
|
|
158
187
|
peerNames: topPkg.packagePeers,
|
|
159
188
|
dependencies: new Set(),
|
|
189
|
+
isWorkspace: true,
|
|
160
190
|
};
|
|
161
191
|
const nodes = new Map();
|
|
162
192
|
const getNodeKey = (name, locator) => `${stringifyLocator(locator)}:${name}`;
|
|
163
|
-
const
|
|
164
|
-
if (pkg.linkType !== LinkType.SOFT || !options.project)
|
|
165
|
-
return false;
|
|
166
|
-
const realSoftLinkPath = fslib_1.npath.toPortablePath(pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`) ? pnp.resolveVirtual(pkg.packageLocation) : pkg.packageLocation);
|
|
167
|
-
return fslib_1.ppath.contains(options.project.cwd, realSoftLinkPath) === null;
|
|
168
|
-
};
|
|
169
|
-
const addPackageToTree = (name, pkg, locator, parent, parentDependencies, parentRelativeCwd, isHoistBorder) => {
|
|
193
|
+
const addPackageToTree = (name, pkg, locator, parent, parentPkg, parentDependencies, parentRelativeCwd, isHoistBorder) => {
|
|
170
194
|
var _a, _b;
|
|
171
195
|
const nodeKey = getNodeKey(name, locator);
|
|
172
196
|
let node = nodes.get(nodeKey);
|
|
@@ -175,17 +199,32 @@ const buildPackageTree = (pnp, options) => {
|
|
|
175
199
|
node = packageTree;
|
|
176
200
|
nodes.set(nodeKey, packageTree);
|
|
177
201
|
}
|
|
202
|
+
const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation);
|
|
178
203
|
if (!node) {
|
|
204
|
+
const isWorkspace = pkg.linkType === LinkType.SOFT && locator.name.endsWith(WORKSPACE_NAME_SUFFIX);
|
|
179
205
|
node = {
|
|
180
206
|
name,
|
|
181
207
|
identName: locator.name,
|
|
182
208
|
reference: locator.reference,
|
|
183
209
|
dependencies: new Set(),
|
|
184
|
-
|
|
210
|
+
// View peer dependencies as regular dependencies for workspaces
|
|
211
|
+
// (meeting workspace peer dependency constraints is sometimes hard, sometimes impossible for the nm linker)
|
|
212
|
+
peerNames: isWorkspace ? new Set() : pkg.packagePeers,
|
|
213
|
+
isWorkspace,
|
|
185
214
|
};
|
|
186
215
|
nodes.set(nodeKey, node);
|
|
187
216
|
}
|
|
188
|
-
|
|
217
|
+
let hoistPriority;
|
|
218
|
+
if (isExternalSoftLinkPackage)
|
|
219
|
+
// External soft link dependencies have the highest priority - we don't want to install inside them
|
|
220
|
+
hoistPriority = 2;
|
|
221
|
+
else if (parentPkg.linkType === LinkType.SOFT)
|
|
222
|
+
// Internal soft link dependencies should have priority over transitive dependencies - to maximize chances having only one top-level node_modules
|
|
223
|
+
hoistPriority = 1;
|
|
224
|
+
else
|
|
225
|
+
hoistPriority = 0;
|
|
226
|
+
node.hoistPriority = Math.max(node.hoistPriority || 0, hoistPriority);
|
|
227
|
+
if (isHoistBorder && !isExternalSoftLinkPackage) {
|
|
189
228
|
const parentLocatorKey = stringifyLocator({ name: parent.identName, reference: parent.reference });
|
|
190
229
|
const dependencyBorders = hoistingLimits.get(parentLocatorKey) || new Set();
|
|
191
230
|
hoistingLimits.set(parentLocatorKey, dependencyBorders);
|
|
@@ -207,15 +246,17 @@ const buildPackageTree = (pnp, options) => {
|
|
|
207
246
|
}
|
|
208
247
|
}
|
|
209
248
|
}
|
|
210
|
-
const locatorKey = stringifyLocator(locator);
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
for (const workspaceLocator of
|
|
249
|
+
const locatorKey = stringifyLocator({ name: locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), reference: locator.reference });
|
|
250
|
+
const innerWorkspaces = workspaceMap.get(locatorKey);
|
|
251
|
+
if (innerWorkspaces) {
|
|
252
|
+
for (const workspaceLocator of innerWorkspaces) {
|
|
214
253
|
allDependencies.set(`${workspaceLocator.name}${WORKSPACE_NAME_SUFFIX}`, workspaceLocator.reference);
|
|
215
254
|
}
|
|
216
255
|
}
|
|
217
|
-
|
|
218
|
-
|
|
256
|
+
if (pkg !== parentPkg || pkg.linkType !== LinkType.SOFT || !options.selfReferencesByCwd || options.selfReferencesByCwd.get(parentRelativeCwd))
|
|
257
|
+
parent.dependencies.add(node);
|
|
258
|
+
const isWorkspaceDependency = locator !== topLocator && pkg.linkType === LinkType.SOFT && !locator.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isExternalSoftLinkPackage;
|
|
259
|
+
if (!isSeen && !isWorkspaceDependency) {
|
|
219
260
|
const siblingPortalDependencyMap = new Map();
|
|
220
261
|
for (const [depName, referencish] of allDependencies) {
|
|
221
262
|
if (referencish !== null) {
|
|
@@ -224,7 +265,7 @@ const buildPackageTree = (pnp, options) => {
|
|
|
224
265
|
const depPkg = pnp.getPackageInformation(pkgLocator);
|
|
225
266
|
if (depPkg === null)
|
|
226
267
|
throw new Error(`Assertion failed: Expected the package to have been registered`);
|
|
227
|
-
const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator);
|
|
268
|
+
const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator, pnp, topPkgPortableLocation);
|
|
228
269
|
if (options.validateExternalSoftLinks && options.project && isExternalSoftLinkDep) {
|
|
229
270
|
if (depPkg.packageDependencies.size > 0)
|
|
230
271
|
preserveSymlinksRequired = true;
|
|
@@ -276,14 +317,20 @@ const buildPackageTree = (pnp, options) => {
|
|
|
276
317
|
const isHoistBorder = parentHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
|
|
277
318
|
|| depHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
|
|
278
319
|
|| depHoistingLimits === NodeModulesHoistingLimits.WORKSPACES;
|
|
279
|
-
addPackageToTree(stringifyLocator(depLocator) === stringifyLocator(locator) ? name : depName, depPkg, depLocator, node, allDependencies, relativeDepCwd, isHoistBorder);
|
|
320
|
+
addPackageToTree(stringifyLocator(depLocator) === stringifyLocator(locator) ? name : depName, depPkg, depLocator, node, pkg, allDependencies, relativeDepCwd, isHoistBorder);
|
|
280
321
|
}
|
|
281
322
|
}
|
|
282
323
|
}
|
|
283
324
|
};
|
|
284
|
-
addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
|
|
325
|
+
addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
|
|
285
326
|
return { packageTree, hoistingLimits, errors, preserveSymlinksRequired };
|
|
286
327
|
};
|
|
328
|
+
function getRealPackageLocation(pkg, locator, pnp) {
|
|
329
|
+
const realPath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
|
|
330
|
+
? pnp.resolveVirtual(pkg.packageLocation)
|
|
331
|
+
: pkg.packageLocation;
|
|
332
|
+
return fslib_1.npath.toPortablePath(realPath || pkg.packageLocation);
|
|
333
|
+
}
|
|
287
334
|
function getTargetLocatorPath(locator, pnp, options) {
|
|
288
335
|
const pkgLocator = pnp.getLocator(locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), locator.reference);
|
|
289
336
|
const info = pnp.getPackageInformation(pkgLocator);
|
|
@@ -301,10 +348,7 @@ function getTargetLocatorPath(locator, pnp, options) {
|
|
|
301
348
|
linkType = LinkType.SOFT;
|
|
302
349
|
}
|
|
303
350
|
else {
|
|
304
|
-
|
|
305
|
-
? pnp.resolveVirtual(info.packageLocation)
|
|
306
|
-
: info.packageLocation;
|
|
307
|
-
target = fslib_1.npath.toPortablePath(truePath || info.packageLocation);
|
|
351
|
+
target = getRealPackageLocation(info, locator, pnp);
|
|
308
352
|
linkType = info.linkType;
|
|
309
353
|
}
|
|
310
354
|
return { linkType, target };
|
|
@@ -334,11 +378,11 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
|
|
|
334
378
|
const getPackageName = (identName) => {
|
|
335
379
|
const [nameOrScope, name] = identName.split(`/`);
|
|
336
380
|
return name ? {
|
|
337
|
-
scope: fslib_1.toFilename(nameOrScope),
|
|
338
|
-
name: fslib_1.toFilename(name),
|
|
381
|
+
scope: (0, fslib_1.toFilename)(nameOrScope),
|
|
382
|
+
name: (0, fslib_1.toFilename)(name),
|
|
339
383
|
} : {
|
|
340
384
|
scope: null,
|
|
341
|
-
name: fslib_1.toFilename(nameOrScope),
|
|
385
|
+
name: (0, fslib_1.toFilename)(nameOrScope),
|
|
342
386
|
};
|
|
343
387
|
};
|
|
344
388
|
const seenNodes = new Set();
|
|
@@ -360,7 +404,13 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
|
|
|
360
404
|
const nodeModulesLocation = fslib_1.ppath.join(nodeModulesDirPath, ...packageNameParts);
|
|
361
405
|
const nodePath = `${parentNodePath}/${locator.name}`;
|
|
362
406
|
const leafNode = makeLeafNode(locator, parentNodePath, references.slice(1));
|
|
363
|
-
|
|
407
|
+
// We don't want to create self-referencing symlinks for anonymous workspaces
|
|
408
|
+
let isAnonymousWorkspace = false;
|
|
409
|
+
if (leafNode.linkType === LinkType.SOFT && options.project) {
|
|
410
|
+
const workspace = options.project.workspacesByCwd.get(leafNode.target.slice(0, -1));
|
|
411
|
+
isAnonymousWorkspace = !!(workspace && !workspace.manifest.name);
|
|
412
|
+
}
|
|
413
|
+
if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isAnonymousWorkspace) {
|
|
364
414
|
const prevNode = tree.get(nodeModulesLocation);
|
|
365
415
|
if (prevNode) {
|
|
366
416
|
if (prevNode.dirList) {
|
|
@@ -382,7 +432,7 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
|
|
|
382
432
|
let segCount = segments.length - 1;
|
|
383
433
|
while (nodeModulesIdx >= 0 && segCount > nodeModulesIdx) {
|
|
384
434
|
const dirPath = fslib_1.npath.toPortablePath(segments.slice(0, segCount).join(fslib_1.ppath.sep));
|
|
385
|
-
const targetDir = fslib_1.toFilename(segments[segCount]);
|
|
435
|
+
const targetDir = (0, fslib_1.toFilename)(segments[segCount]);
|
|
386
436
|
const subdirs = tree.get(dirPath);
|
|
387
437
|
if (!subdirs) {
|
|
388
438
|
tree.set(dirPath, { dirList: new Set([targetDir]) });
|
|
@@ -422,7 +472,7 @@ const benchmarkRawHoisting = (packageTree) => {
|
|
|
422
472
|
const iterCount = 10;
|
|
423
473
|
const startTime = Date.now();
|
|
424
474
|
for (let iter = 0; iter < iterCount; iter++)
|
|
425
|
-
hoist_1.hoist(packageTree);
|
|
475
|
+
(0, hoist_1.hoist)(packageTree);
|
|
426
476
|
const endTime = Date.now();
|
|
427
477
|
return (endTime - startTime) / iterCount;
|
|
428
478
|
};
|
|
@@ -442,7 +492,7 @@ const benchmarkBuildTree = (pnp, options) => {
|
|
|
442
492
|
const startTime = Date.now();
|
|
443
493
|
for (let iter = 0; iter < iterCount; iter++) {
|
|
444
494
|
const { packageTree, hoistingLimits } = buildPackageTree(pnp, options);
|
|
445
|
-
const hoistedTree = hoist_1.hoist(packageTree, { hoistingLimits });
|
|
495
|
+
const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
|
|
446
496
|
populateNodeModulesTree(pnp, hoistedTree, options);
|
|
447
497
|
}
|
|
448
498
|
const endTime = Date.now();
|
package/lib/hoist.d.ts
CHANGED
package/lib/hoist.js
CHANGED
|
@@ -88,10 +88,6 @@ const getUsedDependencies = (rootNodePath) => {
|
|
|
88
88
|
const rootNode = rootNodePath[rootNodePath.length - 1];
|
|
89
89
|
const usedDependencies = new Map();
|
|
90
90
|
const seenNodes = new Set();
|
|
91
|
-
const reachableDependencies = new Map();
|
|
92
|
-
for (const node of rootNodePath)
|
|
93
|
-
for (const dep of node.dependencies.values())
|
|
94
|
-
reachableDependencies.set(dep.name, dep);
|
|
95
91
|
const hiddenDependencies = new Set();
|
|
96
92
|
const addUsedDependencies = (node, hiddenDependencies) => {
|
|
97
93
|
if (seenNodes.has(node))
|
|
@@ -99,9 +95,12 @@ const getUsedDependencies = (rootNodePath) => {
|
|
|
99
95
|
seenNodes.add(node);
|
|
100
96
|
for (const dep of node.hoistedDependencies.values()) {
|
|
101
97
|
if (!hiddenDependencies.has(dep.name)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
let reachableDependency;
|
|
99
|
+
for (const node of rootNodePath) {
|
|
100
|
+
reachableDependency = node.dependencies.get(dep.name);
|
|
101
|
+
if (reachableDependency) {
|
|
102
|
+
usedDependencies.set(reachableDependency.name, reachableDependency);
|
|
103
|
+
}
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
}
|
|
@@ -135,7 +134,7 @@ const getUsedDependencies = (rootNodePath) => {
|
|
|
135
134
|
const decoupleGraphNode = (parent, node) => {
|
|
136
135
|
if (node.decoupled)
|
|
137
136
|
return node;
|
|
138
|
-
const { name, references, ident, locator, dependencies, originalDependencies, hoistedDependencies, peerNames, reasons, isHoistBorder } = node;
|
|
137
|
+
const { name, references, ident, locator, dependencies, originalDependencies, hoistedDependencies, peerNames, reasons, isHoistBorder, hoistPriority, isWorkspace, hoistedFrom, hoistedTo } = node;
|
|
139
138
|
// To perform node hoisting from parent node we must clone parent nodes up to the root node,
|
|
140
139
|
// because some other package in the tree might depend on the parent package where hoisting
|
|
141
140
|
// cannot be performed
|
|
@@ -151,7 +150,10 @@ const decoupleGraphNode = (parent, node) => {
|
|
|
151
150
|
reasons: new Map(reasons),
|
|
152
151
|
decoupled: true,
|
|
153
152
|
isHoistBorder,
|
|
154
|
-
|
|
153
|
+
hoistPriority,
|
|
154
|
+
isWorkspace,
|
|
155
|
+
hoistedFrom: new Map(hoistedFrom),
|
|
156
|
+
hoistedTo: new Map(hoistedTo),
|
|
155
157
|
};
|
|
156
158
|
const selfDep = clone.dependencies.get(name);
|
|
157
159
|
if (selfDep && selfDep.ident == clone.ident)
|
|
@@ -181,7 +183,10 @@ const getHoistIdentMap = (rootNode, preferenceMap) => {
|
|
|
181
183
|
keyList.sort((key1, key2) => {
|
|
182
184
|
const entry1 = preferenceMap.get(key1);
|
|
183
185
|
const entry2 = preferenceMap.get(key2);
|
|
184
|
-
if (entry2.
|
|
186
|
+
if (entry2.hoistPriority !== entry1.hoistPriority) {
|
|
187
|
+
return entry2.hoistPriority - entry1.hoistPriority;
|
|
188
|
+
}
|
|
189
|
+
else if (entry2.peerDependents.size !== entry1.peerDependents.size) {
|
|
185
190
|
return entry2.peerDependents.size - entry1.peerDependents.size;
|
|
186
191
|
}
|
|
187
192
|
else {
|
|
@@ -305,7 +310,7 @@ const hoistTo = (tree, rootNodePath, rootNodePathLocators, parentShadowedNodes,
|
|
|
305
310
|
}
|
|
306
311
|
return { anotherRoundNeeded, isGraphChanged };
|
|
307
312
|
};
|
|
308
|
-
const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason }) => {
|
|
313
|
+
const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason, fastLookupPossible }) => {
|
|
309
314
|
let reasonRoot;
|
|
310
315
|
let reason = null;
|
|
311
316
|
let dependsOn = new Set();
|
|
@@ -317,6 +322,29 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
|
|
|
317
322
|
let isHoistable = !isSelfReference;
|
|
318
323
|
if (outputReason && !isHoistable)
|
|
319
324
|
reason = `- self-reference`;
|
|
325
|
+
if (isHoistable) {
|
|
326
|
+
isHoistable = !node.isWorkspace;
|
|
327
|
+
if (outputReason && !isHoistable) {
|
|
328
|
+
reason = `- workspace`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (isHoistable) {
|
|
332
|
+
// Direct workspace dependencies must be hoisted to any common ancestor workspace of all the
|
|
333
|
+
// graph paths that include the dependency, because otherwise running app with
|
|
334
|
+
// `--preserve-symlinks` will become broken (without this flag the Node.js will pick dependency
|
|
335
|
+
// from the ancestor on the file system and with this flag it will pick ancestor from the graph
|
|
336
|
+
// and if these ancestors are different, the behavious of the application will be different).
|
|
337
|
+
// Another problem, which is prevented - is a creation of multiple hoisting layouts
|
|
338
|
+
// for the same workspace, because different dependencies of the same workspace might be hoisted
|
|
339
|
+
// differently, depending on the recepient workspace.
|
|
340
|
+
// It is difficult to find all common ancestors, but there is one easy to find common ancestor -
|
|
341
|
+
// the root workspace, so, for now, we either hoist direct dependencies into the root workspace, or we keep them
|
|
342
|
+
// unhoisted, thus we are safe from various pathological cases with `--preserve-symlinks`
|
|
343
|
+
isHoistable = !parentNode.isWorkspace || parentNode.hoistedFrom.has(node.name) || rootNodePathLocators.size === 1;
|
|
344
|
+
if (outputReason && !isHoistable) {
|
|
345
|
+
reason = parentNode.reasons.get(node.name);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
320
348
|
if (isHoistable) {
|
|
321
349
|
isHoistable = !rootNode.peerNames.has(node.name);
|
|
322
350
|
if (outputReason && !isHoistable) {
|
|
@@ -385,6 +413,17 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
|
|
|
385
413
|
}
|
|
386
414
|
isHoistable = arePeerDepsSatisfied;
|
|
387
415
|
}
|
|
416
|
+
if (isHoistable && !fastLookupPossible) {
|
|
417
|
+
for (const origDep of node.hoistedDependencies.values()) {
|
|
418
|
+
const usedDep = usedDependencies.get(origDep.name);
|
|
419
|
+
if (!usedDep || origDep.ident !== usedDep.ident) {
|
|
420
|
+
isHoistable = false;
|
|
421
|
+
if (outputReason)
|
|
422
|
+
reason = `- previously hoisted dependency mismatch, needed: ${prettyPrintLocator(origDep.locator)}, available: ${prettyPrintLocator(usedDep === null || usedDep === void 0 ? void 0 : usedDep.locator)}`;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
388
427
|
if (dependsOn !== null && dependsOn.size > 0) {
|
|
389
428
|
return { isHoistable: Hoistable.DEPENDS, dependsOn, reason };
|
|
390
429
|
}
|
|
@@ -413,7 +452,7 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
|
|
|
413
452
|
const dependantTree = new Map();
|
|
414
453
|
const hoistInfos = new Map();
|
|
415
454
|
for (const subDependency of getSortedRegularDependencies(parentNode)) {
|
|
416
|
-
const hoistInfo = getNodeHoistInfo(rootNode, rootNodePathLocators, [rootNode, ...nodePath, parentNode], subDependency, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason: options.debugLevel >= DebugLevel.REASONS });
|
|
455
|
+
const hoistInfo = getNodeHoistInfo(rootNode, rootNodePathLocators, [rootNode, ...nodePath, parentNode], subDependency, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason: options.debugLevel >= DebugLevel.REASONS, fastLookupPossible: options.fastLookupPossible });
|
|
417
456
|
hoistInfos.set(subDependency, hoistInfo);
|
|
418
457
|
if (hoistInfo.isHoistable === Hoistable.DEPENDS) {
|
|
419
458
|
for (const node of hoistInfo.dependsOn) {
|
|
@@ -446,25 +485,27 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
|
|
|
446
485
|
parentNode.hoistedDependencies.set(node.name, node);
|
|
447
486
|
parentNode.reasons.delete(node.name);
|
|
448
487
|
const hoistedNode = rootNode.dependencies.get(node.name);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
488
|
+
if (options.debugLevel >= DebugLevel.REASONS) {
|
|
489
|
+
const hoistedFrom = Array.from(locatorPath).concat([parentNode.locator]).map(x => prettyPrintLocator(x)).join(`→`);
|
|
490
|
+
let hoistedFromArray = rootNode.hoistedFrom.get(node.name);
|
|
491
|
+
if (!hoistedFromArray) {
|
|
492
|
+
hoistedFromArray = [];
|
|
493
|
+
rootNode.hoistedFrom.set(node.name, hoistedFromArray);
|
|
494
|
+
}
|
|
495
|
+
hoistedFromArray.push(hoistedFrom);
|
|
496
|
+
parentNode.hoistedTo.set(node.name, Array.from(rootNodePath).map(x => prettyPrintLocator(x.locator)).join(`→`));
|
|
497
|
+
}
|
|
452
498
|
// Add hoisted node to root node, in case it is not already there
|
|
453
499
|
if (!hoistedNode) {
|
|
454
500
|
// Avoid adding other version of root node to itself
|
|
455
501
|
if (rootNode.ident !== node.ident) {
|
|
456
502
|
rootNode.dependencies.set(node.name, node);
|
|
457
|
-
if (options.debugLevel >= DebugLevel.REASONS)
|
|
458
|
-
node.hoistedFrom.push(hoistedFrom);
|
|
459
503
|
newNodes.add(node);
|
|
460
504
|
}
|
|
461
505
|
}
|
|
462
506
|
else {
|
|
463
507
|
for (const reference of node.references) {
|
|
464
508
|
hoistedNode.references.add(reference);
|
|
465
|
-
if (options.debugLevel >= DebugLevel.REASONS) {
|
|
466
|
-
hoistedNode.hoistedFrom.push(hoistedFrom);
|
|
467
|
-
}
|
|
468
509
|
}
|
|
469
510
|
}
|
|
470
511
|
}
|
|
@@ -509,7 +550,7 @@ const selfCheck = (tree) => {
|
|
|
509
550
|
const log = [];
|
|
510
551
|
const seenNodes = new Set();
|
|
511
552
|
const parents = new Set();
|
|
512
|
-
const checkNode = (node, parentDeps) => {
|
|
553
|
+
const checkNode = (node, parentDeps, parent) => {
|
|
513
554
|
if (seenNodes.has(node))
|
|
514
555
|
return;
|
|
515
556
|
seenNodes.add(node);
|
|
@@ -529,23 +570,28 @@ const selfCheck = (tree) => {
|
|
|
529
570
|
}
|
|
530
571
|
}
|
|
531
572
|
else {
|
|
573
|
+
const hoistedFrom = parent.hoistedFrom.get(node.name);
|
|
574
|
+
const originalHoistedTo = node.hoistedTo.get(origDep.name);
|
|
575
|
+
const prettyHoistedFrom = `${hoistedFrom ? ` hoisted from ${hoistedFrom.join(`, `)}` : ``}`;
|
|
576
|
+
const prettyOriginalHoistedTo = `${originalHoistedTo ? ` hoisted to ${originalHoistedTo}` : ``}`;
|
|
577
|
+
const prettyNodePath = `${prettyPrintTreePath()}${prettyHoistedFrom}`;
|
|
532
578
|
if (!dep) {
|
|
533
|
-
log.push(`${
|
|
579
|
+
log.push(`${prettyNodePath} - broken require promise: no required dependency ${origDep.name}${prettyOriginalHoistedTo} found`);
|
|
534
580
|
}
|
|
535
581
|
else if (dep.ident !== origDep.ident) {
|
|
536
|
-
log.push(`${
|
|
582
|
+
log.push(`${prettyNodePath} - broken require promise for ${origDep.name}${prettyOriginalHoistedTo}: expected ${origDep.ident}, but found: ${dep.ident}`);
|
|
537
583
|
}
|
|
538
584
|
}
|
|
539
585
|
}
|
|
540
586
|
parents.add(node);
|
|
541
587
|
for (const dep of node.dependencies.values()) {
|
|
542
588
|
if (!node.peerNames.has(dep.name)) {
|
|
543
|
-
checkNode(dep, dependencies);
|
|
589
|
+
checkNode(dep, dependencies, node);
|
|
544
590
|
}
|
|
545
591
|
}
|
|
546
592
|
parents.delete(node);
|
|
547
593
|
};
|
|
548
|
-
checkNode(tree, tree.dependencies);
|
|
594
|
+
checkNode(tree, tree.dependencies, tree);
|
|
549
595
|
return log.join(`\n`);
|
|
550
596
|
};
|
|
551
597
|
/**
|
|
@@ -567,14 +613,17 @@ const cloneTree = (tree, options) => {
|
|
|
567
613
|
reasons: new Map(),
|
|
568
614
|
decoupled: true,
|
|
569
615
|
isHoistBorder: true,
|
|
570
|
-
|
|
616
|
+
hoistPriority: 0,
|
|
617
|
+
isWorkspace: true,
|
|
618
|
+
hoistedFrom: new Map(),
|
|
619
|
+
hoistedTo: new Map(),
|
|
571
620
|
};
|
|
572
621
|
const seenNodes = new Map([[tree, treeCopy]]);
|
|
573
622
|
const addNode = (node, parentNode) => {
|
|
574
623
|
let workNode = seenNodes.get(node);
|
|
575
624
|
const isSeen = !!workNode;
|
|
576
625
|
if (!workNode) {
|
|
577
|
-
const { name, identName, reference, peerNames } = node;
|
|
626
|
+
const { name, identName, reference, peerNames, hoistPriority, isWorkspace } = node;
|
|
578
627
|
const dependenciesNmHoistingLimits = options.hoistingLimits.get(parentNode.locator);
|
|
579
628
|
workNode = {
|
|
580
629
|
name,
|
|
@@ -588,7 +637,10 @@ const cloneTree = (tree, options) => {
|
|
|
588
637
|
reasons: new Map(),
|
|
589
638
|
decoupled: true,
|
|
590
639
|
isHoistBorder: dependenciesNmHoistingLimits ? dependenciesNmHoistingLimits.has(name) : false,
|
|
591
|
-
|
|
640
|
+
hoistPriority: hoistPriority || 0,
|
|
641
|
+
isWorkspace: isWorkspace || false,
|
|
642
|
+
hoistedFrom: new Map(),
|
|
643
|
+
hoistedTo: new Map(),
|
|
592
644
|
};
|
|
593
645
|
seenNodes.set(node, workNode);
|
|
594
646
|
}
|
|
@@ -679,7 +731,7 @@ const buildPreferenceMap = (rootNode) => {
|
|
|
679
731
|
const key = getPreferenceKey(node);
|
|
680
732
|
let entry = preferenceMap.get(key);
|
|
681
733
|
if (!entry) {
|
|
682
|
-
entry = { dependents: new Set(), peerDependents: new Set() };
|
|
734
|
+
entry = { dependents: new Set(), peerDependents: new Set(), hoistPriority: 0 };
|
|
683
735
|
preferenceMap.set(key, entry);
|
|
684
736
|
}
|
|
685
737
|
return entry;
|
|
@@ -691,8 +743,9 @@ const buildPreferenceMap = (rootNode) => {
|
|
|
691
743
|
if (!isSeen) {
|
|
692
744
|
seenNodes.add(node);
|
|
693
745
|
for (const dep of node.dependencies.values()) {
|
|
746
|
+
const entry = getOrCreatePreferenceEntry(dep);
|
|
747
|
+
entry.hoistPriority = Math.max(entry.hoistPriority, dep.hoistPriority);
|
|
694
748
|
if (node.peerNames.has(dep.name)) {
|
|
695
|
-
const entry = getOrCreatePreferenceEntry(dep);
|
|
696
749
|
entry.peerDependents.add(node.ident);
|
|
697
750
|
}
|
|
698
751
|
else {
|
|
@@ -707,6 +760,8 @@ const buildPreferenceMap = (rootNode) => {
|
|
|
707
760
|
return preferenceMap;
|
|
708
761
|
};
|
|
709
762
|
const prettyPrintLocator = (locator) => {
|
|
763
|
+
if (!locator)
|
|
764
|
+
return `none`;
|
|
710
765
|
const idx = locator.indexOf(`@`, 1);
|
|
711
766
|
let name = locator.substring(0, idx);
|
|
712
767
|
if (name.endsWith(`$wsroot$`))
|
|
@@ -754,7 +809,8 @@ const dumpDepTree = (tree) => {
|
|
|
754
809
|
if (!pkg.peerNames.has(dep.name) && dep !== pkg) {
|
|
755
810
|
const reason = pkg.reasons.get(dep.name);
|
|
756
811
|
const identName = getIdentName(dep.locator);
|
|
757
|
-
|
|
812
|
+
const hoistedFrom = pkg.hoistedFrom.get(dep.name) || [];
|
|
813
|
+
str += `${prefix}${idx < dependencies.length - 1 ? `├─` : `└─`}${(parents.has(dep) ? `>` : ``) + (identName !== dep.name ? `a:${dep.name}:` : ``) + prettyPrintLocator(dep.locator) + (reason ? ` ${reason}` : ``) + (dep !== pkg && hoistedFrom.length > 0 ? `, hoisted from: ${hoistedFrom.join(`, `)}` : ``)}\n`;
|
|
758
814
|
str += dumpPackage(dep, parents, `${prefix}${idx < dependencies.length - 1 ? `│ ` : ` `}`);
|
|
759
815
|
}
|
|
760
816
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yarnpkg/nm",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-rc.11",
|
|
4
4
|
"license": "BSD-2-Clause",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@yarnpkg/core": "^3.
|
|
10
|
-
"@yarnpkg/fslib": "^2.
|
|
9
|
+
"@yarnpkg/core": "^3.1.0-rc.11",
|
|
10
|
+
"@yarnpkg/fslib": "^2.6.0-rc.10"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@yarnpkg/pnp": "3.
|
|
13
|
+
"@yarnpkg/pnp": "^3.1.0-rc.7"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"postpack": "rm -rf lib",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=12 <14 || 14.2 - 14.9 || >14.10.0"
|
|
35
35
|
},
|
|
36
|
-
"stableVersion": "2.4.
|
|
36
|
+
"stableVersion": "2.4.1"
|
|
37
37
|
}
|