@npmcli/arborist 6.2.8 → 6.2.10
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 +271 -269
- package/lib/arborist/index.js +1 -1
- package/lib/arborist/load-actual.js +104 -107
- package/lib/arborist/load-virtual.js +58 -78
- package/lib/arborist/reify.js +23 -11
- package/lib/arborist/{load-workspaces.js → set-workspaces.js} +2 -2
- package/lib/calc-dep-flags.js +14 -10
- package/lib/dep-valid.js +15 -9
- package/lib/edge.js +168 -161
- package/lib/from-path.js +21 -15
- package/lib/inventory.js +68 -55
- package/lib/node.js +72 -75
- package/lib/query-selector-all.js +1 -1
- package/package.json +5 -12
|
@@ -31,75 +31,75 @@ const Link = require('../link.js')
|
|
|
31
31
|
const addRmPkgDeps = require('../add-rm-pkg-deps.js')
|
|
32
32
|
const optionalSet = require('../optional-set.js')
|
|
33
33
|
const { checkEngine, checkPlatform } = require('npm-install-checks')
|
|
34
|
-
|
|
35
34
|
const relpath = require('../relpath.js')
|
|
35
|
+
const resetDepFlags = require('../reset-dep-flags.js')
|
|
36
36
|
|
|
37
37
|
// note: some of these symbols are shared so we can hit
|
|
38
38
|
// them with unit tests and reuse them across mixins
|
|
39
|
-
const _complete = Symbol('complete')
|
|
40
|
-
const _depsSeen = Symbol('depsSeen')
|
|
41
|
-
const _depsQueue = Symbol('depsQueue')
|
|
42
|
-
const _currentDep = Symbol('currentDep')
|
|
43
39
|
const _updateAll = Symbol.for('updateAll')
|
|
44
|
-
const _mutateTree = Symbol('mutateTree')
|
|
45
40
|
const _flagsSuspect = Symbol.for('flagsSuspect')
|
|
46
41
|
const _workspaces = Symbol.for('workspaces')
|
|
47
|
-
const
|
|
48
|
-
const _preferDedupe = Symbol('preferDedupe')
|
|
49
|
-
const _parseSettings = Symbol('parseSettings')
|
|
50
|
-
const _initTree = Symbol('initTree')
|
|
51
|
-
const _applyUserRequests = Symbol('applyUserRequests')
|
|
52
|
-
const _applyUserRequestsToNode = Symbol('applyUserRequestsToNode')
|
|
53
|
-
const _inflateAncientLockfile = Symbol('inflateAncientLockfile')
|
|
54
|
-
const _buildDeps = Symbol('buildDeps')
|
|
55
|
-
const _buildDepStep = Symbol('buildDepStep')
|
|
56
|
-
const _nodeFromEdge = Symbol('nodeFromEdge')
|
|
57
|
-
const _nodeFromSpec = Symbol('nodeFromSpec')
|
|
58
|
-
const _fetchManifest = Symbol('fetchManifest')
|
|
59
|
-
const _problemEdges = Symbol('problemEdges')
|
|
60
|
-
const _manifests = Symbol('manifests')
|
|
61
|
-
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
62
|
-
const _linkFromSpec = Symbol('linkFromSpec')
|
|
63
|
-
const _loadPeerSet = Symbol('loadPeerSet')
|
|
42
|
+
const _setWorkspaces = Symbol.for('setWorkspaces')
|
|
64
43
|
const _updateNames = Symbol.for('updateNames')
|
|
65
|
-
const _fixDepFlags = Symbol('fixDepFlags')
|
|
66
|
-
const _resolveLinks = Symbol('resolveLinks')
|
|
67
|
-
const _rootNodeFromPackage = Symbol('rootNodeFromPackage')
|
|
68
|
-
const _add = Symbol('add')
|
|
69
44
|
const _resolvedAdd = Symbol.for('resolvedAdd')
|
|
70
|
-
const _queueNamedUpdates = Symbol('queueNamedUpdates')
|
|
71
|
-
const _queueVulnDependents = Symbol('queueVulnDependents')
|
|
72
|
-
const _avoidRange = Symbol('avoidRange')
|
|
73
|
-
const _shouldUpdateNode = Symbol('shouldUpdateNode')
|
|
74
|
-
const resetDepFlags = require('../reset-dep-flags.js')
|
|
75
|
-
const _loadFailures = Symbol('loadFailures')
|
|
76
|
-
const _pruneFailedOptional = Symbol('pruneFailedOptional')
|
|
77
|
-
const _linkNodes = Symbol('linkNodes')
|
|
78
|
-
const _follow = Symbol('follow')
|
|
79
|
-
const _installStrategy = Symbol('installStrategy')
|
|
80
|
-
const _globalRootNode = Symbol('globalRootNode')
|
|
81
45
|
const _usePackageLock = Symbol.for('usePackageLock')
|
|
82
46
|
const _rpcache = Symbol.for('realpathCache')
|
|
83
47
|
const _stcache = Symbol.for('statCache')
|
|
84
|
-
const _strictPeerDeps = Symbol('strictPeerDeps')
|
|
85
|
-
const _checkEngineAndPlatform = Symbol('checkEngineAndPlatform')
|
|
86
|
-
const _virtualRoots = Symbol('virtualRoots')
|
|
87
|
-
const _virtualRoot = Symbol('virtualRoot')
|
|
88
48
|
const _includeWorkspaceRoot = Symbol.for('includeWorkspaceRoot')
|
|
89
49
|
|
|
90
|
-
const _failPeerConflict = Symbol('failPeerConflict')
|
|
91
|
-
const _explainPeerConflict = Symbol('explainPeerConflict')
|
|
92
|
-
const _edgesOverridden = Symbol('edgesOverridden')
|
|
93
50
|
// exposed symbol for unit testing the placeDep method directly
|
|
94
51
|
const _peerSetSource = Symbol.for('peerSetSource')
|
|
95
52
|
|
|
96
53
|
// used by Reify mixin
|
|
97
54
|
const _force = Symbol.for('force')
|
|
98
|
-
const _explicitRequests = Symbol('explicitRequests')
|
|
99
55
|
const _global = Symbol.for('global')
|
|
100
56
|
const _idealTreePrune = Symbol.for('idealTreePrune')
|
|
101
57
|
|
|
58
|
+
// Push items in, pop them sorted by depth and then path
|
|
59
|
+
class DepsQueue {
|
|
60
|
+
#deps = []
|
|
61
|
+
#sorted = true
|
|
62
|
+
|
|
63
|
+
get length () {
|
|
64
|
+
return this.#deps.length
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
push (item) {
|
|
68
|
+
if (!this.#deps.includes(item)) {
|
|
69
|
+
this.#sorted = false
|
|
70
|
+
this.#deps.push(item)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pop () {
|
|
75
|
+
if (!this.#sorted) {
|
|
76
|
+
// sort physically shallower deps up to the front of the queue, because
|
|
77
|
+
// they'll affect things deeper in, then alphabetical
|
|
78
|
+
this.#deps.sort((a, b) =>
|
|
79
|
+
(a.depth - b.depth) || localeCompare(a.path, b.path))
|
|
80
|
+
this.#sorted = true
|
|
81
|
+
}
|
|
82
|
+
return this.#deps.shift()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
102
86
|
module.exports = cls => class IdealTreeBuilder extends cls {
|
|
87
|
+
#complete
|
|
88
|
+
#currentDep = null
|
|
89
|
+
#depsQueue = new DepsQueue()
|
|
90
|
+
#depsSeen = new Set()
|
|
91
|
+
#explicitRequests = new Set()
|
|
92
|
+
#follow
|
|
93
|
+
#installStrategy
|
|
94
|
+
#linkNodes = new Set()
|
|
95
|
+
#loadFailures = new Set()
|
|
96
|
+
#manifests = new Map()
|
|
97
|
+
#mutateTree = false
|
|
98
|
+
#preferDedupe = false
|
|
99
|
+
#prune
|
|
100
|
+
#strictPeerDeps
|
|
101
|
+
#virtualRoots = new Map()
|
|
102
|
+
|
|
103
103
|
constructor (options) {
|
|
104
104
|
super(options)
|
|
105
105
|
|
|
@@ -123,7 +123,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
123
123
|
|
|
124
124
|
this[_workspaces] = workspaces || []
|
|
125
125
|
this[_force] = !!force
|
|
126
|
-
this
|
|
126
|
+
this.#strictPeerDeps = !!strictPeerDeps
|
|
127
127
|
|
|
128
128
|
this.idealTree = idealTree
|
|
129
129
|
this.installLinks = installLinks
|
|
@@ -131,38 +131,27 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
131
131
|
|
|
132
132
|
this[_usePackageLock] = packageLock
|
|
133
133
|
this[_global] = !!global
|
|
134
|
-
this
|
|
135
|
-
this
|
|
134
|
+
this.#installStrategy = global ? 'shallow' : installStrategy
|
|
135
|
+
this.#follow = !!follow
|
|
136
136
|
|
|
137
137
|
if (this[_workspaces].length && this[_global]) {
|
|
138
138
|
throw new Error('Cannot operate on workspaces in global mode')
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
this[_explicitRequests] = new Set()
|
|
142
|
-
this[_preferDedupe] = false
|
|
143
|
-
this[_depsSeen] = new Set()
|
|
144
|
-
this[_depsQueue] = []
|
|
145
|
-
this[_currentDep] = null
|
|
146
|
-
this[_updateNames] = []
|
|
147
141
|
this[_updateAll] = false
|
|
148
|
-
this[
|
|
149
|
-
this[_loadFailures] = new Set()
|
|
150
|
-
this[_linkNodes] = new Set()
|
|
151
|
-
this[_manifests] = new Map()
|
|
152
|
-
this[_edgesOverridden] = new Set()
|
|
142
|
+
this[_updateNames] = []
|
|
153
143
|
this[_resolvedAdd] = []
|
|
154
144
|
|
|
155
145
|
// a map of each module in a peer set to the thing that depended on
|
|
156
146
|
// that set of peers in the first place. Use a WeakMap so that we
|
|
157
147
|
// don't hold onto references for nodes that are garbage collected.
|
|
158
148
|
this[_peerSetSource] = new WeakMap()
|
|
159
|
-
this[_virtualRoots] = new Map()
|
|
160
149
|
|
|
161
150
|
this[_includeWorkspaceRoot] = includeWorkspaceRoot
|
|
162
151
|
}
|
|
163
152
|
|
|
164
153
|
get explicitRequests () {
|
|
165
|
-
return new Set(this
|
|
154
|
+
return new Set(this.#explicitRequests)
|
|
166
155
|
}
|
|
167
156
|
|
|
168
157
|
// public method
|
|
@@ -195,19 +184,19 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
195
184
|
// from there, we start adding nodes to it to satisfy the deps requested
|
|
196
185
|
// by the package.json in the root.
|
|
197
186
|
|
|
198
|
-
this
|
|
187
|
+
this.#parseSettings(options)
|
|
199
188
|
|
|
200
189
|
// start tracker block
|
|
201
190
|
this.addTracker('idealTree')
|
|
202
191
|
|
|
203
192
|
try {
|
|
204
|
-
await this
|
|
205
|
-
await this
|
|
206
|
-
await this
|
|
207
|
-
await this
|
|
208
|
-
await this
|
|
209
|
-
await this
|
|
210
|
-
await this
|
|
193
|
+
await this.#initTree()
|
|
194
|
+
await this.#inflateAncientLockfile()
|
|
195
|
+
await this.#applyUserRequests(options)
|
|
196
|
+
await this.#buildDeps()
|
|
197
|
+
await this.#fixDepFlags()
|
|
198
|
+
await this.#pruneFailedOptional()
|
|
199
|
+
await this.#checkEngineAndPlatform()
|
|
211
200
|
} finally {
|
|
212
201
|
process.emit('timeEnd', 'idealTree')
|
|
213
202
|
this.finishTracker('idealTree')
|
|
@@ -216,7 +205,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
216
205
|
return treeCheck(this.idealTree)
|
|
217
206
|
}
|
|
218
207
|
|
|
219
|
-
async
|
|
208
|
+
async #checkEngineAndPlatform () {
|
|
220
209
|
const { engineStrict, npmVersion, nodeVersion } = this.options
|
|
221
210
|
for (const node of this.idealTree.inventory.values()) {
|
|
222
211
|
if (!node.optional) {
|
|
@@ -237,7 +226,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
237
226
|
}
|
|
238
227
|
}
|
|
239
228
|
|
|
240
|
-
|
|
229
|
+
#parseSettings (options) {
|
|
241
230
|
const update = options.update === true ? { all: true }
|
|
242
231
|
: Array.isArray(options.update) ? { names: options.update }
|
|
243
232
|
: options.update || {}
|
|
@@ -246,8 +235,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
246
235
|
update.names = []
|
|
247
236
|
}
|
|
248
237
|
|
|
249
|
-
this
|
|
250
|
-
this
|
|
238
|
+
this.#complete = !!options.complete
|
|
239
|
+
this.#preferDedupe = !!options.preferDedupe
|
|
251
240
|
|
|
252
241
|
// validates list of update names, they must
|
|
253
242
|
// be dep names only, no semver ranges are supported
|
|
@@ -267,11 +256,11 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
267
256
|
|
|
268
257
|
this[_updateAll] = update.all
|
|
269
258
|
// we prune by default unless explicitly set to boolean false
|
|
270
|
-
this
|
|
259
|
+
this.#prune = options.prune !== false
|
|
271
260
|
|
|
272
261
|
// set if we add anything, but also set here if we know we'll make
|
|
273
262
|
// changes and thus have to maybe prune later.
|
|
274
|
-
this
|
|
263
|
+
this.#mutateTree = !!(
|
|
275
264
|
options.add ||
|
|
276
265
|
options.rm ||
|
|
277
266
|
update.all ||
|
|
@@ -281,20 +270,23 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
281
270
|
|
|
282
271
|
// load the initial tree, either the virtualTree from a shrinkwrap,
|
|
283
272
|
// or just the root node from a package.json
|
|
284
|
-
|
|
273
|
+
async #initTree () {
|
|
285
274
|
process.emit('time', 'idealTree:init')
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
275
|
+
let root
|
|
276
|
+
if (this[_global]) {
|
|
277
|
+
root = await this.#globalRootNode()
|
|
278
|
+
} else {
|
|
279
|
+
try {
|
|
280
|
+
const pkg = await rpj(this.path + '/package.json')
|
|
281
|
+
root = await this.#rootNodeFromPackage(pkg)
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (err.code === 'EJSONPARSE') {
|
|
284
|
+
throw err
|
|
295
285
|
}
|
|
296
|
-
|
|
297
|
-
|
|
286
|
+
root = await this.#rootNodeFromPackage({})
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return this[_setWorkspaces](root)
|
|
298
290
|
// ok to not have a virtual tree. probably initial install.
|
|
299
291
|
// When updating all, we load the shrinkwrap, but don't bother
|
|
300
292
|
// to build out the full virtual tree from it, since we'll be
|
|
@@ -336,12 +328,18 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
336
328
|
// the depsQueue so that we'll fix it later
|
|
337
329
|
depth({
|
|
338
330
|
tree,
|
|
339
|
-
getChildren: (node) =>
|
|
331
|
+
getChildren: (node) => {
|
|
332
|
+
const children = []
|
|
333
|
+
for (const edge of node.edgesOut.values()) {
|
|
334
|
+
children.push(edge.to)
|
|
335
|
+
}
|
|
336
|
+
return children
|
|
337
|
+
},
|
|
340
338
|
filter: node => node,
|
|
341
339
|
visit: node => {
|
|
342
340
|
for (const edge of node.edgesOut.values()) {
|
|
343
341
|
if (!edge.valid) {
|
|
344
|
-
this
|
|
342
|
+
this.#depsQueue.push(node)
|
|
345
343
|
break // no need to continue the loop after the first hit
|
|
346
344
|
}
|
|
347
345
|
}
|
|
@@ -356,8 +354,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
356
354
|
})
|
|
357
355
|
}
|
|
358
356
|
|
|
359
|
-
async
|
|
360
|
-
const root = await this
|
|
357
|
+
async #globalRootNode () {
|
|
358
|
+
const root = await this.#rootNodeFromPackage({ dependencies: {} })
|
|
361
359
|
// this is a gross kludge to handle the fact that we don't save
|
|
362
360
|
// metadata on the root node in global installs, because the "root"
|
|
363
361
|
// node is something like /usr/local/lib.
|
|
@@ -371,7 +369,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
371
369
|
return root
|
|
372
370
|
}
|
|
373
371
|
|
|
374
|
-
async
|
|
372
|
+
async #rootNodeFromPackage (pkg) {
|
|
375
373
|
// if the path doesn't exist, then we explode at this point. Note that
|
|
376
374
|
// this is not a problem for reify(), since it creates the root path
|
|
377
375
|
// before ever loading trees.
|
|
@@ -414,19 +412,19 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
414
412
|
|
|
415
413
|
// process the add/rm requests by modifying the root node, and the
|
|
416
414
|
// update.names request by queueing nodes dependent on those named.
|
|
417
|
-
async
|
|
415
|
+
async #applyUserRequests (options) {
|
|
418
416
|
process.emit('time', 'idealTree:userRequests')
|
|
419
417
|
const tree = this.idealTree.target
|
|
420
418
|
|
|
421
419
|
if (!this[_workspaces].length) {
|
|
422
|
-
await this
|
|
420
|
+
await this.#applyUserRequestsToNode(tree, options)
|
|
423
421
|
} else {
|
|
424
422
|
const nodes = this.workspaceNodes(tree, this[_workspaces])
|
|
425
423
|
if (this[_includeWorkspaceRoot]) {
|
|
426
424
|
nodes.push(tree)
|
|
427
425
|
}
|
|
428
426
|
const appliedRequests = nodes.map(
|
|
429
|
-
node => this
|
|
427
|
+
node => this.#applyUserRequestsToNode(node, options)
|
|
430
428
|
)
|
|
431
429
|
await Promise.all(appliedRequests)
|
|
432
430
|
}
|
|
@@ -434,12 +432,12 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
434
432
|
process.emit('timeEnd', 'idealTree:userRequests')
|
|
435
433
|
}
|
|
436
434
|
|
|
437
|
-
async
|
|
435
|
+
async #applyUserRequestsToNode (tree, options) {
|
|
438
436
|
// If we have a list of package names to update, and we know it's
|
|
439
437
|
// going to update them wherever they are, add any paths into those
|
|
440
438
|
// named nodes to the buildIdealTree queue.
|
|
441
439
|
if (!this[_global] && this[_updateNames].length) {
|
|
442
|
-
this
|
|
440
|
+
this.#queueNamedUpdates()
|
|
443
441
|
}
|
|
444
442
|
|
|
445
443
|
// global updates only update the globalTop nodes, but we need to know
|
|
@@ -448,7 +446,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
448
446
|
if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
|
|
449
447
|
const nm = resolve(this.path, 'node_modules')
|
|
450
448
|
const paths = await readdirScoped(nm).catch(() => [])
|
|
451
|
-
for (const
|
|
449
|
+
for (const p of paths) {
|
|
450
|
+
const name = p.replace(/\\/g, '/')
|
|
452
451
|
tree.package.dependencies = tree.package.dependencies || {}
|
|
453
452
|
const updateName = this[_updateNames].includes(name)
|
|
454
453
|
if (this[_updateAll] || updateName) {
|
|
@@ -470,7 +469,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
470
469
|
}
|
|
471
470
|
|
|
472
471
|
if (this.auditReport && this.auditReport.size > 0) {
|
|
473
|
-
await this
|
|
472
|
+
await this.#queueVulnDependents(options)
|
|
474
473
|
}
|
|
475
474
|
|
|
476
475
|
const { add, rm } = options
|
|
@@ -478,12 +477,12 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
478
477
|
if (rm && rm.length) {
|
|
479
478
|
addRmPkgDeps.rm(tree.package, rm)
|
|
480
479
|
for (const name of rm) {
|
|
481
|
-
this
|
|
480
|
+
this.#explicitRequests.add({ from: tree, name, action: 'DELETE' })
|
|
482
481
|
}
|
|
483
482
|
}
|
|
484
483
|
|
|
485
484
|
if (add && add.length) {
|
|
486
|
-
await this
|
|
485
|
+
await this.#add(tree, options)
|
|
487
486
|
}
|
|
488
487
|
|
|
489
488
|
// triggers a refresh of all edgesOut. this has to be done BEFORE
|
|
@@ -495,19 +494,19 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
495
494
|
|
|
496
495
|
for (const spec of this[_resolvedAdd]) {
|
|
497
496
|
if (spec.tree === tree) {
|
|
498
|
-
this
|
|
497
|
+
this.#explicitRequests.add(tree.edgesOut.get(spec.name))
|
|
499
498
|
}
|
|
500
499
|
}
|
|
501
500
|
for (const name of globalExplicitUpdateNames) {
|
|
502
|
-
this
|
|
501
|
+
this.#explicitRequests.add(tree.edgesOut.get(name))
|
|
503
502
|
}
|
|
504
503
|
|
|
505
|
-
this
|
|
504
|
+
this.#depsQueue.push(tree)
|
|
506
505
|
}
|
|
507
506
|
|
|
508
507
|
// This returns a promise because we might not have the name yet, and need to
|
|
509
508
|
// call pacote.manifest to find the name.
|
|
510
|
-
async
|
|
509
|
+
async #add (tree, { add, saveType = null, saveBundle = false }) {
|
|
511
510
|
// If we have a link it will need to be added relative to the target's path
|
|
512
511
|
const path = tree.target.path
|
|
513
512
|
|
|
@@ -564,7 +563,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
564
563
|
// what's in the bundle at each published manifest. Without that, we
|
|
565
564
|
// can't possibly fix bundled deps without breaking a ton of other stuff,
|
|
566
565
|
// and leaving the user subject to getting it overwritten later anyway.
|
|
567
|
-
async
|
|
566
|
+
async #queueVulnDependents (options) {
|
|
568
567
|
for (const vuln of this.auditReport.values()) {
|
|
569
568
|
for (const node of vuln.nodes) {
|
|
570
569
|
const bundler = node.getBundler()
|
|
@@ -584,7 +583,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
584
583
|
|
|
585
584
|
for (const edge of node.edgesIn) {
|
|
586
585
|
this.addTracker('idealTree', edge.from.name, edge.from.location)
|
|
587
|
-
this
|
|
586
|
+
this.#depsQueue.push(edge.from)
|
|
588
587
|
}
|
|
589
588
|
}
|
|
590
589
|
}
|
|
@@ -629,7 +628,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
629
628
|
log.warn('audit', `Updating ${fixName} to ${version}, ` +
|
|
630
629
|
`which is ${breakingMessage}.`)
|
|
631
630
|
|
|
632
|
-
await this
|
|
631
|
+
await this.#add(node, { add: [`${fixName}@${version}`] })
|
|
633
632
|
nodesTouched.add(node)
|
|
634
633
|
}
|
|
635
634
|
}
|
|
@@ -639,7 +638,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
639
638
|
}
|
|
640
639
|
}
|
|
641
640
|
|
|
642
|
-
|
|
641
|
+
#avoidRange (name) {
|
|
643
642
|
if (!this.auditReport) {
|
|
644
643
|
return null
|
|
645
644
|
}
|
|
@@ -650,7 +649,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
650
649
|
return vuln.range
|
|
651
650
|
}
|
|
652
651
|
|
|
653
|
-
|
|
652
|
+
#queueNamedUpdates () {
|
|
654
653
|
// ignore top nodes, since they are not loaded the same way, and
|
|
655
654
|
// probably have their own project associated with them.
|
|
656
655
|
|
|
@@ -661,25 +660,19 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
661
660
|
|
|
662
661
|
// XXX this could be faster by doing a series of inventory.query('name')
|
|
663
662
|
// calls rather than walking over everything in the tree.
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
663
|
+
for (const node of this.idealTree.inventory.values()) {
|
|
664
|
+
// XXX add any invalid edgesOut to the queue
|
|
665
|
+
if (this[_updateNames].includes(node.name) &&
|
|
666
|
+
!node.isTop && !node.inDepBundle && !node.inShrinkwrap) {
|
|
667
|
+
for (const edge of node.edgesIn) {
|
|
668
|
+
this.addTracker('idealTree', edge.from.name, edge.from.location)
|
|
669
|
+
this.#depsQueue.push(edge.from)
|
|
670
|
+
}
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
-
|
|
676
|
-
return this[_updateNames].includes(node.name) &&
|
|
677
|
-
!node.isTop &&
|
|
678
|
-
!node.inDepBundle &&
|
|
679
|
-
!node.inShrinkwrap
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
async [_inflateAncientLockfile] () {
|
|
675
|
+
async #inflateAncientLockfile () {
|
|
683
676
|
const { meta, inventory } = this.idealTree
|
|
684
677
|
const ancient = meta.ancientLockfile
|
|
685
678
|
const old = meta.loadedFromDisk && !(meta.originalLockfileVersion >= 2)
|
|
@@ -764,38 +757,33 @@ This is a one-time fix-up, please be patient...
|
|
|
764
757
|
// at this point we have a virtual tree with the actual root node's
|
|
765
758
|
// package deps, which may be partly or entirely incomplete, invalid
|
|
766
759
|
// or extraneous.
|
|
767
|
-
|
|
760
|
+
#buildDeps () {
|
|
768
761
|
process.emit('time', 'idealTree:buildDeps')
|
|
769
762
|
const tree = this.idealTree.target
|
|
770
763
|
tree.assertRootOverrides()
|
|
771
|
-
this
|
|
764
|
+
this.#depsQueue.push(tree)
|
|
772
765
|
// XXX also push anything that depends on a node with a name
|
|
773
766
|
// in the override list
|
|
774
767
|
log.silly('idealTree', 'buildDeps')
|
|
775
768
|
this.addTracker('idealTree', tree.name, '')
|
|
776
|
-
return this
|
|
769
|
+
return this.#buildDepStep()
|
|
777
770
|
.then(() => process.emit('timeEnd', 'idealTree:buildDeps'))
|
|
778
771
|
}
|
|
779
772
|
|
|
780
|
-
async
|
|
773
|
+
async #buildDepStep () {
|
|
781
774
|
// removes tracker of previous dependency in the queue
|
|
782
|
-
if (this
|
|
783
|
-
const { location, name } = this
|
|
775
|
+
if (this.#currentDep) {
|
|
776
|
+
const { location, name } = this.#currentDep
|
|
784
777
|
process.emit('timeEnd', `idealTree:${location || '#root'}`)
|
|
785
778
|
this.finishTracker('idealTree', name, location)
|
|
786
|
-
this
|
|
779
|
+
this.#currentDep = null
|
|
787
780
|
}
|
|
788
781
|
|
|
789
|
-
if (!this
|
|
790
|
-
return this
|
|
782
|
+
if (!this.#depsQueue.length) {
|
|
783
|
+
return this.#resolveLinks()
|
|
791
784
|
}
|
|
792
785
|
|
|
793
|
-
|
|
794
|
-
// because they'll affect things deeper in, then alphabetical
|
|
795
|
-
this[_depsQueue].sort((a, b) =>
|
|
796
|
-
(a.depth - b.depth) || localeCompare(a.path, b.path))
|
|
797
|
-
|
|
798
|
-
const node = this[_depsQueue].shift()
|
|
786
|
+
const node = this.#depsQueue.pop()
|
|
799
787
|
const bd = node.package.bundleDependencies
|
|
800
788
|
const hasBundle = bd && Array.isArray(bd) && bd.length
|
|
801
789
|
const { hasShrinkwrap } = node
|
|
@@ -804,14 +792,14 @@ This is a one-time fix-up, please be patient...
|
|
|
804
792
|
// tree, skip over it and process the rest of the queue. If a node has
|
|
805
793
|
// a shrinkwrap, also skip it, because it's going to get its deps
|
|
806
794
|
// satisfied by whatever's in that file anyway.
|
|
807
|
-
if (this
|
|
795
|
+
if (this.#depsSeen.has(node) ||
|
|
808
796
|
node.root !== this.idealTree ||
|
|
809
|
-
hasShrinkwrap && !this
|
|
810
|
-
return this
|
|
797
|
+
hasShrinkwrap && !this.#complete) {
|
|
798
|
+
return this.#buildDepStep()
|
|
811
799
|
}
|
|
812
800
|
|
|
813
|
-
this
|
|
814
|
-
this
|
|
801
|
+
this.#depsSeen.add(node)
|
|
802
|
+
this.#currentDep = node
|
|
815
803
|
process.emit('time', `idealTree:${node.location || '#root'}`)
|
|
816
804
|
|
|
817
805
|
// if we're loading a _complete_ ideal tree, for a --package-lock-only
|
|
@@ -821,7 +809,7 @@ This is a one-time fix-up, please be patient...
|
|
|
821
809
|
// ideal tree by reading bundles/shrinkwraps in place.
|
|
822
810
|
// Don't bother if the node is from the actual tree and hasn't
|
|
823
811
|
// been resolved, because we can't fetch it anyway, could be anything!
|
|
824
|
-
const crackOpen = this
|
|
812
|
+
const crackOpen = this.#complete &&
|
|
825
813
|
node !== this.idealTree &&
|
|
826
814
|
node.resolved &&
|
|
827
815
|
(hasBundle || hasShrinkwrap)
|
|
@@ -891,7 +879,7 @@ This is a one-time fix-up, please be patient...
|
|
|
891
879
|
|
|
892
880
|
const tasks = []
|
|
893
881
|
const peerSource = this[_peerSetSource].get(node) || node
|
|
894
|
-
for (const edge of this
|
|
882
|
+
for (const edge of this.#problemEdges(node)) {
|
|
895
883
|
if (edge.peerConflicted) {
|
|
896
884
|
continue
|
|
897
885
|
}
|
|
@@ -902,7 +890,7 @@ This is a one-time fix-up, please be patient...
|
|
|
902
890
|
// so we VR the node itself if the edge is not a peer
|
|
903
891
|
const source = edge.peer ? peerSource : node
|
|
904
892
|
|
|
905
|
-
const virtualRoot = this
|
|
893
|
+
const virtualRoot = this.#virtualRoot(source, true)
|
|
906
894
|
// reuse virtual root if we already have one, but don't
|
|
907
895
|
// try to do the override ahead of time, since we MAY be able
|
|
908
896
|
// to create a more correct tree than the virtual root could.
|
|
@@ -926,7 +914,7 @@ This is a one-time fix-up, please be patient...
|
|
|
926
914
|
const required = new Set([edge.from])
|
|
927
915
|
const parent = edge.peer ? virtualRoot : null
|
|
928
916
|
const dep = vrDep && vrDep.satisfies(edge) ? vrDep
|
|
929
|
-
: await this
|
|
917
|
+
: await this.#nodeFromEdge(edge, parent, null, required)
|
|
930
918
|
|
|
931
919
|
/* istanbul ignore next */
|
|
932
920
|
debug(() => {
|
|
@@ -938,25 +926,24 @@ This is a one-time fix-up, please be patient...
|
|
|
938
926
|
tasks.push({ edge, dep })
|
|
939
927
|
}
|
|
940
928
|
|
|
941
|
-
const placeDeps = tasks
|
|
942
|
-
|
|
943
|
-
|
|
929
|
+
const placeDeps = tasks.sort((a, b) => localeCompare(a.edge.name, b.edge.name))
|
|
930
|
+
|
|
931
|
+
const promises = []
|
|
932
|
+
for (const { edge, dep } of placeDeps) {
|
|
933
|
+
const pd = new PlaceDep({
|
|
944
934
|
edge,
|
|
945
935
|
dep,
|
|
946
936
|
|
|
947
937
|
auditReport: this.auditReport,
|
|
948
|
-
explicitRequest: this
|
|
938
|
+
explicitRequest: this.#explicitRequests.has(edge),
|
|
949
939
|
force: this[_force],
|
|
950
940
|
installLinks: this.installLinks,
|
|
951
|
-
installStrategy: this
|
|
941
|
+
installStrategy: this.#installStrategy,
|
|
952
942
|
legacyPeerDeps: this.legacyPeerDeps,
|
|
953
|
-
preferDedupe: this
|
|
954
|
-
strictPeerDeps: this
|
|
943
|
+
preferDedupe: this.#preferDedupe,
|
|
944
|
+
strictPeerDeps: this.#strictPeerDeps,
|
|
955
945
|
updateNames: this[_updateNames],
|
|
956
|
-
})
|
|
957
|
-
|
|
958
|
-
const promises = []
|
|
959
|
-
for (const pd of placeDeps) {
|
|
946
|
+
})
|
|
960
947
|
// placing a dep is actually a tree of placing the dep itself
|
|
961
948
|
// and all of its peer group that aren't already met by the tree
|
|
962
949
|
depth({
|
|
@@ -971,18 +958,18 @@ This is a one-time fix-up, please be patient...
|
|
|
971
958
|
|
|
972
959
|
// we placed something, that means we changed the tree
|
|
973
960
|
if (placed.errors.length) {
|
|
974
|
-
this
|
|
961
|
+
this.#loadFailures.add(placed)
|
|
975
962
|
}
|
|
976
|
-
this
|
|
963
|
+
this.#mutateTree = true
|
|
977
964
|
if (cpd.canPlaceSelf === OK) {
|
|
978
965
|
for (const edgeIn of placed.edgesIn) {
|
|
979
966
|
if (edgeIn === edge) {
|
|
980
967
|
continue
|
|
981
968
|
}
|
|
982
969
|
const { from, valid, peerConflicted } = edgeIn
|
|
983
|
-
if (!peerConflicted && !valid && !this
|
|
970
|
+
if (!peerConflicted && !valid && !this.#depsSeen.has(from)) {
|
|
984
971
|
this.addTracker('idealTree', from.name, from.location)
|
|
985
|
-
this
|
|
972
|
+
this.#depsQueue.push(edgeIn.from)
|
|
986
973
|
}
|
|
987
974
|
}
|
|
988
975
|
} else {
|
|
@@ -1000,8 +987,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1000
987
|
if (!valid && !peerConflicted) {
|
|
1001
988
|
// if it's already been visited, we have to re-visit
|
|
1002
989
|
// otherwise, just enqueue normally.
|
|
1003
|
-
this
|
|
1004
|
-
this
|
|
990
|
+
this.#depsSeen.delete(edgeIn.from)
|
|
991
|
+
this.#depsQueue.push(edgeIn.from)
|
|
1005
992
|
}
|
|
1006
993
|
}
|
|
1007
994
|
}
|
|
@@ -1018,45 +1005,48 @@ This is a one-time fix-up, please be patient...
|
|
|
1018
1005
|
|
|
1019
1006
|
// lastly, also check for the missing deps of the node we placed,
|
|
1020
1007
|
// and any holes created by pruning out conflicted peer sets.
|
|
1021
|
-
this
|
|
1008
|
+
this.#depsQueue.push(placed)
|
|
1022
1009
|
for (const dep of pd.needEvaluation) {
|
|
1023
|
-
this
|
|
1024
|
-
this
|
|
1010
|
+
this.#depsSeen.delete(dep)
|
|
1011
|
+
this.#depsQueue.push(dep)
|
|
1025
1012
|
}
|
|
1026
1013
|
|
|
1027
1014
|
// pre-fetch any problem edges, since we'll need these soon
|
|
1028
1015
|
// if it fails at this point, though, dont' worry because it
|
|
1029
1016
|
// may well be an optional dep that has gone missing. it'll
|
|
1030
1017
|
// fail later anyway.
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
.
|
|
1018
|
+
for (const e of this.#problemEdges(placed)) {
|
|
1019
|
+
promises.push(
|
|
1020
|
+
this.#fetchManifest(npa.resolve(e.name, e.spec, fromPath(placed, e)))
|
|
1021
|
+
.catch(er => null)
|
|
1022
|
+
)
|
|
1023
|
+
}
|
|
1034
1024
|
},
|
|
1035
1025
|
})
|
|
1036
1026
|
}
|
|
1037
1027
|
|
|
1038
1028
|
for (const { to } of node.edgesOut.values()) {
|
|
1039
1029
|
if (to && to.isLink && to.target) {
|
|
1040
|
-
this
|
|
1030
|
+
this.#linkNodes.add(to)
|
|
1041
1031
|
}
|
|
1042
1032
|
}
|
|
1043
1033
|
|
|
1044
1034
|
await Promise.all(promises)
|
|
1045
|
-
return this
|
|
1035
|
+
return this.#buildDepStep()
|
|
1046
1036
|
}
|
|
1047
1037
|
|
|
1048
1038
|
// loads a node from an edge, and then loads its peer deps (and their
|
|
1049
1039
|
// peer deps, on down the line) into a virtual root parent.
|
|
1050
|
-
async
|
|
1040
|
+
async #nodeFromEdge (edge, parent_, secondEdge, required) {
|
|
1051
1041
|
// create a virtual root node with the same deps as the node that
|
|
1052
1042
|
// is requesting this one, so that we can get all the peer deps in
|
|
1053
1043
|
// a context where they're likely to be resolvable.
|
|
1054
1044
|
// Note that the virtual root will also have virtual copies of the
|
|
1055
1045
|
// targets of any child Links, so that they resolve appropriately.
|
|
1056
|
-
const parent = parent_ || this
|
|
1046
|
+
const parent = parent_ || this.#virtualRoot(edge.from)
|
|
1057
1047
|
|
|
1058
1048
|
const spec = npa.resolve(edge.name, edge.spec, edge.from.path)
|
|
1059
|
-
const first = await this
|
|
1049
|
+
const first = await this.#nodeFromSpec(edge.name, spec, parent, edge)
|
|
1060
1050
|
|
|
1061
1051
|
// we might have a case where the parent has a peer dependency on
|
|
1062
1052
|
// `foo@*` which resolves to v2, but another dep in the set has a
|
|
@@ -1071,7 +1061,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1071
1061
|
secondEdge.from.path
|
|
1072
1062
|
)
|
|
1073
1063
|
const second = secondEdge && !secondEdge.valid
|
|
1074
|
-
? await this
|
|
1064
|
+
? await this.#nodeFromSpec(edge.name, spec2, parent, secondEdge)
|
|
1075
1065
|
: null
|
|
1076
1066
|
|
|
1077
1067
|
// pick the second one if they're both happy with that, otherwise first
|
|
@@ -1098,12 +1088,12 @@ This is a one-time fix-up, please be patient...
|
|
|
1098
1088
|
}
|
|
1099
1089
|
|
|
1100
1090
|
// otherwise, we have to make sure that our peers can go along with us.
|
|
1101
|
-
return this
|
|
1091
|
+
return this.#loadPeerSet(node, required)
|
|
1102
1092
|
}
|
|
1103
1093
|
|
|
1104
|
-
|
|
1105
|
-
if (reuse && this
|
|
1106
|
-
return this
|
|
1094
|
+
#virtualRoot (node, reuse = false) {
|
|
1095
|
+
if (reuse && this.#virtualRoots.has(node)) {
|
|
1096
|
+
return this.#virtualRoots.get(node)
|
|
1107
1097
|
}
|
|
1108
1098
|
|
|
1109
1099
|
const vr = new Node({
|
|
@@ -1126,11 +1116,11 @@ This is a one-time fix-up, please be patient...
|
|
|
1126
1116
|
}
|
|
1127
1117
|
}
|
|
1128
1118
|
|
|
1129
|
-
this
|
|
1119
|
+
this.#virtualRoots.set(node, vr)
|
|
1130
1120
|
return vr
|
|
1131
1121
|
}
|
|
1132
1122
|
|
|
1133
|
-
|
|
1123
|
+
#problemEdges (node) {
|
|
1134
1124
|
// skip over any bundled deps, they're not our problem.
|
|
1135
1125
|
// Note that this WILL fetch bundled meta-deps which are also dependencies
|
|
1136
1126
|
// but not listed as bundled deps. When reifying, we first unpack any
|
|
@@ -1145,85 +1135,90 @@ This is a one-time fix-up, please be patient...
|
|
|
1145
1135
|
: node.package.bundleDependencies
|
|
1146
1136
|
const bundled = new Set(bd || [])
|
|
1147
1137
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
// If it's already been logged as a load failure, skip it.
|
|
1156
|
-
if (edge.to && this[_loadFailures].has(edge.to)) {
|
|
1157
|
-
return false
|
|
1158
|
-
}
|
|
1138
|
+
const problems = []
|
|
1139
|
+
for (const edge of node.edgesOut.values()) {
|
|
1140
|
+
// If it's included in a bundle, we take whatever is specified.
|
|
1141
|
+
if (bundled.has(edge.name)) {
|
|
1142
|
+
continue
|
|
1143
|
+
}
|
|
1159
1144
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1145
|
+
// If it's already been logged as a load failure, skip it.
|
|
1146
|
+
if (edge.to && this.#loadFailures.has(edge.to)) {
|
|
1147
|
+
continue
|
|
1148
|
+
}
|
|
1164
1149
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
this[_explicitRequests].has(edge)
|
|
1170
|
-
}
|
|
1150
|
+
// If it's shrinkwrapped, we use what the shrinkwap wants.
|
|
1151
|
+
if (edge.to && edge.to.inShrinkwrap) {
|
|
1152
|
+
continue
|
|
1153
|
+
}
|
|
1171
1154
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1155
|
+
// If the edge has no destination, that's a problem, unless
|
|
1156
|
+
// if it's peerOptional and not explicitly requested.
|
|
1157
|
+
if (!edge.to) {
|
|
1158
|
+
if (edge.type !== 'peerOptional' ||
|
|
1159
|
+
this.#explicitRequests.has(edge)) {
|
|
1160
|
+
problems.push(edge)
|
|
1175
1161
|
}
|
|
1162
|
+
continue
|
|
1163
|
+
}
|
|
1176
1164
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1165
|
+
// If the edge has an error, there's a problem.
|
|
1166
|
+
if (!edge.valid) {
|
|
1167
|
+
problems.push(edge)
|
|
1168
|
+
continue
|
|
1169
|
+
}
|
|
1181
1170
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1171
|
+
// If the edge is a workspace, and it's valid, leave it alone
|
|
1172
|
+
if (edge.to.isWorkspace) {
|
|
1173
|
+
continue
|
|
1174
|
+
}
|
|
1186
1175
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1176
|
+
// user explicitly asked to update this package by name, problem
|
|
1177
|
+
if (this[_updateNames].includes(edge.name)) {
|
|
1178
|
+
problems.push(edge)
|
|
1179
|
+
continue
|
|
1180
|
+
}
|
|
1191
1181
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1182
|
+
// fixing a security vulnerability with this package, problem
|
|
1183
|
+
if (this.auditReport && this.auditReport.isVulnerable(edge.to)) {
|
|
1184
|
+
problems.push(edge)
|
|
1185
|
+
continue
|
|
1186
|
+
}
|
|
1196
1187
|
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1188
|
+
// user has explicitly asked to install this package, problem
|
|
1189
|
+
if (this.#explicitRequests.has(edge)) {
|
|
1190
|
+
problems.push(edge)
|
|
1191
|
+
continue
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
return problems
|
|
1200
1195
|
}
|
|
1201
1196
|
|
|
1202
|
-
async
|
|
1197
|
+
async #fetchManifest (spec) {
|
|
1203
1198
|
const options = {
|
|
1204
1199
|
...this.options,
|
|
1205
|
-
avoid: this
|
|
1200
|
+
avoid: this.#avoidRange(spec.name),
|
|
1206
1201
|
}
|
|
1207
1202
|
// get the intended spec and stored metadata from yarn.lock file,
|
|
1208
1203
|
// if available and valid.
|
|
1209
1204
|
spec = this.idealTree.meta.checkYarnLock(spec, options)
|
|
1210
1205
|
|
|
1211
|
-
if (this
|
|
1212
|
-
return this
|
|
1206
|
+
if (this.#manifests.has(spec.raw)) {
|
|
1207
|
+
return this.#manifests.get(spec.raw)
|
|
1213
1208
|
} else {
|
|
1214
1209
|
const cleanRawSpec = cleanUrl(spec.rawSpec)
|
|
1215
1210
|
log.silly('fetch manifest', spec.raw.replace(spec.rawSpec, cleanRawSpec))
|
|
1216
1211
|
const p = pacote.manifest(spec, options)
|
|
1217
1212
|
.then(mani => {
|
|
1218
|
-
this
|
|
1213
|
+
this.#manifests.set(spec.raw, mani)
|
|
1219
1214
|
return mani
|
|
1220
1215
|
})
|
|
1221
|
-
this
|
|
1216
|
+
this.#manifests.set(spec.raw, p)
|
|
1222
1217
|
return p
|
|
1223
1218
|
}
|
|
1224
1219
|
}
|
|
1225
1220
|
|
|
1226
|
-
|
|
1221
|
+
#nodeFromSpec (name, spec, parent, edge) {
|
|
1227
1222
|
// pacote will slap integrity on its options, so we have to clone
|
|
1228
1223
|
// the object so it doesn't get mutated.
|
|
1229
1224
|
// Don't bother to load the manifest for link deps, because the target
|
|
@@ -1234,7 +1229,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1234
1229
|
// spec is a directory, link it unless installLinks is set or it's a workspace
|
|
1235
1230
|
// TODO post arborist refactor, will need to check for installStrategy=linked
|
|
1236
1231
|
if (spec.type === 'directory' && (isWorkspace || !installLinks)) {
|
|
1237
|
-
return this
|
|
1232
|
+
return this.#linkFromSpec(name, spec, parent, edge)
|
|
1238
1233
|
}
|
|
1239
1234
|
|
|
1240
1235
|
// if the spec matches a workspace name, then see if the workspace node will
|
|
@@ -1249,7 +1244,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1249
1244
|
|
|
1250
1245
|
// spec isn't a directory, and either isn't a workspace or the workspace we have
|
|
1251
1246
|
// doesn't satisfy the edge. try to fetch a manifest and build a node from that.
|
|
1252
|
-
return this
|
|
1247
|
+
return this.#fetchManifest(spec)
|
|
1253
1248
|
.then(pkg => new Node({ name, pkg, parent, installLinks, legacyPeerDeps }), error => {
|
|
1254
1249
|
error.requiredBy = edge.from.location || '.'
|
|
1255
1250
|
|
|
@@ -1263,17 +1258,17 @@ This is a one-time fix-up, please be patient...
|
|
|
1263
1258
|
installLinks,
|
|
1264
1259
|
legacyPeerDeps,
|
|
1265
1260
|
})
|
|
1266
|
-
this
|
|
1261
|
+
this.#loadFailures.add(n)
|
|
1267
1262
|
return n
|
|
1268
1263
|
})
|
|
1269
1264
|
}
|
|
1270
1265
|
|
|
1271
|
-
|
|
1266
|
+
#linkFromSpec (name, spec, parent, edge) {
|
|
1272
1267
|
const realpath = spec.fetchSpec
|
|
1273
1268
|
const { installLinks, legacyPeerDeps } = this
|
|
1274
1269
|
return rpj(realpath + '/package.json').catch(() => ({})).then(pkg => {
|
|
1275
1270
|
const link = new Link({ name, parent, realpath, pkg, installLinks, legacyPeerDeps })
|
|
1276
|
-
this
|
|
1271
|
+
this.#linkNodes.add(link)
|
|
1277
1272
|
return link
|
|
1278
1273
|
})
|
|
1279
1274
|
}
|
|
@@ -1291,7 +1286,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1291
1286
|
// gets placed first. In non-strict mode, we behave strictly if the
|
|
1292
1287
|
// virtual root is based on the root project, and allow non-peer parent
|
|
1293
1288
|
// deps to override, but throw if no preference can be determined.
|
|
1294
|
-
async
|
|
1289
|
+
async #loadPeerSet (node, required) {
|
|
1295
1290
|
const peerEdges = [...node.edgesOut.values()]
|
|
1296
1291
|
// we typically only install non-optional peers, but we have to
|
|
1297
1292
|
// factor them into the peerSet so that we can avoid conflicts
|
|
@@ -1307,12 +1302,12 @@ This is a one-time fix-up, please be patient...
|
|
|
1307
1302
|
const parentEdge = node.parent.edgesOut.get(edge.name)
|
|
1308
1303
|
const { isProjectRoot, isWorkspace } = node.parent.sourceReference
|
|
1309
1304
|
const isMine = isProjectRoot || isWorkspace
|
|
1310
|
-
const conflictOK = this[_force] || !isMine && !this
|
|
1305
|
+
const conflictOK = this[_force] || !isMine && !this.#strictPeerDeps
|
|
1311
1306
|
|
|
1312
1307
|
if (!edge.to) {
|
|
1313
1308
|
if (!parentEdge) {
|
|
1314
1309
|
// easy, just put the thing there
|
|
1315
|
-
await this
|
|
1310
|
+
await this.#nodeFromEdge(edge, node.parent, null, required)
|
|
1316
1311
|
continue
|
|
1317
1312
|
} else {
|
|
1318
1313
|
// if the parent's edge is very broad like >=1, and the edge in
|
|
@@ -1323,7 +1318,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1323
1318
|
// a conflict. this is always a problem in strict mode, never
|
|
1324
1319
|
// in force mode, and a problem in non-strict mode if this isn't
|
|
1325
1320
|
// on behalf of our project. in all such cases, we warn at least.
|
|
1326
|
-
const dep = await this
|
|
1321
|
+
const dep = await this.#nodeFromEdge(
|
|
1327
1322
|
parentEdge,
|
|
1328
1323
|
node.parent,
|
|
1329
1324
|
edge,
|
|
@@ -1344,7 +1339,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1344
1339
|
}
|
|
1345
1340
|
|
|
1346
1341
|
// problem
|
|
1347
|
-
this
|
|
1342
|
+
this.#failPeerConflict(edge, parentEdge)
|
|
1348
1343
|
}
|
|
1349
1344
|
}
|
|
1350
1345
|
|
|
@@ -1352,9 +1347,9 @@ This is a one-time fix-up, please be patient...
|
|
|
1352
1347
|
// See if the thing we WOULD be happy with is also going to satisfy
|
|
1353
1348
|
// the other dependents on the current node.
|
|
1354
1349
|
const current = edge.to
|
|
1355
|
-
const dep = await this
|
|
1350
|
+
const dep = await this.#nodeFromEdge(edge, null, null, required)
|
|
1356
1351
|
if (dep.canReplace(current)) {
|
|
1357
|
-
await this
|
|
1352
|
+
await this.#nodeFromEdge(edge, node.parent, null, required)
|
|
1358
1353
|
continue
|
|
1359
1354
|
}
|
|
1360
1355
|
|
|
@@ -1371,17 +1366,17 @@ This is a one-time fix-up, please be patient...
|
|
|
1371
1366
|
}
|
|
1372
1367
|
|
|
1373
1368
|
// ok, it's the root, or we're in unforced strict mode, so this is bad
|
|
1374
|
-
this
|
|
1369
|
+
this.#failPeerConflict(edge, parentEdge)
|
|
1375
1370
|
}
|
|
1376
1371
|
return node
|
|
1377
1372
|
}
|
|
1378
1373
|
|
|
1379
|
-
|
|
1380
|
-
const expl = this
|
|
1374
|
+
#failPeerConflict (edge, currentEdge) {
|
|
1375
|
+
const expl = this.#explainPeerConflict(edge, currentEdge)
|
|
1381
1376
|
throw Object.assign(new Error('unable to resolve dependency tree'), expl)
|
|
1382
1377
|
}
|
|
1383
1378
|
|
|
1384
|
-
|
|
1379
|
+
#explainPeerConflict (edge, currentEdge) {
|
|
1385
1380
|
const node = edge.from
|
|
1386
1381
|
const curNode = node.resolve(edge.name)
|
|
1387
1382
|
const current = curNode.explain()
|
|
@@ -1393,12 +1388,12 @@ This is a one-time fix-up, please be patient...
|
|
|
1393
1388
|
// the tree handling logic.
|
|
1394
1389
|
currentEdge: currentEdge ? currentEdge.explain() : null,
|
|
1395
1390
|
edge: edge.explain(),
|
|
1396
|
-
strictPeerDeps: this
|
|
1391
|
+
strictPeerDeps: this.#strictPeerDeps,
|
|
1397
1392
|
force: this[_force],
|
|
1398
1393
|
}
|
|
1399
1394
|
}
|
|
1400
1395
|
|
|
1401
|
-
// go through all the links in the this
|
|
1396
|
+
// go through all the links in the this.#linkNodes set
|
|
1402
1397
|
// for each one:
|
|
1403
1398
|
// - if outside the root, ignore it, assume it's fine, it's not our problem
|
|
1404
1399
|
// - if a node in the tree already, assign the target to that node.
|
|
@@ -1406,9 +1401,9 @@ This is a one-time fix-up, please be patient...
|
|
|
1406
1401
|
// and add it to the _depsQueue
|
|
1407
1402
|
//
|
|
1408
1403
|
// call buildDepStep if anything was added to the queue, otherwise we're done
|
|
1409
|
-
|
|
1410
|
-
for (const link of this
|
|
1411
|
-
this
|
|
1404
|
+
#resolveLinks () {
|
|
1405
|
+
for (const link of this.#linkNodes) {
|
|
1406
|
+
this.#linkNodes.delete(link)
|
|
1412
1407
|
|
|
1413
1408
|
// link we never ended up placing, skip it
|
|
1414
1409
|
if (link.root !== this.idealTree) {
|
|
@@ -1419,34 +1414,34 @@ This is a one-time fix-up, please be patient...
|
|
|
1419
1414
|
const external = !link.target.isDescendantOf(tree)
|
|
1420
1415
|
|
|
1421
1416
|
// outside the root, somebody else's problem, ignore it
|
|
1422
|
-
if (external && !this
|
|
1417
|
+
if (external && !this.#follow) {
|
|
1423
1418
|
continue
|
|
1424
1419
|
}
|
|
1425
1420
|
|
|
1426
1421
|
// didn't find a parent for it or it has not been seen yet
|
|
1427
1422
|
// so go ahead and process it.
|
|
1428
1423
|
const unseenLink = (link.target.parent || link.target.fsParent) &&
|
|
1429
|
-
!this
|
|
1424
|
+
!this.#depsSeen.has(link.target)
|
|
1430
1425
|
|
|
1431
|
-
if (this
|
|
1426
|
+
if (this.#follow &&
|
|
1432
1427
|
!link.target.parent &&
|
|
1433
1428
|
!link.target.fsParent ||
|
|
1434
1429
|
unseenLink) {
|
|
1435
1430
|
this.addTracker('idealTree', link.target.name, link.target.location)
|
|
1436
|
-
this
|
|
1431
|
+
this.#depsQueue.push(link.target)
|
|
1437
1432
|
}
|
|
1438
1433
|
}
|
|
1439
1434
|
|
|
1440
|
-
if (this
|
|
1441
|
-
return this
|
|
1435
|
+
if (this.#depsQueue.length) {
|
|
1436
|
+
return this.#buildDepStep()
|
|
1442
1437
|
}
|
|
1443
1438
|
}
|
|
1444
1439
|
|
|
1445
|
-
|
|
1440
|
+
#fixDepFlags () {
|
|
1446
1441
|
process.emit('time', 'idealTree:fixDepFlags')
|
|
1447
1442
|
const metaFromDisk = this.idealTree.meta.loadedFromDisk
|
|
1448
1443
|
const flagsSuspect = this[_flagsSuspect]
|
|
1449
|
-
const mutateTree = this
|
|
1444
|
+
const mutateTree = this.#mutateTree
|
|
1450
1445
|
// if the options set prune:false, then we don't prune, but we still
|
|
1451
1446
|
// mark the extraneous items in the tree if we modified it at all.
|
|
1452
1447
|
// If we did no modifications, we just iterate over the extraneous nodes.
|
|
@@ -1481,21 +1476,28 @@ This is a one-time fix-up, please be patient...
|
|
|
1481
1476
|
// then the tree is suspect. Prune what is marked as extraneous.
|
|
1482
1477
|
// otherwise, don't bother.
|
|
1483
1478
|
const needPrune = metaFromDisk && (mutateTree || flagsSuspect)
|
|
1484
|
-
if (this
|
|
1479
|
+
if (this.#prune && needPrune) {
|
|
1485
1480
|
this[_idealTreePrune]()
|
|
1481
|
+
for (const node of this.idealTree.inventory.values()) {
|
|
1482
|
+
if (node.extraneous) {
|
|
1483
|
+
node.parent = null
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
1486
|
}
|
|
1487
1487
|
|
|
1488
1488
|
process.emit('timeEnd', 'idealTree:fixDepFlags')
|
|
1489
1489
|
}
|
|
1490
1490
|
|
|
1491
1491
|
[_idealTreePrune] () {
|
|
1492
|
-
for (const node of this.idealTree.inventory.
|
|
1493
|
-
node.
|
|
1492
|
+
for (const node of this.idealTree.inventory.values()) {
|
|
1493
|
+
if (node.extraneous) {
|
|
1494
|
+
node.parent = null
|
|
1495
|
+
}
|
|
1494
1496
|
}
|
|
1495
1497
|
}
|
|
1496
1498
|
|
|
1497
|
-
|
|
1498
|
-
for (const node of this
|
|
1499
|
+
#pruneFailedOptional () {
|
|
1500
|
+
for (const node of this.#loadFailures) {
|
|
1499
1501
|
if (!node.optional) {
|
|
1500
1502
|
throw node.errors[0]
|
|
1501
1503
|
}
|