@npmcli/arborist 9.4.1 → 9.4.3

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.
@@ -950,7 +950,7 @@ This is a one-time fix-up, please be patient...
950
950
  tree: pd,
951
951
  getChildren: pd => pd.children,
952
952
  visit: pd => {
953
- const { placed, edge, canPlace: cpd } = pd
953
+ const { placed, edge, canPlace: cpd, parent } = pd
954
954
  // if we didn't place anything, nothing to do here
955
955
  if (!placed) {
956
956
  return
@@ -1011,8 +1011,7 @@ This is a one-time fix-up, please be patient...
1011
1011
  return
1012
1012
  }
1013
1013
 
1014
- // lastly, also check for the missing deps of the node we placed,
1015
- // and any holes created by pruning out conflicted peer sets.
1014
+ // lastly, also check for the missing deps of the node we placed, and any holes created by pruning out conflicted peer sets.
1016
1015
  this.#depsQueue.push(placed)
1017
1016
  for (const dep of pd.needEvaluation) {
1018
1017
  this.#depsSeen.delete(dep)
@@ -1020,16 +1019,14 @@ This is a one-time fix-up, please be patient...
1020
1019
  }
1021
1020
 
1022
1021
  // pre-fetch any problem edges, since we'll need these soon
1023
- // if it fails at this point, though, don't worry because it
1024
- // may well be an optional dep that has gone missing. it'll
1025
- // fail later anyway.
1022
+ // if it fails at this point, though, don't worry because it may well be an optional dep that has gone missing
1023
+ // it'll fail later anyway
1026
1024
  for (const e of this.#problemEdges(placed)) {
1027
- // XXX This is somehow load bearing. This makes tests that print
1028
- // the ideal tree of a tree with tarball dependencies fail. This
1029
- // can't be changed or removed till we figure out why
1025
+ // XXX This is somehow load bearing. This makes tests that print the ideal tree of a tree with tarball dependencies fail
1026
+ // This can't be changed or removed till we figure out why
1030
1027
  // The test is named "tarball deps with transitive tarball deps"
1031
1028
  promises.push(() =>
1032
- this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)))
1029
+ this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)), parent)
1033
1030
  .catch(() => null)
1034
1031
  )
1035
1032
  }
@@ -1047,26 +1044,18 @@ This is a one-time fix-up, please be patient...
1047
1044
  return this.#buildDepStep()
1048
1045
  }
1049
1046
 
1050
- // loads a node from an edge, and then loads its peer deps (and their
1051
- // peer deps, on down the line) into a virtual root parent.
1047
+ // loads a node from an edge, and then loads its peer deps (and their peer deps, on down the line) into a virtual root parent.
1052
1048
  async #nodeFromEdge (edge, parent_, secondEdge, required) {
1053
- // create a virtual root node with the same deps as the node that
1054
- // is requesting this one, so that we can get all the peer deps in
1055
- // a context where they're likely to be resolvable.
1056
- // Note that the virtual root will also have virtual copies of the
1057
- // targets of any child Links, so that they resolve appropriately.
1049
+ // create a virtual root node with the same deps as the node that is requesting this one, so that we can get all the peer deps in a context where they're likely to be resolvable.
1050
+ // Note that the virtual root will also have virtual copies of the targets of any child Links, so that they resolve appropriately.
1058
1051
  const parent = parent_ || this.#virtualRoot(edge.from)
1059
1052
 
1060
1053
  const spec = npa.resolve(edge.name, edge.spec, edge.from.path)
1061
1054
  const first = await this.#nodeFromSpec(edge.name, spec, parent, edge)
1062
1055
 
