@npmcli/arborist 2.2.9 → 2.4.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/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 +84 -82
- package/lib/arborist/build-ideal-tree.js +261 -73
- package/lib/arborist/index.js +4 -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 +210 -49
- package/lib/audit-report.js +13 -6
- package/lib/debug.js +8 -1
- package/lib/diff.js +70 -11
- package/lib/index.js +1 -0
- package/lib/inventory.js +1 -1
- package/lib/link.js +13 -7
- package/lib/node.js +66 -24
- package/lib/printable.js +20 -2
- package/lib/shrinkwrap.js +5 -5
- package/lib/tree-check.js +46 -1
- package/lib/update-root-package-json.js +14 -2
- package/lib/vuln.js +3 -0
- package/package.json +5 -8
- package/lib/dep-spec.js +0 -43
package/lib/arborist/rebuild.js
CHANGED
|
@@ -115,10 +115,6 @@ module.exports = cls => class Builder extends cls {
|
|
|
115
115
|
await this[_runScripts]('preinstall')
|
|
116
116
|
if (this[_binLinks] && type !== 'links')
|
|
117
117
|
await this[_linkAllBins]()
|
|
118
|
-
if (!this[_ignoreScripts]) {
|
|
119
|
-
await this[_runScripts]('install')
|
|
120
|
-
await this[_runScripts]('postinstall')
|
|
121
|
-
}
|
|
122
118
|
|
|
123
119
|
// links should also run prepare scripts and only link bins after that
|
|
124
120
|
if (type === 'links') {
|
|
@@ -128,6 +124,11 @@ module.exports = cls => class Builder extends cls {
|
|
|
128
124
|
await this[_linkAllBins]()
|
|
129
125
|
}
|
|
130
126
|
|
|
127
|
+
if (!this[_ignoreScripts]) {
|
|
128
|
+
await this[_runScripts]('install')
|
|
129
|
+
await this[_runScripts]('postinstall')
|
|
130
|
+
}
|
|
131
|
+
|
|
131
132
|
process.emit('timeEnd', `build:${type}`)
|
|
132
133
|
}
|
|
133
134
|
|
package/lib/arborist/reify.js
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
const onExit = require('../signal-handling.js')
|
|
4
4
|
const pacote = require('pacote')
|
|
5
5
|
const rpj = require('read-package-json-fast')
|
|
6
|
-
const { updateDepSpec } = require('../dep-spec.js')
|
|
7
6
|
const AuditReport = require('../audit-report.js')
|
|
8
|
-
const {subset} = require('semver')
|
|
7
|
+
const {subset, intersects} = require('semver')
|
|
9
8
|
const npa = require('npm-package-arg')
|
|
10
9
|
|
|
11
10
|
const {dirname, resolve, relative} = require('path')
|
|
@@ -14,6 +13,7 @@ const fs = require('fs')
|
|
|
14
13
|
const {promisify} = require('util')
|
|
15
14
|
const symlink = promisify(fs.symlink)
|
|
16
15
|
const mkdirp = require('mkdirp-infer-owner')
|
|
16
|
+
const justMkdirp = require('mkdirp')
|
|
17
17
|
const moveFile = require('@npmcli/move-file')
|
|
18
18
|
const rimraf = promisify(require('rimraf'))
|
|
19
19
|
const packageContents = require('@npmcli/installed-package-contents')
|
|
@@ -26,6 +26,8 @@ const retirePath = require('../retire-path.js')
|
|
|
26
26
|
const promiseAllRejectLate = require('promise-all-reject-late')
|
|
27
27
|
const optionalSet = require('../optional-set.js')
|
|
28
28
|
const updateRootPackageJson = require('../update-root-package-json.js')
|
|
29
|
+
const calcDepFlags = require('../calc-dep-flags.js')
|
|
30
|
+
const { saveTypeMap, hasSubKey } = require('../add-rm-pkg-deps.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
|
})
|
|
@@ -356,11 +406,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
356
406
|
return
|
|
357
407
|
|
|
358
408
|
process.emit('time', 'reify:trashOmits')
|
|
409
|
+
// node.parent is checked to make sure this is a node that's in the tree, and
|
|
410
|
+
// not the parent-less top level nodes
|
|
359
411
|
const filter = node =>
|
|
360
|
-
node.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
412
|
+
node.isDescendantOf(this.idealTree) &&
|
|
413
|
+
(node.peer && this[_omitPeer] ||
|
|
414
|
+
node.dev && this[_omitDev] ||
|
|
415
|
+
node.optional && this[_omitOptional] ||
|
|
416
|
+
node.devOptional && this[_omitOptional] && this[_omitDev])
|
|
364
417
|
|
|
365
418
|
for (const node of this.idealTree.inventory.filter(filter))
|
|
366
419
|
this[_addNodeToTrashList](node)
|
|
@@ -375,7 +428,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
375
428
|
const dirs = this.diff.leaves
|
|
376
429
|
.filter(diff => {
|
|
377
430
|
return (diff.action === 'ADD' || diff.action === 'CHANGE') &&
|
|
378
|
-
!this[_sparseTreeDirs].has(diff.ideal.path)
|
|
431
|
+
!this[_sparseTreeDirs].has(diff.ideal.path) &&
|
|
432
|
+
!diff.ideal.isLink
|
|
379
433
|
})
|
|
380
434
|
.map(diff => diff.ideal.path)
|
|
381
435
|
|
|
@@ -409,9 +463,9 @@ module.exports = cls => class Reifier extends cls {
|
|
|
409
463
|
// we need to unpack them, read that shrinkwrap file, and then update
|
|
410
464
|
// the tree by calling loadVirtual with the node as the root.
|
|
411
465
|
[_loadShrinkwrapsAndUpdateTrees] () {
|
|
412
|
-
const seen = this[
|
|
466
|
+
const seen = this[_shrinkwrapInflated]
|
|
413
467
|
const shrinkwraps = this.diff.leaves
|
|
414
|
-
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD') &&
|
|
468
|
+
.filter(d => (d.action === 'CHANGE' || d.action === 'ADD' || !d.action) &&
|
|
415
469
|
d.ideal.hasShrinkwrap && !seen.has(d.ideal) &&
|
|
416
470
|
!this[_trashList].has(d.ideal.path))
|
|
417
471
|
|
|
@@ -424,7 +478,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
424
478
|
return promiseAllRejectLate(shrinkwraps.map(diff => {
|
|
425
479
|
const node = diff.ideal
|
|
426
480
|
seen.add(node)
|
|
427
|
-
return this[_reifyNode](node)
|
|
481
|
+
return diff.action ? this[_reifyNode](node) : node
|
|
428
482
|
}))
|
|
429
483
|
.then(nodes => promiseAllRejectLate(nodes.map(node => new Arborist({
|
|
430
484
|
...this.options,
|
|
@@ -455,7 +509,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
455
509
|
|
|
456
510
|
const { npmVersion, nodeVersion } = this.options
|
|
457
511
|
const p = Promise.resolve()
|
|
458
|
-
.then(() => {
|
|
512
|
+
.then(async () => {
|
|
459
513
|
// when we reify an optional node, check the engine and platform
|
|
460
514
|
// first. be sure to ignore the --force and --engine-strict flags,
|
|
461
515
|
// since we always want to skip any optional packages we can't install.
|
|
@@ -465,11 +519,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
465
519
|
checkEngine(node.package, npmVersion, nodeVersion, false)
|
|
466
520
|
checkPlatform(node.package, false)
|
|
467
521
|
}
|
|
522
|
+
await this[_checkBins](node)
|
|
523
|
+
await this[_extractOrLink](node)
|
|
524
|
+
await this[_warnDeprecated](node)
|
|
525
|
+
await this[_loadAncientPackageDetails](node)
|
|
468
526
|
})
|
|
469
|
-
.then(() => this[_checkBins](node))
|
|
470
|
-
.then(() => this[_extractOrLink](node))
|
|
471
|
-
.then(() => this[_warnDeprecated](node))
|
|
472
|
-
.then(() => this[_loadAncientPackageDetails](node))
|
|
473
527
|
|
|
474
528
|
return this[_handleOptionalFailure](node, p)
|
|
475
529
|
.then(() => {
|
|
@@ -488,8 +542,8 @@ module.exports = cls => class Reifier extends cls {
|
|
|
488
542
|
// Do the best with what we have, or else remove it from the tree
|
|
489
543
|
// entirely, since we can't possibly reify it.
|
|
490
544
|
const res = node.resolved ? `${node.name}@${this[_registryResolved](node.resolved)}`
|
|
491
|
-
: node.
|
|
492
|
-
? `${node.
|
|
545
|
+
: node.packageName && node.version
|
|
546
|
+
? `${node.packageName}@${node.version}`
|
|
493
547
|
: null
|
|
494
548
|
|
|
495
549
|
// no idea what this thing is. remove it from the tree.
|
|
@@ -515,10 +569,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
515
569
|
})
|
|
516
570
|
}
|
|
517
571
|
|
|
518
|
-
[_symlink] (node) {
|
|
572
|
+
async [_symlink] (node) {
|
|
519
573
|
const dir = dirname(node.path)
|
|
520
574
|
const target = node.realpath
|
|
521
575
|
const rel = relative(dir, target)
|
|
576
|
+
await mkdirp(dir)
|
|
522
577
|
return symlink(rel, node.path, 'junction')
|
|
523
578
|
}
|
|
524
579
|
|
|
@@ -585,8 +640,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
585
640
|
[_loadBundlesAndUpdateTrees] (
|
|
586
641
|
depth = 0, bundlesByDepth = this[_getBundlesByDepth]()
|
|
587
642
|
) {
|
|
588
|
-
if (depth === 0)
|
|
643
|
+
if (depth === 0) {
|
|
644
|
+
this[_bundleUnpacked] = new Set()
|
|
589
645
|
process.emit('time', 'reify:loadBundles')
|
|
646
|
+
}
|
|
590
647
|
const maxBundleDepth = bundlesByDepth.get('maxBundleDepth')
|
|
591
648
|
if (depth > maxBundleDepth) {
|
|
592
649
|
// if we did something, then prune the tree and update the diffs
|
|
@@ -602,13 +659,17 @@ module.exports = cls => class Reifier extends cls {
|
|
|
602
659
|
// shallower bundle overwriting them with a bundled meta-dep.
|
|
603
660
|
const set = (bundlesByDepth.get(depth) || [])
|
|
604
661
|
.filter(node => node.root === this.idealTree &&
|
|
662
|
+
node.target !== node.root &&
|
|
605
663
|
!this[_trashList].has(node.path))
|
|
606
664
|
|
|
607
665
|
if (!set.length)
|
|
608
666
|
return this[_loadBundlesAndUpdateTrees](depth + 1, bundlesByDepth)
|
|
609
667
|
|
|
610
668
|
// extract all the nodes with bundles
|
|
611
|
-
return promiseAllRejectLate(set.map(node =>
|
|
669
|
+
return promiseAllRejectLate(set.map(node => {
|
|
670
|
+
this[_bundleUnpacked].add(node)
|
|
671
|
+
return this[_reifyNode](node)
|
|
672
|
+
}))
|
|
612
673
|
// then load their unpacked children and move into the ideal tree
|
|
613
674
|
.then(nodes =>
|
|
614
675
|
promiseAllRejectLate(nodes.map(node => new this.constructor({
|
|
@@ -630,8 +691,13 @@ module.exports = cls => class Reifier extends cls {
|
|
|
630
691
|
tree: this.diff,
|
|
631
692
|
visit: diff => {
|
|
632
693
|
const node = diff.ideal
|
|
633
|
-
if (
|
|
634
|
-
|
|
694
|
+
if (!node)
|
|
695
|
+
return
|
|
696
|
+
if (node.isProjectRoot || (node.target && node.target.isProjectRoot))
|
|
697
|
+
return
|
|
698
|
+
|
|
699
|
+
const { bundleDependencies } = node.package
|
|
700
|
+
if (bundleDependencies && bundleDependencies.length) {
|
|
635
701
|
maxBundleDepth = Math.max(maxBundleDepth, node.depth)
|
|
636
702
|
if (!bundlesByDepth.has(node.depth))
|
|
637
703
|
bundlesByDepth.set(node.depth, [node])
|
|
@@ -736,14 +802,14 @@ module.exports = cls => class Reifier extends cls {
|
|
|
736
802
|
return
|
|
737
803
|
|
|
738
804
|
const node = diff.ideal
|
|
739
|
-
const bd = node
|
|
740
|
-
const sw = this[
|
|
805
|
+
const bd = this[_bundleUnpacked].has(node)
|
|
806
|
+
const sw = this[_shrinkwrapInflated].has(node)
|
|
741
807
|
|
|
742
808
|
// check whether we still need to unpack this one.
|
|
743
809
|
// test the inDepBundle last, since that's potentially a tree walk.
|
|
744
810
|
const doUnpack = node && // can't unpack if removed!
|
|
745
811
|
!node.isRoot && // root node already exists
|
|
746
|
-
!
|
|
812
|
+
!bd && // already unpacked to read bundle
|
|
747
813
|
!sw && // already unpacked to read sw
|
|
748
814
|
!node.inDepBundle // already unpacked by another dep's bundle
|
|
749
815
|
|
|
@@ -886,7 +952,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
886
952
|
// to things like git repos and tarball file/urls. However, if the
|
|
887
953
|
// user requested 'foo@', and we have a foo@file:../foo, then we should
|
|
888
954
|
// end up saving the spec we actually used, not whatever they gave us.
|
|
889
|
-
if (this[_resolvedAdd]) {
|
|
955
|
+
if (this[_resolvedAdd].length) {
|
|
890
956
|
const root = this.idealTree
|
|
891
957
|
const pkg = root.package
|
|
892
958
|
for (const { name } of this[_resolvedAdd]) {
|
|
@@ -896,6 +962,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
896
962
|
const spec = subSpec ? subSpec.rawSpec : rawSpec
|
|
897
963
|
const child = root.children.get(name)
|
|
898
964
|
|
|
965
|
+
let newSpec
|
|
899
966
|
if (req.registry) {
|
|
900
967
|
const version = child.version
|
|
901
968
|
const prefixRange = version ? this[_savePrefix] + version : '*'
|
|
@@ -907,16 +974,17 @@ module.exports = cls => class Reifier extends cls {
|
|
|
907
974
|
const isRange = (subSpec || req).type === 'range'
|
|
908
975
|
const range = !isRange || subset(prefixRange, spec, { loose: true })
|
|
909
976
|
? prefixRange : spec
|
|
910
|
-
const pname = child.
|
|
977
|
+
const pname = child.packageName
|
|
911
978
|
const alias = name !== pname
|
|
912
|
-
|
|
979
|
+
newSpec = alias ? `npm:${pname}@${range}` : range
|
|
913
980
|
} else if (req.hosted) {
|
|
914
981
|
// save the git+https url if it has auth, otherwise shortcut
|
|
915
982
|
const h = req.hosted
|
|
916
983
|
const opt = { noCommittish: false }
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
984
|
+
if (h.https && h.auth)
|
|
985
|
+
newSpec = `git+${h.https(opt)}`
|
|
986
|
+
else
|
|
987
|
+
newSpec = h.shortcut(opt)
|
|
920
988
|
} else if (req.type === 'directory' || req.type === 'file') {
|
|
921
989
|
// save the relative path in package.json
|
|
922
990
|
// Normally saveSpec is updated with the proper relative
|
|
@@ -925,9 +993,37 @@ module.exports = cls => class Reifier extends cls {
|
|
|
925
993
|
// thing, so just get the ultimate fetchSpec and relativize it.
|
|
926
994
|
const p = req.fetchSpec.replace(/^file:/, '')
|
|
927
995
|
const rel = relpath(root.realpath, p)
|
|
928
|
-
|
|
996
|
+
newSpec = `file:${rel}`
|
|
929
997
|
} else
|
|
930
|
-
|
|
998
|
+
newSpec = req.saveSpec
|
|
999
|
+
|
|
1000
|
+
if (options.saveType) {
|
|
1001
|
+
const depType = saveTypeMap.get(options.saveType)
|
|
1002
|
+
pkg[depType][name] = newSpec
|
|
1003
|
+
// rpj will have moved it here if it was in both
|
|
1004
|
+
// if it is empty it will be deleted later
|
|
1005
|
+
if (options.saveType === 'prod' && pkg.optionalDependencies)
|
|
1006
|
+
delete pkg.optionalDependencies[name]
|
|
1007
|
+
} else {
|
|
1008
|
+
if (hasSubKey(pkg, 'dependencies', name))
|
|
1009
|
+
pkg.dependencies[name] = newSpec
|
|
1010
|
+
|
|
1011
|
+
if (hasSubKey(pkg, 'devDependencies', name)) {
|
|
1012
|
+
pkg.devDependencies[name] = newSpec
|
|
1013
|
+
// don't update peer or optional if we don't have to
|
|
1014
|
+
if (hasSubKey(pkg, 'peerDependencies', name) && !intersects(newSpec, pkg.peerDependencies[name]))
|
|
1015
|
+
pkg.peerDependencies[name] = newSpec
|
|
1016
|
+
|
|
1017
|
+
if (hasSubKey(pkg, 'optionalDependencies', name) && !intersects(newSpec, pkg.optionalDependencies[name]))
|
|
1018
|
+
pkg.optionalDependencies[name] = newSpec
|
|
1019
|
+
} else {
|
|
1020
|
+
if (hasSubKey(pkg, 'peerDependencies', name))
|
|
1021
|
+
pkg.peerDependencies[name] = newSpec
|
|
1022
|
+
|
|
1023
|
+
if (hasSubKey(pkg, 'optionalDependencies', name))
|
|
1024
|
+
pkg.optionalDependencies[name] = newSpec
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
931
1027
|
}
|
|
932
1028
|
|
|
933
1029
|
// refresh the edges so they have the correct specs
|
|
@@ -966,20 +1062,85 @@ module.exports = cls => class Reifier extends cls {
|
|
|
966
1062
|
return meta.save(saveOpt)
|
|
967
1063
|
}
|
|
968
1064
|
|
|
969
|
-
[_copyIdealToActual] () {
|
|
1065
|
+
async [_copyIdealToActual] () {
|
|
1066
|
+
// clean up any trash that is still in the tree
|
|
1067
|
+
for (const path of this[_trashList]) {
|
|
1068
|
+
const loc = relpath(this.idealTree.realpath, path)
|
|
1069
|
+
const node = this.idealTree.inventory.get(loc)
|
|
1070
|
+
if (node && node.root === this.idealTree)
|
|
1071
|
+
node.parent = null
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// if we filtered to only certain nodes, then anything ELSE needs
|
|
1075
|
+
// to be untouched in the resulting actual tree, even if it differs
|
|
1076
|
+
// in the idealTree. Copy over anything that was in the actual and
|
|
1077
|
+
// was not changed, delete anything in the ideal and not actual.
|
|
1078
|
+
// Then we move the entire idealTree over to this.actualTree, and
|
|
1079
|
+
// save the hidden lockfile.
|
|
1080
|
+
if (this.diff && this.diff.filterSet.size) {
|
|
1081
|
+
const { filterSet } = this.diff
|
|
1082
|
+
const seen = new Set()
|
|
1083
|
+
for (const [loc, ideal] of this.idealTree.inventory.entries()) {
|
|
1084
|
+
if (seen.has(loc))
|
|
1085
|
+
continue
|
|
1086
|
+
seen.add(loc)
|
|
1087
|
+
|
|
1088
|
+
// if it's an ideal node from the filter set, then skip it
|
|
1089
|
+
// because we already made whatever changes were necessary
|
|
1090
|
+
if (filterSet.has(ideal))
|
|
1091
|
+
continue
|
|
1092
|
+
|
|
1093
|
+
// otherwise, if it's not in the actualTree, then it's not a thing
|
|
1094
|
+
// that we actually added. And if it IS in the actualTree, then
|
|
1095
|
+
// it's something that we left untouched, so we need to record
|
|
1096
|
+
// that.
|
|
1097
|
+
const actual = this.actualTree.inventory.get(loc)
|
|
1098
|
+
if (!actual)
|
|
1099
|
+
ideal.root = null
|
|
1100
|
+
else {
|
|
1101
|
+
if ([...actual.linksIn].some(link => filterSet.has(link))) {
|
|
1102
|
+
seen.add(actual.location)
|
|
1103
|
+
continue
|
|
1104
|
+
}
|
|
1105
|
+
const { realpath, isLink } = actual
|
|
1106
|
+
if (isLink && ideal.isLink && ideal.realpath === realpath)
|
|
1107
|
+
continue
|
|
1108
|
+
else
|
|
1109
|
+
actual.root = this.idealTree
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// now find any actual nodes that may not be present in the ideal
|
|
1114
|
+
// tree, but were left behind by virtue of not being in the filter
|
|
1115
|
+
for (const [loc, actual] of this.actualTree.inventory.entries()) {
|
|
1116
|
+
if (seen.has(loc))
|
|
1117
|
+
continue
|
|
1118
|
+
seen.add(loc)
|
|
1119
|
+
if (filterSet.has(actual))
|
|
1120
|
+
continue
|
|
1121
|
+
actual.root = this.idealTree
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// prune out any tops that lack a linkIn
|
|
1125
|
+
for (const top of this.idealTree.tops) {
|
|
1126
|
+
if (top.linksIn.size === 0)
|
|
1127
|
+
top.root = null
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// need to calculate dep flags, since nodes may have been marked
|
|
1131
|
+
// as extraneous or otherwise incorrect during transit.
|
|
1132
|
+
calcDepFlags(this.idealTree)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
970
1135
|
// save the ideal's meta as a hidden lockfile after we actualize it
|
|
971
1136
|
this.idealTree.meta.filename =
|
|
972
|
-
this.
|
|
1137
|
+
this.idealTree.realpath + '/node_modules/.package-lock.json'
|
|
973
1138
|
this.idealTree.meta.hiddenLockfile = true
|
|
1139
|
+
|
|
974
1140
|
this.actualTree = this.idealTree
|
|
975
1141
|
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
1142
|
|
|
983
|
-
|
|
1143
|
+
if (!this[_global])
|
|
1144
|
+
await this.actualTree.meta.save()
|
|
984
1145
|
}
|
|
985
1146
|
}
|
package/lib/audit-report.js
CHANGED
|
@@ -101,13 +101,14 @@ class AuditReport extends Map {
|
|
|
101
101
|
|
|
102
102
|
async run () {
|
|
103
103
|
this.report = await this[_getReport]()
|
|
104
|
+
this.log.silly('audit report', this.report)
|
|
104
105
|
if (this.report)
|
|
105
106
|
await this[_init]()
|
|
106
107
|
return this
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
isVulnerable (node) {
|
|
110
|
-
const vuln = this.get(node.
|
|
111
|
+
const vuln = this.get(node.packageName)
|
|
111
112
|
return !!(vuln && vuln.isVulnerable(node))
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -144,7 +145,7 @@ class AuditReport extends Map {
|
|
|
144
145
|
super.set(name, vuln)
|
|
145
146
|
|
|
146
147
|
const p = []
|
|
147
|
-
for (const node of this.tree.inventory.query('
|
|
148
|
+
for (const node of this.tree.inventory.query('packageName', name)) {
|
|
148
149
|
if (shouldOmit(node, this[_omit]))
|
|
149
150
|
continue
|
|
150
151
|
|
|
@@ -167,7 +168,7 @@ class AuditReport extends Map {
|
|
|
167
168
|
this[_checkTopNode](dep, vuln, spec)
|
|
168
169
|
else {
|
|
169
170
|
// calculate a metavuln, if necessary
|
|
170
|
-
p.push(this.calculator.calculate(dep.
|
|
171
|
+
p.push(this.calculator.calculate(dep.packageName, advisory).then(meta => {
|
|
171
172
|
if (meta.testVersion(dep.version, spec))
|
|
172
173
|
advisories.add(meta)
|
|
173
174
|
}))
|
|
@@ -228,6 +229,9 @@ class AuditReport extends Map {
|
|
|
228
229
|
if (!specObj.registry)
|
|
229
230
|
return false
|
|
230
231
|
|
|
232
|
+
if (specObj.subSpec)
|
|
233
|
+
spec = specObj.subSpec.rawSpec
|
|
234
|
+
|
|
231
235
|
// We don't provide fixes for top nodes other than root, but we
|
|
232
236
|
// still check to see if the node is fixable with a different version,
|
|
233
237
|
// and if that is a semver major bump.
|
|
@@ -289,6 +293,7 @@ class AuditReport extends Map {
|
|
|
289
293
|
try {
|
|
290
294
|
// first try the super fast bulk advisory listing
|
|
291
295
|
const body = prepareBulkData(this.tree, this[_omit])
|
|
296
|
+
this.log.silly('audit', 'bulk request', body)
|
|
292
297
|
|
|
293
298
|
// no sense asking if we don't have anything to audit,
|
|
294
299
|
// we know it'll be empty
|
|
@@ -304,7 +309,8 @@ class AuditReport extends Map {
|
|
|
304
309
|
})
|
|
305
310
|
|
|
306
311
|
return await res.json()
|
|
307
|
-
} catch (
|
|
312
|
+
} catch (er) {
|
|
313
|
+
this.log.silly('audit', 'bulk request failed', String(er.body))
|
|
308
314
|
// that failed, try the quick audit endpoint
|
|
309
315
|
const body = prepareData(this.tree, this.options)
|
|
310
316
|
const res = await fetch('/-/npm/v1/security/audits/quick', {
|
|
@@ -330,6 +336,7 @@ class AuditReport extends Map {
|
|
|
330
336
|
// return true if we should ignore this one
|
|
331
337
|
const shouldOmit = (node, omit) =>
|
|
332
338
|
!node.version ? true
|
|
339
|
+
: node.isRoot ? true
|
|
333
340
|
: omit.size === 0 ? false
|
|
334
341
|
: node.dev && omit.has('dev') ||
|
|
335
342
|
node.optional && omit.has('optional') ||
|
|
@@ -338,9 +345,9 @@ const shouldOmit = (node, omit) =>
|
|
|
338
345
|
|
|
339
346
|
const prepareBulkData = (tree, omit) => {
|
|
340
347
|
const payload = {}
|
|
341
|
-
for (const name of tree.inventory.query('
|
|
348
|
+
for (const name of tree.inventory.query('packageName')) {
|
|
342
349
|
const set = new Set()
|
|
343
|
-
for (const node of tree.inventory.query('
|
|
350
|
+
for (const node of tree.inventory.query('packageName', name)) {
|
|
344
351
|
if (shouldOmit(node, omit))
|
|
345
352
|
continue
|
|
346
353
|
|
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
|
+
})
|