@npmcli/arborist 2.7.0 → 2.8.2

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.
@@ -3,14 +3,23 @@ const rpj = require('read-package-json-fast')
3
3
  const npa = require('npm-package-arg')
4
4
  const pacote = require('pacote')
5
5
  const cacache = require('cacache')
6
- const semver = require('semver')
7
6
  const promiseCallLimit = require('promise-call-limit')
8
- const getPeerSet = require('../peer-set.js')
9
7
  const realpath = require('../../lib/realpath.js')
10
8
  const { resolve, dirname } = require('path')
11
9
  const { promisify } = require('util')
12
10
  const treeCheck = require('../tree-check.js')
13
11
  const readdir = promisify(require('readdir-scoped-modules'))
12
+ const fs = require('fs')
13
+ const lstat = promisify(fs.lstat)
14
+ const readlink = promisify(fs.readlink)
15
+ const { depth } = require('treeverse')
16
+
17
+ const {
18
+ OK,
19
+ REPLACE,
20
+ CONFLICT,
21
+ } = require('../can-place-dep.js')
22
+ const PlaceDep = require('../place-dep.js')
14
23
 
15
24
  const debug = require('../debug.js')
16
25
  const fromPath = require('../from-path.js')
@@ -19,20 +28,9 @@ const Shrinkwrap = require('../shrinkwrap.js')
19
28
  const Node = require('../node.js')
20
29
  const Link = require('../link.js')
21
30
  const addRmPkgDeps = require('../add-rm-pkg-deps.js')
22
- const gatherDepSet = require('../gather-dep-set.js')
23
31
  const optionalSet = require('../optional-set.js')
24
32
  const {checkEngine, checkPlatform} = require('npm-install-checks')
25
33
 
26
- // enum of return values for canPlaceDep.
27
- // No, this is a conflict, you may not put that package here
28
- const CONFLICT = Symbol('CONFLICT')
29
- // Yes, this is fine, and should not be a problem
30
- const OK = Symbol('OK')
31
- // No need, because the package already here is fine
32
- const KEEP = Symbol('KEEP')
33
- // Yes, clobber the package that is already here
34
- const REPLACE = Symbol('REPLACE')
35
-
36
34
  const relpath = require('../relpath.js')
37
35
 
38
36
  // note: some of these symbols are shared so we can hit
@@ -47,7 +45,6 @@ const _flagsSuspect = Symbol.for('flagsSuspect')
47
45
  const _workspaces = Symbol.for('workspaces')
48
46
  const _prune = Symbol('prune')
49
47
  const _preferDedupe = Symbol('preferDedupe')
50
- const _pruneDedupable = Symbol('pruneDedupable')
51
48
  const _legacyBundling = Symbol('legacyBundling')
52
49
  const _parseSettings = Symbol('parseSettings')
53
50
  const _initTree = Symbol('initTree')
@@ -65,10 +62,6 @@ const _loadWorkspaces = Symbol.for('loadWorkspaces')
65
62
  const _linkFromSpec = Symbol('linkFromSpec')
66
63
  const _loadPeerSet = Symbol('loadPeerSet')
67
64
  const _updateNames = Symbol.for('updateNames')
68
- const _placeDep = Symbol.for('placeDep')
69
- const _canPlaceDep = Symbol.for('canPlaceDep')
70
- const _canPlacePeers = Symbol('canPlacePeers')
71
- const _pruneForReplacement = Symbol('pruneForReplacement')
72
65
  const _fixDepFlags = Symbol('fixDepFlags')
73
66
  const _resolveLinks = Symbol('resolveLinks')
74
67
  const _rootNodeFromPackage = Symbol('rootNodeFromPackage')
@@ -100,12 +93,8 @@ const _checkPlatform = Symbol('checkPlatform')
100
93
  const _virtualRoots = Symbol('virtualRoots')
101
94
  const _virtualRoot = Symbol('virtualRoot')
102
95
 
103
- // used for the ERESOLVE error to show the last peer conflict encountered
104
- const _peerConflict = Symbol('peerConflict')
105
-
106
96
  const _failPeerConflict = Symbol('failPeerConflict')
107
97
  const _explainPeerConflict = Symbol('explainPeerConflict')
108
- const _warnPeerConflict = Symbol('warnPeerConflict')
109
98
  const _edgesOverridden = Symbol('edgesOverridden')
110
99
  // exposed symbol for unit testing the placeDep method directly
111
100
  const _peerSetSource = Symbol.for('peerSetSource')
@@ -163,7 +152,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
163
152
  this[_loadFailures] = new Set()
164
153
  this[_linkNodes] = new Set()
165
154
  this[_manifests] = new Map()
166
- this[_peerConflict] = null
167
155
  this[_edgesOverridden] = new Set()
168
156
  this[_resolvedAdd] = []
169
157
 
@@ -227,17 +215,13 @@ module.exports = cls => class IdealTreeBuilder extends cls {
227
215
  return treeCheck(this.idealTree)
228
216
  }
229
217
 