1063
- // we might have a case where the parent has a peer dependency on
1064
- // `foo@*` which resolves to v2, but another dep in the set has a
1065
- // peerDependency on `foo@1`. In that case, if we force it to be v2,
1066
- // we're unnecessarily triggering an ERESOLVE.
1067
- // If we have a second edge to worry about, and it's not satisfied
1068
- // by the first node, try a second and see if that satisfies the
1069
- // original edge here.
1056
+ // we might have a case where the parent has a peer dependency on `foo@*` which resolves to v2, but another dep in the set has a peerDependency on `foo@1`.
1057
+ // In that case, if we force it to be v2, we're unnecessarily triggering an ERESOLVE.
1058
+ // If we have a second edge to worry about, and it's not satisfied by the first node, try a second and see if that satisfies the original edge here.
1070
1059
  const spec2 = secondEdge && npa.resolve(
1071
1060
  edge.name,
1072
1061
  secondEdge.spec,
@@ -1210,11 +1199,12 @@ This is a one-time fix-up, please be patient...
1210
1199
  return problems
1211
1200
  }
1212
1201
 
1213
- async #fetchManifest (spec) {
1202
+ async #fetchManifest (spec, parent) {
1214
1203
  const options = {
1215
1204
  ...this.options,
1216
1205
  avoid: this.#avoidRange(spec.name),
1217
1206
  fullMetadata: true,
1207
+ _isRoot: parent?.isProjectRoot || parent?.isWorkspace,
1218
1208
  }
1219
1209
  // get the intended spec and stored metadata from yarn.lock file,
1220
1210
  // if available and valid.
@@ -1231,10 +1221,8 @@ This is a one-time fix-up, please be patient...
1231
1221
  }
1232
1222
 
1233
1223
  async #nodeFromSpec (name, spec, parent, edge) {
1234
- // pacote will slap integrity on its options, so we have to clone
1235
- // the object so it doesn't get mutated.
1236
- // Don't bother to load the manifest for link deps, because the target
1237
- // might be within another package that doesn't exist yet.
1224
+ // pacote will slap integrity on its options, so we have to clone the object so it doesn't get mutated.
1225
+ // Don't bother to load the manifest for link deps, because the target might be within another package that doesn't exist yet.
1238
1226
  const { installLinks, legacyPeerDeps } = this
1239
1227
  const isWorkspace = this.idealTree.workspaces && this.idealTree.workspaces.has(spec.name)
1240
1228
 
@@ -1287,7 +1275,7 @@ This is a one-time fix-up, please be patient...
1287
1275
 
1288
1276
  // spec isn't a directory, and either isn't a workspace or the workspace we have
1289
1277
  // doesn't satisfy the edge. try to fetch a manifest and build a node from that.
1290
- return this.#fetchManifest(spec)
1278
+ return this.#fetchManifest(spec, parent)
1291
1279
  .then(pkg => new Node({ name, pkg, parent, installLinks, legacyPeerDeps }), error => {
1292
1280
  error.requiredBy = edge.from.location || '.'
1293
1281
 
@@ -113,7 +113,11 @@ module.exports = cls => class IsolatedReifier extends cls {
113
113
  })
114
114
  // local `file:` deps are in fsChildren but are not workspaces.
115
115
  // they are already handled as workspace-like proxies above and should not go through the external/store extraction path.
116
- if (!next.isProjectRoot && !next.isWorkspace && !next.inert && !idealTree.fsChildren.has(next) && !idealTree.fsChildren.has(next.target)) {
116
+ // Links with file: resolved paths (from `npm link`) should also be treated as local dependencies and symlinked directly instead of being extracted into the store.
117
+ const isLocalFileDep = next.isLink && next.resolved?.startsWith('file:')
118
+ if (isLocalFileDep && !idealTree.fsChildren.has(next) && !idealTree.fsChildren.has(next.target)) {
119
+ this.idealGraph.workspaces.push(await this.#workspaceProxy(next.target))
120
+ } else if (!next.isProjectRoot && !next.isWorkspace && !next.inert && !idealTree.fsChildren.has(next) && !idealTree.fsChildren.has(next.target)) {
117
121
  this.idealGraph.external.push(await this.#externalProxy(next))
118
122
  }
119
123
  }
@@ -157,6 +161,7 @@ module.exports = cls => class IsolatedReifier extends cls {
157
161
  ...this.options,
158
162
  resolved: node.resolved,
159
163
  integrity: node.integrity,
164
+ // TODO _isRoot
160
165
  })
161
166
  const Arborist = this.constructor
162
167
  const arb = new Arborist({ ...this.options, path: dir })
@@ -117,9 +117,11 @@ module.exports = cls => class Reifier extends cls {
117
117
  // of Node/Link trees
118
118
  log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.')
119
119
  this.idealTree = await this.createIsolatedTree()
120
- this.#linkedActualForDiff = this.#buildLinkedActualForDiff(
121
- this.idealTree, this.actualTree
122
- )
120
+ if (this.actualTree) {
121
+ this.#linkedActualForDiff = this.#buildLinkedActualForDiff(
122
+ this.idealTree, this.actualTree
123
+ )
124
+ }
123
125
  }
