@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
|
@@ -44,12 +44,15 @@ const _currentDep = Symbol('currentDep')
|
|
|
44
44
|
const _updateAll = Symbol('updateAll')
|
|
45
45
|
const _mutateTree = Symbol('mutateTree')
|
|
46
46
|
const _flagsSuspect = Symbol.for('flagsSuspect')
|
|
47
|
+
const _workspaces = Symbol.for('workspaces')
|
|
47
48
|
const _prune = Symbol('prune')
|
|
48
49
|
const _preferDedupe = Symbol('preferDedupe')
|
|
50
|
+
const _pruneDedupable = Symbol('pruneDedupable')
|
|
49
51
|
const _legacyBundling = Symbol('legacyBundling')
|
|
50
52
|
const _parseSettings = Symbol('parseSettings')
|
|
51
53
|
const _initTree = Symbol('initTree')
|
|
52
54
|
const _applyUserRequests = Symbol('applyUserRequests')
|
|
55
|
+
const _applyUserRequestsToNode = Symbol('applyUserRequestsToNode')
|
|
53
56
|
const _inflateAncientLockfile = Symbol('inflateAncientLockfile')
|
|
54
57
|
const _buildDeps = Symbol('buildDeps')
|
|
55
58
|
const _buildDepStep = Symbol('buildDepStep')
|
|
@@ -109,7 +112,7 @@ const _peerSetSource = Symbol.for('peerSetSource')
|
|
|
109
112
|
|
|
110
113
|
// used by Reify mixin
|
|
111
114
|
const _force = Symbol.for('force')
|
|
112
|
-
const _explicitRequests = Symbol
|
|
115
|
+
const _explicitRequests = Symbol('explicitRequests')
|
|
113
116
|
const _global = Symbol.for('global')
|
|
114
117
|
const _idealTreePrune = Symbol.for('idealTreePrune')
|
|
115
118
|
|
|
@@ -130,8 +133,10 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
130
133
|
force = false,
|
|
131
134
|
packageLock = true,
|
|
132
135
|
strictPeerDeps = false,
|
|
136
|
+
workspaces = [],
|
|
133
137
|
} = options
|
|
134
138
|
|
|
139
|
+
this[_workspaces] = workspaces || []
|
|
135
140
|
this[_force] = !!force
|
|
136
141
|
this[_strictPeerDeps] = !!strictPeerDeps
|
|
137
142
|
|
|
@@ -143,6 +148,9 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
143
148
|
this[_globalStyle] = this[_global] || globalStyle
|
|
144
149
|
this[_follow] = !!follow
|
|
145
150
|
|
|
151
|
+
if (this[_workspaces].length && this[_global])
|
|
152
|
+
throw new Error('Cannot operate on workspaces in global mode')
|
|
153
|
+
|
|
146
154
|
this[_explicitRequests] = new Set()
|
|
147
155
|
this[_preferDedupe] = false
|
|
148
156
|
this[_legacyBundling] = false
|
|
@@ -157,6 +165,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
157
165
|
this[_manifests] = new Map()
|
|
158
166
|
this[_peerConflict] = null
|
|
159
167
|
this[_edgesOverridden] = new Set()
|
|
168
|
+
this[_resolvedAdd] = []
|
|
160
169
|
|
|
161
170
|
// a map of each module in a peer set to the thing that depended on
|
|
162
171
|
// that set of peers in the first place. Use a WeakMap so that we
|
|
@@ -204,8 +213,8 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
204
213
|
|
|
205
214
|
try {
|
|
206
215
|
await this[_initTree]()
|
|
207
|
-
await this[_applyUserRequests](options)
|
|
208
216
|
await this[_inflateAncientLockfile]()
|
|
217
|
+
await this[_applyUserRequests](options)
|
|
209
218
|
await this[_buildDeps]()
|
|
210
219
|
await this[_fixDepFlags]()
|
|
211
220
|
await this[_pruneFailedOptional]()
|
|
@@ -266,6 +275,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
266
275
|
this[_preferDedupe] = !!options.preferDedupe
|
|
267
276
|
this[_legacyBundling] = !!options.legacyBundling
|
|
268
277
|
this[_updateNames] = update.names
|
|
278
|
+
|
|
269
279
|
this[_updateAll] = update.all
|
|
270
280
|
// we prune by default unless explicitly set to boolean false
|
|
271
281
|
this[_prune] = options.prune !== false
|
|
@@ -387,6 +397,42 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
387
397
|
async [_applyUserRequests] (options) {
|
|
388
398
|
process.emit('time', 'idealTree:userRequests')
|
|
389
399
|
const tree = this.idealTree.target || this.idealTree
|
|
400
|
+
|
|
401
|
+
if (!this[_workspaces].length) {
|
|
402
|
+
return this[_applyUserRequestsToNode](tree, options).then(() =>
|
|
403
|
+
process.emit('timeEnd', 'idealTree:userRequests'))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const wsMap = tree.workspaces
|
|
407
|
+
if (!wsMap) {
|
|
408
|
+
this.log.warn('idealTree', 'Workspace filter set, but no workspaces present')
|
|
409
|
+
return
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const promises = []
|
|
413
|
+
for (const name of this[_workspaces]) {
|
|
414
|
+
const path = wsMap.get(name)
|
|
415
|
+
if (!path) {
|
|
416
|
+
this.log.warn('idealTree', `Workspace ${name} in filter set, but not in workspaces`)
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
const loc = relpath(tree.realpath, path)
|
|
420
|
+
const node = tree.inventory.get(loc)
|
|
421
|
+
|
|
422
|
+
/* istanbul ignore if - should be impossible */
|
|
423
|
+
if (!node) {
|
|
424
|
+
this.log.warn('idealTree', `Workspace ${name} in filter set, but no workspace folder present`)
|
|
425
|
+
continue
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
promises.push(this[_applyUserRequestsToNode](node, options))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return Promise.all(promises).then(() =>
|
|
432
|
+
process.emit('timeEnd', 'idealTree:userRequests'))
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async [_applyUserRequestsToNode] (tree, options) {
|
|
390
436
|
// If we have a list of package names to update, and we know it's
|
|
391
437
|
// going to update them wherever they are, add any paths into those
|
|
392
438
|
// named nodes to the buildIdealTree queue.
|
|
@@ -395,38 +441,49 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
395
441
|
|
|
396
442
|
// global updates only update the globalTop nodes, but we need to know
|
|
397
443
|
// that they're there, and not reinstall the world unnecessarily.
|
|
444
|
+
const globalExplicitUpdateNames = []
|
|
398
445
|
if (this[_global] && (this[_updateAll] || this[_updateNames].length)) {
|
|
399
446
|
const nm = resolve(this.path, 'node_modules')
|
|
400
447
|
for (const name of await readdir(nm).catch(() => [])) {
|
|
401
|
-
if (this[_updateNames].includes(name))
|
|
402
|
-
this[_explicitRequests].add(name)
|
|
403
448
|
tree.package.dependencies = tree.package.dependencies || {}
|
|
404
|
-
|
|
449
|
+
const updateName = this[_updateNames].includes(name)
|
|
450
|
+
if (this[_updateAll] || updateName) {
|
|
451
|
+
if (updateName)
|
|
452
|
+
globalExplicitUpdateNames.push(name)
|
|
405
453
|
tree.package.dependencies[name] = '*'
|
|
454
|
+
}
|
|
406
455
|
}
|
|
407
456
|
}
|
|
408
457
|
|
|
409
458
|
if (this.auditReport && this.auditReport.size > 0)
|
|
410
459
|
this[_queueVulnDependents](options)
|
|
411
460
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
461
|
+
const { add, rm } = options
|
|
462
|
+
|
|
463
|
+
if (rm && rm.length) {
|
|
464
|
+
addRmPkgDeps.rm(tree.package, rm)
|
|
465
|
+
for (const name of rm)
|
|
466
|
+
this[_explicitRequests].add({ from: tree, name, action: 'DELETE' })
|
|
416
467
|
}
|
|
417
468
|
|
|
418
|
-
if (
|
|
419
|
-
await this[_add](options)
|
|
469
|
+
if (add && add.length)
|
|
470
|
+
await this[_add](tree, options)
|
|
420
471
|
|
|
421
|
-
// triggers a refresh of all edgesOut
|
|
422
|
-
|
|
472
|
+
// triggers a refresh of all edgesOut. this has to be done BEFORE
|
|
473
|
+
// adding the edges to explicitRequests, because the package setter
|
|
474
|
+
// resets all edgesOut.
|
|
475
|
+
if (add && add.length || rm && rm.length || this[_global])
|
|
423
476
|
tree.package = tree.package
|
|
424
|
-
|
|
477
|
+
|
|
478
|
+
for (const spec of this[_resolvedAdd])
|
|
479
|
+
this[_explicitRequests].add(tree.edgesOut.get(spec.name))
|
|
480
|
+
for (const name of globalExplicitUpdateNames)
|
|
481
|
+
this[_explicitRequests].add(tree.edgesOut.get(name))
|
|
425
482
|
}
|
|
426
483
|
|
|
427
484
|
// This returns a promise because we might not have the name yet,
|
|
428
485
|
// and need to call pacote.manifest to find the name.
|
|
429
|
-
[_add] ({add, saveType = null, saveBundle = false}) {
|
|
486
|
+
[_add] (tree, {add, saveType = null, saveBundle = false}) {
|
|
430
487
|
// get the name for each of the specs in the list.
|
|
431
488
|
// ie, doing `foo@bar` we just return foo
|
|
432
489
|
// but if it's a url or git, we don't know the name until we
|
|
@@ -438,19 +495,17 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
438
495
|
.then(add => this[_updateFilePath](add))
|
|
439
496
|
.then(add => this[_followSymlinkPath](add))
|
|
440
497
|
})).then(add => {
|
|
441
|
-
this[_resolvedAdd]
|
|
498
|
+
this[_resolvedAdd].push(...add)
|
|
442
499
|
// now add is a list of spec objects with names.
|
|
443
500
|
// find a home for each of them!
|
|
444
|
-
const tree = this.idealTree.target || this.idealTree
|
|
445
501
|
addRmPkgDeps.add({
|
|
446
502
|
pkg: tree.package,
|
|
447
503
|
add,
|
|
448
504
|
saveBundle,
|
|
449
505
|
saveType,
|
|
450
506
|
path: this.path,
|
|
507
|
+
log: this.log,
|
|
451
508
|
})
|
|
452
|
-
for (const spec of add)
|
|
453
|
-
this[_explicitRequests].add(spec.name)
|
|
454
509
|
})
|
|
455
510
|
}
|
|
456
511
|
|
|
@@ -991,7 +1046,7 @@ This is a one-time fix-up, please be patient...
|
|
|
991
1046
|
// if it's peerOptional and not explicitly requested.
|
|
992
1047
|
if (!edge.to) {
|
|
993
1048
|
return edge.type !== 'peerOptional' ||
|
|
994
|
-
this[_explicitRequests].has(edge
|
|
1049
|
+
this[_explicitRequests].has(edge)
|
|
995
1050
|
}
|
|
996
1051
|
|
|
997
1052
|
// If the edge has an error, there's a problem.
|
|
@@ -1007,7 +1062,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1007
1062
|
return true
|
|
1008
1063
|
|
|
1009
1064
|
// If the user has explicitly asked to install this package, it's a problem.
|
|
1010
|
-
if (node.isProjectRoot && this[_explicitRequests].has(edge
|
|
1065
|
+
if (node.isProjectRoot && this[_explicitRequests].has(edge))
|
|
1011
1066
|
return true
|
|
1012
1067
|
|
|
1013
1068
|
// No problems!
|
|
@@ -1131,10 +1186,20 @@ This is a one-time fix-up, please be patient...
|
|
|
1131
1186
|
continue
|
|
1132
1187
|
|
|
1133
1188
|
// problem
|
|
1134
|
-
this[_failPeerConflict](edge)
|
|
1189
|
+
this[_failPeerConflict](edge, parentEdge)
|
|
1135
1190
|
}
|
|
1136
1191
|
}
|
|
1137
1192
|
|
|
1193
|
+
// There is something present already, and we're not happy about it
|
|
1194
|
+
// See if the thing we WOULD be happy with is also going to satisfy
|
|
1195
|
+
// the other dependents on the current node.
|
|
1196
|
+
const current = edge.to
|
|
1197
|
+
const dep = await this[_nodeFromEdge](edge, null, null, required)
|
|
1198
|
+
if (dep.canReplace(current)) {
|
|
1199
|
+
await this[_nodeFromEdge](edge, node.parent, null, required)
|
|
1200
|
+
continue
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1138
1203
|
// at this point we know that there is a dep there, and
|
|
1139
1204
|
// we don't like it. always fail strictly, always allow forcibly or
|
|
1140
1205
|
// in non-strict mode if it's not our fault. don't warn here, because
|
|
@@ -1147,17 +1212,17 @@ This is a one-time fix-up, please be patient...
|
|
|
1147
1212
|
continue
|
|
1148
1213
|
|
|
1149
1214
|
// ok, it's the root, or we're in unforced strict mode, so this is bad
|
|
1150
|
-
this[_failPeerConflict](edge)
|
|
1215
|
+
this[_failPeerConflict](edge, parentEdge)
|
|
1151
1216
|
}
|
|
1152
1217
|
return node
|
|
1153
1218
|
}
|
|
1154
1219
|
|
|
1155
|
-
[_failPeerConflict] (edge) {
|
|
1156
|
-
const expl = this[_explainPeerConflict](edge)
|
|
1220
|
+
[_failPeerConflict] (edge, currentEdge) {
|
|
1221
|
+
const expl = this[_explainPeerConflict](edge, currentEdge)
|
|
1157
1222
|
throw Object.assign(new Error('unable to resolve dependency tree'), expl)
|
|
1158
1223
|
}
|
|
1159
1224
|
|
|
1160
|
-
[_explainPeerConflict] (edge) {
|
|
1225
|
+
[_explainPeerConflict] (edge, currentEdge) {
|
|
1161
1226
|
const node = edge.from
|
|
1162
1227
|
const curNode = node.resolve(edge.name)
|
|
1163
1228
|
const pc = this[_peerConflict] || { peer: null, current: null }
|
|
@@ -1166,6 +1231,10 @@ This is a one-time fix-up, please be patient...
|
|
|
1166
1231
|
return {
|
|
1167
1232
|
code: 'ERESOLVE',
|
|
1168
1233
|
current,
|
|
1234
|
+
// it SHOULD be impossible to get here without a current node in place,
|
|
1235
|
+
// but this at least gives us something report on when bugs creep into
|
|
1236
|
+
// the tree handling logic.
|
|
1237
|
+
currentEdge: currentEdge ? currentEdge.explain() : null,
|
|
1169
1238
|
edge: edge.explain(),
|
|
1170
1239
|
peerConflict,
|
|
1171
1240
|
strictPeerDeps: this[_strictPeerDeps],
|
|
@@ -1190,7 +1259,7 @@ This is a one-time fix-up, please be patient...
|
|
|
1190
1259
|
[_placeDep] (dep, node, edge, peerEntryEdge = null, peerPath = []) {
|
|
1191
1260
|
if (edge.to &&
|
|
1192
1261
|
!edge.error &&
|
|
1193
|
-
!this[_explicitRequests].has(edge
|
|
1262
|
+
!this[_explicitRequests].has(edge) &&
|
|
1194
1263
|
!this[_updateNames].includes(edge.name) &&
|
|
1195
1264
|
!this[_isVulnerable](edge.to))
|
|
1196
1265
|
return []
|
|
@@ -1275,6 +1344,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1275
1344
|
// this is an overridden peer dep
|
|
1276
1345
|
this[_warnPeerConflict](edge)
|
|
1277
1346
|
}
|
|
1347
|
+
|
|
1348
|
+
// if we get a KEEP in a update scenario, then we MAY have something
|
|
1349
|
+
// already duplicating this unnecessarily! For example:
|
|
1350
|
+
// ```
|
|
1351
|
+
// root
|
|
1352
|
+
// +-- x (dep: y@1.x)
|
|
1353
|
+
// | +-- y@1.0.0
|
|
1354
|
+
// +-- y@1.1.0
|
|
1355
|
+
// ```
|
|
1356
|
+
// Now say we do `reify({update:['y']})`, and the latest version is
|
|
1357
|
+
// 1.1.0, which we already have in the root. We'll try to place y@1.1.0
|
|
1358
|
+
// first in x, then in the root, ending with KEEP, because we already
|
|
1359
|
+
// have it. In that case, we ought to REMOVE the nm/x/nm/y node, because
|
|
1360
|
+
// it is an unnecessary duplicate.
|
|
1361
|
+
this[_pruneDedupable](target, true)
|
|
1278
1362
|
return []
|
|
1279
1363
|
}
|
|
1280
1364
|
|
|
@@ -1330,8 +1414,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1330
1414
|
// MAY end up putting a better/identical node further up the tree in
|
|
1331
1415
|
// a way that causes an unnecessary duplication. If so, remove the
|
|
1332
1416
|
// now-unnecessary node.
|
|
1333
|
-
if (edge.valid && edge.to
|
|
1334
|
-
edge.to
|
|
1417
|
+
if (edge.valid && edge.to && edge.to !== newDep)
|
|
1418
|
+
this[_pruneDedupable](edge.to, false)
|
|
1335
1419
|
|
|
1336
1420
|
// visit any dependents who are upset by this change
|
|
1337
1421
|
// if it's an angry overridden peer edge, however, make sure we
|
|
@@ -1347,30 +1431,8 @@ This is a one-time fix-up, please be patient...
|
|
|
1347
1431
|
// prune anything deeper in the tree that can be replaced by this
|
|
1348
1432
|
if (this.idealTree) {
|
|
1349
1433
|
for (const node of this.idealTree.inventory.query('name', newDep.name)) {
|
|
1350
|
-
if (node
|
|
1351
|
-
|
|
1352
|
-
!node.inShrinkwrap &&
|
|
1353
|
-
!node.inBundle &&
|
|
1354
|
-
node.canReplaceWith(newDep)) {
|
|
1355
|
-
// don't prune if the dupe is necessary!
|
|
1356
|
-
// root (a, d)
|
|
1357
|
-
// +-- a (b, c2)
|
|
1358
|
-
// | +-- b (c2) <-- place c2 for b, lands at root
|
|
1359
|
-
// +-- d (e)
|
|
1360
|
-
// +-- e (c1, d)
|
|
1361
|
-
// +-- c1
|
|
1362
|
-
// +-- f (c2)
|
|
1363
|
-
// +-- c2 <-- pruning this would be bad
|
|
1364
|
-
|
|
1365
|
-
const mask = node.parent !== target &&
|
|
1366
|
-
node.parent &&
|
|
1367
|
-
node.parent.parent &&
|
|
1368
|
-
node.parent.parent !== target &&
|
|
1369
|
-
node.parent.parent.resolve(newDep.name)
|
|
1370
|
-
|
|
1371
|
-
if (!mask || mask === newDep || node.canReplaceWith(mask))
|
|
1372
|
-
node.parent = null
|
|
1373
|
-
}
|
|
1434
|
+
if (node.isDescendantOf(target))
|
|
1435
|
+
this[_pruneDedupable](node, false)
|
|
1374
1436
|
}
|
|
1375
1437
|
}
|
|
1376
1438
|
|
|
@@ -1403,6 +1465,21 @@ This is a one-time fix-up, please be patient...
|
|
|
1403
1465
|
return placed
|
|
1404
1466
|
}
|
|
1405
1467
|
|
|
1468
|
+
// prune all the nodes in a branch of the tree that can be safely removed
|
|
1469
|
+
// This is only the most basic duplication detection; it finds if there
|
|
1470
|
+
// is another satisfying node further up the tree, and if so, dedupes.
|
|
1471
|
+
// Even in legacyBundling mode, we do this amount of deduplication.
|
|
1472
|
+
[_pruneDedupable] (node, descend = true) {
|
|
1473
|
+
if (node.canDedupe(this[_preferDedupe])) {
|
|
1474
|
+
node.root = null
|
|
1475
|
+
return
|
|
1476
|
+
}
|
|
1477
|
+
if (descend) {
|
|
1478
|
+
for (const child of node.children.values())
|
|
1479
|
+
this[_pruneDedupable](child)
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1406
1483
|
[_pruneForReplacement] (node, oldDeps) {
|
|
1407
1484
|
// gather up all the invalid edgesOut, and any now-extraneous
|
|
1408
1485
|
// deps that the new node doesn't depend on but the old one did.
|
|
@@ -1480,9 +1557,15 @@ This is a one-time fix-up, please be patient...
|
|
|
1480
1557
|
if (target.children.has(edge.name)) {
|
|
1481
1558
|
const current = target.children.get(edge.name)
|
|
1482
1559
|
|
|
1483
|
-
// same thing = keep
|
|
1484
|
-
if
|
|
1485
|
-
|
|
1560
|
+
// same thing = keep, UNLESS the current doesn't satisfy and new
|
|
1561
|
+
// one does satisfy. This can happen if it's a link to a matching target
|
|
1562
|
+
// at a different location, which satisfies a version dep, but not a
|
|
1563
|
+
// file: dep. If neither of them satisfy, then we can replace it,
|
|
1564
|
+
// because presumably it's better for a peer or something.
|
|
1565
|
+
if (dep.matches(current)) {
|
|
1566
|
+
if (current.satisfies(edge) || !dep.satisfies(edge))
|
|
1567
|
+
return KEEP
|
|
1568
|
+
}
|
|
1486
1569
|
|
|
1487
1570
|
const { version: curVer } = current
|
|
1488
1571
|
const { version: newVer } = dep
|
|
@@ -1549,32 +1632,137 @@ This is a one-time fix-up, please be patient...
|
|
|
1549
1632
|
// placed here as well. the virtualRoot already has the appropriate
|
|
1550
1633
|
// overrides applied.
|
|
1551
1634
|
if (peerEntryEdge) {
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1635
|
+
const currentPeerSet = getPeerSet(current)
|
|
1636
|
+
|
|
1637
|
+
// We are effectively replacing currentPeerSet with newPeerSet
|
|
1638
|
+
// If there are any non-peer deps coming into the currentPeerSet,
|
|
1639
|
+
// which are currently valid, and are from the target, then that
|
|
1640
|
+
// means that we have to ensure that they're not going to be made
|
|
1641
|
+
// invalid by putting the newPeerSet in place.
|
|
1642
|
+
// If the edge comes from somewhere deeper than the target, then
|
|
1643
|
+
// that's fine, because we'll create an invalid edge, detect it,
|
|
1644
|
+
// and duplicate the node further into the tree.
|
|
1645
|
+
// loop through the currentPeerSet checking for valid edges on
|
|
1646
|
+
// the members of the peer set which will be made invalid.
|
|
1647
|
+
const targetEdges = new Set()
|
|
1648
|
+
for (const p of currentPeerSet) {
|
|
1556
1649
|
for (const edge of p.edgesIn) {
|
|
1557
|
-
|
|
1650
|
+
// edge from within the peerSet, ignore
|
|
1651
|
+
if (currentPeerSet.has(edge.from))
|
|
1652
|
+
continue
|
|
1653
|
+
// only care about valid edges from target.
|
|
1654
|
+
// edges from elsewhere can dupe if offended, invalid edges
|
|
1655
|
+
// are already being fixed or will be later.
|
|
1656
|
+
if (edge.from !== target || !edge.valid)
|
|
1558
1657
|
continue
|
|
1658
|
+
targetEdges.add(edge)
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1559
1661
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
//
|
|
1566
|
-
//
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
const
|
|
1570
|
-
if (
|
|
1662
|
+
for (const edge of targetEdges) {
|
|
1663
|
+
// see if we intend to replace this one anyway
|
|
1664
|
+
const rep = dep.parent.children.get(edge.name)
|
|
1665
|
+
const current = edge.to
|
|
1666
|
+
if (!rep) {
|
|
1667
|
+
// this isn't one we're replacing. but it WAS included in the
|
|
1668
|
+
// peerSet for some reason, so make sure that it's still
|
|
1669
|
+
// ok with the replacements in the new peerSet
|
|
1670
|
+
for (const curEdge of current.edgesOut.values()) {
|
|
1671
|
+
const newRepDep = dep.parent.children.get(curEdge.name)
|
|
1672
|
+
if (curEdge.valid && newRepDep && !newRepDep.satisfies(curEdge)) {
|
|
1571
1673
|
canReplace = false
|
|
1572
|
-
break
|
|
1674
|
+
break
|
|
1573
1675
|
}
|
|
1574
1676
|
}
|
|
1677
|
+
continue
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
// was this replacement already an override of some sort?
|
|
1681
|
+
const override = [...rep.edgesIn].some(e => !e.valid)
|
|
1682
|
+
// if we have a rep, and it's ok to put in this location, and
|
|
1683
|
+
// it's not already part of an override in the peerSet, then
|
|
1684
|
+
// we can continue with it.
|
|
1685
|
+
if (rep.satisfies(edge) && !override)
|
|
1686
|
+
continue
|
|
1687
|
+
// Otherwise, we cannot replace.
|
|
1688
|
+
canReplace = false
|
|
1689
|
+
break
|
|
1690
|
+
}
|
|
1691
|
+
// if we're going to be replacing the peerSet, we have to remove
|
|
1692
|
+
// and re-resolve any members of the old peerSet that are not
|
|
1693
|
+
// present in the new one, and which will have invalid edges.
|
|
1694
|
+
// We know that they're not depended upon by the target, or else
|
|
1695
|
+
// they would have caused a conflict, so they'll get landed deeper
|
|
1696
|
+
// in the tree, if possible.
|
|
1697
|
+
if (canReplace) {
|
|
1698
|
+
let needNesting = false
|
|
1699
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1700
|
+
const rep = dep.parent.children.get(node.name)
|
|
1701
|
+
// has a replacement, already addressed above
|
|
1702
|
+
if (rep)
|
|
1703
|
+
continue
|
|
1704
|
+
|
|
1705
|
+
// ok, it has been placed here to dedupe, see if it needs to go
|
|
1706
|
+
// back deeper within the tree.
|
|
1707
|
+
for (const edge of node.edgesOut.values()) {
|
|
1708
|
+
const repDep = dep.parent.children.get(edge.name)
|
|
1709
|
+
// not in new peerSet, maybe fine.
|
|
1710
|
+
if (!repDep)
|
|
1711
|
+
continue
|
|
1712
|
+
|
|
1713
|
+
// new thing will be fine, no worries
|
|
1714
|
+
if (repDep.satisfies(edge))
|
|
1715
|
+
continue
|
|
1716
|
+
|
|
1717
|
+
// uhoh, we'll have to nest them.
|
|
1718
|
+
needNesting = true
|
|
1719
|
+
break OUTER
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
// to nest, just delete everything without a target dep
|
|
1724
|
+
// that's in the current peerSet, and add their dependants
|
|
1725
|
+
// to the _depsQueue for evaluation. Some of these MAY end
|
|
1726
|
+
// up in the same location again, and that's fine.
|
|
1727
|
+
if (needNesting) {
|
|
1728
|
+
// avoid mutating the tree while we're examining it
|
|
1729
|
+
const dependants = new Set()
|
|
1730
|
+
const reresolve = new Set()
|
|
1731
|
+
OUTER: for (const node of currentPeerSet) {
|
|
1732
|
+
const rep = dep.parent.children.get(node.name)
|
|
1733
|
+
if (rep)
|
|
1734
|
+
continue
|
|
1735
|
+
// create a separate set for each one, so we can skip any
|
|
1736
|
+
// that might somehow have an incoming target edge
|
|
1737
|
+
const deps = new Set()
|
|
1738
|
+
for (const edge of node.edgesIn) {
|
|
1739
|
+
// a target dep, skip this dep entirely, already addressed
|
|
1740
|
+
// ignoring for coverage, because it really ought to be
|
|
1741
|
+
// impossible, but I can't prove it yet, so this is here
|
|
1742
|
+
// for safety.
|
|
1743
|
+
/* istanbul ignore if - should be impossible */
|
|
1744
|
+
if (edge.from === target)
|
|
1745
|
+
continue OUTER
|
|
1746
|
+
// ignore this edge, it'll either be replaced or re-resolved
|
|
1747
|
+
if (currentPeerSet.has(edge.from))
|
|
1748
|
+
continue
|
|
1749
|
+
// ok, we care about this one.
|
|
1750
|
+
deps.add(edge.from)
|
|
1751
|
+
}
|
|
1752
|
+
reresolve.add(node)
|
|
1753
|
+
for (const d of deps)
|
|
1754
|
+
dependants.add(d)
|
|
1755
|
+
}
|
|
1756
|
+
for (const dependant of dependants) {
|
|
1757
|
+
this[_depsQueue].push(dependant)
|
|
1758
|
+
this[_depsSeen].delete(dependant)
|
|
1759
|
+
}
|
|
1760
|
+
for (const node of reresolve)
|
|
1761
|
+
node.root = null
|
|
1575
1762
|
}
|
|
1576
1763
|
}
|
|
1577
1764
|
}
|
|
1765
|
+
|
|
1578
1766
|
if (canReplace) {
|
|
1579
1767
|
const ret = this[_canPlacePeers](dep, target, edge, REPLACE, peerEntryEdge, peerPath, isSource)
|
|
1580
1768
|
/* istanbul ignore else - extremely rare that the peer set would
|
package/lib/arborist/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
const {resolve} = require('path')
|
|
30
30
|
const {homedir} = require('os')
|
|
31
31
|
const procLog = require('../proc-log.js')
|
|
32
|
+
const { saveTypeMap } = require('../add-rm-pkg-deps.js')
|
|
32
33
|
|
|
33
34
|
const mixins = [
|
|
34
35
|
require('../tracker.js'),
|
|
@@ -54,9 +55,11 @@ class Arborist extends Base {
|
|
|
54
55
|
...options,
|
|
55
56
|
path: options.path || '.',
|
|
56
57
|
cache: options.cache || `${homedir()}/.npm/_cacache`,
|
|
57
|
-
packumentCache: new Map(),
|
|
58
|
+
packumentCache: options.packumentCache || new Map(),
|
|
58
59
|
log: options.log || procLog,
|
|
59
60
|
}
|
|
61
|
+
if (options.saveType && !saveTypeMap.get(options.saveType))
|
|
62
|
+
throw new Error(`Invalid saveType ${options.saveType}`)
|
|
60
63
|
this.cache = resolve(this.options.cache)
|
|
61
64
|
this.path = resolve(this.options.path)
|
|
62
65
|
process.emit('timeEnd', 'arborist:ctor')
|
|
@@ -32,6 +32,7 @@ const _loadActual = Symbol('loadActual')
|
|
|
32
32
|
const _loadActualVirtually = Symbol('loadActualVirtually')
|
|
33
33
|
const _loadActualActually = Symbol('loadActualActually')
|
|
34
34
|
const _loadWorkspaces = Symbol.for('loadWorkspaces')
|
|
35
|
+
const _loadWorkspaceTargets = Symbol('loadWorkspaceTargets')
|
|
35
36
|
const _actualTreePromise = Symbol('actualTreePromise')
|
|
36
37
|
const _actualTree = Symbol('actualTree')
|
|
37
38
|
const _transplant = Symbol('transplant')
|
|
@@ -150,18 +151,22 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
150
151
|
await new this.constructor({...this.options}).loadVirtual({
|
|
151
152
|
root: this[_actualTree],
|
|
152
153
|
})
|
|
154
|
+
await this[_loadWorkspaces](this[_actualTree])
|
|
155
|
+
if (this[_actualTree].workspaces && this[_actualTree].workspaces.size)
|
|
156
|
+
calcDepFlags(this[_actualTree], !root)
|
|
153
157
|
this[_transplant](root)
|
|
154
158
|
return this[_actualTree]
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
async [_loadActualActually] ({ root, ignoreMissing, global }) {
|
|
158
162
|
await this[_loadFSTree](this[_actualTree])
|
|
163
|
+
await this[_loadWorkspaces](this[_actualTree])
|
|
164
|
+
await this[_loadWorkspaceTargets](this[_actualTree])
|
|
159
165
|
if (!ignoreMissing)
|
|
160
166
|
await this[_findMissingEdges]()
|
|
161
167
|
this[_findFSParents]()
|
|
162
168
|
this[_transplant](root)
|
|
163
169
|
|
|
164
|
-
await this[_loadWorkspaces](this[_actualTree])
|
|
165
170
|
if (global) {
|
|
166
171
|
// need to depend on the children, or else all of them
|
|
167
172
|
// will end up being flagged as extraneous, since the
|
|
@@ -178,16 +183,37 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
178
183
|
return this[_actualTree]
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
// if there are workspace targets without Link nodes created, load
|
|
187
|
+
// the targets, so that we know what they are.
|
|
188
|
+
async [_loadWorkspaceTargets] (tree) {
|
|
189
|
+
if (!tree.workspaces || !tree.workspaces.size)
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
const promises = []
|
|
193
|
+
for (const path of tree.workspaces.values()) {
|
|
194
|
+
if (!this[_cache].has(path)) {
|
|
195
|
+
const p = this[_loadFSNode]({ path, root: this[_actualTree] })
|
|
196
|
+
.then(node => this[_loadFSTree](node))
|
|
197
|
+
promises.push(p)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
await Promise.all(promises)
|
|
201
|
+
}
|
|
202
|
+
|
|
181
203
|
[_transplant] (root) {
|
|
182
204
|
if (!root || root === this[_actualTree])
|
|
183
205
|
return
|
|
206
|
+
|
|
184
207
|
this[_actualTree][_changePath](root.path)
|
|
185
208
|
for (const node of this[_actualTree].children.values()) {
|
|
186
209
|
if (!this[_transplantFilter](node))
|
|
187
|
-
node.
|
|
210
|
+
node.root = null
|
|
188
211
|
}
|
|
189
212
|
|
|
190
213
|
root.replace(this[_actualTree])
|
|
214
|
+
for (const node of this[_actualTree].fsChildren)
|
|
215
|
+
node.root = this[_transplantFilter](node) ? root : null
|
|
216
|
+
|
|
191
217
|
this[_actualTree] = root
|
|
192
218
|
}
|
|
193
219
|
|
|
@@ -93,7 +93,8 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
93
93
|
this.virtualTree = root
|
|
94
94
|
const {links, nodes} = this[resolveNodes](s, root)
|
|
95
95
|
await this[resolveLinks](links, nodes)
|
|
96
|
-
|
|
96
|
+
if (!(s.originalLockfileVersion >= 2))
|
|
97
|
+
this[assignBundles](nodes)
|
|
97
98
|
if (this[flagsSuspect])
|
|
98
99
|
this[reCalcDepFlags](nodes.values())
|
|
99
100
|
return root
|
|
@@ -220,22 +221,24 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
220
221
|
[assignBundles] (nodes) {
|
|
221
222
|
for (const [location, node] of nodes) {
|
|
222
223
|
// Skip assignment of parentage for the root package
|
|
223
|
-
if (!location)
|
|
224
|
+
if (!location || node.target && !node.target.location)
|
|
224
225
|
continue
|
|
225
226
|
const { name, parent, package: { inBundle }} = node
|
|
227
|
+
|
|
226
228
|
if (!parent)
|
|
227
229
|
continue
|
|
228
230
|
|
|
229
231
|
// read inBundle from package because 'package' here is
|
|
230
232
|
// actually a v2 lockfile metadata entry.
|
|
231
|
-
// If the *parent* is also bundled, though,
|
|
232
|
-
// that it's being pulled in
|
|
233
|
+
// If the *parent* is also bundled, though, or if the parent has
|
|
234
|
+
// no dependency on it, then we assume that it's being pulled in
|
|
235
|
+
// just by virtue of its parent or a transitive dep being bundled.
|
|
233
236
|
const { package: ppkg } = parent
|
|
234
237
|
const { inBundle: parentBundled } = ppkg
|
|
235
|
-
if (inBundle && !parentBundled) {
|
|
238
|
+
if (inBundle && !parentBundled && parent.edgesOut.has(node.name)) {
|
|
236
239
|
if (!ppkg.bundleDependencies)
|
|
237
240
|
ppkg.bundleDependencies = [name]
|
|
238
|
-
else
|
|
241
|
+
else
|
|
239
242
|
ppkg.bundleDependencies.push(name)
|
|
240
243
|
}
|
|
241
244
|
}
|