@npmcli/arborist 7.3.0 → 7.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/lib/arborist/build-ideal-tree.js +98 -62
- package/lib/arborist/index.js +102 -14
- package/lib/arborist/load-actual.js +2 -4
- package/lib/arborist/rebuild.js +55 -84
- package/lib/arborist/reify.js +39 -24
- package/lib/case-insensitive-map.js +20 -20
- package/lib/index.js +0 -2
- package/lib/query-selector-all.js +80 -2
- package/lib/tracker.js +28 -29
- package/lib/version-from-tgz.js +4 -5
- package/lib/vuln.js +28 -31
- package/package.json +3 -3
- package/lib/arborist/audit.js +0 -51
- package/lib/arborist/deduper.js +0 -19
- package/lib/arborist/pruner.js +0 -30
- package/lib/arborist/set-workspaces.js +0 -19
- package/lib/get-workspace-nodes.js +0 -36
|
@@ -4,7 +4,7 @@ const rpj = require('read-package-json-fast')
|
|
|
4
4
|
const npa = require('npm-package-arg')
|
|
5
5
|
const pacote = require('pacote')
|
|
6
6
|
const cacache = require('cacache')
|
|
7
|
-
const promiseCallLimit = require('promise-call-limit')
|
|
7
|
+
const { callLimit: promiseCallLimit } = require('promise-call-limit')
|
|
8
8
|
const realpath = require('../../lib/realpath.js')
|
|
9
9
|
const { resolve, dirname } = require('path')
|
|
10
10
|
const treeCheck = require('../tree-check.js')
|
|
@@ -38,48 +38,63 @@ const resetDepFlags = require('../reset-dep-flags.js')
|
|
|
38
38
|
// them with unit tests and reuse them across mixins
|
|
39
39
|
const _updateAll = Symbol.for('updateAll')
|
|
40
40
|
const _flagsSuspect = Symbol.for('flagsSuspect')
|
|
41
|
-
const _workspaces = Symbol.for('workspaces')
|
|
42
41
|
const _setWorkspaces = Symbol.for('setWorkspaces')
|
|
43
42
|
const _updateNames = Symbol.for('updateNames')
|
|
44
43
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
45
44
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
46
45
|
const _rpcache = Symbol.for('realpathCache')
|
|
47
46
|
const _stcache = Symbol.for('statCache')
|
|
48
|
-
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
|
|
49
|
-
|
|
50
|
-
// exposed symbol for unit testing the placeDep method directly
|
|
51
|
-
const _peerSetSource = Symbol.for('peerSetSource')
|
|
52
47
|
|
|
53
48
|
// used by Reify mixin
|
|
54
|
-
const
|
|
55
|
-
const _global = Symbol.for('global')
|
|
56
|
-
const _idealTreePrune = Symbol.for('idealTreePrune')
|
|
49
|
+
const _addNodeToTrashList = Symbol.for('addNodeToTrashList')
|
|
57
50
|
|
|
58
51
|
// Push items in, pop them sorted by depth and then path
|
|
52
|
+
// Sorts physically shallower deps up to the front of the queue, because
|
|
53
|
+
// they'll affect things deeper in, then alphabetical for consistency between
|
|
54
|
+
// installs
|
|
59
55
|
class DepsQueue {
|
|
56
|
+
// [{ sorted, items }] indexed by depth
|
|
60
57
|
#deps = []
|
|
61
58
|
#sorted = true
|
|
59
|
+
#minDepth = 0
|
|
60
|
+
#length = 0
|
|
62
61
|
|
|
63
62
|
get length () {
|
|
64
|
-
return this.#
|
|
63
|
+
return this.#length
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
push (item) {
|
|
68
|
-
if (!this.#deps.
|
|
69
|
-
this.#
|
|
70
|
-
this.#deps.
|
|
67
|
+
if (!this.#deps[item.depth]) {
|
|
68
|
+
this.#length++
|
|
69
|
+
this.#deps[item.depth] = { sorted: true, items: [item] }
|
|
70
|
+
// no minDepth check needed, this branch is only reached when we are in
|
|
71
|
+
// the middle of a shallower depth and creating a new one
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
if (!this.#deps[item.depth].items.includes(item)) {
|
|
75
|
+
this.#length++
|
|
76
|
+
this.#deps[item.depth].sorted = false
|
|
77
|
+
this.#deps[item.depth].items.push(item)
|
|
78
|
+
if (item.depth < this.#minDepth) {
|
|
79
|
+
this.#minDepth = item.depth
|
|
80
|
+
}
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
pop () {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
let depth
|
|
86
|
+
while (!depth?.items.length) {
|
|
87
|
+
depth = this.#deps[this.#minDepth]
|
|
88
|
+
if (!depth?.items.length) {
|
|
89
|
+
this.#minDepth++
|
|
90
|
+
}
|
|
81
91
|
}
|
|
82
|
-
|
|
92
|
+
if (!depth.sorted) {
|
|
93
|
+
depth.items.sort((a, b) => localeCompare(a.path, b.path))
|
|
94
|
+
depth.sorted = true
|
|
95
|
+
}
|
|
96
|
+
this.#length--
|
|
97
|
+
return depth.items.shift()
|
|
83
98
|
}
|
|
84
99
|
}
|
|
85
100
|
|
|
@@ -95,6 +110,10 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
95
110
|
#loadFailures = new Set()
|
|
96
111
|
#manifests = new Map()
|
|
97
112
|
#mutateTree = false
|
|
113
|
+
// a map of each module in a peer set to the thing that depended on
|
|
114
|
+
// that set of peers in the first place. Use a WeakMap so that we
|
|
115
|
+
// don't hold onto references for nodes that are garbage collected.
|
|
116
|
+
#peerSetSource = new WeakMap()
|
|
98
117
|
#preferDedupe = false
|
|
99
118
|
#prune
|
|
100
119
|
#strictPeerDeps
|
|
@@ -109,20 +128,16 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
109
128
|
|
|
110
129
|
const {
|
|
111
130
|
follow = false,
|
|
112
|
-
force = false,
|
|
113
|
-
global = false,
|
|
114
131
|
installStrategy = 'hoisted',
|
|
115
132
|
idealTree = null,
|
|
116
|
-
includeWorkspaceRoot = false,
|
|
117
133
|
installLinks = false,
|
|
118
134
|
legacyPeerDeps = false,
|
|
119
135
|
packageLock = true,
|
|
120
136
|
strictPeerDeps = false,
|
|
121
|
-
workspaces
|
|
137
|
+
workspaces,
|
|
138
|
+
global,
|
|
122
139
|
} = options
|
|
123
140
|
|
|
124
|
-
this[_workspaces] = workspaces || []
|
|
125
|
-
this[_force] = !!force
|
|
126
141
|
this.#strictPeerDeps = !!strictPeerDeps
|
|
127
142
|
|
|
128
143
|
this.idealTree = idealTree
|
|
@@ -130,24 +145,16 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
130
145
|
this.legacyPeerDeps = legacyPeerDeps
|
|
131
146
|
|
|
132
147
|
this[_usePackageLock] = packageLock
|
|
133
|
-
this[_global] = !!global
|
|
134
148
|
this.#installStrategy = global ? 'shallow' : installStrategy
|
|
135
149
|
this.#follow = !!follow
|
|
136
150
|
|
|
137
|
-
if (
|
|
151
|
+
if (workspaces?.length && global) {
|
|
138
152
|
throw new Error('Cannot operate on workspaces in global mode')
|
|
139
153
|
}
|
|
140
154
|
|
|
141
155
|
this[_updateAll] = false
|
|
142
156
|
this[_updateNames] = []
|
|
143
157
|
this[_resolvedAdd] = []
|
|
144
|
-
|
|
145
|
-
// a map of each module in a peer set to the thing that depended on
|
|
146
|
-
// that set of peers in the first place. Use a WeakMap so that we
|
|
147
|
-
// don't hold onto references for nodes that are garbage collected.
|
|
148
|
-
this[_peerSetSource] = new WeakMap()
|
|
149
|
-
|
|
150
|
-
this[_includeWorkspaceRoot] = includeWorkspaceRoot
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
get explicitRequests () {
|
|
@@ -174,7 +181,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
174
181
|
|
|
175
182
|
process.emit('time', 'idealTree')
|
|
176
183
|
|
|
177
|
-
if (!options.add && !options.rm && !options.update && this
|
|
184
|
+
if (!options.add && !options.rm && !options.update && this.options.global) {
|
|
178
185
|
throw new Error('global requires add, rm, or update option')
|
|
179
186
|
}
|
|
180
187
|
|
|
@@ -210,7 +217,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
210
217
|
for (const node of this.idealTree.inventory.values()) {
|
|
211
218
|
if (!node.optional) {
|
|
212
219
|
try {
|
|
213
|
-
checkEngine(node.package, npmVersion, nodeVersion, this
|
|
220
|
+
checkEngine(node.package, npmVersion, nodeVersion, this.options.force)
|
|
214
221
|
} catch (err) {
|
|
215
222
|
if (engineStrict) {
|
|
216
223
|
throw err
|
|
@@ -221,7 +228,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
221
228
|
current: err.current,
|
|
222
229
|
})
|
|
223
230
|
}
|
|
224
|
-
checkPlatform(node.package, this
|
|
231
|
+
checkPlatform(node.package, this.options.force)
|
|
225
232
|
}
|
|
226
233
|
}
|
|
227
234
|
}
|
|
@@ -273,7 +280,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
273
280
|
async #initTree () {
|
|
274
281
|
process.emit('time', 'idealTree:init')
|
|
275
282
|
let root
|
|
276
|
-
if (this
|
|
283
|
+
if (this.options.global) {
|
|
277
284
|
root = await this.#globalRootNode()
|
|
278
285
|
} else {
|
|
279
286
|
try {
|
|
@@ -291,7 +298,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
291
298
|
// When updating all, we load the shrinkwrap, but don't bother
|
|
292
299
|
// to build out the full virtual tree from it, since we'll be
|
|
293
300
|
// reconstructing it anyway.
|
|
294
|
-
.then(root => this
|
|
301
|
+
.then(root => this.options.global ? root
|
|
295
302
|
: !this[_usePackageLock] || this[_updateAll]
|
|
296
303
|
? Shrinkwrap.reset({
|
|
297
304
|
path: this.path,
|
|
@@ -307,7 +314,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
307
314
|
// Load on a new Arborist object, so the Nodes aren't the same,
|
|
308
315
|
// or else it'll get super confusing when we change them!
|
|
309
316
|
.then(async root => {
|
|
310
|
-
if ((!this[_updateAll] && !this
|
|
317
|
+
if ((!this[_updateAll] && !this.options.global && !root.meta.loadedFromDisk) || (this.options.global && this[_updateNames].length)) {
|
|
311
318
|
await new this.constructor(this.options).loadActual({ root })
|
|
312
319
|
const tree = root.target
|
|
313
320
|
// even though we didn't load it from a package-lock.json FILE,
|
|
@@ -386,7 +393,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
386
393
|
devOptional: false,
|
|
387
394
|
peer: false,
|
|
388
395
|
optional: false,
|
|
389
|
-
global: this
|
|
396
|
+
global: this.options.global,
|
|
390
397
|
installLinks: this.installLinks,
|
|
391
398
|
legacyPeerDeps: this.legacyPeerDeps,
|
|
392
399
|
loadOverrides: true,
|
|
@@ -401,7 +408,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
401
408
|
devOptional: false,
|
|
402
409
|
peer: false,
|
|
403
410
|
optional: false,
|
|
404
|
-
global: this
|
|
411
|
+
global: this.options.global,
|
|
405
412
|
installLinks: this.installLinks,
|
|
406
413
|
legacyPeerDeps: this.legacyPeerDeps,
|
|
407
414
|
root,
|
|
@@ -416,11 +423,11 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
416
423
|
process.emit('time', 'idealTree:userRequests')
|
|
417
424
|
const tree = this.idealTree.target
|
|
418
425
|
|
|
419
|
-
if (!this
|
|
426
|
+
if (!this.options.workspaces.length) {
|
|
420
427
|
await this.#applyUserRequestsToNode(tree, options)
|
|
421
428
|
} else {
|
|
422
|
-
const nodes = this.workspaceNodes(tree, this
|
|
423
|
-
if (this
|
|
429
|
+
const nodes = this.workspaceNodes(tree, this.options.workspaces)
|
|
430
|
+
if (this.options.includeWorkspaceRoot) {
|
|
424
431
|
nodes.push(tree)
|
|
425
432
|
}
|
|
426
433
|
const appliedRequests = nodes.map(
|
|
@@ -436,14 +443,14 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
436
443
|
// If we have a list of package names to update, and we know it's
|
|
437
444
|
// going to update them wherever they are, add any paths into those
|
|
438
445
|
// named nodes to the buildIdealTree queue.
|
|
439
|
-
if (!this
|
|
446
|
+
if (!this.options.global && this[_updateNames].length) {
|
|
440
447
|
this.#queueNamedUpdates()
|
|
441
448
|
}
|
|
442
449
|
|
|
443
450
|
// global updates only update the globalTop nodes, but we need to know
|
|
444
451
|
// that they're there, and not reinstall the world unnecessarily.
|
|
445
452
|
const globalExplicitUpdateNames = []
|
|
446
|
-
if (this
|
|
453
|
+
if (this.options.global && (this[_updateAll] || this[_updateNames].length)) {
|
|
447
454
|
const nm = resolve(this.path, 'node_modules')
|
|
448
455
|
const paths = await readdirScoped(nm).catch(() => [])
|
|
449
456
|
for (const p of paths) {
|
|
@@ -488,7 +495,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
488
495
|
// triggers a refresh of all edgesOut. this has to be done BEFORE
|
|
489
496
|
// adding the edges to explicitRequests, because the package setter
|
|
490
497
|
// resets all edgesOut.
|
|
491
|
-
if (add && add.length || rm && rm.length || this
|
|
498
|
+
if (add && add.length || rm && rm.length || this.options.global) {
|
|
492
499
|
tree.package = tree.package
|
|
493
500
|
}
|
|
494
501
|
|
|
@@ -594,7 +601,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
594
601
|
//
|
|
595
602
|
// XXX: how to handle top nodes that aren't the root? Maybe the report
|
|
596
603
|
// just tells the user to cd into that directory and fix it?
|
|
597
|
-
if (this
|
|
604
|
+
if (this.options.force && this.auditReport && this.auditReport.topVulns.size) {
|
|
598
605
|
options.add = options.add || []
|
|
599
606
|
options.rm = options.rm || []
|
|
600
607
|
const nodesTouched = new Set()
|
|
@@ -878,7 +885,7 @@ This is a one-time fix-up, please be patient...
|
|
|
878
885
|
// dep if allowed.
|
|
879
886
|
|
|
880
887
|
const tasks = []
|
|
881
|
-
const peerSource = this
|
|
888
|
+
const peerSource = this.#peerSetSource.get(node) || node
|
|
882
889
|
for (const edge of this.#problemEdges(node)) {
|
|
883
890
|
if (edge.peerConflicted) {
|
|
884
891
|
continue
|
|
@@ -936,7 +943,7 @@ This is a one-time fix-up, please be patient...
|
|
|
936
943
|
|
|
937
944
|
auditReport: this.auditReport,
|
|
938
945
|
explicitRequest: this.#explicitRequests.has(edge),
|
|
939
|
-
force: this
|
|
946
|
+
force: this.options.force,
|
|
940
947
|
installLinks: this.installLinks,
|
|
941
948
|
installStrategy: this.#installStrategy,
|
|
942
949
|
legacyPeerDeps: this.legacyPeerDeps,
|
|
@@ -1016,7 +1023,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1016
1023
|
// may well be an optional dep that has gone missing. it'll
|
|
1017
1024
|
// fail later anyway.
|
|
1018
1025
|
for (const e of this.#problemEdges(placed)) {
|
|
1019
|
-
promises.push(
|
|
1026
|
+
promises.push(() =>
|
|
1020
1027
|
this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)))
|
|
1021
1028
|
.catch(er => null)
|
|
1022
1029
|
)
|
|
@@ -1031,7 +1038,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1031
1038
|
}
|
|
1032
1039
|
}
|
|
1033
1040
|
|
|
1034
|
-
await
|
|
1041
|
+
await promiseCallLimit(promises)
|
|
1035
1042
|
return this.#buildDepStep()
|
|
1036
1043
|
}
|
|
1037
1044
|
|
|
@@ -1077,13 +1084,13 @@ This is a one-time fix-up, please be patient...
|
|
|
1077
1084
|
|
|
1078
1085
|
// keep track of the thing that caused this node to be included.
|
|
1079
1086
|
const src = parent.sourceReference
|
|
1080
|
-
this
|
|
1087
|
+
this.#peerSetSource.set(node, src)
|
|
1081
1088
|
|
|
1082
1089
|
// do not load the peers along with the set if this is a global top pkg
|
|
1083
1090
|
// otherwise we'll be tempted to put peers as other top-level installed
|
|
1084
1091
|
// things, potentially clobbering what's there already, which is not
|
|
1085
1092
|
// what we want. the missing edges will be picked up on the next pass.
|
|
1086
|
-
if (this
|
|
1093
|
+
if (this.options.global && edge.from.isProjectRoot) {
|
|
1087
1094
|
return node
|
|
1088
1095
|
}
|
|
1089
1096
|
|
|
@@ -1208,8 +1215,12 @@ This is a one-time fix-up, please be patient...
|
|
|
1208
1215
|
} else {
|
|
1209
1216
|
const cleanRawSpec = cleanUrl(spec.rawSpec)
|
|
1210
1217
|
log.silly('fetch manifest', spec.raw.replace(spec.rawSpec, cleanRawSpec))
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1218
|
+
const o = {
|
|
1219
|
+
...options,
|
|
1220
|
+
fullMetadata: true,
|
|
1221
|
+
}
|
|
1222
|
+
const p = pacote.manifest(spec, o)
|
|
1223
|
+
.then(({ license, ...mani }) => {
|
|
1213
1224
|
this.#manifests.set(spec.raw, mani)
|
|
1214
1225
|
return mani
|
|
1215
1226
|
})
|
|
@@ -1302,7 +1313,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1302
1313
|
const parentEdge = node.parent.edgesOut.get(edge.name)
|
|
1303
1314
|
const { isProjectRoot, isWorkspace } = node.parent.sourceReference
|
|
1304
1315
|
const isMine = isProjectRoot || isWorkspace
|
|
1305
|
-
const conflictOK = this
|
|
1316
|
+
const conflictOK = this.options.force || !isMine && !this.#strictPeerDeps
|
|
1306
1317
|
|
|
1307
1318
|
if (!edge.to) {
|
|
1308
1319
|
if (!parentEdge) {
|
|
@@ -1389,7 +1400,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1389
1400
|
currentEdge: currentEdge ? currentEdge.explain() : null,
|
|
1390
1401
|
edge: edge.explain(),
|
|
1391
1402
|
strictPeerDeps: this.#strictPeerDeps,
|
|
1392
|
-
force: this
|
|
1403
|
+
force: this.options.force,
|
|
1393
1404
|
}
|
|
1394
1405
|
}
|
|
1395
1406
|
|
|
@@ -1477,7 +1488,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1477
1488
|
// otherwise, don't bother.
|
|
1478
1489
|
const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
|
|
1479
1490
|
if (this.#prune && needPrune) {
|
|
1480
|
-
this
|
|
1491
|
+
this.#idealTreePrune()
|
|
1481
1492
|
for (const node of this.idealTree.inventory.values()) {
|
|
1482
1493
|
if (node.extraneous) {
|
|
1483
1494
|
node.parent = null
|
|
@@ -1488,7 +1499,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1488
1499
|
process.emit('timeEnd', 'idealTree:fixDepFlags')
|
|
1489
1500
|
}
|
|
1490
1501
|
|
|
1491
|
-
|
|
1502
|
+
#idealTreePrune () {
|
|
1492
1503
|
for (const node of this.idealTree.inventory.values()) {
|
|
1493
1504
|
if (node.extraneous) {
|
|
1494
1505
|
node.parent = null
|
|
@@ -1508,4 +1519,29 @@ This is a one-time fix-up, please be patient...
|
|
|
1508
1519
|
}
|
|
1509
1520
|
}
|
|
1510
1521
|
}
|
|
1522
|
+
|
|
1523
|
+
async prune (options = {}) {
|
|
1524
|
+
// allow the user to set options on the ctor as well.
|
|
1525
|
+
// XXX: deprecate separate method options objects.
|
|
1526
|
+
options = { ...this.options, ...options }
|
|
1527
|
+
|
|
1528
|
+
await this.buildIdealTree(options)
|
|
1529
|
+
|
|
1530
|
+
this.#idealTreePrune()
|
|
1531
|
+
|
|
1532
|
+
if (!this.options.workspacesEnabled) {
|
|
1533
|
+
const excludeNodes = this.excludeWorkspacesDependencySet(this.idealTree)
|
|
1534
|
+
for (const node of this.idealTree.inventory.values()) {
|
|
1535
|
+
if (
|
|
1536
|
+
node.parent !== null
|
|
1537
|
+
&& !node.isProjectRoot
|
|
1538
|
+
&& !excludeNodes.has(node)
|
|
1539
|
+
) {
|
|
1540
|
+
this[_addNodeToTrashList](node)
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
return this.reify(options)
|
|
1546
|
+
}
|
|
1511
1547
|
}
|
package/lib/arborist/index.js
CHANGED
|
@@ -29,15 +29,16 @@
|
|
|
29
29
|
const { resolve } = require('path')
|
|
30
30
|
const { homedir } = require('os')
|
|
31
31
|
const { depth } = require('treeverse')
|
|
32
|
+
const mapWorkspaces = require('@npmcli/map-workspaces')
|
|
33
|
+
const log = require('proc-log')
|
|
34
|
+
|
|
32
35
|
const { saveTypeMap } = require('../add-rm-pkg-deps.js')
|
|
36
|
+
const AuditReport = require('../audit-report.js')
|
|
37
|
+
const relpath = require('../relpath.js')
|
|
33
38
|
|
|
34
39
|
const mixins = [
|
|
35
40
|
require('../tracker.js'),
|
|
36
|
-
require('./pruner.js'),
|
|
37
|
-
require('./deduper.js'),
|
|
38
|
-
require('./audit.js'),
|
|
39
41
|
require('./build-ideal-tree.js'),
|
|
40
|
-
require('./set-workspaces.js'),
|
|
41
42
|
require('./load-actual.js'),
|
|
42
43
|
require('./load-virtual.js'),
|
|
43
44
|
require('./rebuild.js'),
|
|
@@ -45,9 +46,8 @@ const mixins = [
|
|
|
45
46
|
require('./isolated-reifier.js'),
|
|
46
47
|
]
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
+
const _setWorkspaces = Symbol.for('setWorkspaces')
|
|
49
50
|
const Base = mixins.reduce((a, b) => b(a), require('events'))
|
|
50
|
-
const getWorkspaceNodes = require('../get-workspace-nodes.js')
|
|
51
51
|
|
|
52
52
|
// if it's 1, 2, or 3, set it explicitly that.
|
|
53
53
|
// if undefined or null, set it null
|
|
@@ -72,20 +72,26 @@ class Arborist extends Base {
|
|
|
72
72
|
nodeVersion: process.version,
|
|
73
73
|
...options,
|
|
74
74
|
Arborist: this.constructor,
|
|
75
|
-
|
|
75
|
+
binLinks: 'binLinks' in options ? !!options.binLinks : true,
|
|
76
76
|
cache: options.cache || `${homedir()}/.npm/_cacache`,
|
|
77
|
+
force: !!options.force,
|
|
78
|
+
global: !!options.global,
|
|
79
|
+
ignoreScripts: !!options.ignoreScripts,
|
|
80
|
+
installStrategy: options.global ? 'shallow' : (options.installStrategy ? options.installStrategy : 'hoisted'),
|
|
81
|
+
lockfileVersion: lockfileVersion(options.lockfileVersion),
|
|
77
82
|
packumentCache: options.packumentCache || new Map(),
|
|
78
|
-
|
|
83
|
+
path: options.path || '.',
|
|
84
|
+
rebuildBundle: 'rebuildBundle' in options ? !!options.rebuildBundle : true,
|
|
79
85
|
replaceRegistryHost: options.replaceRegistryHost,
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
scriptShell: options.scriptShell,
|
|
87
|
+
workspaces: options.workspaces || [],
|
|
88
|
+
workspacesEnabled: options.workspacesEnabled !== false,
|
|
82
89
|
}
|
|
90
|
+
// TODO is this even used? If not is that a bug?
|
|
83
91
|
this.replaceRegistryHost = this.options.replaceRegistryHost =
|
|
84
92
|
(!this.options.replaceRegistryHost || this.options.replaceRegistryHost === 'npmjs') ?
|
|
85
93
|
'registry.npmjs.org' : this.options.replaceRegistryHost
|
|
86
94
|
|
|
87
|
-
this[_workspacesEnabled] = this.options.workspacesEnabled
|
|
88
|
-
|
|
89
95
|
if (options.saveType && !saveTypeMap.get(options.saveType)) {
|
|
90
96
|
throw new Error(`Invalid saveType ${options.saveType}`)
|
|
91
97
|
}
|
|
@@ -97,12 +103,40 @@ class Arborist extends Base {
|
|
|
97
103
|
// TODO: We should change these to static functions instead
|
|
98
104
|
// of methods for the next major version
|
|
99
105
|
|
|
100
|
-
//
|
|
106
|
+
// Get the actual nodes corresponding to a root node's child workspaces,
|
|
107
|
+
// given a list of workspace names.
|
|
101
108
|
workspaceNodes (tree, workspaces) {
|
|
102
|
-
|
|
109
|
+
const wsMap = tree.workspaces
|
|
110
|
+
if (!wsMap) {
|
|
111
|
+
log.warn('workspaces', 'filter set, but no workspaces present')
|
|
112
|
+
return []
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const nodes = []
|
|
116
|
+
for (const name of workspaces) {
|
|
117
|
+
const path = wsMap.get(name)
|
|
118
|
+
if (!path) {
|
|
119
|
+
log.warn('workspaces', `${name} in filter set, but not in workspaces`)
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const loc = relpath(tree.realpath, path)
|
|
124
|
+
const node = tree.inventory.get(loc)
|
|
125
|
+
|
|
126
|
+
if (!node) {
|
|
127
|
+
log.warn('workspaces', `${name} in filter set, but no workspace folder present`)
|
|
128
|
+
continue
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
nodes.push(node)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return nodes
|
|
103
135
|
}
|
|
104
136
|
|
|
105
137
|
// returns a set of workspace nodes and all their deps
|
|
138
|
+
// TODO why is includeWorkspaceRoot a param?
|
|
139
|
+
// TODO why is workspaces a param?
|
|
106
140
|
workspaceDependencySet (tree, workspaces, includeWorkspaceRoot) {
|
|
107
141
|
const wsNodes = this.workspaceNodes(tree, workspaces)
|
|
108
142
|
if (includeWorkspaceRoot) {
|
|
@@ -162,6 +196,60 @@ class Arborist extends Base {
|
|
|
162
196
|
})
|
|
163
197
|
return rootDepSet
|
|
164
198
|
}
|
|
199
|
+
|
|
200
|
+
async [_setWorkspaces] (node) {
|
|
201
|
+
const workspaces = await mapWorkspaces({
|
|
202
|
+
cwd: node.path,
|
|
203
|
+
pkg: node.package,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
if (node && workspaces.size) {
|
|
207
|
+
node.workspaces = workspaces
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return node
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async audit (options = {}) {
|
|
214
|
+
this.addTracker('audit')
|
|
215
|
+
if (this.options.global) {
|
|
216
|
+
throw Object.assign(
|
|
217
|
+
new Error('`npm audit` does not support testing globals'),
|
|
218
|
+
{ code: 'EAUDITGLOBAL' }
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// allow the user to set options on the ctor as well.
|
|
223
|
+
// XXX: deprecate separate method options objects.
|
|
224
|
+
options = { ...this.options, ...options }
|
|
225
|
+
|
|
226
|
+
process.emit('time', 'audit')
|
|
227
|
+
let tree
|
|
228
|
+
if (options.packageLock === false) {
|
|
229
|
+
// build ideal tree
|
|
230
|
+
await this.loadActual(options)
|
|
231
|
+
await this.buildIdealTree()
|
|
232
|
+
tree = this.idealTree
|
|
233
|
+
} else {
|
|
234
|
+
tree = await this.loadVirtual()
|
|
235
|
+
}
|
|
236
|
+
if (this.options.workspaces.length) {
|
|
237
|
+
options.filterSet = this.workspaceDependencySet(
|
|
238
|
+
tree,
|
|
239
|
+
this.options.workspaces,
|
|
240
|
+
this.options.includeWorkspaceRoot
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
if (!options.workspacesEnabled) {
|
|
244
|
+
options.filterSet =
|
|
245
|
+
this.excludeWorkspacesDependencySet(tree)
|
|
246
|
+
}
|
|
247
|
+
this.auditReport = await AuditReport.load(tree, options)
|
|
248
|
+
const ret = options.fix ? this.reify(options) : this.auditReport
|
|
249
|
+
process.emit('timeEnd', 'audit')
|
|
250
|
+
this.finishTracker('audit')
|
|
251
|
+
return ret
|
|
252
|
+
}
|
|
165
253
|
}
|
|
166
254
|
|
|
167
255
|
module.exports = Arborist
|
|
@@ -16,7 +16,6 @@ const realpath = require('../realpath.js')
|
|
|
16
16
|
|
|
17
17
|
// public symbols
|
|
18
18
|
const _changePath = Symbol.for('_changePath')
|
|
19
|
-
const _global = Symbol.for('global')
|
|
20
19
|
const _setWorkspaces = Symbol.for('setWorkspaces')
|
|
21
20
|
const _rpcache = Symbol.for('realpathCache')
|
|
22
21
|
const _stcache = Symbol.for('statCache')
|
|
@@ -45,8 +44,6 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
45
44
|
constructor (options) {
|
|
46
45
|
super(options)
|
|
47
46
|
|
|
48
|
-
this[_global] = !!options.global
|
|
49
|
-
|
|
50
47
|
// the tree of nodes on disk
|
|
51
48
|
this.actualTree = options.actualTree
|
|
52
49
|
|
|
@@ -58,6 +55,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
// public method
|
|
58
|
+
// TODO remove options param in next semver major
|
|
61
59
|
async loadActual (options = {}) {
|
|
62
60
|
// In the past this.actualTree was set as a promise that eventually
|
|
63
61
|
// resolved, and overwrite this.actualTree with the resolved value. This
|
|
@@ -100,7 +98,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
100
98
|
async #loadActual (options) {
|
|
101
99
|
// mostly realpath to throw if the root doesn't exist
|
|
102
100
|
const {
|
|
103
|
-
global
|
|
101
|
+
global,
|
|
104
102
|
filter = () => true,
|
|
105
103
|
root = null,
|
|
106
104
|
transplantFilter = () => true,
|