@npmcli/arborist 9.0.0-pre.1 → 9.0.1
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 +3 -3
- package/lib/arborist/load-actual.js +1 -1
- package/lib/arborist/load-virtual.js +1 -1
- package/lib/arborist/reify.js +1 -1
- package/lib/consistent-resolve.js +2 -3
- package/lib/dep-valid.js +1 -1
- package/lib/edge.js +57 -14
- package/lib/link.js +1 -1
- package/lib/node.js +125 -16
- package/lib/override-set.js +63 -1
- package/lib/shrinkwrap.js +3 -3
- package/package.json +3 -3
|
@@ -447,7 +447,7 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
447
447
|
.catch(/* istanbul ignore next */ () => null)
|
|
448
448
|
if (st && st.isSymbolicLink()) {
|
|
449
449
|
const target = await readlink(dir)
|
|
450
|
-
const real = resolve(dirname(dir), target)
|
|
450
|
+
const real = resolve(dirname(dir), target)
|
|
451
451
|
tree.package.dependencies[name] = `file:${real}`
|
|
452
452
|
} else {
|
|
453
453
|
tree.package.dependencies[name] = '*'
|
|
@@ -522,12 +522,12 @@ module.exports = cls => class IdealTreeBuilder extends cls {
|
|
|
522
522
|
|
|
523
523
|
const { name } = spec
|
|
524
524
|
if (spec.type === 'file') {
|
|
525
|
-
spec = npa(`file:${relpath(path, spec.fetchSpec)
|
|
525
|
+
spec = npa(`file:${relpath(path, spec.fetchSpec)}`, path)
|
|
526
526
|
spec.name = name
|
|
527
527
|
} else if (spec.type === 'directory') {
|
|
528
528
|
try {
|
|
529
529
|
const real = await realpath(spec.fetchSpec, this[_rpcache], this[_stcache])
|
|
530
|
-
spec = npa(`file:${relpath(path, real)
|
|
530
|
+
spec = npa(`file:${relpath(path, real)}`, path)
|
|
531
531
|
spec.name = name
|
|
532
532
|
} catch {
|
|
533
533
|
// TODO: create synthetic test case to simulate realpath failure
|
|
@@ -216,7 +216,7 @@ module.exports = cls => class ActualLoader extends cls {
|
|
|
216
216
|
const actualRoot = tree.isLink ? tree.target : tree
|
|
217
217
|
const { dependencies = {} } = actualRoot.package
|
|
218
218
|
for (const [name, kid] of actualRoot.children.entries()) {
|
|
219
|
-
const def = kid.isLink ? `file:${kid.realpath
|
|
219
|
+
const def = kid.isLink ? `file:${kid.realpath}` : '*'
|
|
220
220
|
dependencies[name] = dependencies[name] || def
|
|
221
221
|
}
|
|
222
222
|
actualRoot.package = { ...actualRoot.package, dependencies }
|
|
@@ -149,7 +149,7 @@ module.exports = cls => class VirtualLoader extends cls {
|
|
|
149
149
|
})
|
|
150
150
|
|
|
151
151
|
for (const [name, path] of workspaces.entries()) {
|
|
152
|
-
lockWS[name] = `file:${path
|
|
152
|
+
lockWS[name] = `file:${path}`
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// Should rootNames exclude optional?
|
package/lib/arborist/reify.js
CHANGED
|
@@ -1364,7 +1364,7 @@ module.exports = cls => class Reifier extends cls {
|
|
|
1364
1364
|
// path initially, in which case we can end up with the wrong
|
|
1365
1365
|
// thing, so just get the ultimate fetchSpec and relativize it.
|
|
1366
1366
|
const p = req.fetchSpec.replace(/^file:/, '')
|
|
1367
|
-
const rel = relpath(addTree.realpath, p)
|
|
1367
|
+
const rel = relpath(addTree.realpath, p)
|
|
1368
1368
|
newSpec = `file:${rel}`
|
|
1369
1369
|
}
|
|
1370
1370
|
} else {
|
|
@@ -20,11 +20,10 @@ const consistentResolve = (resolved, fromPath, toPath, relPaths = false) => {
|
|
|
20
20
|
raw,
|
|
21
21
|
} = npa(resolved, fromPath)
|
|
22
22
|
if (type === 'file' || type === 'directory') {
|
|
23
|
-
const cleanFetchSpec = fetchSpec.replace(/#/g, '%23')
|
|
24
23
|
if (relPaths && toPath) {
|
|
25
|
-
return `file:${relpath(toPath,
|
|
24
|
+
return `file:${relpath(toPath, fetchSpec)}`
|
|
26
25
|
}
|
|
27
|
-
return `file:${
|
|
26
|
+
return `file:${fetchSpec}`
|
|
28
27
|
}
|
|
29
28
|
if (hosted) {
|
|
30
29
|
return `git+${hosted.auth ? hosted.https(hostedOpt) : hosted.sshurl(hostedOpt)}`
|
package/lib/dep-valid.js
CHANGED
package/lib/edge.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const util = require('node:util')
|
|
5
5
|
const npa = require('npm-package-arg')
|
|
6
6
|
const depValid = require('./dep-valid.js')
|
|
7
|
+
const OverrideSet = require('./override-set.js')
|
|
7
8
|
|
|
8
9
|
class ArboristEdge {
|
|
9
10
|
constructor (edge) {
|
|
@@ -103,7 +104,7 @@ class Edge {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
satisfiedBy (node) {
|
|
106
|
-
if (node.name !== this.#name) {
|
|
107
|
+
if (node.name !== this.#name || !this.#from) {
|
|
107
108
|
return false
|
|
108
109
|
}
|
|
109
110
|
|
|
@@ -112,7 +113,31 @@ class Edge {
|
|
|
112
113
|
if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) {
|
|
113
114
|
return depValid(node, this.rawSpec, this.#accept, this.#from)
|
|
114
115
|
}
|
|
115
|
-
|
|
116
|
+
|
|
117
|
+
// If there's no override we just use the spec.
|
|
118
|
+
if (!this.overrides?.keySpec) {
|
|
119
|
+
return depValid(node, this.spec, this.#accept, this.#from)
|
|
120
|
+
}
|
|
121
|
+
// There's some override. If the target node satisfies the overriding spec
|
|
122
|
+
// then it's okay.
|
|
123
|
+
if (depValid(node, this.spec, this.#accept, this.#from)) {
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
// If it doesn't, then it should at least satisfy the original spec.
|
|
127
|
+
if (!depValid(node, this.rawSpec, this.#accept, this.#from)) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
// It satisfies the original spec, not the overriding spec. We need to make
|
|
131
|
+
// sure it doesn't use the overridden spec.
|
|
132
|
+
// For example:
|
|
133
|
+
// we might have an ^8.0.0 rawSpec, and an override that makes
|
|
134
|
+
// keySpec=8.23.0 and the override value spec=9.0.0.
|
|
135
|
+
// If the node is 9.0.0, then it's okay because it's consistent with spec.
|
|
136
|
+
// If the node is 8.24.0, then it's okay because it's consistent with the rawSpec.
|
|
137
|
+
// If the node is 8.23.0, then it's not okay because even though it's consistent
|
|
138
|
+
// with the rawSpec, it's also consistent with the keySpec.
|
|
139
|
+
// So we're looking for ^8.0.0 or 9.0.0 and not 8.23.0.
|
|
140
|
+
return !depValid(node, this.overrides.keySpec, this.#accept, this.#from)
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
// return the edge data, and an explanation of how that edge came to be here
|
|
@@ -181,11 +206,9 @@ class Edge {
|
|
|
181
206
|
if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.#name) {
|
|
182
207
|
if (this.overrides.value.startsWith('$')) {
|
|
183
208
|
const ref = this.overrides.value.slice(1)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
? this.#from.sourceReference.root.package
|
|
188
|
-
: this.#from.root.package
|
|
209
|
+
const pkg = this.#from?.sourceReference
|
|
210
|
+
? this.#from?.sourceReference.root.package
|
|
211
|
+
: this.#from?.root?.package
|
|
189
212
|
if (pkg.devDependencies?.[ref]) {
|
|
190
213
|
return pkg.devDependencies[ref]
|
|
191
214
|
}
|
|
@@ -234,10 +257,15 @@ class Edge {
|
|
|
234
257
|
} else {
|
|
235
258
|
this.#error = 'MISSING'
|
|
236
259
|
}
|
|
237
|
-
} else if (this.peer && this.#from === this.#to.parent && !this.#from
|
|
260
|
+
} else if (this.peer && this.#from === this.#to.parent && !this.#from?.isTop) {
|
|
238
261
|
this.#error = 'PEER LOCAL'
|
|
239
262
|
} else if (!this.satisfiedBy(this.#to)) {
|
|
240
263
|
this.#error = 'INVALID'
|
|
264
|
+
} else if (this.overrides && this.#to.edgesOut.size && OverrideSet.doOverrideSetsConflict(this.overrides, this.#to.overrides)) {
|
|
265
|
+
// Any inconsistency between the edge's override set and the target's override set is potentially problematic.
|
|
266
|
+
// But we only say the edge is in error if the override sets are plainly conflicting.
|
|
267
|
+
// Note that if the target doesn't have any dependencies of their own, then this inconsistency is irrelevant.
|
|
268
|
+
this.#error = 'INVALID'
|
|
241
269
|
} else {
|
|
242
270
|
this.#error = 'OK'
|
|
243
271
|
}
|
|
@@ -250,15 +278,26 @@ class Edge {
|
|
|
250
278
|
|
|
251
279
|
reload (hard = false) {
|
|
252
280
|
this.#explanation = null
|
|
253
|
-
|
|
254
|
-
|
|
281
|
+
|
|
282
|
+
let needToUpdateOverrideSet = false
|
|
283
|
+
let newOverrideSet
|
|
284
|
+
let oldOverrideSet
|
|
285
|
+
if (this.#from?.overrides) {
|
|
286
|
+
newOverrideSet = this.#from.overrides.getEdgeRule(this)
|
|
287
|
+
if (newOverrideSet && !newOverrideSet.isEqual(this.overrides)) {
|
|
288
|
+
// If there's a new different override set we need to propagate it to the nodes.
|
|
289
|
+
// If we're deleting the override set then there's no point propagating it right now since it will be filled with another value later.
|
|
290
|
+
needToUpdateOverrideSet = true
|
|
291
|
+
oldOverrideSet = this.overrides
|
|
292
|
+
this.overrides = newOverrideSet
|
|
293
|
+
}
|
|
255
294
|
} else {
|
|
256
295
|
delete this.overrides
|
|
257
296
|
}
|
|
258
|
-
const newTo = this.#from
|
|
297
|
+
const newTo = this.#from?.resolve(this.#name)
|
|
259
298
|
if (newTo !== this.#to) {
|
|
260
299
|
if (this.#to) {
|
|
261
|
-
this.#to.
|
|
300
|
+
this.#to.deleteEdgeIn(this)
|
|
262
301
|
}
|
|
263
302
|
this.#to = newTo
|
|
264
303
|
this.#error = null
|
|
@@ -267,15 +306,19 @@ class Edge {
|
|
|
267
306
|
}
|
|
268
307
|
} else if (hard) {
|
|
269
308
|
this.#error = null
|
|
309
|
+
} else if (needToUpdateOverrideSet && this.#to) {
|
|
310
|
+
// Propagate the new override set to the target node.
|
|
311
|
+
this.#to.updateOverridesEdgeInRemoved(oldOverrideSet)
|
|
312
|
+
this.#to.updateOverridesEdgeInAdded(newOverrideSet)
|
|
270
313
|
}
|
|
271
314
|
}
|
|
272
315
|
|
|
273
316
|
detach () {
|
|
274
317
|
this.#explanation = null
|
|
275
318
|
if (this.#to) {
|
|
276
|
-
this.#to.
|
|
319
|
+
this.#to.deleteEdgeIn(this)
|
|
277
320
|
}
|
|
278
|
-
this.#from
|
|
321
|
+
this.#from?.edgesOut.delete(this.#name)
|
|
279
322
|
this.#to = null
|
|
280
323
|
this.#error = 'DETACHED'
|
|
281
324
|
this.#from = null
|
package/lib/link.js
CHANGED
|
@@ -99,7 +99,7 @@ class Link extends Node {
|
|
|
99
99
|
// the path/realpath guard is there for the benefit of setting
|
|
100
100
|
// these things in the "wrong" order
|
|
101
101
|
return this.path && this.realpath
|
|
102
|
-
? `file:${relpath(dirname(this.path), this.realpath)
|
|
102
|
+
? `file:${relpath(dirname(this.path), this.realpath)}`
|
|
103
103
|
: null
|
|
104
104
|
}
|
|
105
105
|
|
package/lib/node.js
CHANGED
|
@@ -40,6 +40,7 @@ const debug = require('./debug.js')
|
|
|
40
40
|
const gatherDepSet = require('./gather-dep-set.js')
|
|
41
41
|
const treeCheck = require('./tree-check.js')
|
|
42
42
|
const { walkUp } = require('walk-up-path')
|
|
43
|
+
const { log } = require('proc-log')
|
|
43
44
|
|
|
44
45
|
const { resolve, relative, dirname, basename } = require('node:path')
|
|
45
46
|
const util = require('node:util')
|
|
@@ -344,7 +345,28 @@ class Node {
|
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
get overridden () {
|
|
347
|
-
|
|
348
|
+
if (!this.overrides) {
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
if (!this.overrides.value) {
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
if (this.overrides.name !== this.name) {
|
|
355
|
+
return false
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// The overrides rule is for a package with this name, but some override rules only apply to specific
|
|
359
|
+
// versions. To make sure this package was actually overridden, we check whether any edge going in
|
|
360
|
+
// had the rule applied to it, in which case its overrides set is different than its source node.
|
|
361
|
+
for (const edge of this.edgesIn) {
|
|
362
|
+
if (edge.overrides && edge.overrides.name === this.name && edge.overrides.value === this.version) {
|
|
363
|
+
if (!edge.overrides.isEqual(edge.from.overrides)) {
|
|
364
|
+
return true
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return false
|
|
348
370
|
}
|
|
349
371
|
|
|
350
372
|
get package () {
|
|
@@ -822,9 +844,6 @@ class Node {
|
|
|
822
844
|
target.root = root
|
|
823
845
|
}
|
|
824
846
|
|
|
825
|
-
if (!this.overrides && this.parent && this.parent.overrides) {
|
|
826
|
-
this.overrides = this.parent.overrides.getNodeRule(this)
|
|
827
|
-
}
|
|
828
847
|
// tree should always be valid upon root setter completion.
|
|
829
848
|
treeCheck(this)
|
|
830
849
|
if (this !== root) {
|
|
@@ -842,7 +861,7 @@ class Node {
|
|
|
842
861
|
}
|
|
843
862
|
|
|
844
863
|
for (const [name, path] of this.#workspaces.entries()) {
|
|
845
|
-
new Edge({ from: this, name, spec: `file:${path
|
|
864
|
+
new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
|
|
846
865
|
}
|
|
847
866
|
}
|
|
848
867
|
|
|
@@ -1006,10 +1025,21 @@ class Node {
|
|
|
1006
1025
|
return false
|
|
1007
1026
|
}
|
|
1008
1027
|
|
|
1009
|
-
//
|
|
1010
|
-
|
|
1011
|
-
|
|
1028
|
+
// If this node has no dependencies, then it's irrelevant to check the override
|
|
1029
|
+
// rules of the replacement node.
|
|
1030
|
+
if (this.edgesOut.size) {
|
|
1031
|
+
// XXX need to check for two root nodes?
|
|
1032
|
+
if (node.overrides) {
|
|
1033
|
+
if (!node.overrides.isEqual(this.overrides)) {
|
|
1034
|
+
return false
|
|
1035
|
+
}
|
|
1036
|
+
} else {
|
|
1037
|
+
if (this.overrides) {
|
|
1038
|
+
return false
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1012
1041
|
}
|
|
1042
|
+
|
|
1013
1043
|
ignorePeers = new Set(ignorePeers)
|
|
1014
1044
|
|
|
1015
1045
|
// gather up all the deps of this node and that are only depended
|
|
@@ -1077,8 +1107,13 @@ class Node {
|
|
|
1077
1107
|
return false
|
|
1078
1108
|
}
|
|
1079
1109
|
|
|
1080
|
-
// if we prefer dedupe, or if the version is
|
|
1081
|
-
if (preferDedupe || semver.
|
|
1110
|
+
// if we prefer dedupe, or if the version is equal, take the other
|
|
1111
|
+
if (preferDedupe || semver.eq(other.version, this.version)) {
|
|
1112
|
+
return true
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// if our current version isn't the result of an override, then prefer to take the greater version
|
|
1116
|
+
if (!this.overridden && semver.gt(other.version, this.version)) {
|
|
1082
1117
|
return true
|
|
1083
1118
|
}
|
|
1084
1119
|
|
|
@@ -1249,10 +1284,6 @@ class Node {
|
|
|
1249
1284
|
this[_changePath](newPath)
|
|
1250
1285
|
}
|
|
1251
1286
|
|
|
1252
|
-
if (parent.overrides) {
|
|
1253
|
-
this.overrides = parent.overrides.getNodeRule(this)
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
1287
|
// clobbers anything at that path, resets all appropriate references
|
|
1257
1288
|
this.root = parent.root
|
|
1258
1289
|
}
|
|
@@ -1346,9 +1377,87 @@ class Node {
|
|
|
1346
1377
|
this.edgesOut.set(edge.name, edge)
|
|
1347
1378
|
}
|
|
1348
1379
|
|
|
1349
|
-
|
|
1380
|
+
recalculateOutEdgesOverrides () {
|
|
1381
|
+
// For each edge out propogate the new overrides through.
|
|
1382
|
+
for (const edge of this.edgesOut.values()) {
|
|
1383
|
+
edge.reload(true)
|
|
1384
|
+
if (edge.to) {
|
|
1385
|
+
edge.to.updateOverridesEdgeInAdded(edge.overrides)
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
updateOverridesEdgeInRemoved (otherOverrideSet) {
|
|
1391
|
+
// If this edge's overrides isn't equal to this node's overrides, then removing it won't change newOverrideSet later.
|
|
1392
|
+
if (!this.overrides || !this.overrides.isEqual(otherOverrideSet)) {
|
|
1393
|
+
return false
|
|
1394
|
+
}
|
|
1395
|
+
let newOverrideSet
|
|
1396
|
+
for (const edge of this.edgesIn) {
|
|
1397
|
+
if (newOverrideSet && edge.overrides) {
|
|
1398
|
+
newOverrideSet = OverrideSet.findSpecificOverrideSet(edge.overrides, newOverrideSet)
|
|
1399
|
+
} else {
|
|
1400
|
+
newOverrideSet = edge.overrides
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
if (this.overrides.isEqual(newOverrideSet)) {
|
|
1404
|
+
return false
|
|
1405
|
+
}
|
|
1406
|
+
this.overrides = newOverrideSet
|
|
1407
|
+
if (this.overrides) {
|
|
1408
|
+
// Optimization: if there's any override set at all, then no non-extraneous node has an empty override set. So if we temporarily have no
|
|
1409
|
+
// override set (for example, we removed all the edges in), there's no use updating all the edges out right now. Let's just wait until
|
|
1410
|
+
// we have an actual override set later.
|
|
1411
|
+
this.recalculateOutEdgesOverrides()
|
|
1412
|
+
}
|
|
1413
|
+
return true
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// This logic isn't perfect either. When we have two edges in that have different override sets, then we have to decide which set is correct.
|
|
1417
|
+
// This function assumes the more specific override set is applicable, so if we have dependencies A->B->C and A->C
|
|
1418
|
+
// and an override set that specifies what happens for C under A->B, this will work even if the new A->C edge comes along and tries to change
|
|
1419
|
+
// the override set.
|
|
1420
|
+
// The strictly correct logic is not to allow two edges with different overrides to point to the same node, because even if this node can satisfy
|
|
1421
|
+
// both, one of its dependencies might need to be different depending on the edge leading to it.
|
|
1422
|
+
// However, this might cause a lot of duplication, because the conflict in the dependencies might never actually happen.
|
|
1423
|
+
updateOverridesEdgeInAdded (otherOverrideSet) {
|
|
1424
|
+
if (!otherOverrideSet) {
|
|
1425
|
+
// Assuming there are any overrides at all, the overrides field is never undefined for any node at the end state of the tree.
|
|
1426
|
+
// So if the new edge's overrides is undefined it will be updated later. So we can wait with updating the node's overrides field.
|
|
1427
|
+
return false
|
|
1428
|
+
}
|
|
1429
|
+
if (!this.overrides) {
|
|
1430
|
+
this.overrides = otherOverrideSet
|
|
1431
|
+
this.recalculateOutEdgesOverrides()
|
|
1432
|
+
return true
|
|
1433
|
+
}
|
|
1434
|
+
if (this.overrides.isEqual(otherOverrideSet)) {
|
|
1435
|
+
return false
|
|
1436
|
+
}
|
|
1437
|
+
const newOverrideSet = OverrideSet.findSpecificOverrideSet(this.overrides, otherOverrideSet)
|
|
1438
|
+
if (newOverrideSet) {
|
|
1439
|
+
if (!this.overrides.isEqual(newOverrideSet)) {
|
|
1440
|
+
this.overrides = newOverrideSet
|
|
1441
|
+
this.recalculateOutEdgesOverrides()
|
|
1442
|
+
return true
|
|
1443
|
+
}
|
|
1444
|
+
return false
|
|
1445
|
+
}
|
|
1446
|
+
// This is an error condition. We can only get here if the new override set is in conflict with the existing.
|
|
1447
|
+
log.silly('Conflicting override sets', this.name)
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
deleteEdgeIn (edge) {
|
|
1451
|
+
this.edgesIn.delete(edge)
|
|
1350
1452
|
if (edge.overrides) {
|
|
1351
|
-
this.
|
|
1453
|
+
this.updateOverridesEdgeInRemoved(edge.overrides)
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
addEdgeIn (edge) {
|
|
1458
|
+
// We need to handle the case where the new edge in has an overrides field which is different from the current value.
|
|
1459
|
+
if (!this.overrides || !this.overrides.isEqual(edge.overrides)) {
|
|
1460
|
+
this.updateOverridesEdgeInAdded(edge.overrides)
|
|
1352
1461
|
}
|
|
1353
1462
|
|
|
1354
1463
|
this.edgesIn.add(edge)
|
package/lib/override-set.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const npa = require('npm-package-arg')
|
|
2
2
|
const semver = require('semver')
|
|
3
|
+
const { log } = require('proc-log')
|
|
3
4
|
|
|
4
5
|
class OverrideSet {
|
|
5
6
|
constructor ({ overrides, key, parent }) {
|
|
@@ -44,6 +45,43 @@ class OverrideSet {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
childrenAreEqual (other) {
|
|
49
|
+
if (this.children.size !== other.children.size) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
for (const [key] of this.children) {
|
|
53
|
+
if (!other.children.has(key)) {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
if (this.children.get(key).value !== other.children.get(key).value) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
if (!this.children.get(key).childrenAreEqual(other.children.get(key))) {
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isEqual (other) {
|
|
67
|
+
if (this === other) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
if (!other) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
if (this.key !== other.key || this.value !== other.value) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
if (!this.childrenAreEqual(other)) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
if (!this.parent) {
|
|
80
|
+
return !other.parent
|
|
81
|
+
}
|
|
82
|
+
return this.parent.isEqual(other.parent)
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
getEdgeRule (edge) {
|
|
48
86
|
for (const rule of this.ruleset.values()) {
|
|
49
87
|
if (rule.name !== edge.name) {
|
|
@@ -55,7 +93,9 @@ class OverrideSet {
|
|
|
55
93
|
return rule
|
|
56
94
|
}
|
|
57
95
|
|
|
58
|
-
|
|
96
|
+
// We need to use the rawSpec here, because the spec has the overrides applied to it already.
|
|
97
|
+
// rawSpec can be undefined, so we need to use the fallback value of spec if it is.
|
|
98
|
+
let spec = npa(`${edge.name}@${edge.rawSpec || edge.spec}`)
|
|
59
99
|
if (spec.type === 'alias') {
|
|
60
100
|
spec = spec.subSpec
|
|
61
101
|
}
|
|
@@ -142,6 +182,28 @@ class OverrideSet {
|
|
|
142
182
|
|
|
143
183
|
return ruleset
|
|
144
184
|
}
|
|
185
|
+
|
|
186
|
+
static findSpecificOverrideSet (first, second) {
|
|
187
|
+
for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) {
|
|
188
|
+
if (overrideSet.isEqual(first)) {
|
|
189
|
+
return second
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) {
|
|
193
|
+
if (overrideSet.isEqual(second)) {
|
|
194
|
+
return first
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The override sets are incomparable. Neither one contains the other.
|
|
199
|
+
log.silly('Conflicting override sets', first, second)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
static doOverrideSetsConflict (first, second) {
|
|
203
|
+
// If override sets contain one another then we can try to use the more specific one.
|
|
204
|
+
// If neither one is more specific, then we consider them to be in conflict.
|
|
205
|
+
return (this.findSpecificOverrideSet(first, second) === undefined)
|
|
206
|
+
}
|
|
145
207
|
}
|
|
146
208
|
|
|
147
209
|
module.exports = OverrideSet
|
package/lib/shrinkwrap.js
CHANGED
|
@@ -817,7 +817,7 @@ class Shrinkwrap {
|
|
|
817
817
|
if (!/^file:/.test(resolved)) {
|
|
818
818
|
pathFixed = resolved
|
|
819
819
|
} else {
|
|
820
|
-
pathFixed = `file:${resolve(this.path, resolved.slice(5))
|
|
820
|
+
pathFixed = `file:${resolve(this.path, resolved.slice(5))}`
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
823
|
|
|
@@ -1011,7 +1011,7 @@ class Shrinkwrap {
|
|
|
1011
1011
|
}
|
|
1012
1012
|
|
|
1013
1013
|
if (node.isLink) {
|
|
1014
|
-
lock.version = `file:${relpath(this.path, node.realpath)
|
|
1014
|
+
lock.version = `file:${relpath(this.path, node.realpath)}`
|
|
1015
1015
|
} else if (spec && (spec.type === 'file' || spec.type === 'remote')) {
|
|
1016
1016
|
lock.version = spec.saveSpec
|
|
1017
1017
|
} else if (spec && spec.type === 'git' || rSpec.type === 'git') {
|
|
@@ -1089,7 +1089,7 @@ class Shrinkwrap {
|
|
|
1089
1089
|
// this especially shows up with workspace edges when the root
|
|
1090
1090
|
// node is also a workspace in the set.
|
|
1091
1091
|
const p = resolve(node.realpath, spec.slice('file:'.length))
|
|
1092
|
-
set[k] = `file:${relpath(node.realpath, p)
|
|
1092
|
+
set[k] = `file:${relpath(node.realpath, p)}`
|
|
1093
1093
|
} else {
|
|
1094
1094
|
set[k] = spec
|
|
1095
1095
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npmcli/arborist",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.1",
|
|
4
4
|
"description": "Manage node_modules trees",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@isaacs/string-locale-compare": "^1.1.0",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@npmcli/eslint-config": "^5.0.1",
|
|
43
43
|
"@npmcli/mock-registry": "^1.0.0",
|
|
44
|
-
"@npmcli/template-oss": "4.23.
|
|
44
|
+
"@npmcli/template-oss": "4.23.6",
|
|
45
45
|
"benchmark": "^2.1.4",
|
|
46
46
|
"minify-registry-metadata": "^4.0.0",
|
|
47
47
|
"nock": "^13.3.3",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
},
|
|
94
94
|
"templateOSS": {
|
|
95
95
|
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
|
|
96
|
-
"version": "4.23.
|
|
96
|
+
"version": "4.23.6",
|
|
97
97
|
"content": "../../scripts/template-oss/index.js"
|
|
98
98
|
}
|
|
99
99
|
}
|