@yarnpkg/nm 3.0.0-rc.7 → 3.0.1-rc.1

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.
@@ -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 */
@@ -84,88 +84,120 @@ 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
+ };
87
93
  /**
88
- * Traverses PnP tree and produces input for the `RawHoister`
89
- *
90
- * @param pnp PnP API
91
- *
92
- * @returns package tree, packages info and locators
94
+ * Builds a map representing layout of nested workspaces and internal portals on the file system.
93
95
  */
94
- const buildPackageTree = (pnp, options) => {
95
- const pnpRoots = pnp.getDependencyTreeRoots();
96
- const errors = [];
97
- let preserveSymlinksRequired = false;
98
- const hoistingLimits = new Map();
99
- const workspaceDependenciesMap = new Map();
96
+ const buildWorkspaceMap = (pnp) => {
100
97
  const topPkg = pnp.getPackageInformation(pnp.topLevel);
101
98
  if (topPkg === null)
102
99
  throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
103
100
  const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
104
101
  if (topLocator === null)
105
102
  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);
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, parentLocator) => {
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
+ const parentLocatorKey = parentLocator ? stringifyLocator(parentLocator) : ``;
118
+ if (stringifyLocator(locator) !== parentLocatorKey && pkg.linkType === LinkType.SOFT && !isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation)) {
119
+ const location = getRealPackageLocation(pkg, locator, pnp);
120
+ const prevLocator = workspaceLikeLocators.get(location);
121
+ // Give workspaces a priority over portals and other protocols pointing to the same location
122
+ // The devDependencies are not installed for portals, but installed for workspaces
123
+ if (!prevLocator || locator.reference.startsWith(`workspace:`)) {
124
+ workspaceLikeLocators.set(location, locator);
119
125
  }
120
- node = nextNode;
121
126
  }
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);
127
+ for (const [name, referencish] of pkg.packageDependencies) {
128
+ if (referencish !== null) {
129
+ if (!pkg.packagePeers.has(name)) {
130
+ visit(pnp.getLocator(name, referencish), locator);
131
+ }
131
132
  }
132
- dependencies.add(node.workspaceLocator);
133
133
  }
