@npmcli/arborist 2.6.4 → 2.8.1

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.
@@ -0,0 +1,551 @@
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
+ // walk up the tree until we hit either a top/root node, or a place
92
+ // where the dep is not a peer dep.
93
+ const start = this.getStartNode()
94
+
95
+ let canPlace = null
96
+ let canPlaceSelf = null
97
+ for (const target of start.ancestry()) {
98
+ // if the current location has a peerDep on it, then we can't place here
99
+ // this is pretty rare to hit, since we always prefer deduping peers,
100
+ // and the getStartNode will start us out above any peers from the
101
+ // thing that depends on it. but we could hit it with something like:
102
+ //
103
+ // a -> (b@1, c@1)
104
+ // +-- c@1
105
+ // +-- b -> PEEROPTIONAL(v) (c@2)
106
+ // +-- c@2 -> (v)
107
+ //
108
+ // So we check if we can place v under c@2, that's fine.
109
+ // Then we check under b, and can't, because of the optional peer dep.
110
+ // but we CAN place it under a, so the correct thing to do is keep
111
+ // walking up the tree.
112
+ const targetEdge = target.edgesOut.get(edge.name)
113
+ if (!target.isTop && targetEdge && targetEdge.peer)
114
+ continue
115
+
116
+ const cpd = new CanPlaceDep({
117
+ dep,
118
+ edge,
119
+ // note: this sets the parent's canPlace as the parent of this
120
+ // canPlace, but it does NOT add this canPlace to the parent's
121
+ // children. This way, we can know that it's a peer dep, and
122
+ // get the top edge easily, while still maintaining the
123
+ // tree of checks that factored into the original decision.
124
+ parent: this.parent && this.parent.canPlace,
125
+ target,
126
+ preferDedupe,
127
+ explicitRequest: this.explicitRequest,
128
+ })
129
+ checks.set(target, cpd)
130
+
131
+ // It's possible that a "conflict" is a conflict among the *peers* of
132
+ // a given node we're trying to place, but there actually is no current
133
+ // node. Eg,
134
+ // root -> (a, b)
135
+ // a -> PEER(c)
136
+ // b -> PEER(d)
137
+ // d -> PEER(c@2)
138
+ // We place (a), and get a peer of (c) along with it.
139
+ // then we try to place (b), and get CONFLICT in the check, because
140
+ // of the conflicting peer from (b)->(d)->(c@2). In that case, we
141
+ // should treat (b) and (d) as OK, and place them in the last place
142
+ // where they did not themselves conflict, and skip c@2 if conflict
143
+ // is ok by virtue of being forced or not ours and not strict.
144
+ if (cpd.canPlaceSelf !== CONFLICT)
145
+ canPlaceSelf = cpd
146
+
147
+ // we found a place this can go, along with all its peer friends.
148
+ // we break when we get the first conflict
149
+ if (cpd.canPlace !== CONFLICT)
150
+ canPlace = cpd
151
+ else
152
+ break
153
+
154
+ // if it's a load failure, just plop it in the first place attempted,
155
+ // since we're going to crash the build or prune it out anyway.
156
+ // but, this will frequently NOT be a successful canPlace, because
157
+ // it'll have no version or other information.
158
+ if (dep.errors.length)
159
+ break
160
+
161
+ // nest packages like npm v1 and v2
162
+ // very disk-inefficient
163
+ if (legacyBundling)
164
+ break
165
+
166
+ // when installing globally, or just in global style, we never place
167
+ // deps above the first level.
168
+ if (globalStyle) {
169
+ const rp = target.resolveParent
170
+ if (rp && rp.isProjectRoot)
171
+ break
172
+ }
173
+ }
174
+
175
+ Object.assign(this, {
176
+ canPlace,
177
+ canPlaceSelf,
178
+ })
179
+ this.current = edge.to
180
+
181
+ // if we can't find a target, that means that the last place checked,
182
+ // and all the places before it, had a conflict.
183
+ if (!canPlace) {
184
+ // if not forced, or it's our dep, or strictPeerDeps is set, then
185
+ // this is an ERESOLVE error.
186
+ if (!this.conflictOk)
187
+ return this.failPeerConflict()
188
+
189
+ // ok! we're gonna allow the conflict, but we should still warn
190
+ // if we have a current, then we treat CONFLICT as a KEEP.
191
+ // otherwise, we just skip it. Only warn on the one that actually
192
+ // could not be placed somewhere.
193
+ if (!canPlaceSelf) {
194
+ this.warnPeerConflict()
195
+ return
196
+ }
197
+
198
+ this.canPlace = canPlaceSelf
199
+ }
200
+
201
+ // now we have a target, a tree of CanPlaceDep results for the peer group,
202
+ // and we are ready to go
203
+ this.placeInTree()
204
+ }
205
+
206
+ placeInTree () {
207
+ const {
208
+ dep,
209
+ canPlace,
210
+ edge,
211
+ } = this
212
+
213
+ /* istanbul ignore next */
214
+ if (!canPlace) {
215
+ debug(() => {
216
+ throw new Error('canPlace not set, but trying to place in tree')
217
+ })
218
+ return
219
+ }
220
+
221
+ const { target } = canPlace
222
+
223
+ log.silly(
224
+ 'placeDep',
225
+ target.location || 'ROOT',
226
+ `${dep.name}@${dep.version}`,
227
+ canPlace.description,
228
+ `for: ${this.edge.from.package._id || this.edge.from.location}`,
229
+ `want: ${edge.spec || '*'}`
230
+ )
231
+
232
+ const placementType = canPlace.canPlace === CONFLICT
233
+ ? canPlace.canPlaceSelf
234
+ : canPlace.canPlace
235
+
236
+ // if we're placing in the tree with --force, we can get here even though
237
+ // it's a conflict. Treat it as a KEEP, but warn and move on.
238
+ if (placementType === KEEP) {
239
+ // this was an overridden peer dep
240
+ if (edge.peer && !edge.valid)
241
+ this.warnPeerConflict()
242
+
243
+ // if we get a KEEP in a update scenario, then we MAY have something
244
+ // already duplicating this unnecessarily! For example:
245
+ // ```
246
+ // root (dep: y@1)
247
+ // +-- x (dep: y@1.1)
248
+ // | +-- y@1.1.0 (replacing with 1.1.2, got KEEP at the root)
249
+ // +-- y@1.1.2 (updated already from 1.0.0)
250
+ // ```
251
+ // Now say we do `reify({update:['y']})`, and the latest version is
252
+ // 1.1.2, which we now have in the root. We'll try to place y@1.1.2
253
+ // first in x, then in the root, ending with KEEP, because we already
254
+ // have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
255
+ // it is an unnecessary duplicate.
256
+ this.pruneDedupable(target)
257
+ return
258
+ }
259
+
260
+ // we were told to place it here in the target, so either it does not
261
+ // already exist in the tree, OR it's shadowed.
262
+ // handle otherwise unresolvable dependency nesting loops by
263
+ // creating a symbolic link
264
+ // a1 -> b1 -> a2 -> b2 -> a1 -> ...
265
+ // instead of nesting forever, when the loop occurs, create
266
+ // a symbolic link to the earlier instance
267
+ for (let p = target; p; p = p.resolveParent) {
268
+ if (p.matches(dep) && !p.isTop) {
269
+ this.placed = new Link({ parent: target, target: p })
270
+ return
271
+ }
272
+ }
273
+
274
+ // XXX if we are replacing SOME of a peer entry group, we will need to
275
+ // remove any that are not being replaced and will now be invalid, and
276
+ // re-evaluate them deeper into the tree.
277
+
278
+ const virtualRoot = dep.parent
279
+ this.placed = new dep.constructor({
280
+ name: dep.name,
281
+ pkg: dep.package,
282
+ resolved: dep.resolved,
283
+ integrity: dep.integrity,
284
+ legacyPeerDeps: this.legacyPeerDeps,
285
+ error: dep.errors[0],
286
+ ...(dep.isLink ? { target: dep.target, realpath: dep.realpath } : {}),
287
+ })
288
+
289
+ this.oldDep = target.children.get(this.name)
290
+ if (this.oldDep)
291
+ this.replaceOldDep()
292
+ else
293
+ this.placed.parent = target
294
+
295
+ // if it's an overridden peer dep, warn about it
296
+ if (edge.peer && !this.placed.satisfies(edge))
297
+ this.warnPeerConflict()
298
+
299
+ // If the edge is not an error, then we're updating something, and
300
+ // MAY end up putting a better/identical node further up the tree in
301
+ // a way that causes an unnecessary duplication. If so, remove the
302
+ // now-unnecessary node.
303
+ if (edge.valid && edge.to && edge.to !== this.placed)
304
+ this.pruneDedupable(edge.to, false)
305
+
306
+ // in case we just made some duplicates that can be removed,
307
+ // prune anything deeper in the tree that can be replaced by this
308
+ for (const node of target.root.inventory.query('name', this.name)) {
309
+ if (node.isDescendantOf(target) && !node.isTop) {
310
+ this.pruneDedupable(node, false)
311
+ // only walk the direct children of the ones we kept
312
+ if (node.root === target.root) {
313
+ for (const kid of node.children.values())
314
+ this.pruneDedupable(kid, false)
315
+ }
316
+ }
317
+ }
318
+
319
+ // also place its unmet or invalid peer deps at this location
320
+ // loop through any peer deps from the thing we just placed, and place
321
+ // those ones as well. it's safe to do this with the virtual nodes,
322
+ // because we're copying rather than moving them out of the virtual root,
323
+ // otherwise they'd be gone and the peer set would change throughout
324
+ // this loop.
325
+ for (const peerEdge of this.placed.edgesOut.values()) {
326
+ if (peerEdge.valid || !peerEdge.peer || peerEdge.overridden)
327
+ continue
328
+
329
+ const peer = virtualRoot.children.get(peerEdge.name)
330
+
331
+ // Note: if the virtualRoot *doesn't* have the peer, then that means
332
+ // it's an optional peer dep. If it's not being properly met (ie,
333
+ // peerEdge.valid is false), then this is likely heading for an
334
+ // ERESOLVE error, unless it can walk further up the tree.
335
+ if (!peer)
336
+ continue
337
+
338
+ // overridden peerEdge, just accept what's there already
339
+ if (!peer.satisfies(peerEdge))
340
+ continue
341
+
342
+ this.children.push(new PlaceDep({
343
+ parent: this,
344
+ dep: peer,
345
+ node: this.placed,
346
+ edge: peerEdge,
347
+ }))
348
+ }
349
+ }
350
+
351
+ replaceOldDep () {
352
+ // XXX handle replacing an entire peer group?
353
+ // what about cases where we need to push some other peer groups deeper
354
+ // into the tree? all the tree updating should be done here, and track
355
+ // all the things that we add and remove, so that we can know what
356
+ // to re-evaluate.
357
+
358
+ // if we're replacing, we should also remove any nodes for edges that
359
+ // are now invalid, and where this (or its deps) is the only dependent,
360
+ // and also recurse on that pruning. Otherwise leaving that dep node
361
+ // around can result in spurious conflicts pushing nodes deeper into
362
+ // the tree than needed in the case of cycles that will be removed
363
+ // later anyway.
364
+ const oldDeps = []
365
+ for (const [name, edge] of this.oldDep.edgesOut.entries()) {
366
+ if (!this.placed.edgesOut.has(name) && edge.to)
367
+ oldDeps.push(...gatherDepSet([edge.to], e => e.to !== edge.to))
368
+ }
369
+ this.placed.replace(this.oldDep)
370
+ this.pruneForReplacement(this.placed, oldDeps)
371
+ }
372
+
373
+ pruneForReplacement (node, oldDeps) {
374
+ // gather up all the now-invalid/extraneous edgesOut, as long as they are
375
+ // only depended upon by the old node/deps
376
+ const invalidDeps = new Set([...node.edgesOut.values()]
377
+ .filter(e => e.to && !e.valid).map(e => e.to))
378
+ for (const dep of oldDeps) {
379
+ const set = gatherDepSet([dep], e => e.to !== dep && e.valid)
380
+ for (const dep of set)
381
+ invalidDeps.add(dep)
382
+ }
383
+
384
+ // ignore dependency edges from the node being replaced, but
385
+ // otherwise filter the set down to just the set with no
386
+ // dependencies from outside the set, except the node in question.
387
+ const deps = gatherDepSet(invalidDeps, edge =>
388
+ edge.from !== node && edge.to !== node && edge.valid)
389
+
390
+ // now just delete whatever's left, because it's junk
391
+ for (const dep of deps)
392
+ dep.root = null
393
+ }
394
+
395
+ // prune all the nodes in a branch of the tree that can be safely removed
396
+ // This is only the most basic duplication detection; it finds if there
397
+ // is another satisfying node further up the tree, and if so, dedupes.
398
+ // Even in legacyBundling mode, we do this amount of deduplication.
399
+ pruneDedupable (node, descend = true) {
400
+ if (node.canDedupe(this.preferDedupe)) {
401
+ // gather up all deps that have no valid edges in from outside
402
+ // the dep set, except for this node we're deduping, so that we
403
+ // also prune deps that would be made extraneous.
404
+ const deps = gatherDepSet([node], e => e.to !== node && e.valid)
405
+ for (const node of deps)
406
+ node.root = null
407
+ return
408
+ }
409
+ if (descend) {
410
+ // sort these so that they're deterministically ordered
411
+ // otherwise, resulting tree shape is dependent on the order
412
+ // in which they happened to be resolved.
413
+ const nodeSort = (a, b) => a.location.localeCompare(b.location, 'en')
414
+
415
+ const children = [...node.children.values()].sort(nodeSort)
416
+ for (const child of children)
417
+ this.pruneDedupable(child)
418
+ const fsChildren = [...node.fsChildren].sort(nodeSort)
419
+ for (const topNode of fsChildren) {
420
+ const children = [...topNode.children.values()].sort(nodeSort)
421
+ for (const child of children)
422
+ this.pruneDedupable(child)
423
+ }
424
+ }
425
+ }
426
+
427
+ get conflictOk () {
428
+ return this.force || (!this.isMine && !this.strictPeerDeps)
429
+ }
430
+
431
+ get isMine () {
432
+ const { edge } = this.top
433
+ const { from: node } = edge
434
+
435
+ if (node.isWorkspace || node.isProjectRoot)
436
+ return true
437
+
438
+ if (!edge.peer)
439
+ return false
440
+
441
+ // re-entry case. check if any non-peer edges come from the project,
442
+ // or any entryEdges on peer groups are from the root.
443
+ let hasPeerEdges = false
444
+ for (const edge of node.edgesIn) {
445
+ if (edge.peer) {
446
+ hasPeerEdges = true
447
+ continue
448
+ }
449
+ if (edge.from.isWorkspace || edge.from.isProjectRoot)
450
+ return true
451
+ }
452
+ if (hasPeerEdges) {
453
+ for (const edge of peerEntrySets(node).keys()) {
454
+ if (edge.from.isWorkspace || edge.from.isProjectRoot)
455
+ return true
456
+ }
457
+ }
458
+
459
+ return false
460
+ }
461
+
462
+ warnPeerConflict () {
463
+ this.edge.overridden = true
464
+ const expl = this.explainPeerConflict()
465
+ log.warn('ERESOLVE', 'overriding peer dependency', expl)
466
+ }
467
+
468
+ failPeerConflict () {
469
+ const expl = this.explainPeerConflict()
470
+ throw Object.assign(new Error('could not resolve'), expl)
471
+ }
472
+
473
+ explainPeerConflict () {
474
+ const { edge, dep } = this.top
475
+ const { from: node } = edge
476
+ const curNode = node.resolve(edge.name)
477
+
478
+ const expl = {
479
+ code: 'ERESOLVE',
480
+ edge: edge.explain(),
481
+ dep: dep.explain(edge),
482
+ }
483
+
484
+ if (this.parent) {
485
+ // this is the conflicted peer
486
+ expl.current = curNode && curNode.explain(edge)
487
+ expl.peerConflict = this.current && this.current.explain(this.edge)
488
+ } else {
489
+ expl.current = curNode && curNode.explain()
490
+ if (this.canPlaceSelf && this.canPlaceSelf.canPlaceSelf !== CONFLICT) {
491
+ // failed while checking for a child dep
492
+ const cps = this.canPlaceSelf
493
+ for (const peer of cps.conflictChildren) {
494
+ if (peer.current) {
495
+ expl.peerConflict = {
496
+ current: peer.current.explain(),
497
+ peer: peer.dep.explain(peer.edge),
498
+ }
499
+ break
500
+ }
501
+ }
502
+ } else {
503
+ expl.peerConflict = {
504
+ current: this.current && this.current.explain(),
505
+ peer: this.dep.explain(this.edge),
506
+ }
507
+ }
508
+ }
509
+
510
+ const {
511
+ strictPeerDeps,
512
+ force,
513
+ isMine,
514
+ } = this
515
+ Object.assign(expl, {
516
+ strictPeerDeps,
517
+ force,
518
+ isMine,
519
+ })
520
+
521
+ // XXX decorate more with this.canPlace and this.canPlaceSelf,
522
+ // this.checks, this.children, walk over conflicted peers, etc.
523
+ return expl
524
+ }
525
+
526
+ getStartNode () {
527
+ // if we are a peer, then we MUST be at least as shallow as the
528
+ // peer dependent
529
+ const from = this.parent ? this.parent.getStartNode() : this.edge.from
530
+ return deepestNestingTarget(from, this.name)
531
+ }
532
+
533
+ get top () {
534
+ return this.parent ? this.parent.top : this
535
+ }
536
+
537
+ isVulnerable (node) {
538
+ return this.auditReport && this.auditReport.isVulnerable(node)
539
+ }
540
+
541
+ get allChildren () {
542
+ const set = new Set(this.children)
543
+ for (const child of set) {
544
+ for (const grandchild of child.children)
545
+ set.add(grandchild)
546
+ }
547
+ return [...set]
548
+ }
549
+ }
550
+
551
+ module.exports = PlaceDep
package/lib/printable.js CHANGED
@@ -31,6 +31,10 @@ class ArboristNode {
31
31
  this.bundled = true
32
32
  if (tree.inDepBundle)
33
33
  this.bundler = tree.getBundler().location
34
+ if (tree.isProjectRoot)
35
+ this.isProjectRoot = true
36
+ if (tree.isWorkspace)
37
+ this.isWorkspace = true
34
38
  const bd = tree.package && tree.package.bundleDependencies
35
39
  if (bd && bd.length)
36
40
  this.bundleDependencies = bd
@@ -107,6 +111,8 @@ class Edge {
107
111
  this.spec = edge.spec || '*'
108
112
  if (edge.error)
109
113
  this.error = edge.error
114
+ if (edge.overridden)
115
+ this.overridden = edge.overridden
110
116
  }
111
117
  }
