@npmcli/arborist 2.7.1 → 2.8.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.
Files changed (50) hide show
  1. package/bin/actual.js +4 -2
  2. package/bin/audit.js +12 -6
  3. package/bin/dedupe.js +49 -0
  4. package/bin/funding.js +4 -2
  5. package/bin/ideal.js +2 -1
  6. package/bin/lib/logging.js +4 -3
  7. package/bin/lib/options.js +14 -12
  8. package/bin/lib/timers.js +6 -3
  9. package/bin/license.js +9 -5
  10. package/bin/prune.js +6 -3
  11. package/bin/reify.js +6 -3
  12. package/bin/virtual.js +4 -2
  13. package/lib/add-rm-pkg-deps.js +25 -14
  14. package/lib/arborist/audit.js +2 -1
  15. package/lib/arborist/build-ideal-tree.js +246 -757
  16. package/lib/arborist/deduper.js +2 -1
  17. package/lib/arborist/index.js +8 -4
  18. package/lib/arborist/load-actual.js +32 -15
  19. package/lib/arborist/load-virtual.js +34 -18
  20. package/lib/arborist/load-workspaces.js +4 -2
  21. package/lib/arborist/rebuild.js +31 -16
  22. package/lib/arborist/reify.js +332 -119
  23. package/lib/audit-report.js +42 -22
  24. package/lib/calc-dep-flags.js +18 -9
  25. package/lib/can-place-dep.js +430 -0
  26. package/lib/case-insensitive-map.js +50 -0
  27. package/lib/consistent-resolve.js +2 -1
  28. package/lib/deepest-nesting-target.js +18 -0
  29. package/lib/dep-valid.js +8 -4
  30. package/lib/diff.js +74 -22
  31. package/lib/edge.js +29 -14
  32. package/lib/gather-dep-set.js +2 -1
  33. package/lib/inventory.js +12 -6
  34. package/lib/link.js +14 -9
  35. package/lib/node.js +269 -118
  36. package/lib/optional-set.js +4 -2
  37. package/lib/peer-entry-sets.js +77 -0
  38. package/lib/place-dep.js +578 -0
  39. package/lib/printable.js +48 -18
  40. package/lib/realpath.js +12 -6
  41. package/lib/shrinkwrap.js +168 -91
  42. package/lib/signal-handling.js +6 -3
  43. package/lib/spec-from-lock.js +7 -4
  44. package/lib/tracker.js +24 -18
  45. package/lib/tree-check.js +12 -6
  46. package/lib/version-from-tgz.js +4 -2
  47. package/lib/vuln.js +28 -16
  48. package/lib/yarn-lock.js +27 -15
  49. package/package.json +9 -13
  50. package/lib/peer-set.js +0 -25
@@ -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')
@@ -148,8 +137,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
148
137
  this[_globalStyle] = this[_global] || globalStyle
149
138
  this[_follow] = !!follow
150
139
 
151
- if (this[_workspaces].length && this[_global])
140
+ if (this[_workspaces].length && this[_global]) {
152
141
  throw new Error('Cannot operate on workspaces in global mode')
142
+ }
153
143
 
154
144
  this[_explicitRequests] = new Set()
155
145
  this[_preferDedupe] = false
