@npmcli/arborist 8.0.3 → 8.0.4
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/reify.js +9 -3
- package/lib/override-set.js +155 -0
- package/package.json +1 -1
package/lib/arborist/reify.js
CHANGED
|
@@ -139,9 +139,11 @@ module.exports = cls => class Reifier extends cls {
|
|
|
139
139
|
// of Node/Link trees
|
|
140
140
|
log.warn('reify', 'The "linked" install strategy is EXPERIMENTAL and may contain bugs.')
|
|
141
141
|
this.idealTree = await this.createIsolatedTree()
|
|
142
|
-
this
|
|
143
|
-
this
|
|
144
|
-
|
|
142
|
+
if (this.actualTree) {
|
|
143
|
+
this.#linkedActualForDiff = this.#buildLinkedActualForDiff(
|
|
144
|
+
this.idealTree, this.actualTree
|
|
145
|
+
)
|
|
146
|
+
}
|
|
145
147
|
}
|
|
146
148
|
await this[_diffTrees]()
|
|
147
149
|
await this[_reifyPackages]()
|
|
@@ -849,6 +851,10 @@ module.exports = cls => class Reifier extends cls {
|
|
|
849
851
|
if (combined.has(child.path) || !existsSync(child.path)) {
|
|
850
852
|
continue
|
|
851
853
|
}
|
|
854
|
+
// Skip store links whose ideal realpath doesn't exist on disk yet — the store hash changed and the symlink needs recreating via ADD.
|
|
855
|
+
if (child.isLink && child.resolved?.startsWith('file:.store/') && !existsSync(child.realpath)) {
|
|
856
|
+
continue
|
|
857
|
+
}
|
|
852
858
|
let entry
|
|
853
859
|
if (child.isLink) {
|
|
854
860
|
entry = new IsolatedLink(child)
|
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) {
|
|
@@ -142,6 +180,123 @@ class OverrideSet {
|
|
|
142
180
|
|
|
143
181
|
return ruleset
|
|
144
182
|
}
|
|
183
|
+
|
|
184
|
+
static findSpecificOverrideSet (first, second) {
|
|
185
|
+
for (let overrideSet = second; overrideSet; overrideSet = overrideSet.parent) {
|
|
186
|
+
if (overrideSet.isEqual(first)) {
|
|
187
|
+
return second
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (let overrideSet = first; overrideSet; overrideSet = overrideSet.parent) {
|
|
191
|
+
if (overrideSet.isEqual(second)) {
|
|
192
|
+
return first
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// The override sets are incomparable (e.g. siblings like the "react" and "react-dom" children of the root override set). Check if they have semantically conflicting rules before treating this as an error.
|
|
197
|
+
if (this.haveConflictingRules(first, second)) {
|
|
198
|
+
log.silly('Conflicting override sets', first, second)
|
|
199
|
+
return undefined
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// The override sets are structurally incomparable but have compatible rules. Fall back to their nearest common ancestor so the node still has a valid override set.
|
|
203
|
+
return this.findCommonAncestor(first, second)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
static findCommonAncestor (first, second) {
|
|
207
|
+
const firstAncestors = []
|
|
208
|
+
for (const ancestor of first.ancestry()) {
|
|
209
|
+
firstAncestors.push(ancestor)
|
|
210
|
+
}
|
|
211
|
+
for (const secondAnc of second.ancestry()) {
|
|
212
|
+
for (const firstAnc of firstAncestors) {
|
|
213
|
+
if (firstAnc.isEqual(secondAnc)) {
|
|
214
|
+
return firstAnc
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static doOverrideSetsConflict (first, second) {
|
|
222
|
+
// If override sets contain one another then we can try to use the more specific one.
|
|
223
|
+
// If neither one is more specific, check for semantic conflicts.
|
|
224
|
+
const specificSet = this.findSpecificOverrideSet(first, second)
|
|
225
|
+
if (specificSet !== undefined) {
|
|
226
|
+
// One contains the other, so no conflict
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The override sets are structurally incomparable, but this doesn't necessarily
|
|
231
|
+
// mean they conflict. We need to check if they have conflicting version requirements
|
|
232
|
+
// for any package that appears in both rulesets.
|
|
233
|
+
return this.haveConflictingRules(first, second)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static haveConflictingRules (first, second) {
|
|
237
|
+
// Get all rules from both override sets
|
|
238
|
+
const firstRules = first.ruleset
|
|
239
|
+
const secondRules = second.ruleset
|
|
240
|
+
|
|
241
|
+
// Check each package that appears in both rulesets
|
|
242
|
+
for (const [key, firstRule] of firstRules) {
|
|
243
|
+
const secondRule = secondRules.get(key)
|
|
244
|
+
if (!secondRule) {
|
|
245
|
+
// Package only appears in one ruleset, no conflict
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Same rule object means no conflict
|
|
250
|
+
if (firstRule === secondRule || firstRule.isEqual(secondRule)) {
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Both rulesets have rules for this package with different values.
|
|
255
|
+
// Check if the version requirements are actually incompatible.
|
|
256
|
+
const firstValue = firstRule.value
|
|
257
|
+
const secondValue = secondRule.value
|
|
258
|
+
|
|
259
|
+
// If either value is a reference (starts with $), we can't determine
|
|
260
|
+
// compatibility here - the reference might resolve to compatible versions.
|
|
261
|
+
// We defer to runtime resolution rather than failing early.
|
|
262
|
+
if (firstValue.startsWith('$') || secondValue.startsWith('$')) {
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if the version ranges are compatible using semver
|
|
267
|
+
// If both specify version ranges, they conflict only if they have no overlap
|
|
268
|
+
try {
|
|
269
|
+
const firstSpec = npa(`${firstRule.name}@${firstValue}`)
|
|
270
|
+
const secondSpec = npa(`${secondRule.name}@${secondValue}`)
|
|
271
|
+
|
|
272
|
+
// For range/version types, check if they intersect
|
|
273
|
+
if ((firstSpec.type === 'range' || firstSpec.type === 'version') &&
|
|
274
|
+
(secondSpec.type === 'range' || secondSpec.type === 'version')) {
|
|
275
|
+
// Check if the ranges intersect
|
|
276
|
+
const firstRange = firstSpec.fetchSpec
|
|
277
|
+
const secondRange = secondSpec.fetchSpec
|
|
278
|
+
|
|
279
|
+
// If the ranges don't intersect, we have a real conflict
|
|
280
|
+
if (!semver.intersects(firstRange, secondRange)) {
|
|
281
|
+
log.silly('Found conflicting override rules', {
|
|
282
|
+
package: firstRule.name,
|
|
283
|
+
first: firstValue,
|
|
284
|
+
second: secondValue,
|
|
285
|
+
})
|
|
286
|
+
return true
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// For other types (git, file, directory, tag), we can't easily determine
|
|
290
|
+
// compatibility, so we conservatively assume no conflict
|
|
291
|
+
} catch {
|
|
292
|
+
// If we can't parse the specs, conservatively assume no conflict
|
|
293
|
+
// Real conflicts will be caught during dependency resolution
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// No conflicting rules found
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
145
300
|
}
|
|
146
301
|
|
|
147
302
|
module.exports = OverrideSet
|