Skip to content
Merged
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
7 changes: 4 additions & 3 deletions workspaces/arborist/lib/arborist/load-actual.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,10 @@ 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))
if (notMissing) {
// An unresolved optional edge reports missing === false, so check the target directly.
// Otherwise an installed optional dep that lives only as a store sibling is never loaded.
const resolved = edge.to && !edge.to.dummy && edge.to.parent === node
if (resolved) {
continue
}

Expand Down
37 changes: 37 additions & 0 deletions workspaces/arborist/test/arborist/load-actual.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,43 @@ t.test('store nodes do not load devDependencies as required edges', async t => {
t.notOk(dep.edgesOut.get('a-dev-dep'), 'devDependency of a store node is not a required edge')
})

t.test('loads an installed transitive optional dep from the linked store', async t => {
// A transitive optional dep lives as a store sibling, and its edge reports missing === false despite having no target.
// #findMissingEdges must still walk it, or npm sbom/query omit the installed dep.
const path = t.testdir({
'package.json': JSON.stringify({
name: 'root',
version: '1.0.0',
dependencies: { dep: '1.0.0' },
}),
node_modules: {
dep: t.fixture('symlink', '.store/dep@1.0.0/node_modules/dep'),
'.store': {
'dep@1.0.0': {
node_modules: {
dep: {
'package.json': JSON.stringify({
name: 'dep',
version: '1.0.0',
optionalDependencies: { opt: '^1.0.0' },
}),
},
// the optional dep is installed as a store sibling of its consumer
opt: { 'package.json': JSON.stringify({ name: 'opt', version: '1.0.0' }) },
},
},
},
},
})

const tree = await loadActual(path)
const dep = tree.children.get('dep').target
const edge = dep.edgesOut.get('opt')
t.ok(edge && !edge.error, 'optional edge resolves')
t.equal(edge.to?.name, 'opt', 'edge resolves to the installed package')
t.ok([...tree.inventory.values()].some(n => n.name === 'opt'), 'opt is present in the inventory')
})

t.test('a project located under a .store path still loads its own devDependencies', async t => {
// The loaded root must never be treated as a store node, even when its own path happens to sit under a node_modules/.store directory.
const path = t.testdir({
Expand Down
Loading