@@ -163,7 +153,6 @@ module.exports = cls => class IdealTreeBuilder extends cls {
163
153
  this[_loadFailures] = new Set()
164
154
  this[_linkNodes] = new Set()
165
155
  this[_manifests] = new Map()
166
- this[_peerConflict] = null
167
156
  this[_edgesOverridden] = new Set()
168
157
  this[_resolvedAdd] = []
169
158
 
@@ -180,18 +169,21 @@ module.exports = cls => class IdealTreeBuilder extends cls {
180
169
 
181
170
  // public method
182
171
  async buildIdealTree (options = {}) {
183
- if (this.idealTree)
172
+ if (this.idealTree) {
184
173
  return Promise.resolve(this.idealTree)
174
+ }
185
175
 
186
176
  // allow the user to set reify options on the ctor as well.
187
177
  // XXX: deprecate separate reify() options object.
188
178
  options = { ...this.options, ...options }
189
179
 
190
180
  // an empty array or any falsey value is the same as null
191
- if (!options.add || options.add.length === 0)
181
+ if (!options.add || options.add.length === 0) {
192
182
  options.add = null
193
- if (!options.rm || options.rm.length === 0)
183
+ }
184
+ if (!options.rm || options.rm.length === 0) {
194
185
  options.rm = null
186
+ }
195
187
 
196
188
  process.emit('time', 'idealTree')
197
189
 
@@ -227,17 +219,13 @@ module.exports = cls => class IdealTreeBuilder extends cls {
227
219
  return treeCheck(this.idealTree)
228
220
  }
229
221
 
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
- })
222
+ async [_checkEngineAndPlatform] () {
223
+ for (const node of this.idealTree.inventory.values()) {
224
+ if (!node.optional) {
225
+ this[_checkEngine](node)
226
+ this[_checkPlatform](node)
227
+ }
228
+ }
241
229
  }
242
230
 
243
231
  [_checkPlatform] (node) {
@@ -246,11 +234,12 @@ module.exports = cls => class IdealTreeBuilder extends cls {
246
234
 
247
235
  [_checkEngine] (node) {
248
236
  const { engineStrict, npmVersion, nodeVersion } = this.options
249
- const c = () => checkEngine(node.package, npmVersion, nodeVersion, this[_force])
237
+ const c = () =>
238
+ checkEngine(node.package, npmVersion, nodeVersion, this[_force])
250
239
 
251
- if (engineStrict)
240
+ if (engineStrict) {
252
241
  c()
253
- else {
242
+ } else {
254
243
  try {
255
244
  c()
256
245
  } catch (er) {
@@ -268,8 +257,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
268
257
  : Array.isArray(options.update) ? { names: options.update }
269
258
  : options.update || {}
270
259
 
271
- if (update.all || !Array.isArray(update.names))
260
+ if (update.all || !Array.isArray(update.names)) {
272
261
  update.names = []
262
+ }
273
263
 
274
264
  this[_complete] = !!options.complete
275
265
  this[_preferDedupe] = !!options.preferDedupe
@@ -299,8 +289,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
299
289
  : rpj(this.path + '/package.json').then(
300
290
  pkg => this[_rootNodeFromPackage](pkg),
301
291
  er => {
302
- if (er.code === 'EJSONPARSE')
292
+ if (er.code === 'EJSONPARSE') {
303
293
  throw er
294
+ }
304
295
  return this[_rootNodeFromPackage]({})
305
296
  }
306
297
  ))
@@ -328,8 +319,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
328
319
  // even though we didn't load it from a package-lock.json FILE,
329
320
  // we still loaded it "from disk", meaning we have to reset
330
321
  // dep flags before assuming that any mutations were reflected.
331
- if (tree.children.size)
322
+ if (tree.children.size) {
332
323
  root.meta.loadedFromDisk = true
324
+ }
333
325
  }
334
326
  return root
335
327
  })
@@ -398,9 +390,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
398
390
  process.emit('time', 'idealTree:userRequests')
399
391
  const tree = this.idealTree.target
400
392
 
401
- if (!this[_workspaces].length)
393
+ if (!this[_workspaces].length) {
402
394
  await this[_applyUserRequestsToNode](tree, options)
403
- else {
395
+ } else {
404
396
  await Promise.all(this.workspaceNodes(tree, this[_workspaces])
405
397
  .map(node => this[_applyUserRequestsToNode](node, options)))
406
398
  }
@@ -412,8 +404,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
412
404
  // If we have a list of package names to update, and we know it's
413
405
  // going to update them wherever they are, add any paths into those
414
406
  // named nodes to the buildIdealTree queue.
415
- if (!this[_global] && this[_updateNames].length)
407
+ if (!this[_global] && this[_updateNames].length) {
416
408
  this[_queueNamedUpdates]()
409
+ }
417
410
 
418
411
  // global updates only update the globalTop nodes, but we need to know
419
412
  // that they're there, and not reinstall the world unnecessarily.
@@ -424,39 +417,55 @@ module.exports = cls => class IdealTreeBuilder extends cls {
424
417
  tree.package.dependencies = tree.package.dependencies || {}
425
418
  const updateName = this[_updateNames].includes(name)
426
419
  if (this[_updateAll] || updateName) {
427
- if (updateName)
420
+ if (updateName) {
428
421
  globalExplicitUpdateNames.push(name)
429
- tree.package.dependencies[name] = '*'
422
+ }
423
+ const dir = resolve(nm, name)
424
+ const st = await lstat(dir)
425
+ .catch(/* istanbul ignore next */ er => null)
426
+ if (st && st.isSymbolicLink()) {
427
+ const target = await readlink(dir)
428
+ const real = resolve(dirname(dir), target)
429
+ tree.package.dependencies[name] = `file:${real}`
430
+ } else {
431
+ tree.package.dependencies[name] = '*'
432
+ }
430
433
  }
431
434
  }
432
435
  }
433
436
 
434
- if (this.auditReport && this.auditReport.size > 0)
437
+ if (this.auditReport && this.auditReport.size > 0) {
435
438
  await this[_queueVulnDependents](options)
439
+ }
436
440
 
437
441
  const { add, rm } = options
438
442
 
439
443
  if (rm && rm.length) {
440
444
  addRmPkgDeps.rm(tree.package, rm)
441
- for (const name of rm)
445
+ for (const name of rm) {
442
446
  this[_explicitRequests].add({ from: tree, name, action: 'DELETE' })
447
+ }
443
448
  }
444
449
 
445
- if (add && add.length)
450
+ if (add && add.length) {
446
451
  await this[_add](tree, options)
452
+ }
447
453
 
448
454
  // triggers a refresh of all edgesOut. this has to be done BEFORE
449
455
  // adding the edges to explicitRequests, because the package setter
450
456
  // resets all edgesOut.
451
- if (add && add.length || rm && rm.length || this[_global])
457
+ if (add && add.length || rm && rm.length || this[_global]) {
452
458
  tree.package = tree.package
459
+ }
453
460
 
454
461
  for (const spec of this[_resolvedAdd]) {
455
- if (spec.tree === tree)
462
+ if (spec.tree === tree) {
456
463
  this[_explicitRequests].add(tree.edgesOut.get(spec.name))
464
+ }
457
465
  }
458
- for (const name of globalExplicitUpdateNames)
466
+ for (const name of globalExplicitUpdateNames) {
459
467
  this[_explicitRequests].add(tree.edgesOut.get(name))
468
+ }
460
469
 
461
470
  this[_depsQueue].push(tree)
462
471
  }
@@ -496,21 +505,24 @@ module.exports = cls => class IdealTreeBuilder extends cls {
496
505
  // if it's an explicit tag, we need to install that specific tag version
497
506
  const isTag = spec.rawSpec && spec.type === 'tag'
498
507
 
499
- if (spec.name && !isTag)
508
+ if (spec.name && !isTag) {
500
509
  return spec
510
+ }
501
511
 
502
512
  const mani = await pacote.manifest(spec, { ...this.options })
503
513
  // if it's a tag type, then we need to run it down to an actual version
504
- if (isTag)
514
+ if (isTag) {
505
515
  return npa(`${mani.name}@${mani.version}`)
516
+ }
506
517
 
507
518
  spec.name = mani.name
508
519
  return spec
509
520
  }
510
521
 
511
522
  async [_updateFilePath] (spec) {
512
- if (spec.type === 'file')
523
+ if (spec.type === 'file') {
513
524
  return this[_getRelpathSpec](spec, spec.fetchSpec)
525
+ }
514
526
 
515
527
  return spec
516
528
  }
@@ -610,8 +622,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
610
622
  nodesTouched.add(node)
611
623
  }
612
624
  }