230
- [_checkEngineAndPlatform] () {
231
- // engine/platform checks throw, so start the promise chain off first
232
- return Promise.resolve()
233
- .then(() => {
234
- for (const node of this.idealTree.inventory.values()) {
235
- if (!node.optional) {
236
- this[_checkEngine](node)
237
- this[_checkPlatform](node)
238
- }
239
- }
240
- })
218
+ async [_checkEngineAndPlatform] () {
219
+ for (const node of this.idealTree.inventory.values()) {
220
+ if (!node.optional) {
221
+ this[_checkEngine](node)
222
+ this[_checkPlatform](node)
223
+ }
224
+ }
241
225
  }
242
226
 
243
227
  [_checkPlatform] (node) {
@@ -426,7 +410,14 @@ module.exports = cls => class IdealTreeBuilder extends cls {
426
410
  if (this[_updateAll] || updateName) {
427
411
  if (updateName)
428
412
  globalExplicitUpdateNames.push(name)
429
- tree.package.dependencies[name] = '*'
413
+ const dir = resolve(nm, name)
414
+ const st = await lstat(dir).catch(/* istanbul ignore next */ er => null)
415
+ if (st && st.isSymbolicLink()) {
416
+ const target = await readlink(dir)
417
+ const real = resolve(dirname(dir), target)
418
+ tree.package.dependencies[name] = `file:${real}`
419
+ } else
420
+ tree.package.dependencies[name] = '*'
430
421
  }
431
422
  }
432
423
  }
@@ -850,7 +841,7 @@ This is a one-time fix-up, please be patient...
850
841
  const tasks = []
851
842
  const peerSource = this[_peerSetSource].get(node) || node
852
843
  for (const edge of this[_problemEdges](node)) {
853
- if (this[_edgesOverridden].has(edge))
844
+ if (edge.overridden)
854
845
  continue
855
846
 
856
847
  // peerSetSource is only relevant when we have a peerEntryEdge
@@ -894,34 +885,101 @@ This is a one-time fix-up, please be patient...
894
885
  tasks.push({edge, dep})
895
886
  }
896
887
 
897
- const placed = tasks
888
+ const placeDeps = tasks
898
889
  .sort((a, b) => a.edge.name.localeCompare(b.edge.name, 'en'))
899
- .map(({ edge, dep }) => this[_placeDep](dep, node, edge))
890
+ .map(({ edge, dep }) => new PlaceDep({
891
+ edge,
892
+ dep,
893
+
894
+ explicitRequest: this[_explicitRequests].has(edge),
895
+ updateNames: this[_updateNames],
896
+ auditReport: this.auditReport,
897
+ force: this[_force],
898
+ preferDedupe: this[_preferDedupe],
899
+ legacyBundling: this[_legacyBundling],
900
+ strictPeerDeps: this[_strictPeerDeps],
901
+ legacyPeerDeps: this.legacyPeerDeps,
902
+ globalStyle: this[_globalStyle],
903
+ }))
900
904
 
901
905
  const promises = []
902
- for (const set of placed) {
903
- for (const node of set) {
904
- this[_mutateTree] = true
905
- this.addTracker('idealTree', node.name, node.location)
906
- this[_depsQueue].push(node)
907
-
908
- // we're certainly going to need these soon, fetch them asap
909
- // if it fails at this point, though, dont' worry because it
910
- // may well be an optional dep that has gone missing. it'll
911
- // fail later anyway.
912
- const from = fromPath(node)
913
- promises.push(...this[_problemEdges](node).map(e =>
914
- this[_fetchManifest](npa.resolve(e.name, e.spec, from))
915
- .catch(er => null)))
916
- }
906
+ for (const pd of placeDeps) {
907
+ // placing a dep is actually a tree of placing the dep itself
908
+ // and all of its peer group that aren't already met by the tree
909
+ depth({
910
+ tree: pd,
911
+ getChildren: pd => pd.children,
912
+ visit: pd => {
913
+ const { placed, edge, canPlace: cpd } = pd
914
+ // if we didn't place anything, nothing to do here
915
+ if (!placed)
916
+ return
917
+
918
+ // we placed something, that means we changed the tree
919
+ if (placed.errors.length)
920
+ this[_loadFailures].add(placed)
921
+ this[_mutateTree] = true
922
+ if (cpd.canPlaceSelf === OK) {
923
+ for (const edgeIn of placed.edgesIn) {
924
+ if (edgeIn === edge)
925
+ continue
926
+ const { from, valid, overridden } = edgeIn
927
+ if (!overridden && !valid && !this[_depsSeen].has(from)) {
928
+ this.addTracker('idealTree', from.name, from.location)
929
+ this[_depsQueue].push(edgeIn.from)
930
+ }
931
+ }
932
+ } else {
933
+ /* istanbul ignore else - should be only OK or REPLACE here */
934
+ if (cpd.canPlaceSelf === REPLACE) {
935
+ // this may also create some invalid edges, for example if we're
936
+ // intentionally causing something to get nested which was
937
+ // previously placed in this location.
938
+ for (const edgeIn of placed.edgesIn) {
939
+ if (edgeIn === edge)
940
+ continue
941
+
942
+ const { valid, overridden } = edgeIn
943
+ if (!valid && !overridden) {
944
+ // if it's already been visited, we have to re-visit
945
+ // otherwise, just enqueue normally.
946
+ this[_depsSeen].delete(edgeIn.from)
947
+ this[_depsQueue].push(edgeIn.from)
948
+ }
949
+ }
950
+ }
951
+ }
952
+
953
+ /* istanbul ignore if - should be impossible */
954
+ if (cpd.canPlaceSelf === CONFLICT) {
955
+ debug(() => {
956
+ const er = new Error('placed with canPlaceSelf=CONFLICT')
957
+ throw Object.assign(er, { placeDep: pd })
958
+ })
959
+ return
960
+ }
961
+
962
+ // lastly, also check for the missing deps of the node we placed
963
+ this[_depsQueue].push(placed)
964
+
965
+ // pre-fetch any problem edges, since we'll need these soon
966
+ // if it fails at this point, though, dont' worry because it
967
+ // may well be an optional dep that has gone missing. it'll
968
+ // fail later anyway.
969
+ const from = fromPath(placed)
970
+ promises.push(...this[_problemEdges](placed).map(e =>
971
+ this[_fetchManifest](npa.resolve(e.name, e.spec, from))
972
+ .catch(er => null)))
973
+ },
974
+ })
917
975
  }
918
- await Promise.all(promises)
919
976
 
920
977
  for (const { to } of node.edgesOut.values()) {
921
978
  if (to && to.isLink && to.target)
922
979
  this[_linkNodes].add(to)
923
980
  }
924
981
 
982
+ await Promise.all(promises)
925
983
  return this[_buildDepStep]()
926
984
  }
927
985
 
@@ -934,7 +992,6 @@ This is a one-time fix-up, please be patient...
934
992
  // Note that the virtual root will also have virtual copies of the
935
993
  // targets of any child Links, so that they resolve appropriately.
936
994
  const parent = parent_ || this[_virtualRoot](edge.from)
937
- const realParent = edge.peer ? edge.from.resolveParent : edge.from
938
995
 
939
996
  const spec = npa.resolve(edge.name, edge.spec, edge.from.path)
940
997
  const first = await this[_nodeFromSpec](edge.name, spec, parent, edge)
@@ -965,19 +1022,18 @@ This is a one-time fix-up, please be patient...
965
1022
  required.has(secondEdge.from) && secondEdge.type !== 'peerOptional'))
