@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
@@ -10,8 +10,9 @@
10
10
 
11
11
  const gatherDepSet = require('./gather-dep-set.js')
12
12
  const optionalSet = node => {
13
- if (!node.optional)
13
+ if (!node.optional) {
14
14
  return new Set()
15
+ }
15
16
 
16
17
  // start with the node, then walk up the dependency graph until we
17
18
  // get to the boundaries that define the optional set. since the
@@ -21,8 +22,9 @@ const optionalSet = node => {
21
22
  const set = new Set([node])
22
23
  for (const node of set) {
23
24
  for (const edge of node.edgesIn) {
24
- if (!edge.optional)
25
+ if (!edge.optional) {
25
26
  set.add(edge.from)
27
+ }
26
28
  }
27
29
  }
28
30
 
@@ -0,0 +1,77 @@
1
+ // Given a node in a tree, return all of the peer dependency sets that
2
+ // it is a part of, with the entry (top or non-peer) edges into the sets
3
+ // identified.
4
+ //
5
+ // With this information, we can determine whether it is appropriate to
6
+ // replace the entire peer set with another (and remove the old one),
7
+ // push the set deeper into the tree, and so on.
8
+ //
9
+ // Returns a Map of { edge => Set(peerNodes) },
10
+
11
+ const peerEntrySets = node => {
12
+ // this is the union of all peer groups that the node is a part of
13
+ // later, we identify all of the entry edges, and create a set of
14
+ // 1 or more overlapping sets that this node is a part of.
15
+ const unionSet = new Set([node])
16
+ for (const node of unionSet) {
17
+ for (const edge of node.edgesOut.values()) {
18
+ if (edge.valid && edge.peer && edge.to) {
19
+ unionSet.add(edge.to)
20
+ }
21
+ }
22
+ for (const edge of node.edgesIn) {
23
+ if (edge.valid && edge.peer) {
24
+ unionSet.add(edge.from)
25
+ }
26
+ }
27
+ }
28
+ const entrySets = new Map()
29
+ for (const peer of unionSet) {
30
+ for (const edge of peer.edgesIn) {
31
+ // if not valid, it doesn't matter anyway. either it's been previously
32
+ // overridden, or it's the thing we're interested in replacing.
33
+ if (!edge.valid) {
34
+ continue
35
+ }
36
+ // this is the entry point into the peer set
37
+ if (!edge.peer || edge.from.isTop) {
38
+ // get the subset of peer brought in by this peer entry edge
39
+ const sub = new Set([peer])
40
+ for (const peer of sub) {
41
+ for (const edge of peer.edgesOut.values()) {
42
+ if (edge.valid && edge.peer && edge.to) {
43
+ sub.add(edge.to)
44
+ }
45
+ }
46
+ }
47
+ // if this subset does not include the node we are focused on,
48
+ // then it is not relevant for our purposes. Example:
49
+ //
50
+ // a -> (b, c, d)
51
+ // b -> PEER(d) b -> d -> e -> f <-> g
52
+ // c -> PEER(f, h) c -> (f <-> g, h -> g)
53
+ // d -> PEER(e) d -> e -> f <-> g
54
+ // e -> PEER(f)
55
+ // f -> PEER(g)
56
+ // g -> PEER(f)
57
+ // h -> PEER(g)
58
+ //
59
+ // The unionSet(e) will include c, but we don't actually care about
60
+ // it. We only expanded to the edge of the peer nodes in order to
61
+ // find the entry edges that caused the inclusion of peer sets
62
+ // including (e), so we want:
63
+ // Map{
64
+ // Edge(a->b) => Set(b, d, e, f, g)
65
+ // Edge(a->d) => Set(d, e, f, g)
66
+ // }
67
+ if (sub.has(node)) {
68
+ entrySets.set(edge, sub)
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ return entrySets
75
+ }
76
+
77
+ module.exports = peerEntrySets
@@ -0,0 +1,578 @@
1
+ // Given a dep, a node that depends on it, and the edge representing that
2
+ // dependency, place the dep somewhere in the node's tree, and all of its
3
+ // peer dependencies.
4
+ //
5
+ // Handles all of the tree updating needed to place the dep, including
6
+ // removing replaced nodes, pruning now-extraneous or invalidated nodes,
7
+ // and saves a set of what was placed and what needs re-evaluation as
8
+ // a result.
9
+
10
+ const log = require('proc-log')
11
+ const deepestNestingTarget = require('./deepest-nesting-target.js')
12
+ const CanPlaceDep = require('./can-place-dep.js')
13
+ const {
14
+ KEEP,
15
+ CONFLICT,
16
+ } = CanPlaceDep
17
+ const debug = require('./debug.js')
18
+
19
+ const Link = require('./link.js')
20
+ const gatherDepSet = require('./gather-dep-set.js')
21
+ const peerEntrySets = require('./peer-entry-sets.js')
22
+
23
+ class PlaceDep {
24
+ constructor (options) {
25
+ const {
26
+ dep,
27
+ edge,
28
+ parent = null,
29
+ } = options
30
+ this.name = edge.name
31
+ this.dep = dep
32
+ this.edge = edge
33
+ this.canPlace = null
34
+
35
+ this.target = null
36
+ this.placed = null
37
+
38
+ // inherit all these fields from the parent to ensure consistency.
39
+ const {
40
+ preferDedupe,
41
+ force,
42
+ explicitRequest,
43
+ updateNames,
44
+ auditReport,
45
+ legacyBundling,
46
+ strictPeerDeps,
47
+ legacyPeerDeps,
48
+ globalStyle,
49
+ } = parent || options
50
+ Object.assign(this, {
51
+ preferDedupe,
52
+ force,
53
+ explicitRequest,
54
+ updateNames,
55
+ auditReport,
56
+ legacyBundling,
57
+ strictPeerDeps,
58
+ legacyPeerDeps,
59
+ globalStyle,
60
+ })
61
+
62
+ this.children = []
63
+ this.parent = parent
64
+ this.peerConflict = null
65
+
66
+ this.checks = new Map()
67
+
68
+ this.place()
69
+ }
70
+
71
+ place () {
72
+ const {
73
+ edge,
74
+ dep,
75
+ preferDedupe,
76
+ globalStyle,
77
+ legacyBundling,
78
+ explicitRequest,
79
+ updateNames,
80
+ checks,
81
+ } = this
82
+
83
+ // nothing to do if the edge is fine as it is
84
+ if (edge.to &&
85
+ !edge.error &&
86
+ !explicitRequest &&
87
+ !updateNames.includes(edge.name) &&
88
+ !this.isVulnerable(edge.to)) {
89
+ return
90
+ }
91
+
92
+ // walk up the tree until we hit either a top/root node, or a place
93
+ // where the dep is not a peer dep.
94
+ const start = this.getStartNode()
95
+
96
+ let canPlace = null
97
+ let canPlaceSelf = null
98
+ for (const target of start.ancestry()) {
99
+ // if the current location has a peerDep on it, then we can't place here
100
+ // this is pretty rare to hit, since we always prefer deduping peers,
101
+ // and the getStartNode will start us out above any peers from the
102
+ // thing that depends on it. but we could hit it with something like:
103
+ //
104
+ // a -> (b@1, c@1)
105
+ // +-- c@1
106
+ // +-- b -> PEEROPTIONAL(v) (c@2)
107
+ // +-- c@2 -> (v)
108
+ //
109
+ // So we check if we can place v under c@2, that's fine.
110
+ // Then we check under b, and can't, because of the optional peer dep.
111
+ // but we CAN place it under a, so the correct thing to do is keep
112
+ // walking up the tree.
113
+ const targetEdge = target.edgesOut.get(edge.name)
114
+ if (!target.isTop && targetEdge && targetEdge.peer) {
115
+ continue
116
+ }
117
+
118
+ const cpd = new CanPlaceDep({
119
+ dep,
120
+ edge,
121
+ // note: this sets the parent's canPlace as the parent of this
122
+ // canPlace, but it does NOT add this canPlace to the parent's
123
+ // children. This way, we can know that it's a peer dep, and
124
+ // get the top edge easily, while still maintaining the
125
+ // tree of checks that factored into the original decision.
126
+ parent: this.parent && this.parent.canPlace,
127
+ target,
128
+ preferDedupe,
129
+ explicitRequest: this.explicitRequest,
130
+ })
131
+ checks.set(target, cpd)
132
+
133
+ // It's possible that a "conflict" is a conflict among the *peers* of
134
+ // a given node we're trying to place, but there actually is no current
135
+ // node. Eg,
136
+ // root -> (a, b)
137
+ // a -> PEER(c)
138
+ // b -> PEER(d)
139
+ // d -> PEER(c@2)
140
+ // We place (a), and get a peer of (c) along with it.
141
+ // then we try to place (b), and get CONFLICT in the check, because
142
+ // of the conflicting peer from (b)->(d)->(c@2). In that case, we
143
+ // should treat (b) and (d) as OK, and place them in the last place
144
+ // where they did not themselves conflict, and skip c@2 if conflict
145
+ // is ok by virtue of being forced or not ours and not strict.
146
+ if (cpd.canPlaceSelf !== CONFLICT) {
147
+ canPlaceSelf = cpd
148
+ }
149
+
150
+ // we found a place this can go, along with all its peer friends.
151
+ // we break when we get the first conflict
152
+ if (cpd.canPlace !== CONFLICT) {
153
+ canPlace = cpd
154
+ } else {
155
+ break
156
+ }
157
+
158
+ // if it's a load failure, just plop it in the first place attempted,
159
+ // since we're going to crash the build or prune it out anyway.
160
+ // but, this will frequently NOT be a successful canPlace, because
161
+ // it'll have no version or other information.
162
+ if (dep.errors.length) {
163
+ break
164
+ }
165
+
166
+ // nest packages like npm v1 and v2
167
+ // very disk-inefficient
168
+ if (legacyBundling) {
169
+ break
170
+ }
171
+
172
+ // when installing globally, or just in global style, we never place
173
+ // deps above the first level.
174
+ if (globalStyle) {
175
+ const rp = target.resolveParent
176
+ if (rp && rp.isProjectRoot) {
177
+ break
178
+ }
179
+ }
180
+ }
181
+
182
+ Object.assign(this, {
183
+ canPlace,
184
+ canPlaceSelf,
185
+ })
186
+ this.current = edge.to
187
+
188
+ // if we can't find a target, that means that the last place checked,
189
+ // and all the places before it, had a conflict.
190
+ if (!canPlace) {
191
+ // if not forced, or it's our dep, or strictPeerDeps is set, then
192
+ // this is an ERESOLVE error.
193
+ if (!this.conflictOk) {
194
+ return this.failPeerConflict()
195
+ }
196
+
197
+ // ok! we're gonna allow the conflict, but we should still warn
198
+ // if we have a current, then we treat CONFLICT as a KEEP.
199
+ // otherwise, we just skip it. Only warn on the one that actually
200
+ // could not be placed somewhere.
201
+ if (!canPlaceSelf) {
202
+ this.warnPeerConflict()
203
+ return
204
+ }
205
+
206
+ this.canPlace = canPlaceSelf
207
+ }
208
+
209
+ // now we have a target, a tree of CanPlaceDep results for the peer group,
210
+ // and we are ready to go
211
+ this.placeInTree()
212
+ }
213
+
214
+ placeInTree () {
215
+ const {
216
+ dep,
217
+ canPlace,
218
+ edge,
219
+ } = this
220
+
221
+ /* istanbul ignore next */
222
+ if (!canPlace) {
223
+ debug(() => {
224
+ throw new Error('canPlace not set, but trying to place in tree')
225
+ })
226
+ return
227
+ }
228
+
229
+ const { target } = canPlace
230
+
231
+ log.silly(
232
+ 'placeDep',
233
+ target.location || 'ROOT',
234
+ `${dep.name}@${dep.version}`,
235
+ canPlace.description,
236
+ `for: ${this.edge.from.package._id || this.edge.from.location}`,
237
+ `want: ${edge.spec || '*'}`
238
+ )
239
+
240
+ const placementType = canPlace.canPlace === CONFLICT
241
+ ? canPlace.canPlaceSelf
242
+ : canPlace.canPlace
243
+
244
+ // if we're placing in the tree with --force, we can get here even though
245
+ // it's a conflict. Treat it as a KEEP, but warn and move on.
246
+ if (placementType === KEEP) {
247
+ // this was an overridden peer dep
248
+ if (edge.peer && !edge.valid) {
249
+ this.warnPeerConflict()
250
+ }
251
+
252
+ // if we get a KEEP in a update scenario, then we MAY have something
253
+ // already duplicating this unnecessarily! For example:
254
+ // ```
255
+ // root (dep: y@1)
256
+ // +-- x (dep: y@1.1)
257
+ // | +-- y@1.1.0 (replacing with 1.1.2, got KEEP at the root)
258
+ // +-- y@1.1.2 (updated already from 1.0.0)
259
+ // ```
260
+ // Now say we do `reify({update:['y']})`, and the latest version is
261
+ // 1.1.2, which we now have in the root. We'll try to place y@1.1.2
262
+ // first in x, then in the root, ending with KEEP, because we already
263
+ // have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
264
+ // it is an unnecessary duplicate.
265
+ this.pruneDedupable(target)
266
+ return
267
+ }
268
+
269
+ // we were told to place it here in the target, so either it does not
270
+ // already exist in the tree, OR it's shadowed.
271
+ // handle otherwise unresolvable dependency nesting loops by
272
+ // creating a symbolic link
273
+ // a1 -> b1 -> a2 -> b2 -> a1 -> ...
274
+ // instead of nesting forever, when the loop occurs, create
275
+ // a symbolic link to the earlier instance
276
+ for (let p = target; p; p = p.resolveParent) {
277
+ if (p.matches(dep) && !p.isTop) {
278
+ this.placed = new Link({ parent: target, target: p })
279
+ return
280
+ }
281
+ }
282
+
283
+ // XXX if we are replacing SOME of a peer entry group, we will need to
284
+ // remove any that are not being replaced and will now be invalid, and
285
+ // re-evaluate them deeper into the tree.
286
+
287
+ const virtualRoot = dep.parent
288
+ this.placed = new dep.constructor({
289
+ name: dep.name,
290
+ pkg: dep.package,
291
+ resolved: dep.resolved,
292
+ integrity: dep.integrity,
293
+ legacyPeerDeps: this.legacyPeerDeps,
294
+ error: dep.errors[0],
295
+ ...(dep.isLink ? { target: dep.target, realpath: dep.realpath } : {}),
296
+ })
297
+
298
+ this.oldDep = target.children.get(this.name)
299
+ if (this.oldDep) {
300
+ this.replaceOldDep()
301
+ } else {
302
+ this.placed.parent = target
303
+ }
304
+
305
+ // if it's an overridden peer dep, warn about it
306
+ if (edge.peer && !this.placed.satisfies(edge)) {
307
+ this.warnPeerConflict()
308
+ }
309
+
310
+ // If the edge is not an error, then we're updating something, and
311
+ // MAY end up putting a better/identical node further up the tree in
312
+ // a way that causes an unnecessary duplication. If so, remove the
313
+ // now-unnecessary node.
314
+ if (edge.valid && edge.to && edge.to !== this.placed) {
315
+ this.pruneDedupable(edge.to, false)
316
+ }
317
+
318
+ // in case we just made some duplicates that can be removed,
319
+ // prune anything deeper in the tree that can be replaced by this
320
+ for (const node of target.root.inventory.query('name', this.name)) {
321
+ if (node.isDescendantOf(target) && !node.isTop) {
322
+ this.pruneDedupable(node, false)
323
+ // only walk the direct children of the ones we kept
324
+ if (node.root === target.root) {
325
+ for (const kid of node.children.values()) {
326
+ this.pruneDedupable(kid, false)
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ // also place its unmet or invalid peer deps at this location
333
+ // loop through any peer deps from the thing we just placed, and place
334
+ // those ones as well. it's safe to do this with the virtual nodes,
335
+ // because we're copying rather than moving them out of the virtual root,
336
+ // otherwise they'd be gone and the peer set would change throughout
337
+ // this loop.
338
+ for (const peerEdge of this.placed.edgesOut.values()) {
339
+ if (peerEdge.valid || !peerEdge.peer || peerEdge.overridden) {
340
+ continue
341
+ }
342
+
343
+ const peer = virtualRoot.children.get(peerEdge.name)
344
+
345
+ // Note: if the virtualRoot *doesn't* have the peer, then that means
346
+ // it's an optional peer dep. If it's not being properly met (ie,
347
+ // peerEdge.valid is false), then this is likely heading for an
348
+ // ERESOLVE error, unless it can walk further up the tree.
349
+ if (!peer) {
350
+ continue
351
+ }
352
+
353
+ // overridden peerEdge, just accept what's there already
354
+ if (!peer.satisfies(peerEdge)) {
355
+ continue
356
+ }
357
+
358
+ this.children.push(new PlaceDep({
359
+ parent: this,
360
+ dep: peer,
361
+ node: this.placed,
362
+ edge: peerEdge,
363
+ }))
364
+ }
365
+ }
366
+
367
+ replaceOldDep () {
368
+ // XXX handle replacing an entire peer group?
369
+ // what about cases where we need to push some other peer groups deeper
370
+ // into the tree? all the tree updating should be done here, and track
371
+ // all the things that we add and remove, so that we can know what
372
+ // to re-evaluate.
373
+
374
+ // if we're replacing, we should also remove any nodes for edges that
375
+ // are now invalid, and where this (or its deps) is the only dependent,
376
+ // and also recurse on that pruning. Otherwise leaving that dep node
377
+ // around can result in spurious conflicts pushing nodes deeper into
378
+ // the tree than needed in the case of cycles that will be removed
379
+ // later anyway.
380
+ const oldDeps = []
381
+ for (const [name, edge] of this.oldDep.edgesOut.entries()) {
382
+ if (!this.placed.edgesOut.has(name) && edge.to) {
383
+ oldDeps.push(...gatherDepSet([edge.to], e => e.to !== edge.to))
384
+ }
385
+ }
386
+ this.placed.replace(this.oldDep)
387
+ this.pruneForReplacement(this.placed, oldDeps)
388
+ }
389
+
390
+ pruneForReplacement (node, oldDeps) {
391
+ // gather up all the now-invalid/extraneous edgesOut, as long as they are
392
+ // only depended upon by the old node/deps
393
+ const invalidDeps = new Set([...node.edgesOut.values()]
394
+ .filter(e => e.to && !e.valid).map(e => e.to))
395
+ for (const dep of oldDeps) {
396
+ const set = gatherDepSet([dep], e => e.to !== dep && e.valid)
397
+ for (const dep of set) {
398
+ invalidDeps.add(dep)
399
+ }
400
+ }
401
+
402
+ // ignore dependency edges from the node being replaced, but
403
+ // otherwise filter the set down to just the set with no
404
+ // dependencies from outside the set, except the node in question.
405
+ const deps = gatherDepSet(invalidDeps, edge =>
406
+ edge.from !== node && edge.to !== node && edge.valid)
407
+
408
+ // now just delete whatever's left, because it's junk
409
+ for (const dep of deps) {
410
+ dep.root = null
411
+ }
412
+ }
413
+
414
+ // prune all the nodes in a branch of the tree that can be safely removed
415
+ // This is only the most basic duplication detection; it finds if there
416
+ // is another satisfying node further up the tree, and if so, dedupes.
417
+ // Even in legacyBundling mode, we do this amount of deduplication.
418
+ pruneDedupable (node, descend = true) {
419
+ if (node.canDedupe(this.preferDedupe)) {
420
+ // gather up all deps that have no valid edges in from outside
421
+ // the dep set, except for this node we're deduping, so that we
422
+ // also prune deps that would be made extraneous.
423
+ const deps = gatherDepSet([node], e => e.to !== node && e.valid)
424
+ for (const node of deps) {
425
+ node.root = null
426
+ }
427
+ return
428
+ }
429
+ if (descend) {
430
+ // sort these so that they're deterministically ordered
431
+ // otherwise, resulting tree shape is dependent on the order
432
+ // in which they happened to be resolved.
433
+ const nodeSort = (a, b) => a.location.localeCompare(b.location, 'en')
434
+
435
+ const children = [...node.children.values()].sort(nodeSort)
436
+ for (const child of children) {
437
+ this.pruneDedupable(child)
438
+ }
439
+ const fsChildren = [...node.fsChildren].sort(nodeSort)
440
+ for (const topNode of fsChildren) {
441
+ const children = [...topNode.children.values()].sort(nodeSort)
442
+ for (const child of children) {
443
+ this.pruneDedupable(child)
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ get conflictOk () {
450
+ return this.force || (!this.isMine && !this.strictPeerDeps)
451
+ }
452
+
453
+ get isMine () {
454
+ const { edge } = this.top
455
+ const { from: node } = edge
456
+
457
+ if (node.isWorkspace || node.isProjectRoot) {
458
+ return true
459
+ }
460
+
461
+ if (!edge.peer) {
462
+ return false
463
+ }
464
+
465
+ // re-entry case. check if any non-peer edges come from the project,
466
+ // or any entryEdges on peer groups are from the root.
467
+ let hasPeerEdges = false
468
+ for (const edge of node.edgesIn) {
469
+ if (edge.peer) {
470
+ hasPeerEdges = true
471
+ continue
472
+ }
473
+ if (edge.from.isWorkspace || edge.from.isProjectRoot) {
474
+ return true
475
+ }
476
+ }
477
+ if (hasPeerEdges) {
478
+ for (const edge of peerEntrySets(node).keys()) {
479
+ if (edge.from.isWorkspace || edge.from.isProjectRoot) {
480
+ return true
481
+ }
482
+ }
483
+ }
484
+
485
+ return false
486
+ }
487
+
488
+ warnPeerConflict () {
489
+ this.edge.overridden = true
490
+ const expl = this.explainPeerConflict()
491
+ log.warn('ERESOLVE', 'overriding peer dependency', expl)
492
+ }
493
+
494
+ failPeerConflict () {
495
+ const expl = this.explainPeerConflict()
496
+ throw Object.assign(new Error('could not resolve'), expl)
497
+ }
498
+
499
+ explainPeerConflict () {
500
+ const { edge, dep } = this.top
501
+ const { from: node } = edge
502
+ const curNode = node.resolve(edge.name)
503
+
504
+ const expl = {
505
+ code: 'ERESOLVE',
506
+ edge: edge.explain(),
507
+ dep: dep.explain(edge),
508
+ }
509
+
510
+ if (this.parent) {
511
+ // this is the conflicted peer
512
+ expl.current = curNode && curNode.explain(edge)
513
+ expl.peerConflict = this.current && this.current.explain(this.edge)
514
+ } else {
515
+ expl.current = curNode && curNode.explain()
516
+ if (this.canPlaceSelf && this.canPlaceSelf.canPlaceSelf !== CONFLICT) {
517
+ // failed while checking for a child dep
518
+ const cps = this.canPlaceSelf
519
+ for (const peer of cps.conflictChildren) {
520
+ if (peer.current) {
521
+ expl.peerConflict = {
522
+ current: peer.current.explain(),
523
+ peer: peer.dep.explain(peer.edge),
524
+ }
525
+ break
526
+ }
527
+ }
528
+ } else {
529
+ expl.peerConflict = {
530
+ current: this.current && this.current.explain(),
531
+ peer: this.dep.explain(this.edge),
532
+ }
533
+ }
534
+ }
535
+
536
+ const {
537
+ strictPeerDeps,
538
+ force,
539
+ isMine,
540
+ } = this
541
+ Object.assign(expl, {
542
+ strictPeerDeps,
543
+ force,
544
+ isMine,
545
+ })
546
+
547
+ // XXX decorate more with this.canPlace and this.canPlaceSelf,
548
+ // this.checks, this.children, walk over conflicted peers, etc.
549
+ return expl
550
+ }
551
+
552
+ getStartNode () {
553
+ // if we are a peer, then we MUST be at least as shallow as the
554
+ // peer dependent
555
+ const from = this.parent ? this.parent.getStartNode() : this.edge.from
556
+ return deepestNestingTarget(from, this.name)
557
+ }
558
+
559
+ get top () {
560
+ return this.parent ? this.parent.top : this
561
+ }
562
+
563
+ isVulnerable (node) {
564
+ return this.auditReport && this.auditReport.isVulnerable(node)
565
+ }
566
+
567
+ get allChildren () {
568
+ const set = new Set(this.children)
569
+ for (const child of set) {
570
+ for (const grandchild of child.children) {
571
+ set.add(grandchild)
572
+ }
573
+ }
574
+ return [...set]
575
+ }
576
+ }
577
+
578
+ module.exports = PlaceDep