613
- for (const node of nodesTouched)
625
+ for (const node of nodesTouched) {
614
626
  node.package = node.package
627
+ }
615
628
  }
616
629
  }
617
630
 
@@ -620,11 +633,13 @@ module.exports = cls => class IdealTreeBuilder extends cls {
620
633
  }
621
634
 
622
635
  [_avoidRange] (name) {
623
- if (!this.auditReport)
636
+ if (!this.auditReport) {
624
637
  return null
638
+ }
625
639
  const vuln = this.auditReport.get(name)
626
- if (!vuln)
640
+ if (!vuln) {
627
641
  return null
642
+ }
628
643
  return vuln.range
629
644
  }
630
645
 
@@ -661,8 +676,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
661
676
  const ancient = meta.ancientLockfile
662
677
  const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)
663
678
 
664
- if (inventory.size === 0 || !ancient && !old)
679
+ if (inventory.size === 0 || !ancient && !old) {
665
680
  return
681
+ }
666
682
 
667
683
  // if the lockfile is from node v5 or earlier, then we'll have to reload
668
684
  // all the manifests of everything we encounter. this is costly, but at
@@ -681,8 +697,9 @@ This is a one-time fix-up, please be patient...
681
697
  this.addTracker('idealTree:inflate')
682
698
  const queue = []
