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
9 changes: 7 additions & 2 deletions workspaces/arborist/lib/query-selector-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -793,9 +793,14 @@ const hasParent = (node, compareNodes) => {
compareNode = compareNode.target
}

// follows logical parent for link ancestors
// Follows logical parent for link ancestors (e.g. workspaces whose target lives outside node_modules).
// Only match if the node has a link whose parent is the compareNode. Without this check, nodes deep in the store (linked strategy) would incorrectly match as children of root via their fsParent chain.
if (node.isTop && (node.resolveParent === compareNode)) {
return true
for (const link of node.linksIn) {
if (link.parent === compareNode) {
return true
}
}
}
// follows edges-in to check if they match a possible parent
for (const edge of node.edgesIn) {
Expand Down
87 changes: 87 additions & 0 deletions workspaces/arborist/test/query-selector-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -1036,3 +1036,90 @@ t.test('query-selector-all', async t => {
['#b *', ['a@1.0.0', 'bar@2.0.0', 'baz@1.0.0', 'lorem@1.0.0', 'moo@3.0.0']],
])
})

// Simulates the linked install strategy layout where packages live in node_modules/.store/ and are symlinked from node_modules/.
t.test('linked strategy: :root > * excludes transitive deps and store nodes', async t => {
/*
fixture tree (linked strategy layout):

linked-test@1.0.0
├── nopt@7.2.1 (symlink -> .store/nopt-hash/node_modules/nopt)
├── ini@4.1.3 (symlink -> .store/ini-hash/node_modules/ini)
└── .store/
├── nopt-hash/
│ └── node_modules/
│ ├── nopt@7.2.1 (actual)
│ └── abbrev (symlink -> ../../abbrev-hash/node_modules/abbrev)
├── ini-hash/
│ └── node_modules/
│ └── ini@4.1.3 (actual)
└── abbrev-hash/
└── node_modules/
└── abbrev@2.0.0 (actual)
*/

const path = t.testdir({
node_modules: {
nopt: t.fixture('symlink', '.store/nopt-hash/node_modules/nopt'),
ini: t.fixture('symlink', '.store/ini-hash/node_modules/ini'),
'.store': {
'nopt-hash': {
node_modules: {
nopt: {
'package.json': JSON.stringify({
name: 'nopt',
version: '7.2.1',
dependencies: { abbrev: '^2.0.0' },
}),
},
abbrev: t.fixture('symlink', '../../abbrev-hash/node_modules/abbrev'),
},
},
'ini-hash': {
node_modules: {
ini: {
'package.json': JSON.stringify({
name: 'ini',
version: '4.1.3',
}),
},
},
},
'abbrev-hash': {
node_modules: {
abbrev: {
'package.json': JSON.stringify({
name: 'abbrev',
version: '2.0.0',
}),
},
},
},
},
},
'package.json': JSON.stringify({
name: 'linked-test',
version: '1.0.0',
dependencies: {
nopt: '^7.0.0',
ini: '^4.0.0',
},
}),
})

const arb = new Arborist({ path })
const tree = await arb.loadActual()

const rootChildren = await querySelectorAll(tree, ':root > *')
t.same(rootChildren, [
'ini@4.1.3',
'nopt@7.2.1',
], ':root > * should only return direct dependencies, not transitive deps or store nodes')

const rootDescendants = await querySelectorAll(tree, ':root *')
t.same(rootDescendants, [
'abbrev@2.0.0',
'ini@4.1.3',
'nopt@7.2.1',
], ':root * should return all descendants including transitive deps')
})
Loading