112
118
 
@@ -122,6 +128,8 @@ class EdgeOut extends Edge {
122
128
  this.to ? ' -> ' + this.to : ''
123
129
  }${
124
130
  this.error ? ' ' + this.error : ''
131
+ }${
132
+ this.overridden ? ' overridden' : ''
125
133
  } }`
126
134
  }
127
135
  }
@@ -136,6 +144,8 @@ class EdgeIn extends Edge {
136
144
  [util.inspect.custom] () {
137
145
  return `{ ${this.from || '""'} ${this.type} ${this.name}@${this.spec}${
138
146
  this.error ? ' ' + this.error : ''
147
+ }${
148
+ this.overridden ? ' overridden' : ''
139
149
  } }`
140
150
  }
141
151
  }
package/lib/shrinkwrap.js CHANGED
@@ -183,8 +183,10 @@ const assertNoNewer = async (path, data, lockTime, dir = path, seen = null) => {
183
183
  await assertNoNewer(path, data, lockTime, child, seen)
184
184
  else if (ent.isSymbolicLink()) {
185
185
  const target = resolve(parent, await readlink(child))
186
- const tstat = await stat(target).catch(() => null)
186
+ const tstat = await stat(target).catch(
187
+ /* istanbul ignore next - windows */ () => null)
187
188
  seen.add(relpath(path, child))
189
+ /* istanbul ignore next - windows cannot do this */
188
190
  if (tstat && tstat.isDirectory() && !seen.has(relpath(path, target)))
189
191
  await assertNoNewer(path, data, lockTime, target, seen)
190
192
  }
@@ -253,9 +255,11 @@ class Shrinkwrap {
253
255
  if (val)
254
256
  meta[key.replace(/^_/, '')] = val
255
257
  })
256
- // we only include name if different from the node path name
258
+ // we only include name if different from the node path name, and for the
259
+ // root to help prevent churn based on the name of the directory the
260
+ // project is in
257
261
  const pname = node.packageName
258
- if (pname && pname !== node.name)
262
+ if (pname && (node === node.root || pname !== node.name))
259
263
  meta.name = pname
260
264
 
261
265
  if (node.isTop && node.package.devDependencies)
@@ -802,7 +806,7 @@ class Shrinkwrap {
802
806
  if (this.tree) {
803
807
  if (this.yarnLock)
804
808
  this.yarnLock.fromTree(this.tree)
805
- const root = Shrinkwrap.metaFromNode(this.tree.target || this.tree, this.path)
809
+ const root = Shrinkwrap.metaFromNode(this.tree.target, this.path)
806
810
  this.data.packages = {}
807
811
  if (Object.keys(root).length)
808
812
  this.data.packages[''] = root
@@ -864,7 +868,7 @@ class Shrinkwrap {
864
868
  const spec = !edge ? rSpec
865
869
  : npa.resolve(node.name, edge.spec, edge.from.realpath)
866
870
 
867
- if (node.target)
871
+ if (node.isLink)
868
872
  lock.version = `file:${relpath(this.path, node.realpath)}`
869
873
  else if (spec && (spec.type === 'file' || spec.type === 'remote'))
870
874
  lock.version = spec.saveSpec
@@ -888,7 +892,7 @@ class Shrinkwrap {
888
892
  // when we didn't resolve to git, file, or dir, and didn't request
889
893
  // git, file, dir, or remote, then the resolved value is necessary.
890
894
  if (node.resolved &&
891
- !node.target &&
895
+ !node.isLink &&
892
896
  rSpec.type !== 'git' &&
893
897
  rSpec.type !== 'file' &&
894
898
  rSpec.type !== 'directory' &&
@@ -917,7 +921,7 @@ class Shrinkwrap {
917
921
  lock.optional = true
918
922
  }
919
923
 
920
- const depender = node.target || node
924
+ const depender = node.target
921
925
  if (depender.edgesOut.size > 0) {
922
926
  if (node !== this.tree) {
923
927
  lock.requires = [...depender.edgesOut.entries()].reduce((set, [k, v]) => {
@@ -942,7 +946,7 @@ class Shrinkwrap {
942
946
  }
943
947
 
944
948
  // now we walk the children, putting them in the 'dependencies' object
945
- const {children} = node.target || node
949
+ const {children} = node.target
946
950
  if (!children.size)
947
951
  delete lock.dependencies
948
952
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npmcli/arborist",
3
- "version": "2.6.4",
3
+ "version": "2.8.1",
4
4
  "description": "Manage node_modules trees",
5
5
  "dependencies": {
6
6
  "@npmcli/installed-package-contents": "^1.0.7",
@@ -16,19 +16,22 @@
16
16
  "common-ancestor-path": "^1.0.1",
17
17
  "json-parse-even-better-errors": "^2.3.1",
18
18
  "json-stringify-nice": "^1.1.4",
19
+ "mkdirp": "^1.0.4",
19
20
  "mkdirp-infer-owner": "^2.0.0",
20
21
  "npm-install-checks": "^4.0.0",
21
- "npm-package-arg": "^8.1.0",
22
+ "npm-package-arg": "^8.1.5",
22
23
  "npm-pick-manifest": "^6.1.0",
23
24
  "npm-registry-fetch": "^11.0.0",
24
- "pacote": "^11.2.6",
25
+ "pacote": "^11.3.5",
25
26
  "parse-conflict-json": "^1.1.1",
26
27
  "proc-log": "^1.0.0",
27
28
  "promise-all-reject-late": "^1.0.0",
28
29
  "promise-call-limit": "^1.0.1",
29
30
  "read-package-json-fast": "^2.0.2",
30
31
  "readdir-scoped-modules": "^1.1.0",
32
+ "rimraf": "^3.0.2",
31
33
  "semver": "^7.3.5",
34
+ "ssri": "^8.0.1",
32
35
  "tar": "^6.1.0",
33
36
  "treeverse": "^1.0.4",
34
37
  "walk-up-path": "^1.0.0"
@@ -50,7 +53,7 @@
50
53
  "test-only": "tap",
51
54
  "posttest": "npm run lint",
52
55
  "snap": "tap",
53
- "postsnap": "npm run lint",
56
+ "postsnap": "npm run lintfix",
54
57
  "test-proxy": "ARBORIST_TEST_PROXY=1 tap --snapshot",
55
58
  "preversion": "npm test",
56
59
  "postversion": "npm publish",