683
699
  for (const node of inventory.values()) {
684
- if (node.isProjectRoot)
700
+ if (node.isProjectRoot) {
685
701
  continue
702
+ }
686
703
 
687
704
  queue.push(async () => {
688
705
  this.log.silly('inflate', node.location)
@@ -747,8 +764,9 @@ This is a one-time fix-up, please be patient...
747
764
  this[_currentDep] = null
748
765
  }
749
766
 
750
- if (!this[_depsQueue].length)
767
+ if (!this[_depsQueue].length) {
751
768
  return this[_resolveLinks]()
769
+ }
752
770
 
753
771
  // sort physically shallower deps up to the front of the queue,
754
772
  // because they'll affect things deeper in, then alphabetical
@@ -766,8 +784,9 @@ This is a one-time fix-up, please be patient...
766
784
  // satisfied by whatever's in that file anyway.
767
785
  if (this[_depsSeen].has(node) ||
768
786
  node.root !== this.idealTree ||
769
- hasShrinkwrap && !this[_complete])
787
+ hasShrinkwrap && !this[_complete]) {
770
788
  return this[_buildDepStep]()
789
+ }
771
790
 
772
791
  this[_depsSeen].add(node)
773
792
  this[_currentDep] = node
@@ -850,8 +869,9 @@ This is a one-time fix-up, please be patient...
850
869
  const tasks = []
851
870
  const peerSource = this[_peerSetSource].get(node) || node
852
871
  for (const edge of this[_problemEdges](node)) {
853
- if (this[_edgesOverridden].has(edge))
872
+ if (edge.overridden) {
854
873
  continue
874
+ }
855
875
 
856
876
  // peerSetSource is only relevant when we have a peerEntryEdge
857
877
  // otherwise we're setting regular non-peer deps as if they have
@@ -887,41 +907,114 @@ This is a one-time fix-up, please be patient...
887
907
 
888
908
  /* istanbul ignore next */
889
909
  debug(() => {
890
- if (!dep)
910
+ if (!dep) {
891
911
  throw new Error('no dep??')
912
+ }
892
913
  })
893
914
 
894
915
  tasks.push({edge, dep})
895
916
  }
896
917
 
897
- const placed = tasks
918
+ const placeDeps = tasks
898
919
  .sort((a, b) => a.edge.name.localeCompare(b.edge.name, 'en'))
899
- .map(({ edge, dep }) => this[_placeDep](dep, node, edge))
920
+ .map(({ edge, dep }) => new PlaceDep({
921
+ edge,
922
+ dep,
923
+
924
+ explicitRequest: this[_explicitRequests].has(edge),
925
+ updateNames: this[_updateNames],
926
+ auditReport: this.auditReport,
927
+ force: this[_force],
928
+ preferDedupe: this[_preferDedupe],
929
+ legacyBundling: this[_legacyBundling],
930
+ strictPeerDeps: this[_strictPeerDeps],
931
+ legacyPeerDeps: this.legacyPeerDeps,
932
+ globalStyle: this[_globalStyle],
933
+ }))
900
934
 
901
935
  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
- }
936
+ for (const pd of placeDeps) {
937
+ // placing a dep is actually a tree of placing the dep itself
938
+ // and all of its peer group that aren't already met by the tree
939
+ depth({
940
+ tree: pd,
941
+ getChildren: pd => pd.children,
942
+ visit: pd => {
943
+ const { placed, edge, canPlace: cpd } = pd
944
+ // if we didn't place anything, nothing to do here
945
+ if (!placed) {
946
+ return
947
+ }
948
+
949
+ // we placed something, that means we changed the tree
950
+ if (placed.errors.length) {
951
+ this[_loadFailures].add(placed)
952
+ }
953
+ this[_mutateTree] = true
954
+ if (cpd.canPlaceSelf === OK) {
955
+ for (const edgeIn of placed.edgesIn) {
956
+ if (edgeIn === edge) {
957
+ continue
958
+ }
959
+ const { from, valid, overridden } = edgeIn
960
+ if (!overridden && !valid && !this[_depsSeen].has(from)) {
961
+ this.addTracker('idealTree', from.name, from.location)
962
+ this[_depsQueue].push(edgeIn.from)
963
+ }
964
+ }
965
+ } else {
966
+ /* istanbul ignore else - should be only OK or REPLACE here */
967
+ if (cpd.canPlaceSelf === REPLACE) {
968
+ // this may also create some invalid edges, for example if we're
969
+ // intentionally causing something to get nested which was
970
+ // previously placed in this location.
971
+ for (const edgeIn of placed.edgesIn) {
972
+ if (edgeIn === edge) {
973
+ continue
974
+ }
975
+
976
+ const { valid, overridden } = edgeIn
977
+ if (!valid && !overridden) {
978
+ // if it's already been visited, we have to re-visit
979
+ // otherwise, just enqueue normally.
980
+ this[_depsSeen].delete(edgeIn.from)
981
+ this[_depsQueue].push(edgeIn.from)
982
+ }
983
+ }
984
+ }
985
+ }
986
+
987
+ /* istanbul ignore if - should be impossible */
988
+ if (cpd.canPlaceSelf === CONFLICT) {
989
+ debug(() => {
990
+ const er = new Error('placed with canPlaceSelf=CONFLICT')
991
+ throw Object.assign(er, { placeDep: pd })
992
+ })
993
+ return
994
+ }
995
+
996
+ // lastly, also check for the missing deps of the node we placed
997
+ this[_depsQueue].push(placed)
998
+
999
+ // pre-fetch any problem edges, since we'll need these soon
1000
+ // if it fails at this point, though, dont' worry because it
1001
+ // may well be an optional dep that has gone missing. it'll
1002
+ // fail later anyway.
1003
+ const from = fromPath(placed)
1004
+ promises.push(...this[_problemEdges](placed).map(e =>
1005
+ this[_fetchManifest](npa.resolve(e.name, e.spec, from))
1006
+ .catch(er => null)))
1007
+ },
1008
+ })
917
1009
  }
