@npmcli/arborist 2.2.7 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/logging.js +10 -2
- package/bin/lib/options.js +7 -1
- package/bin/lib/timers.js +6 -2
- package/bin/virtual.js +2 -1
- package/lib/add-rm-pkg-deps.js +1 -1
- package/lib/arborist/build-ideal-tree.js +118 -31
- package/lib/arborist/index.js +1 -1
- package/lib/arborist/load-actual.js +28 -2
- package/lib/arborist/load-virtual.js +9 -6
- package/lib/arborist/rebuild.js +5 -4
- package/lib/arborist/reify.js +163 -35
- package/lib/debug.js +8 -1
- package/lib/diff.js +70 -11
- package/lib/index.js +1 -0
- package/lib/link.js +13 -7
- package/lib/node.js +5 -2
- package/lib/printable.js +23 -3
- package/lib/shrinkwrap.js +14 -4
- package/lib/tree-check.js +46 -1
- package/package.json +6 -9
package/lib/arborist/reify.js
CHANGED
|
@@ -14,6 +14,7 @@ const fs = require('fs')
|
|
|
14
14
|
const {promisify} = require('util')
|
|
15
15
|
const symlink = promisify(fs.symlink)
|
|
16
16
|
const mkdirp = require('mkdirp-infer-owner')
|
|
17
|
+
const justMkdirp = require('mkdirp')
|
|
17
18
|
const moveFile = require('@npmcli/move-file')
|
|
18
19
|
const rimraf = promisify(require('rimraf'))
|
|
19
20
|
const packageContents = require('@npmcli/installed-package-contents')
|
|
@@ -26,6 +27,7 @@ const retirePath = require('../retire-path.js')
|
|
|
26
27
|
const promiseAllRejectLate = require('promise-all-reject-late')
|
|
27
28
|
const optionalSet = require('../optional-set.js')
|
|
28
29
|
const updateRootPackageJson = require('../update-root-package-json.js')
|
|
30
|
+
const calcDepFlags = require('../calc-dep-flags.js')
|
|
29
31
|
|
|
30
32
|
const _retiredPaths = Symbol('retiredPaths')
|
|
31
33
|
const _retiredUnchanged = Symbol('retiredUnchanged')
|
|
@@ -36,6 +38,8 @@ const _retireShallowNodes = Symbol.for('retireShallowNodes')
|
|
|
36
38
|
const _getBundlesByDepth = Symbol('getBundlesByDepth')
|
|
37
39
|
const _registryResolved = Symbol('registryResolved')
|
|
38
40
|
const _addNodeToTrashList = Symbol('addNodeToTrashList')
|
|
41
|
+
const _workspaces = Symbol.for('workspaces')
|
|
42
|
+
|
|
39
43
|
// shared by rebuild mixin
|
|
40
44
|
const _trashList = Symbol.for('trashList')
|
|
41
45
|
const _handleOptionalFailure = Symbol.for('handleOptionalFailure')
|
|
@@ -45,7 +49,8 @@ const _loadTrees = Symbol.for('loadTrees')
|
|
|
45
49
|
const _diffTrees = Symbol.for('diffTrees')
|
|
46
50
|
const _createSparseTree = Symbol.for('createSparseTree')
|
|
47
51
|
const _loadShrinkwrapsAndUpdateTrees = Symbol.for('loadShrinkwrapsAndUpdateTrees')
|
|
48
|
-
const
|
|
52
|
+
const _shrinkwrapInflated = Symbol('shrinkwrapInflated')
|
|
53
|
+
const _bundleUnpacked = Symbol('bundleUnpacked')
|
|
49
54
|
const _reifyNode = Symbol.for('reifyNode')
|
|
50
55
|
const _extractOrLink = Symbol('extractOrLink')
|
|
51
56
|
// defined by rebuild mixin
|
|
@@ -82,7 +87,6 @@ const _global = Symbol.for('global')
|
|
|
82
87
|
|
|
83
88
|
// defined by Ideal mixin
|
|
84
89
|
const _pruneBundledMetadeps = Symbol.for('pruneBundledMetadeps')
|
|
85
|
-
const _explicitRequests = Symbol.for('explicitRequests')
|
|
86
90
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
87
91
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
88
92
|
const _formatPackageLock = Symbol.for('formatPackageLock')
|
|
@@ -105,7 +109,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
105
109
|
|
|
106
110
|
this.diff = null
|
|
107
111
|
this[_retiredPaths] = {}
|
|
108
|
-
this[
|
|
112
|
+
this[_shrinkwrapInflated] = new Set()
|
|
109
113
|
this[_retiredUnchanged] = {}
|
|
110
114
|
this[_sparseTreeDirs] = new Set()
|
|
111
115
|
this[_sparseTreeRoots] = new Set()
|
|
@@ -146,7 +150,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
146
150
|
if (this[_packageLockOnly] || this[_dryRun])
|
|
147
151
|
return
|
|
148
152
|
|
|
149
|
-
|
|
153
|
+
// we do NOT want to set ownership on this folder, especially
|
|
154
|
+
// recursively, because it can have other side effects to do that
|
|
155
|
+
// in a project directory. We just want to make it if it's missing.
|
|
156
|
+
await justMkdirp(resolve(this.path))
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
async [_reifyPackages] () {
|
|
@@ -237,9 +244,25 @@ module.exports = cls => class Reifier extends cls {
|
|
|
237
244
|
const actualOpt = this[_global] ? {
|
|
238
245
|
ignoreMissing: true,
|
|
239
246
|
global: true,
|
|
240
|
-
filter: (node, kid) =>
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
filter: (node, kid) => {
|
|
248
|
+
// if it's not the project root, and we have no explicit requests,
|
|
249
|
+
// then we're already into a nested dep, so we keep it
|
|
250
|
+
if (this.explicitRequests.size === 0 || !node.isProjectRoot)
|
|
251
|
+
return true
|
|
252
|
+
|
|
253
|
+
// if we added it as an edgeOut, then we want it
|
|
254
|
+
if (this.idealTree.edgesOut.has(kid))
|
|
255
|
+
return true
|
|
256
|
+
|
|
257
|
+
// if it's an explicit request, then we want it
|
|
258
|
+
const hasExplicit = [...this.explicitRequests]
|
|
259
|
+
.some(edge => edge.name === kid)
|
|
260
|
+
if (hasExplicit)
|
|
261
|
+
return true
|
|
262
|
+
|
|
263
|
+
// ignore the rest of the global install folder
|
|
264
|
+
return false
|
|
265
|
+
},
|
|
243
266
|
} : { ignoreMissing: true }
|
|
244
267
|
|
|
245
268
|
if (!this[_global]) {
|
|
@@ -266,9 +289,36 @@ module.exports = cls => class Reifier extends cls {
|
|
|
266
289
|
// to just invalidate the parts that changed, but avoid walking the
|
|
267
290
|
// whole tree again.
|
|
268
291
|
|
|
292
|
+
const filterNodes = []
|
|
293
|
+
if (this[_global] && this.explicitRequests.size) {
|
|
294
|
+
const idealTree = this.idealTree.target || this.idealTree
|
|
295
|
+
const actualTree = this.actualTree.target || this.actualTree
|
|
296
|
+
// we ONLY are allowed to make changes in the global top-level
|
|
297
|
+
// children where there's an explicit request.
|
|
298
|
+
for (const { name } of this.explicitRequests) {
|
|
299
|
+
const ideal = idealTree.children.get(name)
|
|
300
|
+
if (ideal)
|
|
301
|
+
filterNodes.push(ideal)
|
|
302
|
+
const actual = actualTree.children.get(name)
|
|
303
|
+
if (actual)
|
|
304
|
+
filterNodes.push(actual)
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
for (const ws of this[_workspaces]) {
|
|
308
|
+
const ideal = this.idealTree.children.get(ws)
|
|
309
|
+
if (ideal)
|
|
310
|
+
filterNodes.push(ideal)
|
|
311
|
+
const actual = this.actualTree.children.get(ws)
|
|
312
|
+
if (actual)
|
|
313
|
+
filterNodes.push(actual)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
269
317
|
// find all the nodes that need to change between the actual
|
|
270
318
|
// and ideal trees.
|
|
271
319
|
this.diff = Diff.calculate({
|
|
320
|
+
shrinkwrapInflated: this[_shrinkwrapInflated],
|
|
321
|
+
filterNodes,
|
|
272
322
|
actual: this.actualTree,
|
|
273
323
|
ideal: this.idealTree,
|
|
274
324
|
})
|
|
@@ -375,7 +425,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
375
425
|
const dirs = this.diff.leaves
|
|
376
426
|
.filter(diff => {
|
|
377
427
|
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
|
378
|
-
!this[_sparseTreeDirs].has(diff.ideal.path)
|
|
428
|
+
!this[_sparseTreeDirs].has(diff.ideal.path) &&
|
|
429
|
+
!diff.ideal.isLink
|
|
379
430
|
})
|
|
380
431
|
.map(diff => diff.ideal.path)
|
|
381
432
|
|
|
@@ -409,9 +460,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
409
460
|
// we need to unpack them, read that shrinkwrap file, and then update
|
|
410
461
|
// the tree by calling loadVirtual with the node as the root.
|
|
411
462
|
[_loadShrinkwrapsAndUpdateTrees] () {
|
|
412
|
-
const seen = this[
|
|
463
|
+
const seen = this[_shrinkwrapInflated]
|
|
413
464
|
const shrinkwraps = this.diff.leaves
|
|
414
|
-
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
|
|
465
|
+
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
|
|
415
466
|
d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
|
|
416
467
|
!this[_trashList].has(d.ideal.path))
|
|
417
468
|
|
|
@@ -424,7 +475,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
424
475
|
return promiseAllRejectLate(shrinkwraps.map(diff => {
|
|
425
476
|
const node = diff.ideal
|
|
426
477
|
seen.add(node)
|
|
427
|
-
return this[_reifyNode](node)
|
|
478
|
+
return diff.action ? this[_reifyNode](node) : node
|
|
428
479
|
}))
|
|
429
480
|
.then(nodes => promiseAllRejectLate(nodes.map(node => new Arborist({
|
|
430
481
|
...this.options,
|
|
@@ -455,7 +506,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
455
506
|
|
|
456
507
|
const { npmVersion, nodeVersion } = this.options
|
|
457
508
|
const p = Promise.resolve()
|
|
458
|
-
.then(() => {
|
|
509
|
+
.then(async () => {
|
|
459
510
|
// when we reify an optional node, check the engine and platform
|
|
460
511
|
// first. be sure to ignore the --force and --engine-strict flags,
|
|
461
512
|
// since we always want to skip any optional packages we can't install.
|
|
@@ -465,11 +516,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
465
516
|
checkEngine(node.package, npmVersion, nodeVersion, false)
|
|
466
517
|
checkPlatform(node.package, false)
|
|
467
518
|
}
|
|
519
|
+
await this[_checkBins](node)
|
|
520
|
+
await this[_extractOrLink](node)
|
|
521
|
+
await this[_warnDeprecated](node)
|
|
522
|
+
await this[_loadAncientPackageDetails](node)
|
|
468
523
|
})
|
|
469
|
-
.then(() => this[_checkBins](node))
|
|
470
|
-
.then(() => this[_extractOrLink](node))
|
|
471
|
-
.then(() => this[_warnDeprecated](node))
|
|
472
|
-
.then(() => this[_loadAncientPackageDetails](node))
|
|
473
524
|
|
|
474
525
|
return this[_handleOptionalFailure](node, p)
|
|
475
526
|
.then(() => {
|
|
@@ -515,10 +566,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
515
566
|
})
|
|
516
567
|
}
|
|
517
568
|
|
|
518
|
-
[_symlink] (node) {
|
|
569
|
+
async [_symlink] (node) {
|
|
519
570
|
const dir = dirname(node.path)
|
|
520
571
|
const target = node.realpath
|
|
521
572
|
const rel = relative(dir, target)
|
|
573
|
+
await mkdirp(dir)
|
|
522
574
|
return symlink(rel, node.path, 'junction')
|
|
523
575
|
}
|
|
524
576
|
|
|
@@ -585,8 +637,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
585
637
|
[_loadBundlesAndUpdateTrees] (
|
|
586
638
|
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
|
|
587
639
|
) {
|
|
588
|
-
if (depth === 0)
|
|
640
|
+
if (depth === 0) {
|
|
641
|
+
this[_bundleUnpacked] = new Set()
|
|
589
642
|
process.emit('time', 'reify:loadBundles')
|
|
643
|
+
}
|
|
590
644
|
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
591
645
|
if (depth > maxBundleDepth) {
|
|
592
646
|
// if we did something, then prune the tree and update the diffs
|
|
@@ -602,13 +656,17 @@ module.exports = cls => class Reifier extends cls {
|
|
|
602
656
|
// shallower bundle overwriting them with a bundled meta-dep.
|
|
603
657
|
const set = (bundlesByDepth.get(depth) || [])
|
|
604
658
|
.filter(node => node.root === this.idealTree &&
|
|
659
|
+
node.target !== node.root &&
|
|
605
660
|
!this[_trashList].has(node.path))
|
|
606
661
|
|
|
607
662
|
if (!set.length)
|
|
608
663
|
return this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth)
|
|
609
664
|
|
|
610
665
|
// extract all the nodes with bundles
|
|
611
|
-
return promiseAllRejectLate(set.map(node =>
|
|
666
|
+
return promiseAllRejectLate(set.map(node => {
|
|
667
|
+
this[_bundleUnpacked].add(node)
|
|
668
|
+
return this[_reifyNode](node)
|
|
669
|
+
}))
|
|
612
670
|
// then load their unpacked children and move into the ideal tree
|
|
613
671
|
.then(nodes =>
|
|
614
672
|
promiseAllRejectLate(nodes.map(node => new this.constructor({
|
|
@@ -630,8 +688,13 @@ module.exports = cls => class Reifier extends cls {
|
|
|
630
688
|
tree: this.diff,
|
|
631
689
|
visit: diff => {
|
|
632
690
|
const node = diff.ideal
|
|
633
|
-
if (
|
|
634
|
-
|
|
691
|
+
if (!node)
|
|
692
|
+
return
|
|
693
|
+
if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
const { bundleDependencies } = node.package
|
|
697
|
+
if (bundleDependencies && bundleDependencies.length) {
|
|
635
698
|
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
|
|
636
699
|
if (!bundlesByDepth.has(node.depth))
|
|
637
700
|
bundlesByDepth.set(node.depth, [node])
|
|
@@ -736,14 +799,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
736
799
|
return
|
|
737
800
|
|
|
738
801
|
const node = diff.ideal
|
|
739
|
-
const bd = node
|
|
740
|
-
const sw = this[
|
|
802
|
+
const bd = this[_bundleUnpacked].has(node)
|
|
803
|
+
const sw = this[_shrinkwrapInflated].has(node)
|
|
741
804
|
|
|
742
805
|
// check whether we still need to unpack this one.
|
|
743
806
|
// test the inDepBundle last, since that's potentially a tree walk.
|
|
744
807
|
const doUnpack = node && // can't unpack if removed!
|
|
745
808
|
!node.isRoot && // root node already exists
|
|
746
|
-
!
|
|
809
|
+
!bd && // already unpacked to read bundle
|
|
747
810
|
!sw && // already unpacked to read sw
|
|
748
811
|
!node.inDepBundle // already unpacked by another dep's bundle
|
|
749
812
|
|
|
@@ -886,11 +949,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
886
949
|
// to things like git repos and tarball file/urls. However, if the
|
|
887
950
|
// user requested 'foo@', and we have a foo@file:../foo, then we should
|
|
888
951
|
// end up saving the spec we actually used, not whatever they gave us.
|
|
889
|
-
if (this[_resolvedAdd]) {
|
|
952
|
+
if (this[_resolvedAdd].length) {
|
|
890
953
|
const root = this.idealTree
|
|
891
954
|
const pkg = root.package
|
|
892
955
|
for (const { name } of this[_resolvedAdd]) {
|
|
893
|
-
const req = npa(root.edgesOut.get(name).spec, root.realpath)
|
|
956
|
+
const req = npa.resolve(name, root.edgesOut.get(name).spec, root.realpath)
|
|
894
957
|
const {rawSpec, subSpec} = req
|
|
895
958
|
|
|
896
959
|
const spec = subSpec ? subSpec.rawSpec : rawSpec
|
|
@@ -966,20 +1029,85 @@ module.exports = cls => class Reifier extends cls {
|
|
|
966
1029
|
return meta.save(saveOpt)
|
|
967
1030
|
}
|
|
968
1031
|
|
|
969
|
-
[_copyIdealToActual] () {
|
|
1032
|
+
async [_copyIdealToActual] () {
|
|
1033
|
+
// clean up any trash that is still in the tree
|
|
1034
|
+
for (const path of this[_trashList]) {
|
|
1035
|
+
const loc = relpath(this.idealTree.realpath, path)
|
|
1036
|
+
const node = this.idealTree.inventory.get(loc)
|
|
1037
|
+
if (node && node.root === this.idealTree)
|
|
1038
|
+
node.parent = null
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// if we filtered to only certain nodes, then anything ELSE needs
|
|
1042
|
+
// to be untouched in the resulting actual tree, even if it differs
|
|
1043
|
+
// in the idealTree. Copy over anything that was in the actual and
|
|
1044
|
+
// was not changed, delete anything in the ideal and not actual.
|
|
1045
|
+
// Then we move the entire idealTree over to this.actualTree, and
|
|
1046
|
+
// save the hidden lockfile.
|
|
1047
|
+
if (this.diff && this.diff.filterSet.size) {
|
|
1048
|
+
const { filterSet } = this.diff
|
|
1049
|
+
const seen = new Set()
|
|
1050
|
+
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
|
|
1051
|
+
if (seen.has(loc))
|
|
1052
|
+
continue
|
|
1053
|
+
seen.add(loc)
|
|
1054
|
+
|
|
1055
|
+
// if it's an ideal node from the filter set, then skip it
|
|
1056
|
+
// because we already made whatever changes were necessary
|
|
1057
|
+
if (filterSet.has(ideal))
|
|
1058
|
+
continue
|
|
1059
|
+
|
|
1060
|
+
// otherwise, if it's not in the actualTree, then it's not a thing
|
|
1061
|
+
// that we actually added. And if it IS in the actualTree, then
|
|
1062
|
+
// it's something that we left untouched, so we need to record
|
|
1063
|
+
// that.
|
|
1064
|
+
const actual = this.actualTree.inventory.get(loc)
|
|
1065
|
+
if (!actual)
|
|
1066
|
+
ideal.root = null
|
|
1067
|
+
else {
|
|
1068
|
+
if ([...actual.linksIn].some(link => filterSet.has(link))) {
|
|
1069
|
+
seen.add(actual.location)
|
|
1070
|
+
continue
|
|
1071
|
+
}
|
|
1072
|
+
const { realpath, isLink } = actual
|
|
1073
|
+
if (isLink && ideal.isLink && ideal.realpath === realpath)
|
|
1074
|
+
continue
|
|
1075
|
+
else
|
|
1076
|
+
actual.root = this.idealTree
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// now find any actual nodes that may not be present in the ideal
|
|
1081
|
+
// tree, but were left behind by virtue of not being in the filter
|
|
1082
|
+
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
1083
|
+
if (seen.has(loc))
|
|
1084
|
+
continue
|
|
1085
|
+
seen.add(loc)
|
|
1086
|
+
if (filterSet.has(actual))
|
|
1087
|
+
continue
|
|
1088
|
+
actual.root = this.idealTree
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// prune out any tops that lack a linkIn
|
|
1092
|
+
for (const top of this.idealTree.tops) {
|
|
1093
|
+
if (top.linksIn.size === 0)
|
|
1094
|
+
top.root = null
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// need to calculate dep flags, since nodes may have been marked
|
|
1098
|
+
// as extraneous or otherwise incorrect during transit.
|
|
1099
|
+
calcDepFlags(this.idealTree)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
970
1102
|
// save the ideal's meta as a hidden lockfile after we actualize it
|
|
971
1103
|
this.idealTree.meta.filename =
|
|
972
|
-
this.
|
|
1104
|
+
this.idealTree.realpath + '/node_modules/.package-lock.json'
|
|
973
1105
|
this.idealTree.meta.hiddenLockfile = true
|
|
1106
|
+
|
|
974
1107
|
this.actualTree = this.idealTree
|
|
975
1108
|
this.idealTree = null
|
|
976
|
-
for (const path of this[_trashList]) {
|
|
977
|
-
const loc = relpath(this.path, path)
|
|
978
|
-
const node = this.actualTree.inventory.get(loc)
|
|
979
|
-
if (node && node.root === this.actualTree)
|
|
980
|
-
node.parent = null
|
|
981
|
-
}
|
|
982
1109
|
|
|
983
|
-
|
|
1110
|
+
if (!this[_global])
|
|
1111
|
+
await this.actualTree.meta.save()
|
|
984
1112
|
}
|
|
985
1113
|
}
|
package/lib/debug.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
// run in debug mode if explicitly requested, running arborist tests,
|
|
14
14
|
// or working in the arborist project directory.
|
|
15
|
+
|
|
15
16
|
const debug = process.env.ARBORIST_DEBUG !== '0' && (
|
|
16
17
|
process.env.ARBORIST_DEBUG === '1' ||
|
|
17
18
|
/\barborist\b/.test(process.env.NODE_DEBUG || '') ||
|
|
@@ -21,4 +22,10 @@ const debug = process.env.ARBORIST_DEBUG !== '0' && (
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
module.exports = debug ? fn => fn() : () => {}
|
|
24
|
-
|
|
25
|
+
const red = process.stderr.isTTY ? msg => `\x1B[31m${msg}\x1B[39m` : m => m
|
|
26
|
+
module.exports.log = (...msg) => module.exports(() => {
|
|
27
|
+
const { format } = require('util')
|
|
28
|
+
const prefix = `\n${process.pid} ${red(format(msg.shift()))} `
|
|
29
|
+
msg = (prefix + format(...msg).trim().split('\n').join(prefix)).trim()
|
|
30
|
+
console.error(msg)
|
|
31
|
+
})
|
package/lib/diff.js
CHANGED
|
@@ -11,7 +11,9 @@ const {existsSync} = require('fs')
|
|
|
11
11
|
const ssri = require('ssri')
|
|
12
12
|
|
|
13
13
|
class Diff {
|
|
14
|
-
constructor ({actual, ideal}) {
|
|
14
|
+
constructor ({actual, ideal, filterSet, shrinkwrapInflated}) {
|
|
15
|
+
this.filterSet = filterSet
|
|
16
|
+
this.shrinkwrapInflated = shrinkwrapInflated
|
|
15
17
|
this.children = []
|
|
16
18
|
this.actual = actual
|
|
17
19
|
this.ideal = ideal
|
|
@@ -29,9 +31,54 @@ class Diff {
|
|
|
29
31
|
this.removed = []
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
static calculate ({actual, ideal}) {
|
|
34
|
+
static calculate ({actual, ideal, filterNodes = [], shrinkwrapInflated = new Set()}) {
|
|
35
|
+
// if there's a filterNode, then:
|
|
36
|
+
// - get the path from the root to the filterNode. The root or
|
|
37
|
+
// root.target should have an edge either to the filterNode or
|
|
38
|
+
// a link to the filterNode. If not, abort. Add the path to the
|
|
39
|
+
// filterSet.
|
|
40
|
+
// - Add set of Nodes depended on by the filterNode to filterSet.
|
|
41
|
+
// - Anything outside of that set should be ignored by getChildren
|
|
42
|
+
const filterSet = new Set()
|
|
43
|
+
for (const filterNode of filterNodes) {
|
|
44
|
+
const { root } = filterNode
|
|
45
|
+
if (root !== ideal && root !== actual)
|
|
46
|
+
throw new Error('invalid filterNode: outside idealTree/actualTree')
|
|
47
|
+
const { target } = root
|
|
48
|
+
const rootTarget = target || root
|
|
49
|
+
const edge = [...rootTarget.edgesOut.values()].filter(e => {
|
|
50
|
+
return e.to && (e.to === filterNode || e.to.target === filterNode)
|
|
51
|
+
})[0]
|
|
52
|
+
filterSet.add(root)
|
|
53
|
+
filterSet.add(rootTarget)
|
|
54
|
+
filterSet.add(ideal)
|
|
55
|
+
filterSet.add(actual)
|
|
56
|
+
if (edge && edge.to) {
|
|
57
|
+
filterSet.add(edge.to)
|
|
58
|
+
if (edge.to.target)
|
|
59
|
+
filterSet.add(edge.to.target)
|
|
60
|
+
}
|
|
61
|
+
filterSet.add(filterNode)
|
|
62
|
+
|
|
63
|
+
depth({
|
|
64
|
+
tree: filterNode,
|
|
65
|
+
visit: node => filterSet.add(node),
|
|
66
|
+
getChildren: node => {
|
|
67
|
+
node = node.target || node
|
|
68
|
+
const loc = node.location
|
|
69
|
+
const idealNode = ideal.inventory.get(loc)
|
|
70
|
+
const ideals = !idealNode ? []
|
|
71
|
+
: [...idealNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
|
|
72
|
+
const actualNode = actual.inventory.get(loc)
|
|
73
|
+
const actuals = !actualNode ? []
|
|
74
|
+
: [...actualNode.edgesOut.values()].filter(e => e.to).map(e => e.to)
|
|
75
|
+
return ideals.concat(actuals)
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
33
80
|
return depth({
|
|
34
|
-
tree: new Diff({actual, ideal}),
|
|
81
|
+
tree: new Diff({actual, ideal, filterSet, shrinkwrapInflated}),
|
|
35
82
|
getChildren,
|
|
36
83
|
leave,
|
|
37
84
|
})
|
|
@@ -89,20 +136,29 @@ const allChildren = node => {
|
|
|
89
136
|
// to create the diff tree
|
|
90
137
|
const getChildren = diff => {
|
|
91
138
|
const children = []
|
|
92
|
-
const {unchanged, removed} = diff
|
|
139
|
+
const {actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated} = diff
|
|
93
140
|
|
|
94
141
|
// Note: we DON'T diff fsChildren themselves, because they are either
|
|
95
142
|
// included in the package contents, or part of some other project, and
|
|
96
143
|
// will never appear in legacy shrinkwraps anyway. but we _do_ include the
|
|
97
144
|
// child nodes of fsChildren, because those are nodes that we are typically
|
|
98
145
|
// responsible for installing.
|
|
99
|
-
const actualKids = allChildren(
|
|
100
|
-
const idealKids = allChildren(
|
|
146
|
+
const actualKids = allChildren(actual)
|
|
147
|
+
const idealKids = allChildren(ideal)
|
|
148
|
+
|
|
149
|
+
if (ideal && ideal.hasShrinkwrap && !shrinkwrapInflated.has(ideal)) {
|
|
150
|
+
// Guaranteed to get a diff.leaves here, because we always
|
|
151
|
+
// be called with a proper Diff object when ideal has a shrinkwrap
|
|
152
|
+
// that has not been inflated.
|
|
153
|
+
diff.leaves.push(diff)
|
|
154
|
+
return children
|
|
155
|
+
}
|
|
156
|
+
|
|
101
157
|
const paths = new Set([...actualKids.keys(), ...idealKids.keys()])
|
|
102
158
|
for (const path of paths) {
|
|
103
159
|
const actual = actualKids.get(path)
|
|
104
160
|
const ideal = idealKids.get(path)
|
|
105
|
-
diffNode(actual, ideal, children, unchanged, removed)
|
|
161
|
+
diffNode(actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated)
|
|
106
162
|
}
|
|
107
163
|
|
|
108
164
|
if (diff.leaves && !children.length)
|
|
@@ -111,15 +167,18 @@ const getChildren = diff => {
|
|
|
111
167
|
return children
|
|
112
168
|
}
|
|
113
169
|
|
|
114
|
-
const diffNode = (actual, ideal, children, unchanged, removed) => {
|
|
170
|
+
const diffNode = (actual, ideal, children, unchanged, removed, filterSet, shrinkwrapInflated) => {
|
|
171
|
+
if (filterSet.size && !(filterSet.has(ideal) || filterSet.has(actual)))
|
|
172
|
+
return
|
|
173
|
+
|
|
115
174
|
const action = getAction({actual, ideal})
|
|
116
175
|
|
|
117
176
|
// if it's a match, then get its children
|
|
118
177
|
// otherwise, this is the child diff node
|
|
119
|
-
if (action) {
|
|
178
|
+
if (action || (!shrinkwrapInflated.has(ideal) && ideal.hasShrinkwrap)) {
|
|
120
179
|
if (action === 'REMOVE')
|
|
121
180
|
removed.push(actual)
|
|
122
|
-
children.push(new Diff({actual, ideal}))
|
|
181
|
+
children.push(new Diff({actual, ideal, filterSet, shrinkwrapInflated}))
|
|
123
182
|
} else {
|
|
124
183
|
unchanged.push(ideal)
|
|
125
184
|
// !*! Weird dirty hack warning !*!
|
|
@@ -150,7 +209,7 @@ const diffNode = (actual, ideal, children, unchanged, removed) => {
|
|
|
150
209
|
for (const node of bundledChildren)
|
|
151
210
|
node.parent = ideal
|
|
152
211
|
}
|
|
153
|
-
children.push(...getChildren({actual, ideal, unchanged, removed}))
|
|
212
|
+
children.push(...getChildren({actual, ideal, unchanged, removed, filterSet, shrinkwrapInflated}))
|
|
154
213
|
}
|
|
155
214
|
}
|
|
156
215
|
|
package/lib/index.js
CHANGED
|
@@ -3,5 +3,6 @@ module.exports.Arborist = module.exports
|
|
|
3
3
|
module.exports.Node = require('./node.js')
|
|
4
4
|
module.exports.Link = require('./link.js')
|
|
5
5
|
module.exports.Edge = require('./edge.js')
|
|
6
|
+
module.exports.Shrinkwrap = require('./shrinkwrap.js')
|
|
6
7
|
// XXX export the other classes, too. shrinkwrap, diff, etc.
|
|
7
8
|
// they're handy!
|
package/lib/link.js
CHANGED
|
@@ -23,13 +23,19 @@ class Link extends Node {
|
|
|
23
23
|
: null),
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
if (target)
|
|
27
|
+
this.target = target
|
|
28
|
+
else if (this.realpath === this.root.path)
|
|
29
|
+
this.target = this.root
|
|
30
|
+
else {
|
|
31
|
+
this.target = new Node({
|
|
32
|
+
...options,
|
|
33
|
+
path: realpath,
|
|
34
|
+
parent: null,
|
|
35
|
+
fsParent: null,
|
|
36
|
+
root: this.root,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
get version () {
|
package/lib/node.js
CHANGED
|
@@ -685,6 +685,7 @@ class Node {
|
|
|
685
685
|
...this.children.values(),
|
|
686
686
|
...this.inventory.values(),
|
|
687
687
|
].filter(n => n !== this))
|
|
688
|
+
|
|
688
689
|
for (const child of family) {
|
|
689
690
|
if (child.root !== root) {
|
|
690
691
|
child[_delistFromMeta]()
|
|
@@ -704,12 +705,14 @@ class Node {
|
|
|
704
705
|
}
|
|
705
706
|
|
|
706
707
|
// if we had a target, and didn't find one in the new root, then bring
|
|
707
|
-
// it over as well
|
|
708
|
-
|
|
708
|
+
// it over as well, but only if we're setting the link into a new root,
|
|
709
|
+
// as we don't want to lose the target any time we remove a link.
|
|
710
|
+
if (this.isLink && target && !this.target && root !== this)
|
|
709
711
|
target.root = root
|
|
710
712
|
|
|
711
713
|
// tree should always be valid upon root setter completion.
|
|
712
714
|
treeCheck(this)
|
|
715
|
+
treeCheck(root)
|
|
713
716
|
}
|
|
714
717
|
|
|
715
718
|
get root () {
|
package/lib/printable.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// of the current node and its descendents
|
|
3
3
|
|
|
4
4
|
const util = require('util')
|
|
5
|
+
const relpath = require('./relpath.js')
|
|
5
6
|
|
|
6
7
|
class ArboristNode {
|
|
7
8
|
constructor (tree, path) {
|
|
@@ -47,6 +48,11 @@ class ArboristNode {
|
|
|
47
48
|
.map(edge => new EdgeIn(edge)))
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
if (tree.workspaces && tree.workspaces.size) {
|
|
52
|
+
this.workspaces = new Map([...tree.workspaces.entries()]
|
|
53
|
+
.map(([name, path]) => [name, relpath(tree.root.realpath, path)]))
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
// fsChildren sorted by path
|
|
51
57
|
if (tree.fsChildren.size) {
|
|
52
58
|
this.fsChildren = new Set([...tree.fsChildren]
|
|
@@ -63,6 +69,13 @@ class ArboristNode {
|
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
class ArboristVirtualNode extends ArboristNode {
|
|
73
|
+
constructor (tree, path) {
|
|
74
|
+
super(tree, path)
|
|
75
|
+
this.sourceReference = printableTree(tree.sourceReference, path)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
class ArboristLink extends ArboristNode {
|
|
67
80
|
constructor (tree, path) {
|
|
68
81
|
super(tree, path)
|
|
@@ -119,10 +132,17 @@ class EdgeIn extends Edge {
|
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
const printableTree = (tree, path = []) => {
|
|
122
|
-
if (
|
|
123
|
-
return
|
|
135
|
+
if (!tree)
|
|
136
|
+
return tree
|
|
137
|
+
|
|
138
|
+
const Cls = tree.isLink ? ArboristLink
|
|
139
|
+
: tree.sourceReference ? ArboristVirtualNode
|
|
140
|
+
: ArboristNode
|
|
141
|
+
if (path.includes(tree)) {
|
|
142
|
+
const obj = Object.create(Cls.prototype)
|
|
143
|
+
return Object.assign(obj, { location: tree.location })
|
|
144
|
+
}
|
|
124
145
|
path.push(tree)
|
|
125
|
-
const Cls = tree.isLink ? ArboristLink : ArboristNode
|
|
126
146
|
return new Cls(tree, path)
|
|
127
147
|
}
|
|
128
148
|
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -41,6 +41,7 @@ const readFile = promisify(fs.readFile)
|
|
|
41
41
|
const writeFile = promisify(fs.writeFile)
|
|
42
42
|
const stat = promisify(fs.stat)
|
|
43
43
|
const readdir_ = promisify(fs.readdir)
|
|
44
|
+
const readlink = promisify(fs.readlink)
|
|
44
45
|
|
|
45
46
|
// XXX remove when drop support for node v10
|
|
46
47
|
const lstat = promisify(fs.lstat)
|
|
@@ -176,10 +177,19 @@ const assertNoNewer = async (path, data, lockTime, dir = path, seen = null) => {
|
|
|
176
177
|
: readdir(parent, { withFileTypes: true })
|
|
177
178
|
|
|
178
179
|
return children.catch(() => [])
|
|
179
|
-
.then(ents => Promise.all(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
.then(ents => Promise.all(ents.map(async ent => {
|
|
181
|
+
const child = resolve(parent, ent.name)
|
|
182
|
+
if (ent.isDirectory() && !/^\./.test(ent.name))
|
|
183
|
+
await assertNoNewer(path, data, lockTime, child, seen)
|
|
184
|
+
else if (ent.isSymbolicLink()) {
|
|
185
|
+
const target = resolve(parent, await readlink(child))
|
|
186
|
+
const tstat = await stat(target).catch(() => null)
|
|
187
|
+
seen.add(relpath(path, child))
|
|
188
|
+
if (tstat && tstat.isDirectory() && !seen.has(relpath(path, target)))
|
|
189
|
+
await assertNoNewer(path, data, lockTime, target, seen)
|
|
190
|
+
}
|
|
191
|
+
})))
|
|
192
|
+
.then(() => {
|
|
183
193
|
if (dir !== path)
|
|
184
194
|
return
|
|
185
195
|
|