966
1023
  required.add(node)
967
1024
 
968
- // handle otherwise unresolvable dependency nesting loops by
969
- // creating a symbolic link
970
- // a1 -> b1 -> a2 -> b2 -> a1 -> ...
971
- // instead of nesting forever, when the loop occurs, create
972
- // a symbolic link to the earlier instance
973
- for (let p = edge.from.resolveParent; p; p = p.resolveParent) {
974
- if (p.matches(node) && !p.isTop)
975
- return new Link({ parent: realParent, target: p })
976
- }
977
-
978
1025
  // keep track of the thing that caused this node to be included.
979
1026
  const src = parent.sourceReference
980
1027
  this[_peerSetSource].set(node, src)
1028
+
1029
+ // do not load the peers along with the set if this is a global top pkg
1030
+ // otherwise we'll be tempted to put peers as other top-level installed
1031
+ // things, potentially clobbering what's there already, which is not
1032
+ // what we want. the missing edges will be picked up on the next pass.
1033
+ if (this[_global] && edge.from.isProjectRoot)
1034
+ return node
1035
+
1036
+ // otherwise, we have to make sure that our peers can go along with us.
981
1037
  return this[_loadPeerSet](node, required)
982
1038
  }
983
1039
 
@@ -1176,8 +1232,10 @@ This is a one-time fix-up, please be patient...
1176
1232
  // allow it. either we're overriding, or it's not something
1177
1233
  // that will be installed by default anyway, and we'll fail when
1178
1234
  // we get to the point where we need to, if we need to.
1179
- if (conflictOK || !required.has(dep))
1235
+ if (conflictOK || !required.has(dep)) {
1236
+ edge.overridden = true
1180
1237
  continue
1238
+ }
1181
1239
 
1182
1240
  // problem
1183
1241
  this[_failPeerConflict](edge, parentEdge)
@@ -1219,9 +1277,7 @@ This is a one-time fix-up, please be patient...
1219
1277
  [_explainPeerConflict] (edge, currentEdge) {
1220
1278
  const node = edge.from
1221
1279
  const curNode = node.resolve(edge.name)
1222
- const pc = this[_peerConflict] || { peer: null, current: null }
1223
- const current = curNode ? curNode.explain() : pc.current
1224
- const peerConflict = pc.peer
1280
+ const current = curNode.explain()
1225
1281
  return {
1226
1282
  code: 'ERESOLVE',
1227
1283
  current,
@@ -1230,640 +1286,11 @@ This is a one-time fix-up, please be patient...
1230
1286
  // the tree handling logic.
1231
1287
  currentEdge: currentEdge ? currentEdge.explain() : null,
1232
1288
  edge: edge.explain(),
1233
- peerConflict,
1234
1289
  strictPeerDeps: this[_strictPeerDeps],
1235
1290
  force: this[_force],
1236
1291
  }
1237
1292
  }
1238
1293
 
