@npmcli/arborist 2.7.0 → 2.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dedupe.js +46 -0
- package/lib/arborist/build-ideal-tree.js +126 -698
- package/lib/arborist/load-actual.js +4 -2
- package/lib/arborist/reify.js +190 -54
- package/lib/can-place-dep.js +402 -0
- package/lib/case-insensitive-map.js +48 -0
- package/lib/deepest-nesting-target.js +16 -0
- package/lib/edge.js +4 -2
- package/lib/node.js +60 -12
- package/lib/peer-entry-sets.js +72 -0
- package/lib/place-dep.js +551 -0
- package/lib/printable.js +10 -0
- package/lib/shrinkwrap.js +7 -3
- package/package.json +7 -5
- package/lib/peer-set.js +0 -25
|
@@ -188,8 +188,10 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
188
188
|
const tree = this[_actualTree]
|
|
189
189
|
const actualRoot = tree.isLink ? tree.target : tree
|
|
190
190
|
const { dependencies = {} } = actualRoot.package
|
|
191
|
-
for (const name of actualRoot.children.
|
|
192
|
-
|
|
191
|
+
for (const [name, kid] of actualRoot.children.entries()) {
|
|
192
|
+
const def = kid.isLink ? `file:${kid.realpath}` : '*'
|
|
193
|
+
dependencies[name] = dependencies[name] || def
|
|
194
|
+
}
|
|
193
195
|
actualRoot.package = { ...actualRoot.package, dependencies }
|
|
194
196
|
}
|
|
195
197
|
return this[_actualTree]
|
package/lib/arborist/reify.js
CHANGED
|
@@ -5,11 +5,14 @@ const pacote = require('pacote')
|
|
|
5
5
|
const AuditReport = require('../audit-report.js')
|
|
6
6
|
const {subset, intersects} = require('semver')
|
|
7
7
|
const npa = require('npm-package-arg')
|
|
8
|
+
const debug = require('../debug.js')
|
|
9
|
+
const walkUp = require('walk-up-path')
|
|
8
10
|
|
|
9
11
|
const {dirname, resolve, relative} = require('path')
|
|
10
12
|
const {depth: dfwalk} = require('treeverse')
|
|
11
13
|
const fs = require('fs')
|
|
12
14
|
const {promisify} = require('util')
|
|
15
|
+
const lstat = promisify(fs.lstat)
|
|
13
16
|
const symlink = promisify(fs.symlink)
|
|
14
17
|
const mkdirp = require('mkdirp-infer-owner')
|
|
15
18
|
const justMkdirp = require('mkdirp')
|
|
@@ -18,6 +21,7 @@ const rimraf = promisify(require('rimraf'))
|
|
|
18
21
|
const PackageJson = require('@npmcli/package-json')
|
|
19
22
|
const packageContents = require('@npmcli/installed-package-contents')
|
|
20
23
|
const { checkEngine, checkPlatform } = require('npm-install-checks')
|
|
24
|
+
const _force = Symbol.for('force')
|
|
21
25
|
|
|
22
26
|
const treeCheck = require('../tree-check.js')
|
|
23
27
|
const relpath = require('../relpath.js')
|
|
@@ -50,6 +54,7 @@ const _createSparseTree = Symbol.for('createSparseTree')
|
|
|
50
54
|
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
|
|
51
55
|
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
|
|
52
56
|
const _bundleUnpacked = Symbol('bundleUnpacked')
|
|
57
|
+
const _bundleMissing = Symbol('bundleMissing')
|
|
53
58
|
const _reifyNode = Symbol.for('reifyNode')
|
|
54
59
|
const _extractOrLink = Symbol('extractOrLink')
|
|
55
60
|
// defined by rebuild mixin
|
|
@@ -74,8 +79,10 @@ const _copyIdealToActual = Symbol('copyIdealToActual')
|
|
|
74
79
|
const _addOmitsToTrashList = Symbol('addOmitsToTrashList')
|
|
75
80
|
const _packageLockOnly = Symbol('packageLockOnly')
|
|
76
81
|
const _dryRun = Symbol('dryRun')
|
|
82
|
+
const _validateNodeModules = Symbol('validateNodeModules')
|
|
83
|
+
const _nmValidated = Symbol('nmValidated')
|
|
77
84
|
const _validatePath = Symbol('validatePath')
|
|
78
|
-
const _reifyPackages = Symbol('reifyPackages')
|
|
85
|
+
const _reifyPackages = Symbol.for('reifyPackages')
|
|
79
86
|
|
|
80
87
|
const _omitDev = Symbol('omitDev')
|
|
81
88
|
const _omitOptional = Symbol('omitOptional')
|
|
@@ -83,8 +90,9 @@ const _omitPeer = Symbol('omitPeer')
|
|
|
83
90
|
|
|
84
91
|
const _global = Symbol.for('global')
|
|
85
92
|
|
|
93
|
+
const _pruneBundledMetadeps = Symbol('pruneBundledMetadeps')
|
|
94
|
+
|
|
86
95
|
// defined by Ideal mixin
|
|
87
|
-
const _pruneBundledMetadeps = Symbol.for('pruneBundledMetadeps')
|
|
88
96
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
89
97
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
90
98
|
const _formatPackageLock = Symbol.for('formatPackageLock')
|
|
@@ -112,6 +120,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
112
120
|
this[_sparseTreeDirs] = new Set()
|
|
113
121
|
this[_sparseTreeRoots] = new Set()
|
|
114
122
|
this[_trashList] = new Set()
|
|
123
|
+
// the nodes we unpack to read their bundles
|
|
124
|
+
this[_bundleUnpacked] = new Set()
|
|
125
|
+
// child nodes we'd EXPECT to be included in a bundle, but aren't
|
|
126
|
+
this[_bundleMissing] = new Set()
|
|
127
|
+
this[_nmValidated] = new Set()
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
// public method
|
|
@@ -152,6 +165,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
152
165
|
// recursively, because it can have other side effects to do that
|
|
153
166
|
// in a project directory. We just want to make it if it's missing.
|
|
154
167
|
await justMkdirp(resolve(this.path))
|
|
168
|
+
|
|
169
|
+
// do not allow the top-level node_modules to be a symlink
|
|
170
|
+
await this[_validateNodeModules](resolve(this.path, 'node_modules'))
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
async [_reifyPackages] () {
|
|
@@ -321,12 +337,13 @@ module.exports = cls => class Reifier extends cls {
|
|
|
321
337
|
ideal: this.idealTree,
|
|
322
338
|
})
|
|
323
339
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
340
|
+
// we don't have to add 'removed' folders to the trashlist, because
|
|
341
|
+
// they'll be moved aside to a retirement folder, and then the retired
|
|
342
|
+
// folder will be deleted at the end. This is important when we have
|
|
343
|
+
// a folder like FOO being "removed" in favor of a folder like "foo",
|
|
344
|
+
// because if we remove node_modules/FOO on case-insensitive systems,
|
|
345
|
+
// it will remove the dep that we *want* at node_modules/foo.
|
|
346
|
+
|
|
330
347
|
process.emit('timeEnd', 'reify:diffTrees')
|
|
331
348
|
}
|
|
332
349
|
|
|
@@ -334,7 +351,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
334
351
|
// removed later on in the process. optionally, also mark them
|
|
335
352
|
// as a retired paths, so that we move them out of the way and
|
|
336
353
|
// replace them when rolling back on failure.
|
|
337
|
-
[_addNodeToTrashList] (node, retire) {
|
|
354
|
+
[_addNodeToTrashList] (node, retire = false) {
|
|
338
355
|
const paths = [node.path, ...node.binPaths]
|
|
339
356
|
const moves = this[_retiredPaths]
|
|
340
357
|
this.log.silly('reify', 'mark', retire ? 'retired' : 'deleted', paths)
|
|
@@ -422,19 +439,40 @@ module.exports = cls => class Reifier extends cls {
|
|
|
422
439
|
process.emit('time', 'reify:createSparse')
|
|
423
440
|
// if we call this fn again, we look for the previous list
|
|
424
441
|
// so that we can avoid making the same directory multiple times
|
|
425
|
-
const
|
|
442
|
+
const leaves = this.diff.leaves
|
|
426
443
|
.filter(diff => {
|
|
427
444
|
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
|
428
445
|
!this[_sparseTreeDirs].has(diff.ideal.path) &&
|
|
429
446
|
!diff.ideal.isLink
|
|
430
447
|
})
|
|
431
|
-
.map(diff => diff.ideal
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
448
|
+
.map(diff => diff.ideal)
|
|
449
|
+
|
|
450
|
+
// we check this in parallel, so guard against multiple attempts to
|
|
451
|
+
// retire the same path at the same time.
|
|
452
|
+
const dirsChecked = new Set()
|
|
453
|
+
return promiseAllRejectLate(leaves.map(async node => {
|
|
454
|
+
for (const d of walkUp(node.path)) {
|
|
455
|
+
if (d === node.top.path)
|
|
456
|
+
break
|
|
457
|
+
if (dirsChecked.has(d))
|
|
458
|
+
continue
|
|
459
|
+
dirsChecked.add(d)
|
|
460
|
+
const st = await lstat(d).catch(er => null)
|
|
461
|
+
// this can happen if we have a link to a package with a name
|
|
462
|
+
// that the filesystem treats as if it is the same thing.
|
|
463
|
+
// would be nice to have conditional istanbul ignores here...
|
|
464
|
+
/* istanbul ignore next - defense in depth */
|
|
465
|
+
if (st && !st.isDirectory()) {
|
|
466
|
+
const retired = retirePath(d)
|
|
467
|
+
this[_retiredPaths][d] = retired
|
|
468
|
+
this[_trashList].add(retired)
|
|
469
|
+
await this[_renamePath](d, retired)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const made = await mkdirp(node.path)
|
|
473
|
+
this[_sparseTreeDirs].add(node.path)
|
|
474
|
+
this[_sparseTreeRoots].add(made)
|
|
475
|
+
}))
|
|
438
476
|
.then(() => process.emit('timeEnd', 'reify:createSparse'))
|
|
439
477
|
}
|
|
440
478
|
|
|
@@ -529,7 +567,20 @@ module.exports = cls => class Reifier extends cls {
|
|
|
529
567
|
})
|
|
530
568
|
}
|
|
531
569
|
|
|
532
|
-
|
|
570
|
+
// do not allow node_modules to be a symlink
|
|
571
|
+
async [_validateNodeModules] (nm) {
|
|
572
|
+
if (this[_force] || this[_nmValidated].has(nm))
|
|
573
|
+
return
|
|
574
|
+
const st = await lstat(nm).catch(() => null)
|
|
575
|
+
if (!st || st.isDirectory()) {
|
|
576
|
+
this[_nmValidated].add(nm)
|
|
577
|
+
return
|
|
578
|
+
}
|
|
579
|
+
this.log.warn('reify', 'Removing non-directory', nm)
|
|
580
|
+
await rimraf(nm)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async [_extractOrLink] (node) {
|
|
533
584
|
// in normal cases, node.resolved should *always* be set by now.
|
|
534
585
|
// however, it is possible when a lockfile is damaged, or very old,
|
|
535
586
|
// or in some other race condition bugs in npm v6, that a previously
|
|
@@ -556,13 +607,29 @@ module.exports = cls => class Reifier extends cls {
|
|
|
556
607
|
return
|
|
557
608
|
}
|
|
558
609
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
610
|
+
const nm = resolve(node.parent.path, 'node_modules')
|
|
611
|
+
await this[_validateNodeModules](nm)
|
|
612
|
+
|
|
613
|
+
if (node.isLink) {
|
|
614
|
+
await rimraf(node.path)
|
|
615
|
+
await this[_symlink](node)
|
|
616
|
+
} else {
|
|
617
|
+
await debug(async () => {
|
|
618
|
+
const st = await lstat(node.path).catch(e => null)
|
|
619
|
+
if (st && !st.isDirectory()) {
|
|
620
|
+
debug.log('unpacking into a non-directory', node)
|
|
621
|
+
throw Object.assign(new Error('ENOTDIR: not a directory'), {
|
|
622
|
+
code: 'ENOTDIR',
|
|
623
|
+
path: node.path,
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
})
|
|
627
|
+
await pacote.extract(res, node.path, {
|
|
562
628
|
...this.options,
|
|
563
629
|
resolved: node.resolved,
|
|
564
630
|
integrity: node.integrity,
|
|
565
631
|
})
|
|
632
|
+
}
|
|
566
633
|
}
|
|
567
634
|
|
|
568
635
|
async [_symlink] (node) {
|
|
@@ -610,10 +677,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
610
677
|
[_loadBundlesAndUpdateTrees] (
|
|
611
678
|
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
|
|
612
679
|
) {
|
|
613
|
-
if (depth === 0)
|
|
614
|
-
this[_bundleUnpacked] = new Set()
|
|
680
|
+
if (depth === 0)
|
|
615
681
|
process.emit('time', 'reify:loadBundles')
|
|
616
|
-
|
|
682
|
+
|
|
617
683
|
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
618
684
|
if (depth > maxBundleDepth) {
|
|
619
685
|
// if we did something, then prune the tree and update the diffs
|
|
@@ -642,14 +708,30 @@ module.exports = cls => class Reifier extends cls {
|
|
|
642
708
|
}))
|
|
643
709
|
// then load their unpacked children and move into the ideal tree
|
|
644
710
|
.then(nodes =>
|
|
645
|
-
promiseAllRejectLate(nodes.map(node =>
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
711
|
+
promiseAllRejectLate(nodes.map(async node => {
|
|
712
|
+
const arb = new this.constructor({
|
|
713
|
+
...this.options,
|
|
714
|
+
path: node.path,
|
|
715
|
+
})
|
|
716
|
+
const notTransplanted = new Set(node.children.keys())
|
|
717
|
+
await arb.loadActual({
|
|
718
|
+
root: node,
|
|
719
|
+
// don't transplant any sparse folders we created
|
|
720
|
+
// loadActual will set node.package to {} for empty directories
|
|
721
|
+
// if by chance there are some empty folders in the node_modules
|
|
722
|
+
// tree for some other reason, then ok, ignore those too.
|
|
723
|
+
transplantFilter: node => {
|
|
724
|
+
if (node.package._id) {
|
|
725
|
+
// it's actually in the bundle if it gets transplanted
|
|
726
|
+
notTransplanted.delete(node.name)
|
|
727
|
+
return true
|
|
728
|
+
} else
|
|
729
|
+
return false
|
|
730
|
+
},
|
|
731
|
+
})
|
|
732
|
+
for (const name of notTransplanted)
|
|
733
|
+
this[_bundleMissing].add(node.children.get(name))
|
|
734
|
+
})))
|
|
653
735
|
// move onto the next level of bundled items
|
|
654
736
|
.then(() => this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth))
|
|
655
737
|
}
|
|
@@ -685,6 +767,27 @@ module.exports = cls => class Reifier extends cls {
|
|
|
685
767
|
// https://github.com/npm/cli/issues/1597#issuecomment-667639545
|
|
686
768
|
[_pruneBundledMetadeps] (bundlesByDepth) {
|
|
687
769
|
const bundleShadowed = new Set()
|
|
770
|
+
|
|
771
|
+
// Example dep graph:
|
|
772
|
+
// root -> (a, c)
|
|
773
|
+
// a -> BUNDLE(b)
|
|
774
|
+
// b -> c
|
|
775
|
+
// c -> b
|
|
776
|
+
//
|
|
777
|
+
// package tree:
|
|
778
|
+
// root
|
|
779
|
+
// +-- a
|
|
780
|
+
// | +-- b(1)
|
|
781
|
+
// | +-- c(1)
|
|
782
|
+
// +-- b(2)
|
|
783
|
+
// +-- c(2)
|
|
784
|
+
// 1. mark everything that's shadowed by anything in the bundle. This
|
|
785
|
+
// marks b(2) and c(2).
|
|
786
|
+
// 2. anything with edgesIn from outside the set, mark not-extraneous,
|
|
787
|
+
// remove from set. This unmarks c(2).
|
|
788
|
+
// 3. continue until no change
|
|
789
|
+
// 4. remove everything in the set from the tree. b(2) is pruned
|
|
790
|
+
|
|
688
791
|
// create the list of nodes shadowed by children of bundlers
|
|
689
792
|
for (const bundles of bundlesByDepth.values()) {
|
|
690
793
|
// skip the 'maxBundleDepth' item
|
|
@@ -700,36 +803,50 @@ module.exports = cls => class Reifier extends cls {
|
|
|
700
803
|
}
|
|
701
804
|
}
|
|
702
805
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
806
|
+
|
|
807
|
+
// lib -> (a@1.x) BUNDLE(a@1.2.3 (b@1.2.3))
|
|
808
|
+
// a@1.2.3 -> (b@1.2.3)
|
|
809
|
+
// a@1.3.0 -> (b@2)
|
|
810
|
+
// b@1.2.3 -> ()
|
|
811
|
+
// b@2 -> (c@2)
|
|
812
|
+
//
|
|
813
|
+
// root
|
|
814
|
+
// +-- lib
|
|
815
|
+
// | +-- a@1.2.3
|
|
816
|
+
// | +-- b@1.2.3
|
|
817
|
+
// +-- b@2 <-- shadowed, now extraneous
|
|
818
|
+
// +-- c@2 <-- also shadowed, because only dependent is shadowed
|
|
819
|
+
for (const shadow of bundleShadowed) {
|
|
820
|
+
for (const shadDep of shadow.edgesOut.values()) {
|
|
821
|
+
/* istanbul ignore else - pretty unusual situation, just being
|
|
822
|
+
* defensive here. Would mean that a bundled dep has a dependency
|
|
823
|
+
* that is unmet. which, weird, but if you bundle it, we take
|
|
824
|
+
* whatever you put there and assume the publisher knows best. */
|
|
825
|
+
if (shadDep.to) {
|
|
826
|
+
bundleShadowed.add(shadDep.to)
|
|
827
|
+
shadDep.to.extraneous = true
|
|
710
828
|
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
711
831
|
|
|
832
|
+
let changed
|
|
833
|
+
do {
|
|
834
|
+
changed = false
|
|
835
|
+
for (const shadow of bundleShadowed) {
|
|
712
836
|
for (const edge of shadow.edgesIn) {
|
|
713
|
-
if (!edge.from
|
|
837
|
+
if (!bundleShadowed.has(edge.from)) {
|
|
714
838
|
shadow.extraneous = false
|
|
715
839
|
bundleShadowed.delete(shadow)
|
|
716
840
|
changed = true
|
|
717
|
-
|
|
718
|
-
for (const shadDep of shadow.edgesOut.values()) {
|
|
719
|
-
/* istanbul ignore else - pretty unusual situation, just being
|
|
720
|
-
* defensive here. Would mean that a bundled dep has a dependency
|
|
721
|
-
* that is unmet. which, weird, but if you bundle it, we take
|
|
722
|
-
* whatever you put there and assume the publisher knows best. */
|
|
723
|
-
if (shadDep.to)
|
|
724
|
-
bundleShadowed.add(shadDep.to)
|
|
725
|
-
}
|
|
841
|
+
break
|
|
726
842
|
}
|
|
727
843
|
}
|
|
728
844
|
}
|
|
729
|
-
}
|
|
845
|
+
} while (changed)
|
|
846
|
+
|
|
730
847
|
for (const shadow of bundleShadowed) {
|
|
731
|
-
shadow.parent = null
|
|
732
848
|
this[_addNodeToTrashList](shadow)
|
|
849
|
+
shadow.root = null
|
|
733
850
|
}
|
|
734
851
|
}
|
|
735
852
|
|
|
@@ -780,6 +897,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
780
897
|
const node = diff.ideal
|
|
781
898
|
const bd = this[_bundleUnpacked].has(node)
|
|
782
899
|
const sw = this[_shrinkwrapInflated].has(node)
|
|
900
|
+
const bundleMissing = this[_bundleMissing].has(node)
|
|
783
901
|
|
|
784
902
|
// check whether we still need to unpack this one.
|
|
785
903
|
// test the inDepBundle last, since that's potentially a tree walk.
|
|
@@ -787,7 +905,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
787
905
|
!node.isRoot && // root node already exists
|
|
788
906
|
!bd && // already unpacked to read bundle
|
|
789
907
|
!sw && // already unpacked to read sw
|
|
790
|
-
!node.inDepBundle // already unpacked by another dep's bundle
|
|
908
|
+
(bundleMissing || !node.inDepBundle) // already unpacked by another dep's bundle
|
|
791
909
|
|
|
792
910
|
if (doUnpack)
|
|
793
911
|
unpacks.push(this[_reifyNode](node))
|
|
@@ -814,8 +932,26 @@ module.exports = cls => class Reifier extends cls {
|
|
|
814
932
|
const moves = this[_retiredPaths]
|
|
815
933
|
this[_retiredUnchanged] = {}
|
|
816
934
|
return promiseAllRejectLate(this.diff.children.map(diff => {
|
|
817
|
-
|
|
935
|
+
// skip if nothing was retired
|
|
936
|
+
if (diff.action !== 'CHANGE' && diff.action !== 'REMOVE')
|
|
937
|
+
return
|
|
938
|
+
|
|
939
|
+
const { path: realFolder } = diff.actual
|
|
818
940
|
const retireFolder = moves[realFolder]
|
|
941
|
+
/* istanbul ignore next - should be impossible */
|
|
942
|
+
debug(() => {
|
|
943
|
+
if (!retireFolder) {
|
|
944
|
+
const er = new Error('trying to un-retire but not retired')
|
|
945
|
+
throw Object.assign(er, {
|
|
946
|
+
realFolder,
|
|
947
|
+
retireFolder,
|
|
948
|
+
actual: diff.actual,
|
|
949
|
+
ideal: diff.ideal,
|
|
950
|
+
action: diff.action,
|
|
951
|
+
})
|
|
952
|
+
}
|
|
953
|
+
})
|
|
954
|
+
|
|
819
955
|
this[_retiredUnchanged][retireFolder] = []
|
|
820
956
|
return promiseAllRejectLate(diff.unchanged.map(node => {
|
|
821
957
|
// no need to roll back links, since we'll just delete them anyway
|
|
@@ -823,7 +959,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
823
959
|
return mkdirp(dirname(node.path)).then(() => this[_reifyNode](node))
|
|
824
960
|
|
|
825
961
|
// will have been moved/unpacked along with bundler
|
|
826
|
-
if (node.inDepBundle)
|
|
962
|
+
if (node.inDepBundle && !this[_bundleMissing].has(node))
|
|
827
963
|
return
|
|
828
964
|
|
|
829
965
|
this[_retiredUnchanged][retireFolder].push(node)
|