Skip to content
This repository has been archived by the owner on Jan 20, 2022. It is now read-only.

Commit

Permalink
feat: allow references in overrides
Browse files Browse the repository at this point in the history
this allows a user to specify an override as $packageName where
'packageName' is a direct dependency of the root package and that
reference will be resolved to the range of the referenced dep
  • Loading branch information
nlf committed Dec 1, 2021
1 parent d8aced9 commit ac31ecc
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 5 deletions.
20 changes: 18 additions & 2 deletions lib/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,24 @@ class Edge {
}

get spec () {
return this.overrides && this.overrides.value ? this.overrides.value
: this[_spec]
if (this.overrides && this.overrides.value && this.overrides.name === this.name) {
if (this.overrides.value.startsWith('$')) {
const ref = this.overrides.value.slice(1)
const pkg = this.from.root.package
const overrideSpec = (pkg.devDependencies && pkg.devDependencies[ref]) ||
(pkg.optionalDependencies && pkg.optionalDependencies[ref]) ||
(pkg.dependencies && pkg.dependencies[ref]) ||
(pkg.peerDependencies && pkg.peerDependencies[ref])

if (overrideSpec) {
return overrideSpec
}

throw new Error(`Unable to resolve reference ${this.overrides.value}`)
}
return this.overrides.value
}
return this[_spec]
}

get accept () {
Expand Down
2 changes: 1 addition & 1 deletion lib/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,7 @@ class Node {
for (const edge of this.edgesOut.values()) {
// if these differ an override has been applied, those are not allowed
// for top level dependencies so throw an error
if (edge.spec !== edge.rawSpec) {
if (edge.spec !== edge.rawSpec && !edge.spec.startsWith('$')) {
throw Object.assign(new Error(`Override for ${edge.name}@${edge.rawSpec} conflicts with direct dependency`), { code: 'EOVERRIDE' })
}
}
Expand Down
84 changes: 84 additions & 0 deletions test/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -3410,6 +3410,90 @@ t.test('overrides', t => {
t.equal(barEdge.to, nestedBarEdge.to, 'deduplicated tree correctly')
})

t.test('overrides a nested dependency with a reference to a direct dependency', async (t) => {
generateNocks(t, {
foo: {
versions: ['1.0.0', '1.0.1', '2.0.0'],
dependencies: ['bar'],
},
bar: {
versions: ['1.0.0', '1.0.1', '2.0.0'],
},
})

const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
dependencies: {
foo: '2.0.0',
bar: '1.0.1',
},
overrides: {
foo: {
bar: '$bar',
},
},
}),
})

const tree = await buildIdeal(path)

const fooEdge = tree.edgesOut.get('foo')
t.equal(fooEdge.valid, true, 'foo is valid')
t.equal(fooEdge.to.version, '2.0.0')

const barEdge = tree.edgesOut.get('bar')
t.equal(barEdge.valid, true, 'top level bar is valid')
t.equal(barEdge.to.version, '1.0.1')

const nestedBarEdge = fooEdge.to.edgesOut.get('bar')
t.equal(nestedBarEdge.valid, true, 'nested bar is valid')
t.equal(nestedBarEdge.to.version, '1.0.1', 'nested bar version was overridden')

t.equal(barEdge.to, nestedBarEdge.to, 'deduplicated tree correctly')
})

t.test('overrides a nested dependency with a reference to a direct dependency without a top level identifier', async (t) => {
generateNocks(t, {
foo: {
versions: ['1.0.0', '1.0.1', '2.0.0'],
dependencies: ['bar'],
},
bar: {
versions: ['1.0.0', '1.0.1', '2.0.0'],
},
})

const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
dependencies: {
foo: '2.0.0',
bar: '1.0.1',
},
overrides: {
bar: '$bar',
},
}),
})

const tree = await buildIdeal(path)

const fooEdge = tree.edgesOut.get('foo')
t.equal(fooEdge.valid, true, 'foo is valid')
t.equal(fooEdge.to.version, '2.0.0')

const barEdge = tree.edgesOut.get('bar')
t.equal(barEdge.valid, true, 'top level bar is valid')
t.equal(barEdge.to.version, '1.0.1')

const nestedBarEdge = fooEdge.to.edgesOut.get('bar')
t.equal(nestedBarEdge.valid, true, 'nested bar is valid')
t.equal(nestedBarEdge.to.version, '1.0.1', 'nested bar version was overridden')

t.equal(barEdge.to, nestedBarEdge.to, 'deduplicated tree correctly')
})