134
- for (const child of node.children.values()) {
135
- addWorkspace(child, node.workspaceLocator || parentWorkspaceLocator);
134
+ }
135
+ };
136
+ for (const locator of pnpRoots)
137
+ visit(locator, null);
138
+ const cwdSegments = topPkgPortableLocation.split(fslib_1.ppath.sep);
139
+ for (const locator of workspaceLikeLocators.values()) {
140
+ const pkg = pnp.getPackageInformation(locator);
141
+ const location = fslib_1.npath.toPortablePath(pkg.packageLocation.slice(0, -1));
142
+ const segments = location.split(fslib_1.ppath.sep).slice(cwdSegments.length);
143
+ let node = workspaceTree;
144
+ for (const segment of segments) {
145
+ let nextNode = node.children.get(segment);
146
+ if (!nextNode) {
147
+ nextNode = { children: new Map() };
148
+ node.children.set(segment, nextNode);
136
149
  }
137
- };
138
- for (const child of workspaceTree.children.values()) {
139
- addWorkspace(child, workspaceTree.workspaceLocator);
150
+ node = nextNode;
140
151
  }
152
+ node.workspaceLocator = locator;
141
153
  }
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);
154
+ const addWorkspace = (node, parentWorkspaceLocator) => {
155
+ if (node.workspaceLocator) {
156
+ const parentLocatorKey = stringifyLocator(parentWorkspaceLocator);
157
+ let dependencies = workspaceMap.get(parentLocatorKey);
158
+ if (!dependencies) {
159
+ dependencies = new Set();
160
+ workspaceMap.set(parentLocatorKey, dependencies);
151
161
  }
162
+ dependencies.add(node.workspaceLocator);
152
163
  }
153
- }
164
+ for (const child of node.children.values()) {
165
+ addWorkspace(child, node.workspaceLocator || parentWorkspaceLocator);
166
+ }
167
+ };
168
+ for (const child of workspaceTree.children.values())
169
+ addWorkspace(child, workspaceTree.workspaceLocator);
170
+ return workspaceMap;
171
+ };
172
+ /**
173
+ * Traverses PnP tree and produces input for the `RawHoister`
174
+ *
175
+ * @param pnp PnP API
176
+ *
177
+ * @returns package tree, packages info and locators
178
+ */
179
+ const buildPackageTree = (pnp, options) => {
180
+ const errors = [];
181
+ let preserveSymlinksRequired = false;
182
+ const hoistingLimits = new Map();
183
+ const workspaceMap = buildWorkspaceMap(pnp);
184
+ const topPkg = pnp.getPackageInformation(pnp.topLevel);
185
+ if (topPkg === null)
186
+ throw new Error(`Assertion failed: Expected the top-level package to have been registered`);
187
+ const topLocator = pnp.findPackageLocator(topPkg.packageLocation);
188
+ if (topLocator === null)
189
+ throw new Error(`Assertion failed: Expected the top-level package to have a physical locator`);
190
+ const topPkgPortableLocation = fslib_1.npath.toPortablePath(topPkg.packageLocation.slice(0, -1));
154
191
  const packageTree = {
155
192
  name: topLocator.name,
156
193
  identName: topLocator.name,
157
194
  reference: topLocator.reference,
158
195
  peerNames: topPkg.packagePeers,
159
196
  dependencies: new Set(),
197
+ isWorkspace: true,
160
198
  };
161
199
  const nodes = new Map();
162
200
  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
201
  const addPackageToTree = (name, pkg, locator, parent, parentPkg, parentDependencies, parentRelativeCwd, isHoistBorder) => {
170
202
  var _a, _b;
171
203
  const nodeKey = getNodeKey(name, locator);
@@ -175,18 +207,22 @@ const buildPackageTree = (pnp, options) => {
175
207
  node = packageTree;
176
208
  nodes.set(nodeKey, packageTree);
177
209
  }
210
+ const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation);
178
211
  if (!node) {
212
+ const isWorkspace = pkg.linkType === LinkType.SOFT && locator.name.endsWith(WORKSPACE_NAME_SUFFIX);
179
213
  node = {
180
214
  name,
181
215
  identName: locator.name,
182
216
  reference: locator.reference,
183
217
  dependencies: new Set(),
184
- peerNames: pkg.packagePeers,
218
+ // View peer dependencies as regular dependencies for workspaces
219
+ // (meeting workspace peer dependency constraints is sometimes hard, sometimes impossible for the nm linker)
220
+ peerNames: isWorkspace ? new Set() : pkg.packagePeers,
221
+ isWorkspace,
185
222
  };
186
223
  nodes.set(nodeKey, node);
187
224
  }
188
225
  let hoistPriority;
189
- const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator);
190
226
  if (isExternalSoftLinkPackage)
191
227
  // External soft link dependencies have the highest priority - we don't want to install inside them
192
228
  hoistPriority = 2;
@@ -218,15 +254,17 @@ const buildPackageTree = (pnp, options) => {
218
254
  }
219
255
  }
220
256
  }
