@yarnpkg/nm 2.4.1-rc.3 → 3.0.0-rc.10

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.
@@ -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 workspaceDependenciesMap = new Map();
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 isExternalSoftLink = (pkg, locator) => {
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
- peerNames: pkg.packagePeers,
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
- if (isHoistBorder && !isExternalSoftLink(pkg, locator)) {
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,16 @@ const buildPackageTree = (pnp, options) => {
207
246
  }
208
247
  }
209
248
  }
210
- const locatorKey = stringifyLocator(locator);
211
- const workspaceDependencies = workspaceDependenciesMap.get(locatorKey);
212
- if (workspaceDependencies) {
213
- for (const workspaceLocator of workspaceDependencies) {
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
256
  parent.dependencies.add(node);
218
- if (!isSeen) {
257
+ const isWorkspaceDependency = locator !== topLocator && pkg.linkType === LinkType.SOFT && !locator.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isExternalSoftLinkPackage;
258
+ if (!isSeen && !isWorkspaceDependency) {
219
259
  const siblingPortalDependencyMap = new Map();
220
260
  for (const [depName, referencish] of allDependencies) {
221
261
  if (referencish !== null) {
@@ -224,7 +264,7 @@ const buildPackageTree = (pnp, options) => {
224
264
  const depPkg = pnp.getPackageInformation(pkgLocator);
225
265
  if (depPkg === null)
226
266
  throw new Error(`Assertion failed: Expected the package to have been registered`);
227
- const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator);
267
+ const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator, pnp, topPkgPortableLocation);
228
268
  if (options.validateExternalSoftLinks && options.project && isExternalSoftLinkDep) {
229
269
  if (depPkg.packageDependencies.size > 0)
230
270
  preserveSymlinksRequired = true;
@@ -276,14 +316,20 @@ const buildPackageTree = (pnp, options) => {
276
316
  const isHoistBorder = parentHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
277
317
  || depHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
278
318
  || depHoistingLimits === NodeModulesHoistingLimits.WORKSPACES;
279
- addPackageToTree(stringifyLocator(depLocator) === stringifyLocator(locator) ? name : depName, depPkg, depLocator, node, allDependencies, relativeDepCwd, isHoistBorder);
319
+ addPackageToTree(stringifyLocator(depLocator) === stringifyLocator(locator) ? name : depName, depPkg, depLocator, node, pkg, allDependencies, relativeDepCwd, isHoistBorder);
280
320
  }
281
321
  }
282
322
  }
283
323
  };
284
- addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
324
+ addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
285
325
  return { packageTree, hoistingLimits, errors, preserveSymlinksRequired };
286
326
  };
327
+ function getRealPackageLocation(pkg, locator, pnp) {
328
+ const realPath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
329
+ ? pnp.resolveVirtual(pkg.packageLocation)
330
+ : pkg.packageLocation;
331
+ return fslib_1.npath.toPortablePath(realPath || pkg.packageLocation);
332
+ }
287
333
  function getTargetLocatorPath(locator, pnp, options) {
288
334
  const pkgLocator = pnp.getLocator(locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), locator.reference);
289
335
  const info = pnp.getPackageInformation(pkgLocator);
@@ -301,10 +347,7 @@ function getTargetLocatorPath(locator, pnp, options) {
301
347
  linkType = LinkType.SOFT;
302
348
  }
303
349
  else {
304
- const truePath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
305
- ? pnp.resolveVirtual(info.packageLocation)
306
- : info.packageLocation;
307
- target = fslib_1.npath.toPortablePath(truePath || info.packageLocation);
350
+ target = getRealPackageLocation(info, locator, pnp);
308
351
  linkType = info.linkType;
309
352
  }
310
353
  return { linkType, target };
@@ -334,11 +377,11 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
334
377
  const getPackageName = (identName) => {
335
378
  const [nameOrScope, name] = identName.split(`/`);
336
379
  return name ? {
337
- scope: fslib_1.toFilename(nameOrScope),
338
- name: fslib_1.toFilename(name),
380
+ scope: (0, fslib_1.toFilename)(nameOrScope),
381
+ name: (0, fslib_1.toFilename)(name),
339
382
  } : {
340
383
  scope: null,
341
- name: fslib_1.toFilename(nameOrScope),
384
+ name: (0, fslib_1.toFilename)(nameOrScope),
342
385
  };
343
386
  };
344
387
  const seenNodes = new Set();
@@ -360,7 +403,13 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
360
403
  const nodeModulesLocation = fslib_1.ppath.join(nodeModulesDirPath, ...packageNameParts);
361
404
  const nodePath = `${parentNodePath}/${locator.name}`;
362
405
  const leafNode = makeLeafNode(locator, parentNodePath, references.slice(1));
363
- if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX)) {
406
+ // We don't want to create self-referencing symlinks for anonymous workspaces
407
+ let isAnonymousWorkspace = false;
408
+ if (leafNode.linkType === LinkType.SOFT && options.project) {
409
+ const workspace = options.project.workspacesByCwd.get(leafNode.target.slice(0, -1));
410
+ isAnonymousWorkspace = !!(workspace && !workspace.manifest.name);
411
+ }
412
+ if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isAnonymousWorkspace) {
364
413
  const prevNode = tree.get(nodeModulesLocation);
365
414
  if (prevNode) {
366
415
  if (prevNode.dirList) {
@@ -382,7 +431,7 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
382
431
  let segCount = segments.length - 1;
383
432
  while (nodeModulesIdx >= 0 && segCount > nodeModulesIdx) {
384
433
  const dirPath = fslib_1.npath.toPortablePath(segments.slice(0, segCount).join(fslib_1.ppath.sep));
385
- const targetDir = fslib_1.toFilename(segments[segCount]);
434
+ const targetDir = (0, fslib_1.toFilename)(segments[segCount]);
386
435
  const subdirs = tree.get(dirPath);
387
436
  if (!subdirs) {
388
437
  tree.set(dirPath, { dirList: new Set([targetDir]) });
@@ -422,7 +471,7 @@ const benchmarkRawHoisting = (packageTree) => {
422
471
  const iterCount = 10;
423
472
  const startTime = Date.now();
424
473
  for (let iter = 0; iter < iterCount; iter++)
425
- hoist_1.hoist(packageTree);
474
+ (0, hoist_1.hoist)(packageTree);
426
475
  const endTime = Date.now();
427
476
  return (endTime - startTime) / iterCount;
428
477
  };
@@ -442,7 +491,7 @@ const benchmarkBuildTree = (pnp, options) => {
442
491
  const startTime = Date.now();
443
492
  for (let iter = 0; iter < iterCount; iter++) {
444
493
  const { packageTree, hoistingLimits } = buildPackageTree(pnp, options);
445
- const hoistedTree = hoist_1.hoist(packageTree, { hoistingLimits });
494
+ const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
446
495
  populateNodeModulesTree(pnp, hoistedTree, options);
447
496
  }
448
497
  const endTime = Date.now();
package/lib/hoist.d.ts CHANGED
@@ -52,6 +52,8 @@ export declare type HoisterTree = {
52
52
  reference: string;
53
53
  dependencies: Set<HoisterTree>;
54
54
  peerNames: Set<PackageName>;
55
+ hoistPriority?: number;
56
+ isWorkspace?: boolean;
55
57
  };
56
58
  export declare type HoisterResult = {
57
59
  name: PackageName;
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
- const reachableDependency = reachableDependencies.get(dep.name);
103
- if (reachableDependency) {
104
- usedDependencies.set(reachableDependency.name, reachableDependency);
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
- hoistedFrom: [],
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.peerDependents.size !== entry1.peerDependents.size) {
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
- let hoistedFrom = null;
450
- if (options.debugLevel >= DebugLevel.REASONS)
451
- hoistedFrom = Array.from(locatorPath).concat([parentNode.locator]).map(x => prettyPrintLocator(x)).join(`→`);
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(`${prettyPrintTreePath()} - broken require promise: no required dependency ${origDep.locator} found`);
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(`${prettyPrintTreePath()} - broken require promise for ${origDep.name}: expected ${origDep.ident}, but found: ${dep.ident}`);
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
- hoistedFrom: [],
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
- hoistedFrom: [],
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
- str += `${prefix}${idx < dependencies.length - 1 ? `├─` : `└─`}${(parents.has(dep) ? `>` : ``) + (identName !== dep.name ? `a:${dep.name}:` : ``) + prettyPrintLocator(dep.locator) + (reason ? ` ${reason}` : ``) + (dep !== pkg && dep.hoistedFrom.length > 0 ? `, hoisted from: ${dep.hoistedFrom.join(`, `)}` : ``)}\n`;
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": "2.4.1-rc.3",
3
+ "version": "3.0.0-rc.10",
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.0.0-rc.12",
10
- "@yarnpkg/fslib": "^2.5.0-rc.12"
9
+ "@yarnpkg/core": "^3.1.0-rc.10",
10
+ "@yarnpkg/fslib": "^2.6.0-rc.9"
11
11
  },
12
12
  "devDependencies": {
13
- "@yarnpkg/pnp": "3.0.0-rc.12"
13
+ "@yarnpkg/pnp": "^3.1.0-rc.6"
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.0"
36
+ "stableVersion": "2.4.1"
37
37
  }