918
- await Promise.all(promises)
919
1010
 
920
1011
  for (const { to } of node.edgesOut.values()) {
921
- if (to && to.isLink && to.target)
1012
+ if (to && to.isLink && to.target) {
922
1013
  this[_linkNodes].add(to)
1014
+ }
923
1015
  }
924
1016
 
1017
+ await Promise.all(promises)
925
1018
  return this[_buildDepStep]()
926
1019
  }
927
1020
 
@@ -934,7 +1027,6 @@ This is a one-time fix-up, please be patient...
934
1027
  // Note that the virtual root will also have virtual copies of the
935
1028
  // targets of any child Links, so that they resolve appropriately.
936
1029
  const parent = parent_ || this[_virtualRoot](edge.from)
937
- const realParent = edge.peer ? edge.from.resolveParent : edge.from
938
1030
 
939
1031
  const spec = npa.resolve(edge.name, edge.spec, edge.from.path)
940
1032
  const first = await this[_nodeFromSpec](edge.name, spec, parent, edge)
@@ -962,28 +1054,30 @@ This is a one-time fix-up, please be patient...
962
1054
 
963
1055
  if (required.has(edge.from) && edge.type !== 'peerOptional' ||
964
1056
  secondEdge && (
965
- required.has(secondEdge.from) && secondEdge.type !== 'peerOptional'))
1057
+ required.has(secondEdge.from) && secondEdge.type !== 'peerOptional')) {
966
1058
  required.add(node)
967
-
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
1059
  }
