Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions workspaces/arborist/lib/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,11 @@ This is a one-time fix-up, please be patient...

if (!edge.to) {
if (!parentEdge) {
// a missing peerOptional is valid and will never be placed from
// here, so don't waste a packument fetch resolving it
if (edge.type === 'peerOptional') {
Comment thread
martinrrm marked this conversation as resolved.
continue
}
// easy, just put the thing there
await this.#nodeFromEdge(edge, node.parent, null, required)
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16755,96 +16755,94 @@ ArboristNode {
exports[`test/arborist/build-ideal-tree.js TAP detect conflicts in transitive peerOptional deps nest when peerOptional conflicts > must match snapshot 1`] = `
ArboristNode {
"children": Map {
"@isaacs/test-conflicted-optional-peer-dep-has-peer" => ArboristNode {
"@isaacs/test-conflicted-optional-peer-dep-has-peer-optional" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"spec": "1",
"type": "prod",
},
},
"edgesOut": Map {
"@isaacs/test-conflicted-optional-peer-dep-peer" => EdgeOut {
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"spec": "1",
"spec": "2",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"type": "peer",
"type": "peerOptional",
},
},
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-has-peer/-/test-conflicted-optional-peer-dep-has-peer-1.0.0.tgz",
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional/-/test-conflicted-optional-peer-dep-has-peer-optional-1.0.0.tgz",
"version": "1.0.0",
},
"@isaacs/test-conflicted-optional-peer-dep-meta-peer" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "",
"name": "@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"spec": "1",
"type": "prod",
},
},
"edgesOut": Map {
"@isaacs/test-conflicted-optional-peer-dep-has-peer" => EdgeOut {
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"spec": "1",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"type": "prod",
},
},
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-meta-peer/-/test-conflicted-optional-peer-dep-meta-peer-1.0.0.tgz",
"version": "1.0.0",
},
"@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional" => ArboristNode {
"children": Map {
"@isaacs/test-conflicted-optional-peer-dep-has-peer-optional" => ArboristNode {
"@isaacs/test-conflicted-optional-peer-dep-has-peer" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"spec": "1",
"type": "prod",
},
},
"edgesOut": Map {
"@isaacs/test-conflicted-optional-peer-dep-peer" => EdgeOut {
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"spec": "2",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"type": "peerOptional",
"spec": "1",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"type": "peer",
},
},
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional/-/test-conflicted-optional-peer-dep-has-peer-optional-1.0.0.tgz",
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-has-peer/-/test-conflicted-optional-peer-dep-has-peer-1.0.0.tgz",
"version": "1.0.0",
},
"@isaacs/test-conflicted-optional-peer-dep-peer" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"spec": "2",
"type": "peerOptional",
"spec": "1",
"type": "peer",
},
},
"extraneous": true,
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"optional": true,
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"peer": true,
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-peer/-/test-conflicted-optional-peer-dep-peer-2.0.0.tgz",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-peer/-/test-conflicted-optional-peer-dep-peer-1.0.0.tgz",
"version": "1.0.0",
},
},
"edgesIn": Set {
EdgeIn {
"from": "",
"name": "@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"spec": "1",
"type": "prod",
},
},
"edgesOut": Map {
"@isaacs/test-conflicted-optional-peer-dep-has-peer" => EdgeOut {
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer",
"spec": "1",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"type": "prod",
},
},
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-meta-peer/-/test-conflicted-optional-peer-dep-meta-peer-1.0.0.tgz",
"version": "1.0.0",
},
"@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "",
Expand All @@ -16857,7 +16855,7 @@ ArboristNode {
"@isaacs/test-conflicted-optional-peer-dep-has-peer-optional" => EdgeOut {
"name": "@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"spec": "1",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-meta-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"to": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"type": "prod",
},
},
Expand All @@ -16870,18 +16868,20 @@ ArboristNode {
"@isaacs/test-conflicted-optional-peer-dep-peer" => ArboristNode {
"edgesIn": Set {
EdgeIn {
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer",
"from": "node_modules/@isaacs/test-conflicted-optional-peer-dep-has-peer-optional",
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"spec": "1",
"type": "peer",
"spec": "2",
"type": "peerOptional",
},
},
"extraneous": true,
"location": "node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"name": "@isaacs/test-conflicted-optional-peer-dep-peer",
"optional": true,
"path": "{CWD}/test/fixtures/test-conflicted-optional-peer-dep/nest-peer-optional/node_modules/@isaacs/test-conflicted-optional-peer-dep-peer",
"peer": true,
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-peer/-/test-conflicted-optional-peer-dep-peer-1.0.0.tgz",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/test-conflicted-optional-peer-dep-peer/-/test-conflicted-optional-peer-dep-peer-2.0.0.tgz",
"version": "2.0.0",
},
},
"edgesOut": Map {
Expand Down
81 changes: 80 additions & 1 deletion workspaces/arborist/test/arborist/build-ideal-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -2358,6 +2358,16 @@ t.test('detect conflicts in transitive peerOptional deps', async t => {
const name = '@isaacs/test-conflicted-optional-peer-dep-peer'
const peers = tree.inventory.query('name', name)
t.equal(peers.size, 2, 'installed the peer dep twice to avoid conflict')
t.strictSame(
Comment thread
martinrrm marked this conversation as resolved.
Outdated
[...peers].map(p => p.version).sort(),
['1.0.0', '2.0.0'],
'both conflicting versions are present'
)
for (const peer of peers) {
for (const edge of peer.edgesIn) {
t.ok(edge.valid, `edge from ${edge.from.name} is valid`)
}
}
})

await t.test('omit peerOptionals when not needed for conflicts', async t => {
Expand Down Expand Up @@ -4885,6 +4895,75 @@ t.test('circular peer back-off does not crash when node is detached mid-resoluti
'backs off to plugin@1.0.0 to satisfy the optional peer instead of crashing')
})

t.test('does not fetch packuments for peerOptional deps that will not be installed', async t => {
const registry = createRegistry(t, false)

const hostPack = registry.packument({
name: 'host',
version: '1.0.0',
peerDependencies: { plugin: '^1.0.0' },
peerDependenciesMeta: { plugin: { optional: true } },
})
const hostManifest = registry.manifest({ name: 'host', packuments: [hostPack] })
await registry.package({ manifest: hostManifest })

const path = t.testdir({
'package.json': JSON.stringify({
dependencies: { host: '^1.0.0' },
}),
})

const arb = newArb(path)
const tree = await arb.buildIdealTree()

t.equal(tree.children.get('host').version, '1.0.0', 'installed host')
t.equal(tree.children.get('plugin'), undefined, 'did not install the optional peer')
const edge = tree.children.get('host').edgesOut.get('plugin')
t.equal(edge.type, 'peerOptional')
t.equal(edge.to, null, 'peerOptional edge left unresolved')
t.ok(edge.valid, 'missing peerOptional edge is valid')
})

t.test('resolves peerOptional deps installed by another dependent', async t => {
const registry = createRegistry(t, false)

const hostPack = registry.packument({
name: 'host',
version: '1.0.0',
peerDependencies: { plugin: '^1.0.0' },
peerDependenciesMeta: { plugin: { optional: true } },
})
const hostManifest = registry.manifest({ name: 'host', packuments: [hostPack] })
await registry.package({ manifest: hostManifest })

const otherPack = registry.packument({
name: 'other',
version: '1.0.0',
dependencies: { plugin: '^1.0.0' },
})
const otherManifest = registry.manifest({ name: 'other', packuments: [otherPack] })
await registry.package({ manifest: otherManifest })

const pluginManifest = registry.manifest({ name: 'plugin' })
await registry.package({ manifest: pluginManifest })

const path = t.testdir({
'package.json': JSON.stringify({
dependencies: { host: '^1.0.0', other: '^1.0.0' },
}),
})

const arb = newArb(path)
const tree = await arb.buildIdealTree()

const plugin = tree.children.get('plugin')
t.ok(plugin, 'installed the plugin for the dependent that requires it')
const edge = tree.children.get('host').edgesOut.get('plugin')
t.equal(edge.type, 'peerOptional')
t.equal(edge.to, plugin, 'peerOptional edge resolved to the installed plugin')
t.ok(edge.valid, 'peerOptional edge is valid')
})

t.test('peerOptional prefers existing tree node over registry fetch (#9249)', async t => {
// Reproduction: ts-jest has peerOptional jest-util@"^29||^30".
// @types/jest@28 → expect@28 → jest-util@28 placed at root first.
Expand Down Expand Up @@ -4930,7 +5009,7 @@ t.test('peerOptional prefers existing tree node over registry fetch (#9249)', as
// Only publish 28, 29, and 30.
const jestUtilPacks = registry.packuments(['28.0.0', '29.0.0', '30.0.0'], 'jest-util')
const jestUtilManifest = registry.manifest({ name: 'jest-util', packuments: jestUtilPacks })
await registry.package({ manifest: jestUtilManifest, times: 3 })
await registry.package({ manifest: jestUtilManifest, times: 2 })

const path = t.testdir({
'package.json': JSON.stringify({
Expand Down
Loading