1239
- [_warnPeerConflict] (edge) {
1240
- // track that we've overridden this edge, so that we don't keep trying
1241
- // to re-resolve it in an infinite loop.
1242
- this[_edgesOverridden].add(edge)
1243
- const expl = this[_explainPeerConflict](edge)
1244
- this.log.warn('ERESOLVE', 'overriding peer dependency', expl)
1245
- }
1246
-
1247
- // starting from either node, or in the case of non-root peer deps,
1248
- // the node's parent, walk up the tree until we find the first spot
1249
- // where this dep cannot be placed, and use the one right before that.
1250
- // place dep, requested by node, to satisfy edge
1251
- // XXX split this out into a separate method or mixin? It's quite a lot
1252
- // of functionality that ought to have its own unit tests more conveniently.
1253
- [_placeDep] (dep, node, edge, peerEntryEdge = null, peerPath = []) {
1254
- if (edge.to &&
1255
- !edge.error &&
1256
- !this[_explicitRequests].has(edge) &&
1257
- !this[_updateNames].includes(edge.name) &&
1258
- !this[_isVulnerable](edge.to))
1259
- return []
1260
-
1261
- // top nodes should still get peer deps from their fsParent if possible,
1262
- // and only install locally if there's no other option, eg for a link
1263
- // outside of the project root, or for a conflicted dep.
1264
- const start = edge.peer && !node.isProjectRoot ? node.resolveParent || node
1265
- : node
1266
-
1267
- let target
1268
- let canPlace = null
1269
- let isSource = false
1270
- const source = this[_peerSetSource].get(dep)
1271
- for (let check = start; check; check = check.resolveParent) {
1272
- // we always give the FIRST place we possibly *can* put this a little
1273
- // extra prioritization with peer dep overrides and deduping
1274
- if (check === source)
1275
- isSource = true
1276
-
1277
- // if the current location has a peerDep on it, then we can't place here
1278
- // this is pretty rare to hit, since we always prefer deduping peers.
1279
- const checkEdge = check.edgesOut.get(edge.name)
1280
- if (!check.isTop && checkEdge && checkEdge.peer)
1281
- continue
1282
-
1283
- const cp = this[_canPlaceDep](dep, check, edge, peerEntryEdge, peerPath, isSource)
1284
- isSource = false
1285
-
1286
- // anything other than a conflict is fine to proceed with
1287
- if (cp !== CONFLICT) {
1288
- canPlace = cp
1289
- target = check
1290
- } else
1291
- break
1292
-
1293
- // nest packages like npm v1 and v2
1294
- // very disk-inefficient
1295
- if (this[_legacyBundling])
1296
- break
1297
-
1298
- // when installing globally, or just in global style, we never place
1299
- // deps above the first level.
1300
- const tree = this.idealTree && this.idealTree.target
1301
- if (this[_globalStyle] && check.resolveParent === tree)
1302
- break
1303
- }
1304
-
1305
- // if we can't find a target, that means that the last placed checked
1306
- // (and all the places before it) had a copy already. if we're in
1307
- // --force mode, then the user has explicitly said that they're ok
1308
- // with conflicts. This can only occur in --force mode in the case
1309
- // when a node was added to the tree with a peerOptional dep that we
1310
- // ignored, and then later, that edge became invalid, and we fail to
1311
- // resolve it. We will warn about it in a moment.
1312
- if (!target) {
1313
- if (this[_force]) {
1314
- // we know that there is a dep (not the root) which is the target
1315
- // of this edge, or else it wouldn't have been a conflict.
1316
- target = edge.to.resolveParent
1317
- canPlace = KEEP
1318
- } else
1319
- this[_failPeerConflict](edge)
1320
- } else {
1321
- // it worked, so we clearly have no peer conflicts at this point.
1322
- this[_peerConflict] = null
1323
- }
1324
-
1325
- this.log.silly(
1326
- 'placeDep',
1327
- target.location || 'ROOT',
1328
- `${dep.name}@${dep.version}`,
1329
- canPlace.description || /* istanbul ignore next */ canPlace,
1330
- `for: ${node.package._id || node.location}`,
1331
- `want: ${edge.spec || '*'}`
1332
- )
1333
-
1334
- // Can only get KEEP here if the original edge was valid,
1335
- // and we're checking for an update but it's already up to date.
1336
- if (canPlace === KEEP) {
1337
- if (edge.peer && !target.children.get(edge.name).satisfies(edge)) {
1338
- // this is an overridden peer dep
1339
- this[_warnPeerConflict](edge)
1340
- }
1341
-
1342
- // if we get a KEEP in a update scenario, then we MAY have something
1343
- // already duplicating this unnecessarily! For example:
1344
- // ```
1345
- // root
1346
- // +-- x (dep: y@1.x)
1347
- // | +-- y@1.0.0
1348
- // +-- y@1.1.0
1349
- // ```
1350
- // Now say we do `reify({update:['y']})`, and the latest version is
1351
- // 1.1.0, which we already have in the root. We'll try to place y@1.1.0
1352
- // first in x, then in the root, ending with KEEP, because we already
1353
- // have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
1354
- // it is an unnecessary duplicate.
1355
- this[_pruneDedupable](target)
1356
- return []
1357
- }
1358
-
1359
- // figure out which of this node's peer deps will get placed as well
1360
- const virtualRoot = dep.parent
1361
-
1362
- const newDep = new dep.constructor({
1363
- name: dep.name,
1364
- pkg: dep.package,
1365
- resolved: dep.resolved,
1366
- integrity: dep.integrity,
1367
- legacyPeerDeps: this.legacyPeerDeps,
1368
- error: dep.errors[0],
1369
- ...(dep.isLink ? { target: dep.target, realpath: dep.target.path } : {}),
1370
- })
1371
- if (this[_loadFailures].has(dep))
1372
- this[_loadFailures].add(newDep)
1373
-
1374
- const placed = [newDep]
1375
- const oldChild = target.children.get(edge.name)
1376
- if (oldChild) {
1377
- // if we're replacing, we should also remove any nodes for edges that
1378
- // are now invalid, and where this (or its deps) is the only dependent,
1379
- // and also recurse on that pruning. Otherwise leaving that dep node
1380
- // around can result in spurious conflicts pushing nodes deeper into
1381
- // the tree than needed in the case of cycles that will be removed
1382
- // later anyway.
1383
- const oldDeps = []
1384
- for (const [name, edge] of oldChild.edgesOut.entries()) {
1385
- if (!newDep.edgesOut.has(name) && edge.to)
1386
- oldDeps.push(...gatherDepSet([edge.to], e => e.to !== edge.to))
1387
- }
1388
- newDep.replace(oldChild)
1389
- this[_pruneForReplacement](newDep, oldDeps)
1390
- // this may also create some invalid edges, for example if we're
1391
- // intentionally causing something to get nested which was previously
1392
- // placed in this location.
1393
- for (const edgeIn of newDep.edgesIn) {
1394
- if (edgeIn.invalid && edgeIn !== edge) {
1395
- this[_depsQueue].push(edgeIn.from)
1396
- this[_depsSeen].delete(edgeIn.from)
1397
- }
1398
- }
1399
- } else
1400
- newDep.parent = target
1401
-
1402
- if (edge.peer && !newDep.satisfies(edge)) {
1403
- // this is an overridden peer dep
1404
- this[_warnPeerConflict](edge)
1405
- }
1406
-
1407
- // If the edge is not an error, then we're updating something, and
1408
- // MAY end up putting a better/identical node further up the tree in
1409
- // a way that causes an unnecessary duplication. If so, remove the
1410
- // now-unnecessary node.
1411
- if (edge.valid && edge.to && edge.to !== newDep)
1412
- this[_pruneDedupable](edge.to, false)
1413
-
1414
- // visit any dependents who are upset by this change
1415
- // if it's an angry overridden peer edge, however, make sure we
1416
- // skip over it!
1417
- for (const edgeIn of newDep.edgesIn) {
1418
- if (edgeIn !== edge && !edgeIn.valid && !this[_depsSeen].has(edge.from)) {
1419
- this.addTracker('idealTree', edgeIn.from.name, edgeIn.from.location)
1420
- this[_depsQueue].push(edgeIn.from)
1421
- }
1422
- }
1423
-
1424
- // in case we just made some duplicates that can be removed,
1425
- // prune anything deeper in the tree that can be replaced by this
1426
- if (this.idealTree) {
1427
- for (const node of this.idealTree.inventory.query('name', newDep.name)) {
1428
- if (!node.isTop && node.isDescendantOf(target))
1429
- this[_pruneDedupable](node, false)
1430
- }
1431
- }
1432
-
1433
- // also place its unmet or invalid peer deps at this location
1434
- // note that newDep has now been removed from the virtualRoot set
1435
- // by virtue of being placed in the target's node_modules.
1436
- // loop through any peer deps from the thing we just placed, and place
1437
- // those ones as well. it's safe to do this with the virtual nodes,
1438
- // because we're copying rather than moving them out of the virtual root,
1439
- // otherwise they'd be gone and the peer set would change throughout
1440
- // this loop.
1441
- for (const peerEdge of newDep.edgesOut.values()) {
1442
- const peer = virtualRoot.children.get(peerEdge.name)
1443
-
1444
- // Note: if the virtualRoot *doesn't* have the peer, then that means
1445
- // it's an optional peer dep. If it's not being properly met (ie,
1446
- // peerEdge.valid is false), that this is likely heading for an
1447
- // ERESOLVE error, unless it can walk further up the tree.
1448
- if (!peerEdge.peer || peerEdge.valid || !peer)
1449
- continue
1450
-
1451
- const peerPlaced = this[_placeDep](
1452
- peer, newDep, peerEdge, peerEntryEdge || edge, peerPath)
1453
- placed.push(...peerPlaced)
1454
- }
1455
-
1456
- // we're done with this now, clean it up.
1457
- this[_virtualRoots].delete(virtualRoot.sourceReference)
1458
-
1459
- return placed
1460
- }
1461
-
1462
- // prune all the nodes in a branch of the tree that can be safely removed
1463
- // This is only the most basic duplication detection; it finds if there
1464
- // is another satisfying node further up the tree, and if so, dedupes.
1465
- // Even in legacyBundling mode, we do this amount of deduplication.
1466
- [_pruneDedupable] (node, descend = true) {
1467
- if (node.canDedupe(this[_preferDedupe])) {
1468
- node.root = null
1469
- return
1470
- }
1471
- if (descend) {
1472
- // sort these so that they're deterministically ordered
1473
- // otherwise, resulting tree shape is dependent on the order
1474
- // in which they happened to be resolved.
1475
- const nodeSort = (a, b) => a.location.localeCompare(b.location, 'en')
1476
-
1477
- const children = [...node.children.values()].sort(nodeSort)
1478
- const fsChildren = [...node.fsChildren].sort(nodeSort)
1479
- for (const child of children)
1480
- this[_pruneDedupable](child)
1481
- for (const topNode of fsChildren) {
1482
- const children = [...topNode.children.values()].sort(nodeSort)
1483
- for (const child of children)
1484
- this[_pruneDedupable](child)
1485
- }
1486
- }
1487
- }
1488
-
1489
- [_pruneForReplacement] (node, oldDeps) {
1490
- // gather up all the invalid edgesOut, and any now-extraneous
1491
- // deps that the new node doesn't depend on but the old one did.
1492
- const invalidDeps = new Set([...node.edgesOut.values()]
1493
- .filter(e => e.to && !e.valid).map(e => e.to))
1494
- for (const dep of oldDeps) {
1495
- const set = gatherDepSet([dep], e => e.to !== dep && e.valid)
1496
- for (const dep of set)
1497
- invalidDeps.add(dep)
1498
- }
1499
-
1500
- // ignore dependency edges from the node being replaced, but
1501
- // otherwise filter the set down to just the set with no
1502
- // dependencies from outside the set, except the node in question.
1503
- const deps = gatherDepSet(invalidDeps, edge =>
1504
- edge.from !== node && edge.to !== node && edge.valid)
1505
-
1506
- // now just delete whatever's left, because it's junk
1507
- for (const dep of deps)
1508
- dep.parent = null
1509
- }
1510
-
1511
- // check if we can place DEP in TARGET to satisfy EDGE
1512
- // Need to verify:
1513
- // - no child by that name there already
1514
- // - target does not have a peer dep on name
1515
- // - no higher-level pkg by that name and incompatible spec is depended on
1516
- // by anything lower in the tree.
1517
- // - node's peer deps and meta-peer deps are siblings in a virtual root at
1518
- // this point. make sure that the whole family can come along, so apply
1519
- // the same checks to each of them. They may land higher up in the tree,
1520
- // but we need to know that they CAN live here.
1521
- // Responses:
1522
- // - OK - Yes, because there is nothing there and no conflicts caused
1523
- // - REPLACE - Yes, and you can clobber what's there
1524
- // - KEEP - No, but what's there is fine
1525
- // - CONFLICT - You may not put that there
1526
- //
1527
- // Check peers on OK or REPLACE. KEEP and CONFLICT do not require peer
1528
- // checking, because either we're leaving it alone, or it won't work anyway.
1529
- // When we check peers, we pass along the peerEntryEdge to track the
1530
- // original edge that caused us to load the family of peer dependencies.
1531
- [_canPlaceDep] (dep, target, edge, peerEntryEdge = null, peerPath = [], isSource = false) {
1532
- /* istanbul ignore next */
1533
- debug(() => {
1534
- if (!dep)
1535
- throw new Error('no dep??')
1536
- })
1537
- const entryEdge = peerEntryEdge || edge
1538
- const source = this[_peerSetSource].get(dep)
1539
-
1540
- isSource = isSource || target === source
1541
- // if we're overriding the source, then we care if the *target* is
1542
- // ours, even if it wasn't actually the original source, since we
1543
- // are depending on something that has a dep that can't go in its own
1544
- // folder. for example, a -> b, b -> PEER(a). Even though a is the
1545
- // source, b has to be installed up a level, and if the root package
1546
- // depends on a, and it has a conflict, it's our problem. So, the root
1547
- // (or whatever is bringing in a) becomes the "effective source" for
1548
- // the purposes of this calculation.
1549
- const { isProjectRoot, isWorkspace } = isSource ? target : source || {}
1550
- const isMine = isProjectRoot || isWorkspace
1551
-
1552
- // Useful testing thingie right here.
1553
- // peerEntryEdge should *always* be a non-peer dependency, or a peer
1554
- // dependency from the root node. When we get spurious ERESOLVE errors,
1555
- // or *don't* get ERESOLVE errors when we should, check to see if this
1556
- // fails, because it MAY mean we got off track somehow.
1557
- /* istanbul ignore next - debug check, should be impossible */
1558
- debug(() => {
1559
- if (peerEntryEdge && peerEntryEdge.peer && !peerEntryEdge.from.isTop)
1560
- throw new Error('lost original peerEntryEdge somehow?')
1561
- })
1562
-
1563
- if (target.children.has(edge.name)) {
1564
- const current = target.children.get(edge.name)
1565
-
1566
- // same thing = keep, UNLESS the current doesn't satisfy and new
1567
- // one does satisfy. This can happen if it's a link to a matching target
1568
- // at a different location, which satisfies a version dep, but not a
1569
- // file: dep. If neither of them satisfy, then we can replace it,
1570
- // because presumably it's better for a peer or something.
1571
- if (dep.matches(current)) {
1572
- if (current.satisfies(edge) || !dep.satisfies(edge))
1573
- return KEEP
1574
- }
1575
-
1576
- const { version: curVer } = current
1577
- const { version: newVer } = dep
1578
- const tryReplace = curVer && newVer && semver.gte(newVer, curVer)
1579
- if (tryReplace && dep.canReplace(current)) {
1580
- const res = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
1581
- /* istanbul ignore else - It's extremely rare that a replaceable
1582
- * node would be a conflict, if the current one wasn't a conflict,
1583
- * but it is theoretically possible if peer deps are pinned. In
1584
- * that case we treat it like any other conflict, and keep trying */
1585
- if (res !== CONFLICT)
1586
- return res
1587
- }
1588
-
1589
- // ok, can't replace the current with new one, but maybe current is ok?
1590
- // no need to check if it's a peer that's valid to be here, because
1591
- // peers are always placed along with their entry source
1592
- if (edge.satisfiedBy(current))
1593
- return KEEP
1594
-
1595
- // if we prefer deduping, then try replacing newer with older
1596
- // we always prefer to dedupe peers, because they are trying
1597
- // a bit harder to be singletons.
1598
- const preferDedupe = this[_preferDedupe] || edge.peer
1599
- if (preferDedupe && !tryReplace && dep.canReplace(current)) {
1600
- const res = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
1601
- /* istanbul ignore else - It's extremely rare that a replaceable
1602
- * node would be a conflict, if the current one wasn't a conflict,
1603
- * but it is theoretically possible if peer deps are pinned. In
1604
- * that case we treat it like any other conflict, and keep trying */
1605
- if (res !== CONFLICT)
1606
- return res
1607
- }
1608
-
1609
- // check for conflict override cases.
1610
- // first: is this the only place this thing can go? If the target is
1611
- // the source, then one of these things are true.
1612
- //
1613
- // 1. the conflicted dep was deduped up to here from a lower dependency
1614
- // w -> (x,y)
1615
- // x -> (z)
1616
- // y -> PEER(p@1)
1617
- // z -> (q)
1618
- // q -> (p@2)
1619
- //
1620
- // When building, let's say that x is fully placed, with all of its
1621
- // deps, and we're _adding_ y. Since the peer on p@1 was not initially
1622
- // present, it's been deduped up to w, and now needs to be pushed out.
1623
- // Replace it, and potentially also replace its peer set (though that'll
1624
- // be accomplished by making the same determination when we call
1625
- // _canPlacePeers)
1626
- //
1627
- // 2. the dep we're TRYING to place here ought to be overridden by the
1628
- // one that's here now, because current is (a) a direct dep of the
1629
- // source, or (b) an already-placed peer in a conflicted peer set, or
1630
- // (c) an already-placed peer in a different peer set at the same level.
1631
- // If strict or ours, conflict. Otherwise, keep.
1632
- if (isSource) {
1633
- // check to see if the current module could go deeper in the tree
1634
- let canReplace = true
1635
- // only do this check when we're placing peers. when we're placing
1636
- // the original in the source, we know that the edge from the source
1637
- // is the thing we're trying to place, so its peer set will need to be
1638
- // placed here as well. the virtualRoot already has the appropriate
1639
- // overrides applied.
1640
- if (peerEntryEdge) {
1641
- const currentPeerSet = getPeerSet(current)
1642
-
1643
- // We are effectively replacing currentPeerSet with newPeerSet
1644
- // If there are any non-peer deps coming into the currentPeerSet,
1645
- // which are currently valid, and are from the target, then that
1646
- // means that we have to ensure that they're not going to be made
1647
- // invalid by putting the newPeerSet in place.
1648
- // If the edge comes from somewhere deeper than the target, then
1649
- // that's fine, because we'll create an invalid edge, detect it,
1650
- // and duplicate the node further into the tree.
1651
- // loop through the currentPeerSet checking for valid edges on
1652
- // the members of the peer set which will be made invalid.
1653
- const targetEdges = new Set()
1654
- for (const p of currentPeerSet) {
1655
- for (const edge of p.edgesIn) {
1656
- // edge from within the peerSet, ignore
1657
- if (currentPeerSet.has(edge.from))
1658
- continue
1659
- // only care about valid edges from target.
1660
- // edges from elsewhere can dupe if offended, invalid edges
1661
- // are already being fixed or will be later.
1662
- if (edge.from !== target || !edge.valid)
1663
- continue
1664
- targetEdges.add(edge)
1665
- }
1666
- }
1667
-
1668
- for (const edge of targetEdges) {
1669
- // see if we intend to replace this one anyway
1670
- const rep = dep.parent.children.get(edge.name)
1671
- const current = edge.to
1672
- if (!rep) {
1673
- // this isn't one we're replacing. but it WAS included in the
1674
- // peerSet for some reason, so make sure that it's still
1675
- // ok with the replacements in the new peerSet
1676
- for (const curEdge of current.edgesOut.values()) {
1677
- const newRepDep = dep.parent.children.get(curEdge.name)
1678
- if (curEdge.valid && newRepDep && !newRepDep.satisfies(curEdge)) {
1679
- canReplace = false
1680
- break
1681
- }
1682
- }
1683
- continue
1684
- }
1685
-
1686
- // was this replacement already an override of some sort?
1687
- const override = [...rep.edgesIn].some(e => !e.valid)
1688
- // if we have a rep, and it's ok to put in this location, and
1689
- // it's not already part of an override in the peerSet, then
1690
- // we can continue with it.
1691
- if (rep.satisfies(edge) && !override)
1692
- continue
1693
- // Otherwise, we cannot replace.
1694
- canReplace = false
1695
- break
1696
- }
1697
- // if we're going to be replacing the peerSet, we have to remove
1698
- // and re-resolve any members of the old peerSet that are not
1699
- // present in the new one, and which will have invalid edges.
1700
- // We know that they're not depended upon by the target, or else
1701
- // they would have caused a conflict, so they'll get landed deeper
1702
- // in the tree, if possible.
1703
- if (canReplace) {
1704
- let needNesting = false
1705
- OUTER: for (const node of currentPeerSet) {
1706
- const rep = dep.parent.children.get(node.name)
1707
- // has a replacement, already addressed above
1708
- if (rep)
1709
- continue
1710
-
1711
- // ok, it has been placed here to dedupe, see if it needs to go
1712
- // back deeper within the tree.
1713
- for (const edge of node.edgesOut.values()) {
1714
- const repDep = dep.parent.children.get(edge.name)
1715
- // not in new peerSet, maybe fine.
1716
- if (!repDep)
1717
- continue
1718
-
1719
- // new thing will be fine, no worries
1720
- if (repDep.satisfies(edge))
1721
- continue
1722
-
1723
- // uhoh, we'll have to nest them.
1724
- needNesting = true
1725
- break OUTER
1726
- }
1727
- }
1728
-
1729
- // to nest, just delete everything without a target dep
1730
- // that's in the current peerSet, and add their dependants
1731
- // to the _depsQueue for evaluation. Some of these MAY end
1732
- // up in the same location again, and that's fine.
1733
- if (needNesting) {
1734
- // avoid mutating the tree while we're examining it
1735
- const dependants = new Set()
1736
- const reresolve = new Set()
1737
- OUTER: for (const node of currentPeerSet) {
1738
- const rep = dep.parent.children.get(node.name)
1739
- if (rep)
1740
- continue
1741
- // create a separate set for each one, so we can skip any
1742
- // that might somehow have an incoming target edge
1743
- const deps = new Set()
1744
- for (const edge of node.edgesIn) {
1745
- // a target dep, skip this dep entirely, already addressed
1746
- // ignoring for coverage, because it really ought to be
1747
- // impossible, but I can't prove it yet, so this is here
1748
- // for safety.
1749
- /* istanbul ignore if - should be impossible */
1750
- if (edge.from === target)
1751
- continue OUTER
1752
- // ignore this edge, it'll either be replaced or re-resolved
1753
- if (currentPeerSet.has(edge.from))
1754
- continue
1755
- // ok, we care about this one.
1756
- deps.add(edge.from)
1757
- }
1758
- reresolve.add(node)
1759
- for (const d of deps)
1760
- dependants.add(d)
1761
- }
1762
- for (const dependant of dependants) {
1763
- this[_depsQueue].push(dependant)
1764
- this[_depsSeen].delete(dependant)
1765
- }
1766
- for (const node of reresolve)
1767
- node.root = null
1768
- }
1769
- }
1770
- }
1771
-
1772
- if (canReplace) {
1773
- const ret = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
1774
- /* istanbul ignore else - extremely rare that the peer set would
1775
- * conflict if we can replace the node in question, but theoretically
1776
- * possible, if peer deps are pinned aggressively. */
1777
- if (ret !== CONFLICT)
1778
- return ret
1779
- }
1780
-
1781
- // so it's not a deeper dep that's been deduped. That means that the
1782
- // only way it could have ended up here is if it's a conflicted peer.
1783
- /* istanbul ignore else - would have already crashed if not forced,
1784
- * and either mine or strict, when creating the peerSet. Keeping this
1785
- * check so that we're not only relying on action at a distance. */
1786
- if (!this[_strictPeerDeps] && !isMine || this[_force]) {
1787
- this[_warnPeerConflict](edge, dep)
1788
- return KEEP
1789
- }
1790
- }
1791
-
1792
- // no justification for overriding, and no agreement possible.
1793
- return CONFLICT
1794
- }
1795
-
1796
- // no existing node at this location!
1797
- // check to see if the target doesn't have a child by that name,
1798
- // but WANTS one, and won't be happy with this one. if this is the
1799
- // edge we're looking to resolve, then not relevant, of course.
1800
- if (target !== entryEdge.from && target.edgesOut.has(dep.name)) {
1801
- const targetEdge = target.edgesOut.get(dep.name)
1802
- // It might be that the dep would not be valid here, BUT some other
1803
- // version would. Could to try to resolve that, but that makes this no
1804
- // longer a pure synchronous function. ugh.
1805
- // This is a pretty unlikely scenario in a normal install, because we
1806
- // resolve the peer dep set against the parent dependencies, and
1807
- // presumably they all worked together SOMEWHERE to get published in the
1808
- // first place, and since we resolve shallower deps before deeper ones,
1809
- // this can only occur by a child having a peer dep that does not satisfy
1810
- // the parent. It can happen if we're doing a deep update limited by
1811
- // a specific name, however, or if a dep makes an incompatible change
1812
- // to its peer dep in a non-semver-major version bump, or if the parent
1813
- // is unbounded in its dependency list.
1814
- if (!targetEdge.satisfiedBy(dep))
1815
- return CONFLICT
1816
- }
1817
-
1818
- // check to see what that name resolves to here, and who may depend on
1819
- // being able to reach it by crawling up past this parent. we know
1820
- // at this point that it's not the target's direct child node. if it's
1821
- // a direct dep of the target, we just make the invalid edge and
1822
- // resolve it later.
1823
- const current = target !== entryEdge.from && target.resolve(dep.name)
1824
- if (current) {
1825
- for (const edge of current.edgesIn.values()) {
1826
- if (!edge.from.isTop && edge.from.isDescendantOf(target) && edge.valid) {
1827
- if (!edge.satisfiedBy(dep))
1828
- return CONFLICT
1829
- }
1830
- }
1831
- }
1832
-
1833
- // no objections! ok to place here
1834
- return this[_canPlacePeers](dep, target, edge, OK, peerEntryEdge, peerPath, isSource)
1835
- }
1836
-
1837
- // make sure the family of peer deps can live here alongside it.
1838
- // this doesn't guarantee that THIS solution will be the one we take,
1839
- // but it does establish that SOME solution exists at this level in
1840
- // the tree.
1841
- [_canPlacePeers] (dep, target, edge, ret, peerEntryEdge, peerPath, isSource) {
1842
- // do not go in cycles when we're resolving a peer group
1843
- if (!dep.parent || peerEntryEdge && peerPath.includes(dep))
1844
- return ret
1845
-
1846
- const entryEdge = peerEntryEdge || edge
1847
- peerPath = [...peerPath, dep]
1848
-
1849
- for (const peerEdge of dep.edgesOut.values()) {
1850
- if (!peerEdge.peer || !peerEdge.to)
1851
- continue
1852
- const peer = peerEdge.to
1853
- const canPlacePeer = this[_canPlaceDep](peer, target, peerEdge, entryEdge, peerPath, isSource)
1854
- if (canPlacePeer !== CONFLICT)
1855
- continue
1856
-
1857
- const current = target.resolve(peer.name)
1858
- this[_peerConflict] = {
1859
- peer: peer.explain(peerEdge),
1860
- current: current && current.explain(),
1861
- }
1862
- return CONFLICT
1863
- }
1864
- return ret
1865
- }
1866
-
1867
1294
  // go through all the links in the this[_linkNodes] set
1868
1295
  // for each one:
1869
1296
  // - if outside the root, ignore it, assume it's fine, it's not our problem
@@ -1945,6 +1372,7 @@ This is a one-time fix-up, please be patient...
1945
1372
  const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
1946
1373
  if (this[_prune] && needPrune)
1947
1374
  this[_idealTreePrune]()
1375
+
1948
1376
  process.emit('timeEnd', 'idealTree:fixDepFlags')
1949
1377
  }
1950
1378