@yarnpkg/nm 3.0.0-rc.1 → 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,18 +199,31 @@ const buildPackageTree = (pnp, options) => {
175
199
  node = packageTree;
176
200
  nodes.set(nodeKey, packageTree);
177
201
  }
178
- const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator);
202
+ const isExternalSoftLinkPackage = isExternalSoftLink(pkg, locator, pnp, topPkgPortableLocation);
179
203
  if (!node) {
204
+ const isWorkspace = pkg.linkType === LinkType.SOFT && locator.name.endsWith(WORKSPACE_NAME_SUFFIX);
180
205
  node = {
181
206
  name,
182
207
  identName: locator.name,
183
208
  reference: locator.reference,
184
209
  dependencies: new Set(),
185
- peerNames: pkg.packagePeers,
186
- hoistPriority: isExternalSoftLinkPackage ? 1 : 0,
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,
187
214
  };
188
215
  nodes.set(nodeKey, node);
189
216
  }
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);
190
227
  if (isHoistBorder && !isExternalSoftLinkPackage) {
191
228
  const parentLocatorKey = stringifyLocator({ name: parent.identName, reference: parent.reference });
192
229
  const dependencyBorders = hoistingLimits.get(parentLocatorKey) || new Set();
@@ -209,15 +246,16 @@ const buildPackageTree = (pnp, options) => {
209
246
  }
210
247
  }
211
248
  }
212
- const locatorKey = stringifyLocator(locator);
213
- const workspaceDependencies = workspaceDependenciesMap.get(locatorKey);
214
- if (workspaceDependencies) {
215
- 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) {
216
253
  allDependencies.set(`${workspaceLocator.name}${WORKSPACE_NAME_SUFFIX}`, workspaceLocator.reference);
217
254
  }
218
255
  }
219
256
  parent.dependencies.add(node);
220
- if (!isSeen) {
257
+ const isWorkspaceDependency = locator !== topLocator && pkg.linkType === LinkType.SOFT && !locator.name.endsWith(WORKSPACE_NAME_SUFFIX) && !isExternalSoftLinkPackage;
258
+ if (!isSeen && !isWorkspaceDependency) {
221
259
  const siblingPortalDependencyMap = new Map();
222
260
  for (const [depName, referencish] of allDependencies) {
223
261
  if (referencish !== null) {
@@ -226,7 +264,7 @@ const buildPackageTree = (pnp, options) => {
226
264
  const depPkg = pnp.getPackageInformation(pkgLocator);
227
265
  if (depPkg === null)
228
266
  throw new Error(`Assertion failed: Expected the package to have been registered`);
229
- const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator);
267
+ const isExternalSoftLinkDep = isExternalSoftLink(depPkg, depLocator, pnp, topPkgPortableLocation);
230
268
  if (options.validateExternalSoftLinks && options.project && isExternalSoftLinkDep) {
231
269
  if (depPkg.packageDependencies.size > 0)
232
270
  preserveSymlinksRequired = true;
@@ -278,14 +316,20 @@ const buildPackageTree = (pnp, options) => {
278
316
  const isHoistBorder = parentHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
279
317
  || depHoistingLimits === NodeModulesHoistingLimits.DEPENDENCIES
280
318
  || depHoistingLimits === NodeModulesHoistingLimits.WORKSPACES;
281
- 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);
282
320
  }
283
321
  }
284
322
  }
285
323
  };
286
- 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);
287
325
  return { packageTree, hoistingLimits, errors, preserveSymlinksRequired };
