Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions workspaces/arborist/lib/arborist/load-actual.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,8 @@ module.exports = cls => class ActualLoader extends cls {

const depPromises = []
for (const [name, edge] of node.edgesOut.entries()) {
const notMissing = !edge.missing &&
!(edge.to && (edge.to.dummy || edge.to.parent !== node))
// An unresolved optional edge has edge.missing === false, so walk any edge with no target to find a store sibling.
const notMissing = edge.to && !(edge.to.dummy || edge.to.parent !== node)
if (notMissing) {
continue
}
Expand Down
33 changes: 33 additions & 0 deletions workspaces/arborist/test/arborist/reify.js
Original file line number Diff line number Diff line change
Expand Up @@ -4491,6 +4491,39 @@ t.test('install strategy linked', async (t) => {
await reify(path, { installStrategy: 'hoisted' })
t.notOk(fs.existsSync(resolve(nm, '.store')), '.store removed by a full hoisted install')
})

t.test('installed transitive optional dep is materialized in the FS-scanned actual tree', async t => {
// Regression for #9622: under linked, #findMissingEdges skipped unresolved optional edges, so an installed transitive optional dep was reported UNMET.
const optionalDependencies = { bar: '^1.0.0', baz: '^1.0.0' }
const path = t.testdir({
'package.json': JSON.stringify({ name: 'root', version: '1.0.0', dependencies: { foo: '1.0.0' } }),
src: {
// bar is installed (store sibling); baz is platform-incompatible so its edge must stay unresolved.
foo: { 'package.json': JSON.stringify({ name: 'foo', version: '1.0.0', optionalDependencies }) },
bar: { 'package.json': JSON.stringify({ name: 'bar', version: '1.2.3' }) },
},
})
const registry = new MockRegistry({ strict: false, tap: t, registry: 'https://registry.npmjs.org' })
const fooManifest = registry.manifest({ name: 'foo', packuments: [{ version: '1.0.0', optionalDependencies }] })
const barManifest = registry.manifest({ name: 'bar', packuments: [{ version: '1.2.3' }] })
const bazManifest = registry.manifest({ name: 'baz', packuments: [{ version: '1.0.0', os: ['nonexistent-platform'] }] })
await registry.package({ manifest: fooManifest, tarballs: { '1.0.0': join(path, 'src/foo') } })
await registry.package({ manifest: barManifest, tarballs: { '1.2.3': join(path, 'src/bar') } })
await registry.package({ manifest: bazManifest })

await reify(path, { installStrategy: 'linked' })
const fooStore = fs.readdirSync(resolve(path, 'node_modules/.store')).find(d => d.startsWith('foo@'))
t.ok(fooStore && fs.existsSync(resolve(path, 'node_modules/.store', fooStore, 'node_modules/bar')),
'bar is installed as a store sibling of foo')

// forceActual skips the hidden lockfile, exercising the FS-scan path.
const actual = await newArb({ path, installStrategy: 'linked' }).loadActual({ forceActual: true })
const foo = [...actual.inventory.values()].find(n => n.name === 'foo' && !n.isLink)
const barEdge = foo.edgesOut.get('bar')
t.ok(barEdge.to, 'the optional bar edge resolves to an installed node, not UNMET')
t.equal(barEdge.to.version, '1.2.3', 'bar resolved to the installed store node')
t.notOk(foo.edgesOut.get('baz').to, 'a genuinely uninstalled optional dep stays unresolved')
})
})

t.test('linked strategy --workspaces=false and --include-workspace-root do not crash', async t => {
Expand Down
Loading