124
126
  await this[_diffTrees]()
125
127
  await this.#reifyPackages()
@@ -735,6 +737,7 @@ module.exports = cls => class Reifier extends cls {
735
737
  ...this.options,
736
738
  resolved: node.resolved,
737
739
  integrity: node.integrity,
740
+ _isRoot: node.parent?.isProjectRoot || node.parent?.isWorkspace,
738
741
  })
739
742
  // store nodes don't use Node class so node.package doesn't get updated
740
743
  if (node.isInStore) {
@@ -815,6 +818,10 @@ module.exports = cls => class Reifier extends cls {
815
818
  if (combined.has(child.path) || !existsSync(child.path)) {
816
819
  continue
817
820
  }
821
+ // Skip store links whose ideal realpath doesn't exist on disk yet — the store hash changed and the symlink needs recreating via ADD.
822
+ if (child.isLink && child.resolved?.startsWith('file:.store/') && !existsSync(child.realpath)) {
823
+ continue
824
+ }
818
825
  let entry
819
826
  if (child.isLink) {
820
827
  entry = new IsolatedLink(child)
@@ -26,7 +26,7 @@ const optionalSet = node => {
26
26
 
27
27
  // now that we've hit the boundary, gather the rest of the nodes in
28
28
  // the optional section that don't have dependents outside the set.
29
- return gatherDepSet(set, edge => !set.has(edge.to))
29
+ return gatherDepSet(set, edge => !set.has(edge.to) && !edge.from?.inert)
30
30
  }
31
31
 
32
32
  module.exports = optionalSet
@@ -195,8 +195,29 @@ class OverrideSet {
195
195
  }
196
196
  }
197
197
 
198
- // The override sets are incomparable. Neither one contains the other.
199
- log.silly('Conflicting override sets', first, second)
198
+ // The override sets are incomparable (e.g. siblings like the "react" and "react-dom" children of the root override set). Check if they have semantically conflicting rules before treating this as an error.
199
+ if (this.haveConflictingRules(first, second)) {
200
+ log.silly('Conflicting override sets', first, second)
201
+ return undefined
202
+ }
203
+
204
+ // The override sets are structurally incomparable but have compatible rules. Fall back to their nearest common ancestor so the node still has a valid override set.
205
+ return this.findCommonAncestor(first, second)
206
+ }
207
+
208
+ static findCommonAncestor (first, second) {
209
+ const firstAncestors = []
210
+ for (const ancestor of first.ancestry()) {
211
+ firstAncestors.push(ancestor)
212
+ }
213
+ for (const secondAnc of second.ancestry()) {
214
+ for (const firstAnc of firstAncestors) {
215
+ if (firstAnc.isEqual(secondAnc)) {
216
+ return firstAnc
217
+ }
218
+ }
219
+ }
220
+ return null
200
221
  }
201
222
 
202
223
  static doOverrideSetsConflict (first, second) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "9.4.1",
3
+ "version": "9.4.3",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@gar/promise-retry": "^1.0.0",