977
1060
 
978
1061
  // keep track of the thing that caused this node to be included.
979
1062
  const src = parent.sourceReference
980
1063
  this[_peerSetSource].set(node, src)
1064
+
1065
+ // do not load the peers along with the set if this is a global top pkg
1066
+ // otherwise we'll be tempted to put peers as other top-level installed
1067
+ // things, potentially clobbering what's there already, which is not
1068
+ // what we want. the missing edges will be picked up on the next pass.
1069
+ if (this[_global] && edge.from.isProjectRoot) {
1070
+ return node
1071
+ }
1072
+
1073
+ // otherwise, we have to make sure that our peers can go along with us.
981
1074
  return this[_loadPeerSet](node, required)
982
1075
  }
983
1076
 
984
1077
  [_virtualRoot] (node, reuse = false) {
985
- if (reuse && this[_virtualRoots].has(node))
1078
+ if (reuse && this[_virtualRoots].has(node)) {
986
1079
  return this[_virtualRoots].get(node)
1080
+ }
987
1081
 
988
1082
  const vr = new Node({
989
1083
  path: node.realpath,
@@ -1025,16 +1119,19 @@ This is a one-time fix-up, please be patient...
1025
1119
  return [...node.edgesOut.values()]
1026
1120
  .filter(edge => {
1027
1121
  // If it's included in a bundle, we take whatever is specified.
1028
- if (bundled.has(edge.name))
1122
+ if (bundled.has(edge.name)) {
1029
1123
  return false
1124
+ }
1030
1125
 
1031
1126
  // If it's already been logged as a load failure, skip it.
1032
- if (edge.to && this[_loadFailures].has(edge.to))
1127
+ if (edge.to && this[_loadFailures].has(edge.to)) {
1033
1128
  return false
1129
+ }
1034
1130
 
1035
1131
  // If it's shrinkwrapped, we use what the shrinkwap wants.
1036
- if (edge.to && edge.to.inShrinkwrap)
1132
+ if (edge.to && edge.to.inShrinkwrap) {
1037
1133
  return false
1134
+ }
1038
1135
 
1039
1136
  // If the edge has no destination, that's a problem, unless
1040
1137
  // if it's peerOptional and not explicitly requested.
@@ -1044,20 +1141,24 @@ This is a one-time fix-up, please be patient...
1044
1141
  }
1045
1142
 
1046
1143
  // If the edge has an error, there's a problem.
1047
- if (!edge.valid)
1144
+ if (!edge.valid) {
1048
1145
  return true
1146
+ }
1049
1147
 
1050
- // If user has explicitly asked to update this package by name, it's a problem.
1051
- if (this[_updateNames].includes(edge.name))
1148
+ // user explicitly asked to update this package by name, problem
1149
+ if (this[_updateNames].includes(edge.name)) {
1052
1150
  return true
1151
+ }
1053
1152
 
1054
- // If we're fixing a security vulnerability with this package, it's a problem.
1055
- if (this[_isVulnerable](edge.to))
1153
+ // fixing a security vulnerability with this package, problem
1154
+ if (this[_isVulnerable](edge.to)) {
1056
1155
  return true
1156
+ }
1057
1157
 
1058
- // If the user has explicitly asked to install this package, it's a "problem".
1059
- if (this[_explicitRequests].has(edge))
1158
+ // user has explicitly asked to install this package, problem
1159
+ if (this[_explicitRequests].has(edge)) {
1060
1160
  return true
1161
+ }
1061
1162
 
1062
1163
  // No problems!
1063
1164
  return false
@@ -1073,9 +1174,9 @@ This is a one-time fix-up, please be patient...
1073
1174
  // if available and valid.
1074
1175
  spec = this.idealTree.meta.checkYarnLock(spec, options)
1075
1176
 
1076
- if (this[_manifests].has(spec.raw))
1177
+ if (this[_manifests].has(spec.raw)) {
1077
1178
  return this[_manifests].get(spec.raw)
1078
- else {
1179
+ } else {
1079
1180
  this.log.silly('fetch manifest', spec.raw)
1080
1181
  const p = pacote.manifest(spec, options)
1081
1182
  .then(mani => {
@@ -1145,8 +1246,9 @@ This is a one-time fix-up, please be patient...
1145
1246
 
1146
1247
  for (const edge of peerEdges) {
1147
1248
  // already placed this one, and we're happy with it.
1148
- if (edge.valid && edge.to)
1249
+ if (edge.valid && edge.to) {
1149
1250
  continue
1251
+ }
1150
1252
 
1151
1253
  const parentEdge = node.parent.edgesOut.get(edge.name)
1152
1254
  const {isProjectRoot, isWorkspace} = node.parent.sourceReference
@@ -1167,17 +1269,25 @@ This is a one-time fix-up, please be patient...
1167
1269
  // a conflict. this is always a problem in strict mode, never
1168
1270
  // in force mode, and a problem in non-strict mode if this isn't
1169
1271
  // on behalf of our project. in all such cases, we warn at least.
1170
- const dep = await this[_nodeFromEdge](parentEdge, node.parent, edge, required)
1272
+ const dep = await this[_nodeFromEdge](
1273
+ parentEdge,
1274
+ node.parent,
1275
+ edge,
1276
+ required
1277
+ )
1171
1278
 
1172
1279
  // hooray! that worked!
1173
- if (edge.valid)
1280
+ if (edge.valid) {
1174
1281
  continue
1282
+ }
1175
1283
 
1176
1284
  // allow it. either we're overriding, or it's not something
1177
1285
  // that will be installed by default anyway, and we'll fail when
1178
1286
  // we get to the point where we need to, if we need to.
1179
- if (conflictOK || !required.has(dep))
1287
+ if (conflictOK || !required.has(dep)) {
1288
+ edge.overridden = true
1180
1289
  continue
1290
+ }
1181
1291
 
1182
1292
  // problem
1183
1293
  this[_failPeerConflict](edge, parentEdge)
@@ -1202,8 +1312,9 @@ This is a one-time fix-up, please be patient...
1202
1312
  // isn't also required, then there's a good chance we won't need it,
1203
1313
  // so allow it for now and let it conflict if it turns out to actually
1204
1314
  // be necessary for the installation.
1205
- if (conflictOK || !required.has(edge.from))
1315
+ if (conflictOK || !required.has(edge.from)) {
1206
1316
  continue
1317
+ }
1207
1318
 
1208
1319
  // ok, it's the root, or we're in unforced strict mode, so this is bad
1209
1320
  this[_failPeerConflict](edge, parentEdge)
@@ -1219,9 +1330,7 @@ This is a one-time fix-up, please be patient...
1219
1330
  [_explainPeerConflict] (edge, currentEdge) {
1220
1331
  const node = edge.from
1221
1332
  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
1333
+ const current = curNode.explain()
1225
1334
  return {
1226
1335
  code: 'ERESOLVE',
1227
1336
  current,
@@ -1230,640 +1339,11 @@ This is a one-time fix-up, please be patient...
1230
1339
  // the tree handling logic.
1231
1340
  currentEdge: currentEdge ? currentEdge.explain() : null,
1232
1341
  edge: edge.explain(),
1233
- peerConflict,
1234
1342
  strictPeerDeps: this[_strictPeerDeps],
1235
1343
  force: this[_force],
1236
1344
  }
1237
1345
  }
1238
1346
 
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
1347
  // go through all the links in the this[_linkNodes] set
1868
1348
  // for each one:
1869
1349
  // - if outside the root, ignore it, assume it's fine, it's not our problem
@@ -1877,15 +1357,17 @@ This is a one-time fix-up, please be patient...
1877
1357
  this[_linkNodes].delete(link)
1878
1358
 
1879
1359
  // link we never ended up placing, skip it
1880
- if (link.root !== this.idealTree)
1360
+ if (link.root !== this.idealTree) {
1881
1361
  continue
1362
+ }
1882
1363
 
1883
1364
  const tree = this.idealTree.target
1884
1365
  const external = !link.target.isDescendantOf(tree)
1885
1366
 
1886
1367
  // outside the root, somebody else's problem, ignore it
1887
- if (external && !this[_follow])
1368
+ if (external && !this[_follow]) {
1888
1369
  continue
1370
+ }
1889
1371
 
1890
1372
  // didn't find a parent for it or it has not been seen yet
1891
1373
  // so go ahead and process it.
@@ -1901,8 +1383,9 @@ This is a one-time fix-up, please be patient...
1901
1383
  }
1902
1384
  }
1903
1385
 
1904
- if (this[_depsQueue].length)
1386
+ if (this[_depsQueue].length) {
1905
1387
  return this[_buildDepStep]()
1388
+ }
1906
1389
  }
1907
1390
 
1908
1391
  [_fixDepFlags] () {
@@ -1917,8 +1400,9 @@ This is a one-time fix-up, please be patient...
1917
1400
  // all set to true, and there can be nothing extraneous, so there's
1918
1401
  // nothing to prune, because we built it from scratch. if we didn't
1919
1402
  // add or remove anything, then also nothing to do.
1920
- if (metaFromDisk && mutateTree)
1403
+ if (metaFromDisk && mutateTree) {
1921
1404
  resetDepFlags(this.idealTree)
1405
+ }
1922
1406
 
1923
1407
  // update all the dev/optional/etc flags in the tree
1924
1408
  // either we started with a fresh tree, or we
@@ -1926,9 +1410,9 @@ This is a one-time fix-up, please be patient...
1926
1410
  //
1927
1411
  // if we started from a blank slate, or changed something, then
1928
1412
  // the dep flags will be all set to true.
1929
- if (!metaFromDisk || mutateTree)
1413
+ if (!metaFromDisk || mutateTree) {
1930
1414
  calcDepFlags(this.idealTree)
1931
- else {
1415
+ } else {
1932
1416
  // otherwise just unset all the flags on the root node
1933
1417
  // since they will sometimes have the default value
1934
1418
  this.idealTree.extraneous = false
@@ -1943,24 +1427,29 @@ This is a one-time fix-up, please be patient...
1943
1427
  // then the tree is suspect. Prune what is marked as extraneous.
1944
1428
  // otherwise, don't bother.
1945
1429
  const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
1946
- if (this[_prune] && needPrune)
1430
+ if (this[_prune] && needPrune) {
1947
1431
  this[_idealTreePrune]()
1432
+ }
1433
+
1948
1434
  process.emit('timeEnd', 'idealTree:fixDepFlags')
1949
1435
  }
1950
1436
 
1951
1437
  [_idealTreePrune] () {
1952
- for (const node of this.idealTree.inventory.filter(n => n.extraneous))
1438
+ for (const node of this.idealTree.inventory.filter(n => n.extraneous)) {
1953
1439
  node.parent = null
1440
+ }
1954
1441
  }
1955
1442
 
1956
1443
  [_pruneFailedOptional] () {
1957
1444
  for (const node of this[_loadFailures]) {
1958
- if (!node.optional)
1445
+ if (!node.optional) {
1959
1446
  throw node.errors[0]
1447
+ }
1960
1448
 
1961
1449
  const set = optionalSet(node)
1962
- for (const node of set)
1450
+ for (const node of set) {
1963
1451
  node.parent = null
1452
+ }
1964
1453
  }
1965
1454
  }
1966
1455
  }