Skip to content

Commit 04f53ce

Browse files
authored
fix(arborist): safely fallback on unresolved $ dependency references (#8180)
fixes #5730 Overrides that use a dollar sign need to resolve to a version string found in one of the package’s dependency fields. We now try two sources in order: Root Manifest (via sourceReference or this.#from.root.package): When a node is loaded from a sourceReference or if the node is part of a larger tree, the root package manifest is the first choice because it reflects the “authoritative” set of dependency versions that were installed. Local Manifest (this.#from.package): If the root manifest does not contain the key (for example, the dependency version isn’t listed there), we fall back to the local package manifest. This is usually more specific to the individual module and may include dependency fields that the root manifest omits. This two-step lookup ensures that if the expected dependency isn’t available at the root level—even though it might be defined locally—the override can still resolve correctly. Without this fallback, the override resolution would fail with an error, even though the local package had the required dependency version.
1 parent 26b6454 commit 04f53ce

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

workspaces/arborist/lib/edge.js

+26-12
Original file line numberDiff line numberDiff line change
@@ -206,29 +206,43 @@ class Edge {
206206
if (this.overrides?.value && this.overrides.value !== '*' && this.overrides.name === this.#name) {
207207
if (this.overrides.value.startsWith('$')) {
208208
const ref = this.overrides.value.slice(1)
209-
const pkg = this.#from?.sourceReference
209+
let pkg = this.#from?.sourceReference
210210
? this.#from?.sourceReference.root.package
211211
: this.#from?.root?.package
212-
if (pkg.devDependencies?.[ref]) {
213-
return pkg.devDependencies[ref]
214-
}
215-
if (pkg.optionalDependencies?.[ref]) {
216-
return pkg.optionalDependencies[ref]
217-
}
218-
if (pkg.dependencies?.[ref]) {
219-
return pkg.dependencies[ref]
220-
}
221-
if (pkg.peerDependencies?.[ref]) {
222-
return pkg.peerDependencies[ref]
212+
213+
let specValue = this.#calculateReferentialOverrideSpec(ref, pkg)
214+
215+
// If the package isn't found in the root package, fall back to the local package.
216+
if (!specValue) {
217+
pkg = this.#from?.package
218+
specValue = this.#calculateReferentialOverrideSpec(ref, pkg)
223219
}
224220

221+
if (specValue) {
222+
return specValue
223+
}
225224
throw new Error(`Unable to resolve reference ${this.overrides.value}`)
226225
}
227226
return this.overrides.value
228227
}
229228
return this.#spec
230229
}
231230

231+
#calculateReferentialOverrideSpec (ref, pkg) {
232+
if (pkg.devDependencies?.[ref]) {
233+
return pkg.devDependencies[ref]
234+
}
235+
if (pkg.optionalDependencies?.[ref]) {
236+
return pkg.optionalDependencies[ref]
237+
}
238+
if (pkg.dependencies?.[ref]) {
239+
return pkg.dependencies[ref]
240+
}
241+
if (pkg.peerDependencies?.[ref]) {
242+
return pkg.peerDependencies[ref]
243+
}
244+
}
245+
232246
get accept () {
233247
return this.#accept
234248
}

workspaces/arborist/test/edge.js

+49
Original file line numberDiff line numberDiff line change
@@ -1195,3 +1195,52 @@ t.test('overrideset comparison logic', (t) => {
11951195
t.ok(!overrides7.isEqual(overrides1), 'overridesets are different')
11961196
t.end()
11971197
})
1198+
1199+
t.test('override fallback to local when root missing dependency with from.overrides set', t => {
1200+
const localFrom = {
1201+
package: {
1202+
devDependencies: {
1203+
foo: '^1.2.3',
1204+
},
1205+
},
1206+
root: {
1207+
package: {
1208+
// no 'foo' defined here
1209+
},
1210+
},
1211+
edgesOut: new Map(),
1212+
edgesIn: new Set(),
1213+
// dummy overrides object that returns an override with isEqual defined
1214+
overrides: {
1215+
getEdgeRule (edge) {
1216+
return {
1217+
value: edge.overrides.value,
1218+
name: edge.overrides.name,
1219+
isEqual (other) {
1220+
return other && this.value === other.value && this.name === other.name
1221+
},
1222+
}
1223+
},
1224+
},
1225+
addEdgeOut (edge) {
1226+
this.edgesOut.set(edge.name, edge)
1227+
},
1228+
resolve () {
1229+
return null
1230+
},
1231+
}
1232+
1233+
const edge = new Edge({
1234+
from: localFrom,
1235+
type: 'prod',
1236+
name: 'foo',
1237+
spec: '1.x',
1238+
overrides: {
1239+
value: '$foo',
1240+
name: 'foo',
1241+
},
1242+
})
1243+
1244+
t.equal(edge.spec, '^1.2.3', 'should fallback to local package version from devDependencies')
1245+
t.end()
1246+
})

0 commit comments

Comments
 (0)