t.test('overrides a peerDependency', async (t) => {
generateNocks(t, {
foo: {
Expand Down
137 changes: 137 additions & 0 deletions test/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,143 @@ t.ok(new Edge({
}).satisfiedBy(c), 'c@2 satisfies spec:1.x, no matching override')
reset(a)

const referenceTop = {
name: 'referenceTop',
packageName: 'referenceTop',
edgesOut: new Map(),
edgesIn: new Set(),
explain: () => 'referenceTop explanation',
package: {
name: 'referenceTop',
version: '1.2.3',
dependencies: {
referenceChild: '^1.0.0',
},
overrides: {
referenceGrandchild: '$referenceChild',
},
},
get version () {
return this.package.version
},
isTop: true,
resolve (n) {
return n === 'referenceChild'
? referenceChild : n === 'referenceGrandchild'
? referenceGrandchild : null
},
addEdgeOut (edge) {
edge.overrides = this.overrides.getEdgeRule(edge)
this.edgesOut.set(edge.name, edge)
},
addEdgeIn (edge) {
this.edgesIn.add(edge)
},
overrides: new OverrideSet({
overrides: {
referenceGrandchild: '$referenceChild',
},
}),
}

const referenceChild = {
name: 'referenceChild',
packageName: 'referenceChild',
edgesOut: new Map(),
edgesIn: new Set(),
explain: () => 'referenceChild explanation',
package: {
name: 'referenceChild',
version: '1.2.3',
dependencies: {
referenceGrandchild: '^2.0.0',
},
},
get version () {
return this.package.version
},
isTop: false,
parent: referenceTop,
root: referenceTop,
resolve (n) {
return n === 'referenceChild'
? referenceChild : n === 'referenceGrandchild'
? referenceGrandchild : null
},
addEdgeOut (edge) {
edge.overrides = this.overrides.getEdgeRule(edge)
this.edgesOut.set(edge.name, edge)
},
addEdgeIn (edge) {
this.overrides = edge.overrides
this.edgesIn.add(edge)
},
}

new Edge({
from: referenceTop,
to: referenceChild,
type: 'prod',
name: 'referenceChild',
spec: '^1.0.0',
})

const referenceGrandchild = {
name: 'referenceGrandchild',
packageName: 'referenceGrandchild',
edgesOut: new Map(),
edgesIn: new Set(),
explain: () => 'referenceGrandchild explanation',
package: {
name: 'referenceGrandchild',
version: '1.2.3',
},
get version () {
return this.package.version
},
isTop: false,
parent: referenceChild,
root: referenceTop,
resolve (n) {
return n === 'referenceChild'
? referenceChild : n === 'referenceGrandchild'
? referenceGrandchild : null
},
addEdgeOut (edge) {
edge.overrides = this.overrides.getEdgeRule(edge)
this.edgesOut.set(edge.name, edge)
},
addEdgeIn (edge) {
this.overrides = edge.overrides
this.edgesIn.add(edge)
},
}

const referenceGrandchildEdge = new Edge({
from: referenceChild,
to: referenceGrandchild,
type: 'prod',
name: 'referenceGrandchild',
spec: '^2.0.0',
})

t.equal(referenceGrandchildEdge.spec, '^1.0.0', 'resolves spec to prod dep')
t.equal(referenceGrandchildEdge.valid, true, 'edge is valid')

delete referenceTop.package.dependencies.referenceChild
t.throws(() => referenceGrandchildEdge.spec, 'spec getter throws for missing dep')

referenceTop.package.devDependencies = { referenceChild: '^2.0.0' }
t.equal(referenceGrandchildEdge.spec, '^2.0.0', 'resolves spec for dev dep')

delete referenceTop.package.devDependencies
referenceTop.package.optionalDependencies = { referenceChild: '^3.0.0' }
t.equal(referenceGrandchildEdge.spec, '^3.0.0', 'resolves spec for optional dep')

delete referenceTop.package.optionalDependencies
referenceTop.package.peerDependencies = { referenceChild: '^4.0.0' }
t.equal(referenceGrandchildEdge.spec, '^4.0.0', 'resolves spec for peer dep')

const badOverride = {
name: 'c',
packageName: 'c',
Expand Down
2 changes: 0 additions & 2 deletions test/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -2840,7 +2840,5 @@ t.test('overrides', (t) => {
t.equal(original.canReplaceWith(goodReplacement), true, 'same overrides passes')
})

// XXX write negative tests too

t.end()
})

0 comments on commit ac31ecc

Please sign in to comment.