diff --git a/workspaces/arborist/lib/edge.js b/workspaces/arborist/lib/edge.js index 83a9c18cb6aac..8a5e060ec1427 100644 --- a/workspaces/arborist/lib/edge.js +++ b/workspaces/arborist/lib/edge.js @@ -110,7 +110,7 @@ class Edge { // NOTE: this condition means we explicitly do not support overriding // bundled or shrinkwrapped dependencies - if (node.hasShrinkwrap || node.inShrinkwrap || node.inBundle) { + if (node.hasShrinkwrap || node.inShrinkwrap || node.inDepBundle) { return depValid(node, this.rawSpec, this.#accept, this.#from) } diff --git a/workspaces/arborist/test/arborist/build-ideal-tree.js b/workspaces/arborist/test/arborist/build-ideal-tree.js index aa632426c03e1..7a87f3ea50826 100644 --- a/workspaces/arborist/test/arborist/build-ideal-tree.js +++ b/workspaces/arborist/test/arborist/build-ideal-tree.js @@ -4571,3 +4571,82 @@ t.test('skip invalid peerOptional edges in problemEdges when save=false (#8726)' 'shared stays at 1.1.0 - peerOptional mismatch is not treated as a problem') t.ok(tree.children.get('util'), 'util is in the tree') }) + +t.test('overrides with bundledDependencies', async t => { + t.test('does not infinite loop with bundledDependencies and overrides', async t => { + // https://github.com/npm/cli/issues/9227 + const registry = createRegistry(t, false) + + const bPacks = registry.packuments([ + { version: '1.0.0', dependencies: { bar: '1.0.0' } }, + ], 'b') + const cPacks = registry.packuments([ + { version: '1.0.0', dependencies: { bar: '1.0.0' } }, + ], 'c') + const barPacks = registry.packuments(['1.0.0', '2.0.0'], 'bar') + await registry.package({ manifest: registry.manifest({ name: 'b', packuments: bPacks }) }) + await registry.package({ manifest: registry.manifest({ name: 'c', packuments: cPacks }) }) + await registry.package({ manifest: registry.manifest({ name: 'bar', packuments: barPacks }), times: 2 }) + + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'root', + dependencies: { b: '1.0.0', c: '1.0.0' }, + bundledDependencies: true, + overrides: { bar: '2.0.0' }, + }), + }) + + const tree = await buildIdeal(path) + t.equal(tree.children.get('bar').version, '2.0.0', 'override applied') + }) + + t.test('overrides apply to deps the root will bundle and edges are valid', async t => { + const registry = createRegistry(t, false) + + const fooPacks = registry.packuments([ + { version: '1.0.0', dependencies: { bar: '1.0.0' } }, + ], 'foo') + const barPacks = registry.packuments(['1.0.0', '2.0.0'], 'bar') + await registry.package({ manifest: registry.manifest({ name: 'foo', packuments: fooPacks }) }) + await registry.package({ manifest: registry.manifest({ name: 'bar', packuments: barPacks }) }) + + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'root', + dependencies: { foo: '1.0.0' }, + bundledDependencies: ['foo'], + overrides: { bar: '2.0.0' }, + }), + }) + + const tree = await buildIdeal(path) + t.equal(tree.children.get('bar').version, '2.0.0', 'override installed correct version') + + const fooBarEdge = tree.edgesOut.get('foo').to.edgesOut.get('bar') + t.equal(fooBarEdge.valid, true, 'overridden edge is valid') + }) + + t.test('overrides do not apply inside a dependency that bundles', async t => { + const registry = createRegistry(t, false) + + const depPacks = registry.packuments([{ + version: '1.0.0', + dependencies: { bar: '1.0.0' }, + bundleDependencies: ['bar'], + }], 'dep') + await registry.package({ manifest: registry.manifest({ name: 'dep', packuments: depPacks }) }) + + const path = t.testdir({ + 'package.json': JSON.stringify({ + name: 'root', + dependencies: { dep: '1.0.0' }, + overrides: { bar: '2.0.0' }, + }), + }) + + const tree = await buildIdeal(path) + t.equal(tree.edgesOut.get('dep').valid, true, 'dep edge is valid') + t.notOk(tree.children.get('bar'), 'bar stays inside dep bundle') + }) +}) diff --git a/workspaces/arborist/test/edge.js b/workspaces/arborist/test/edge.js index e3f6204da9556..739adbbffe258 100644 --- a/workspaces/arborist/test/edge.js +++ b/workspaces/arborist/test/edge.js @@ -1332,3 +1332,81 @@ t.test('edge with overrides should not crash when target has no overrides', t => t.ok(edge.valid, 'edge should be valid') t.end() }) + +t.test('overrides and bundled/shrinkwrapped deps in satisfiedBy', t => { + const makeNode = (name, version, extras = {}) => ({ + name, + packageName: name, + version, + package: { name, version }, + edgesOut: new Map(), + edgesIn: new Set(), + explain: () => `${name}@${version}`, + isTop: false, + parent: top, + resolve: () => undefined, + addEdgeOut (edge) { + this.edgesOut.set(edge.name, edge) + }, + addEdgeIn (edge) { + this.edgesIn.add(edge) + }, + deleteEdgeIn (edge) { + this.edgesIn.delete(edge) + }, + hasShrinkwrap: false, + inShrinkwrap: false, + inBundle: false, + inDepBundle: false, + ...extras, + }) + + const overrides = new OverrideSet({ overrides: { bar: '2.x' } }) + a.overrides = overrides + + const makeOverriddenEdge = () => { + const edge = new Edge({ + from: a, + type: 'prod', + name: 'bar', + spec: '1.x', + overrides: overrides.getEdgeRule({ name: 'bar', spec: '1.x' }), + }) + reset(a) + a.overrides = overrides + return edge + } + + t.test('node bundled by root uses overridden spec', t => { + const edge = makeOverriddenEdge() + const node = makeNode('bar', '2.0.0', { inBundle: true, inDepBundle: false }) + t.ok(edge.satisfiedBy(node), 'bar@2.0.0 bundled by root satisfies override 2.x') + + const nodeOld = makeNode('bar', '1.0.0', { inBundle: true, inDepBundle: false }) + t.notOk(edge.satisfiedBy(nodeOld), 'bar@1.0.0 bundled by root does not satisfy override 2.x') + t.end() + }) + + t.test('node bundled inside a dependency uses rawSpec', t => { + const edge = makeOverriddenEdge() + const node = makeNode('bar', '1.0.0', { inBundle: true, inDepBundle: true }) + t.ok(edge.satisfiedBy(node), 'bar@1.0.0 in dep bundle satisfies rawSpec 1.x') + + const nodeNew = makeNode('bar', '2.0.0', { inBundle: true, inDepBundle: true }) + t.notOk(edge.satisfiedBy(nodeNew), 'bar@2.0.0 in dep bundle does not satisfy rawSpec 1.x') + t.end() + }) + + t.test('node inside a shrinkwrap uses rawSpec', t => { + const edge = makeOverriddenEdge() + const node = makeNode('bar', '1.0.0', { inShrinkwrap: true }) + t.ok(edge.satisfiedBy(node), 'bar@1.0.0 in shrinkwrap satisfies rawSpec 1.x') + + const nodeNew = makeNode('bar', '2.0.0', { inShrinkwrap: true }) + t.notOk(edge.satisfiedBy(nodeNew), 'bar@2.0.0 in shrinkwrap does not satisfy rawSpec 1.x') + t.end() + }) + + delete a.overrides + t.end() +})