288
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
+ }
289
333
  function getTargetLocatorPath(locator, pnp, options) {
290
334
  const pkgLocator = pnp.getLocator(locator.name.replace(WORKSPACE_NAME_SUFFIX, ``), locator.reference);
291
335
  const info = pnp.getPackageInformation(pkgLocator);
@@ -303,10 +347,7 @@ function getTargetLocatorPath(locator, pnp, options) {
303
347
  linkType = LinkType.SOFT;
304
348
  }
305
349
  else {
306
- const truePath = pnp.resolveVirtual && locator.reference && locator.reference.startsWith(`virtual:`)
307
- ? pnp.resolveVirtual(info.packageLocation)
308
- : info.packageLocation;
309
- target = fslib_1.npath.toPortablePath(truePath || info.packageLocation);
350
+ target = getRealPackageLocation(info, locator, pnp);
310
351
  linkType = info.linkType;
311
352
  }
312
353
  return { linkType, target };
@@ -336,11 +377,11 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
336
377
  const getPackageName = (identName) => {
337
378
  const [nameOrScope, name] = identName.split(`/`);
338
379
  return name ? {
339
- scope: fslib_1.toFilename(nameOrScope),
340
- name: fslib_1.toFilename(name),
380
+ scope: (0, fslib_1.toFilename)(nameOrScope),
381
+ name: (0, fslib_1.toFilename)(name),
341
382
  } : {
342
383
  scope: null,
343
- name: fslib_1.toFilename(nameOrScope),
384
+ name: (0, fslib_1.toFilename)(nameOrScope),
344
385
  };
345
386
  };
346
387
  const seenNodes = new Set();
@@ -362,7 +403,13 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
362
403
  const nodeModulesLocation = fslib_1.ppath.join(nodeModulesDirPath, ...packageNameParts);
363
404
  const nodePath = `${parentNodePath}/${locator.name}`;
364
405
  const leafNode = makeLeafNode(locator, parentNodePath, references.slice(1));
365
- 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) {
366
413
  const prevNode = tree.get(nodeModulesLocation);
367
414
  if (prevNode) {
368
415
  if (prevNode.dirList) {
@@ -384,7 +431,7 @@ const populateNodeModulesTree = (pnp, hoistedTree, options) => {
384
431
  let segCount = segments.length - 1;
385
432
  while (nodeModulesIdx >= 0 && segCount > nodeModulesIdx) {
386
433
  const dirPath = fslib_1.npath.toPortablePath(segments.slice(0, segCount).join(fslib_1.ppath.sep));
387
- const targetDir = fslib_1.toFilename(segments[segCount]);
434
+ const targetDir = (0, fslib_1.toFilename)(segments[segCount]);
388
435
  const subdirs = tree.get(dirPath);
389
436
  if (!subdirs) {
390
437
  tree.set(dirPath, { dirList: new Set([targetDir]) });
@@ -424,7 +471,7 @@ const benchmarkRawHoisting = (packageTree) => {
424
471
  const iterCount = 10;
425
472
  const startTime = Date.now();
426
473
  for (let iter = 0; iter < iterCount; iter++)
427
- hoist_1.hoist(packageTree);
474
+ (0, hoist_1.hoist)(packageTree);
428
475
  const endTime = Date.now();
429
476
  return (endTime - startTime) / iterCount;
430
477
  };
@@ -444,7 +491,7 @@ const benchmarkBuildTree = (pnp, options) => {
444
491
  const startTime = Date.now();
445
492
  for (let iter = 0; iter < iterCount; iter++) {
446
493
  const { packageTree, hoistingLimits } = buildPackageTree(pnp, options);
447
- const hoistedTree = hoist_1.hoist(packageTree, { hoistingLimits });
494
+ const hoistedTree = (0, hoist_1.hoist)(packageTree, { hoistingLimits });
448
495
  populateNodeModulesTree(pnp, hoistedTree, options);
449
496
  }
450
497
  const endTime = Date.now();
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
@@ -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, hoistPriority } = 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
@@ -152,7 +151,9 @@ const decoupleGraphNode = (parent, node) => {
152
151
  decoupled: true,
153
152
  isHoistBorder,
154
153
  hoistPriority,
155
- hoistedFrom: [],
154
+ isWorkspace,
155
+ hoistedFrom: new Map(hoistedFrom),
156
+ hoistedTo: new Map(hoistedTo),
156
157
  };
157
158
  const selfDep = clone.dependencies.get(name);
158
159
  if (selfDep && selfDep.ident == clone.ident)
@@ -309,7 +310,7 @@ const hoistTo = (tree, rootNodePath, rootNodePathLocators, parentShadowedNodes,
309
310
  }
310
311
  return { anotherRoundNeeded, isGraphChanged };
311
312
  };
312
- const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason }) => {
313
+ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDependencies, hoistIdents, hoistIdentMap, shadowedNodes, { outputReason, fastLookupPossible }) => {
313
314
  let reasonRoot;
314
315
  let reason = null;
315
316
  let dependsOn = new Set();
@@ -321,6 +322,29 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
321
322
  let isHoistable = !isSelfReference;
322
323
  if (outputReason && !isHoistable)
323
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
+ }
324
348
  if (isHoistable) {
325
349
  isHoistable = !rootNode.peerNames.has(node.name);
326
350
  if (outputReason && !isHoistable) {
@@ -389,6 +413,17 @@ const getNodeHoistInfo = (rootNode, rootNodePathLocators, nodePath, node, usedDe
389
413
  }
390
414
  isHoistable = arePeerDepsSatisfied;
391
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
+ }
392
427
  if (dependsOn !== null && dependsOn.size > 0) {
393
428
  return { isHoistable: Hoistable.DEPENDS, dependsOn, reason };
394
429
  }
@@ -417,7 +452,7 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
417
452
  const dependantTree = new Map();
418
453
  const hoistInfos = new Map();
419
454
  for (const subDependency of getSortedRegularDependencies(parentNode)) {
420
- 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 });
421
456
  hoistInfos.set(subDependency, hoistInfo);
422
457
  if (hoistInfo.isHoistable === Hoistable.DEPENDS) {
423
458
  for (const node of hoistInfo.dependsOn) {
@@ -450,25 +485,27 @@ const hoistGraph = (tree, rootNodePath, rootNodePathLocators, usedDependencies,
450
485
  parentNode.hoistedDependencies.set(node.name, node);
451
486
  parentNode.reasons.delete(node.name);
452
487
  const hoistedNode = rootNode.dependencies.get(node.name);
453
- let hoistedFrom = null;
454
- if (options.debugLevel >= DebugLevel.REASONS)
455
- 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
+ }
456
498
  // Add hoisted node to root node, in case it is not already there
457
499
  if (!hoistedNode) {
458
500
  // Avoid adding other version of root node to itself
459
501
  if (rootNode.ident !== node.ident) {
460
502
  rootNode.dependencies.set(node.name, node);
461
- if (options.debugLevel >= DebugLevel.REASONS)
462
- node.hoistedFrom.push(hoistedFrom);
463
503
  newNodes.add(node);
464
504
  }
465
505
  }
466
506
  else {
467
507
  for (const reference of node.references) {
468
508
  hoistedNode.references.add(reference);
469
- if (options.debugLevel >= DebugLevel.REASONS) {
470
- hoistedNode.hoistedFrom.push(hoistedFrom);
471
- }
472
509
  }
473
510
  }
474
511
  }
@@ -513,7 +550,7 @@ const selfCheck = (tree) => {
513
550
  const log = [];
514
551
  const seenNodes = new Set();
515
552
  const parents = new Set();
516
- const checkNode = (node, parentDeps) => {
553
+ const checkNode = (node, parentDeps, parent) => {
517
554
  if (seenNodes.has(node))
518
555
  return;
519
556
  seenNodes.add(node);
@@ -533,23 +570,28 @@ const selfCheck = (tree) => {
533
570
  }
534
571
  }
535
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}`;
536
578
  if (!dep) {
537
- 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`);
538
580
  }
539
581
  else if (dep.ident !== origDep.ident) {
540
- 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}`);
541
583
  }
542
584
  }
543
585
  }
544
586
  parents.add(node);
545
587
  for (const dep of node.dependencies.values()) {
546
588
  if (!node.peerNames.has(dep.name)) {
547
- checkNode(dep, dependencies);
589
+ checkNode(dep, dependencies, node);
548
590
  }
549
591
  }
550
592
  parents.delete(node);
551
593
  };
552
- checkNode(tree, tree.dependencies);
594
+ checkNode(tree, tree.dependencies, tree);
553
595
  return log.join(`\n`);
554
596
  };
555
597
  /**
@@ -572,14 +614,16 @@ const cloneTree = (tree, options) => {
572
614
  decoupled: true,
573
615
  isHoistBorder: true,
574
616
  hoistPriority: 0,
575
- hoistedFrom: [],
617
+ isWorkspace: true,
618
+ hoistedFrom: new Map(),
619
+ hoistedTo: new Map(),
576
620
  };
577
621
  const seenNodes = new Map([[tree, treeCopy]]);
578
622
  const addNode = (node, parentNode) => {
579
623
  let workNode = seenNodes.get(node);
580
624
  const isSeen = !!workNode;
581
625
  if (!workNode) {
582
- const { name, identName, reference, peerNames, hoistPriority } = node;
626
+ const { name, identName, reference, peerNames, hoistPriority, isWorkspace } = node;
583
627
  const dependenciesNmHoistingLimits = options.hoistingLimits.get(parentNode.locator);
584
628
  workNode = {
585
629
  name,
@@ -594,7 +638,9 @@ const cloneTree = (tree, options) => {
594
638
  decoupled: true,
595
639
  isHoistBorder: dependenciesNmHoistingLimits ? dependenciesNmHoistingLimits.has(name) : false,
596
640
  hoistPriority: hoistPriority || 0,
597
- hoistedFrom: [],
641
+ isWorkspace: isWorkspace || false,
642
+ hoistedFrom: new Map(),
643
+ hoistedTo: new Map(),
598
644
  };
599
645
  seenNodes.set(node, workNode);
600
646
  }
@@ -698,7 +744,7 @@ const buildPreferenceMap = (rootNode) => {
698
744
  seenNodes.add(node);
699
745
  for (const dep of node.dependencies.values()) {
700
746
  const entry = getOrCreatePreferenceEntry(dep);
701
- entry.hoistPriority = Math.max(entry.hoistPriority, node.hoistPriority);
747
+ entry.hoistPriority = Math.max(entry.hoistPriority, dep.hoistPriority);
702
748
  if (node.peerNames.has(dep.name)) {
703
749
  entry.peerDependents.add(node.ident);
704
750
  }
@@ -714,6 +760,8 @@ const buildPreferenceMap = (rootNode) => {
714
760
  return preferenceMap;
715
761
  };
716
762
  const prettyPrintLocator = (locator) => {
763
+ if (!locator)
764
+ return `none`;
717
765
  const idx = locator.indexOf(`@`, 1);
718
766
  let name = locator.substring(0, idx);
719
767
  if (name.endsWith(`$wsroot$`))
@@ -761,7 +809,8 @@ const dumpDepTree = (tree) => {
761
809
  if (!pkg.peerNames.has(dep.name) && dep !== pkg) {
762
810
  const reason = pkg.reasons.get(dep.name);
763
811
  const identName = getIdentName(dep.locator);
764
- 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`;
765
814
  str += dumpPackage(dep, parents, `${prefix}${idx < dependencies.length - 1 ? `│ ` : ` `}`);
766
815
  }
767
816
  }
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@yarnpkg/nm",
3
- "version": "3.0.0-rc.1",
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.1.0-rc.1",
10
- "@yarnpkg/fslib": "^2.5.1-rc.1"
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.1-rc.1"
13
+ "@yarnpkg/pnp": "^3.1.0-rc.6"
14
14
  },
15
15
  "scripts": {
16
16
  "postpack": "rm -rf lib",