221
- const locatorKey = stringifyLocator(locator);
222
- const workspaceDependencies = workspaceDependenciesMap.get(locatorKey);
223
- if (workspaceDependencies) {
224
- for (const workspaceLocator of workspaceDependencies) {
257
+ const locatorKey = stringifyLocator({ name: locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), reference: locator.reference });
258
+ const innerWorkspaces = workspaceMap.get(locatorKey);
259
+ if (innerWorkspaces) {
260
+ for (const workspaceLocator of innerWorkspaces) {
225
261
  allDependencies.set(`${workspaceLocator.name}${WORKSPACE_NAME_SUFFIX}`, workspaceLocator.reference);
226
262
  }
227
263
  }
228
- parent.dependencies.add(node);
229
- if (!isSeen) {
264
+ if (pkg !== parentPkg || pkg.linkType !== LinkType.SOFT || !options.selfReferencesByCwd || options.selfReferencesByCwd.get(parentRelativeCwd))
265
+ parent.dependencies.add(node);
266
+ const isWorkspaceDependency = locator !== topLocator && pkg.linkType === LinkType.SOFT && !locator.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isExternalSoftLinkPackage;
267
+ if (!isSeen && !isWorkspaceDependency) {
230
268
  const siblingPortalDependencyMap = new Map();
231
269
  for (const [depName, referencish] of allDependencies) {
232
270
  if (referencish !== null) {
@@ -235,7 +273,7 @@ const buildPackageTree = (pnp, options) => {
235
273
  const depPkg = pnp.getPackageInformation(pkgLocator);
236
274
  if (depPkg === null)
237
275
  throw new Error(`Assertion failed: Expected the package to have been registered`);
238
- const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator);
276
+ const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator, pnp, topPkgPortableLocation);
239
277
  if (options.validateExternalSoftLinks && options.project && isExternalSoftLinkDep) {
240
278
  if (depPkg.packageDependencies.size > 0)
241
279
  preserveSymlinksRequired = true;
@@ -287,7 +325,7 @@ const buildPackageTree = (pnp, options) => {
287
325
  const isHoistBorder = parentHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
288
326
  || depHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
289
327
  || depHoistingLimits === NodeModulesHoistingLimits.WORKSPACES;
290
- addPackageToTree(stringifyLocator(depLocator) === stringifyLocator(locator) ? name : depName, depPkg, depLocator, node, pkg, allDependencies, relativeDepCwd, isHoistBorder);
328
+ addPackageToTree(depName, depPkg, depLocator, node, pkg, allDependencies, relativeDepCwd, isHoistBorder);
291
329
  }
292
330
  }
293
331
  }
@@ -295,6 +333,12 @@ const buildPackageTree = (pnp, options) => {
295
333
  addPackageToTree(topLocator.name, topPkg, topLocator, packageTree, topPkg, topPkg.packageDependencies, fslib_2.PortablePath.dot, false);
296
334
  return { packageTree, hoistingLimits, errors, preserveSymlinksRequired };
297
335
  };
336
+ function getRealPackageLocation(pkg, locator, pnp) {
337
+ const realPath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
338
+ ? pnp.resolveVirtual(pkg.packageLocation)
339
+ : pkg.packageLocation;
340
+ return fslib_1.npath.toPortablePath(realPath || pkg.packageLocation);
341
+ }
298
342
  function getTargetLocatorPath(locator, pnp, options) {
299
343
  const pkgLocator = pnp.getLocator(locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), locator.reference);
300
344
  const info = pnp.getPackageInformation(pkgLocator);
@@ -312,10 +356,7 @@ function getTargetLocatorPath(locator, pnp, options) {
312
356
  linkType = LinkType.SOFT;
313
357
  }
314
358
  else {
315
- const truePath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
316
- ? pnp.resolveVirtual(info.packageLocation)
317
- : info.packageLocation;
318
- target = fslib_1.npath.toPortablePath(truePath || info.packageLocation);
359
+ target = getRealPackageLocation(info, locator, pnp);
319
360
  linkType = info.linkType;
320
361
  }
321
362
  return { linkType, target };
@@ -371,7 +412,13 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
371
412
  const nodeModulesLocation = fslib_1.ppath.join(nodeModulesDirPath, ...packageNameParts);
372
413
  const nodePath = `${parentNodePath}/${locator.name}`;
373
414
  const leafNode = makeLeafNode(locator, parentNodePath, references.slice(1));
374
- if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX)) {
415
+ // We don't want to create self-referencing symlinks for anonymous workspaces
416
+ let isAnonymousWorkspace = false;
417
+ if (leafNode.linkType === LinkType.SOFT && options.project) {
418
+ const workspace = options.project.workspacesByCwd.get(leafNode.target.slice(0, -1));
419
+ isAnonymousWorkspace = !!(workspace && !workspace.manifest.name);
420
+ }
421
+ if (!dep.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isAnonymousWorkspace) {
375
422
  const prevNode = tree.get(nodeModulesLocation);
376
423
  if (prevNode) {
377
424
  if (prevNode.dirList) {
package/lib/hoist.d.ts CHANGED
@@ -53,6 +53,7 @@ export declare type HoisterTree = {
53
53
  dependencies: Set<HoisterTree>;
54
54
  peerNames: Set<PackageName>;
55
55
  hoistPriority?: number;
56
+ isWorkspace?: boolean;
56
57
  };
57
58
  export declare type HoisterResult = {
58
59
  name: PackageName;
package/lib/hoist.js CHANGED
@@ -134,7 +134,7 @@ const getUsedDependencies = (rootNodePath) => {
134
134
  const decoupleGraphNode = (parent, node) => {
135
135
  if (node.decoupled)
136
136
  return node;
137
- const { name, references, ident, locator, dependencies, originalDependencies, hoistedDependencies, peerNames, reasons, isHoistBorder, hoistPriority } = node;
137
+ const { name, references, ident, locator, dependencies, originalDependencies, hoistedDependencies, peerNames, reasons, isHoistBorder, hoistPriority, isWorkspace, hoistedFrom, hoistedTo } = node;
138
138
  // To perform node hoisting from parent node we must clone parent nodes up to the root node,
139
139
  // because some other package in the tree might depend on the parent package where hoisting
140
140
  // cannot be performed
@@ -151,7 +151,9 @@ const decoupleGraphNode = (parent, node) => {
151
151
  decoupled: true,
152
152
  isHoistBorder,
153
153
  hoistPriority,
154
- hoistedFrom: [],
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)
@@ -308,7 +310,7 @@ const hoistTo = (tree, rootNodePath, rootNodePathLocators, parentShadowedNodes,
308
310
  }
309
311
  return { anotherRoundNeeded, isGraphChanged };
310
312
  };
311
- const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason }) => {
313
+ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason, fastLookupPossible }) => {
312
314
  let reasonRoot;
313
315
  let reason = null;
314
316
  let dependsOn = new Set();
@@ -320,6 +322,29 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
320
322
  let isHoistable = !isSelfReference;
321
323
  if (outputReason && !isHoistable)
322
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
+ }
323
348
  if (isHoistable) {
324
349
  isHoistable = !rootNode.peerNames.has(node.name);
325
350
  if (outputReason && !isHoistable) {
@@ -388,6 +413,17 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
388
413
  }
389
414
  isHoistable = arePeerDepsSatisfied;
390
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
+ }
391
427
  if (dependsOn !== null && dependsOn.size > 0) {
392
428
  return { isHoistable: Hoistable.DEPENDS, dependsOn, reason };
393
429
  }
@@ -416,7 +452,7 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
416
452
  const dependantTree = new Map();
417
453
  const hoistInfos = new Map();
418
454
  for (const subDependency of getSortedRegularDependencies(parentNode)) {
419
- 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 });
420
456
  hoistInfos.set(subDependency, hoistInfo);
421
457
  if (hoistInfo.isHoistable === Hoistable.DEPENDS) {
422
458
  for (const node of hoistInfo.dependsOn) {
@@ -449,25 +485,27 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
449
485
  parentNode.hoistedDependencies.set(node.name, node);
450
486
  parentNode.reasons.delete(node.name);
451
487
  const hoistedNode = rootNode.dependencies.get(node.name);
452
- let hoistedFrom = null;
453
- if (options.debugLevel >= DebugLevel.REASONS)
454
- 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
+ }
455
498
  // Add hoisted node to root node, in case it is not already there
456
499
  if (!hoistedNode) {
457
500
  // Avoid adding other version of root node to itself
458
501
  if (rootNode.ident !== node.ident) {
459
502
  rootNode.dependencies.set(node.name, node);
460
- if (options.debugLevel >= DebugLevel.REASONS)
461
- node.hoistedFrom.push(hoistedFrom);
462
503
  newNodes.add(node);
463
504
  }
464
505
  }
465
506
  else {
466
507
  for (const reference of node.references) {
467
508
  hoistedNode.references.add(reference);
468
- if (options.debugLevel >= DebugLevel.REASONS) {
469
- hoistedNode.hoistedFrom.push(hoistedFrom);
470
- }
471
509
  }
472
510
  }
473
511
  }
@@ -512,7 +550,7 @@ const selfCheck = (tree) => {
512
550
  const log = [];
513
551
  const seenNodes = new Set();
514
552
  const parents = new Set();
515
- const checkNode = (node, parentDeps) => {
553
+ const checkNode = (node, parentDeps, parent) => {
516
554
  if (seenNodes.has(node))
517
555
  return;
518
556
  seenNodes.add(node);
@@ -532,23 +570,28 @@ const selfCheck = (tree) => {
532
570
  }
533
571
  }
534
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}`;
535
578
  if (!dep) {
536
- 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`);
537
580
  }
538
581
  else if (dep.ident !== origDep.ident) {
539
- 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}`);
540
583
  }
541
584
  }
542
585
  }
543
586
  parents.add(node);
544
587
  for (const dep of node.dependencies.values()) {
545
588
  if (!node.peerNames.has(dep.name)) {
546
- checkNode(dep, dependencies);
589
+ checkNode(dep, dependencies, node);
547
590
  }
548
591
  }
549
592
  parents.delete(node);
550
593
  };
551
- checkNode(tree, tree.dependencies);
594
+ checkNode(tree, tree.dependencies, tree);
552
595
  return log.join(`\n`);
553
596
  };
554
597
  /**
@@ -571,14 +614,16 @@ const cloneTree = (tree, options) => {
571
614
  decoupled: true,
572
615
  isHoistBorder: true,
573
616
  hoistPriority: 0,
574
- hoistedFrom: [],
617
+ isWorkspace: true,
618
+ hoistedFrom: new Map(),
619
+ hoistedTo: new Map(),
575
620
  };
576
621
  const seenNodes = new Map([[tree, treeCopy]]);
577
622
  const addNode = (node, parentNode) => {
578
623
  let workNode = seenNodes.get(node);
579
624
  const isSeen = !!workNode;
580
625
  if (!workNode) {
581
- const { name, identName, reference, peerNames, hoistPriority } = node;
626
+ const { name, identName, reference, peerNames, hoistPriority, isWorkspace } = node;
582
627
  const dependenciesNmHoistingLimits = options.hoistingLimits.get(parentNode.locator);
583
628
  workNode = {
584
629
  name,
@@ -593,7 +638,9 @@ const cloneTree = (tree, options) => {
593
638
  decoupled: true,
594
639
  isHoistBorder: dependenciesNmHoistingLimits ? dependenciesNmHoistingLimits.has(name) : false,
595
640
  hoistPriority: hoistPriority || 0,
596
- hoistedFrom: [],
641
+ isWorkspace: isWorkspace || false,
642
+ hoistedFrom: new Map(),
643
+ hoistedTo: new Map(),
597
644
  };
598
645
  seenNodes.set(node, workNode);
599
646
  }
@@ -713,6 +760,8 @@ const buildPreferenceMap = (rootNode) => {
713
760
  return preferenceMap;
714
761
  };
715
762
  const prettyPrintLocator = (locator) => {
763
+ if (!locator)
764
+ return `none`;
716
765
  const idx = locator.indexOf(`@`, 1);
717
766
  let name = locator.substring(0, idx);
718
767
  if (name.endsWith(`$wsroot$`))
@@ -760,7 +809,8 @@ const dumpDepTree = (tree) => {
760
809
  if (!pkg.peerNames.has(dep.name) && dep !== pkg) {
761
810
  const reason = pkg.reasons.get(dep.name);
762
811
  const identName = getIdentName(dep.locator);
763
- 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`;
764
814
  str += dumpPackage(dep, parents, `${prefix}${idx < dependencies.length - 1 ? `│ ` : ` `}`);
765
815
  }
766
816
  }
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@yarnpkg/nm",
3
- "version": "3.0.0-rc.7",
3
+ "version": "3.0.1-rc.1",
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.1.0-rc.7",
10
- "@yarnpkg/fslib": "^2.6.0-rc.6"
9
+ "@yarnpkg/core": "^3.2.0-rc.1",
10
+ "@yarnpkg/fslib": "^2.6.0"
11
11
  },
12
12
  "devDependencies": {
13
- "@yarnpkg/pnp": "^3.1.0-rc.3"
13
+ "@yarnpkg/pnp": "^3.1.1-rc.1"
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.1"
36
+ "stableVersion": "3.0.0"
37
37
  }