@npmcli/arborist 2.6.3 → 2.8.0

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