fix: pnpm why -r --parseable (#10595)

close #8100
This commit is contained in:
Zoltan Kochan
2026-02-26 02:00:45 +01:00
committed by GitHub
parent 37091b7291
commit dcd16c7b36
3 changed files with 170 additions and 14 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/list": patch
"pnpm": patch
---
Fix `pnpm why -r --parseable` missing dependents when multiple workspace packages share the same dependency [#8100](https://github.com/pnpm/pnpm/issues/8100).

View File

@@ -30,18 +30,17 @@ function renderParseableForPackage (
},
pkg: PackageDependencyHierarchy
): string {
const pkgs = sortPackages(
flatten(
depPaths,
[
...(pkg.optionalDependencies ?? []),
...(pkg.dependencies ?? []),
...(pkg.devDependencies ?? []),
...(pkg.unsavedDependencies ?? []),
]
)
)
if (!opts.alwaysPrintRootPackage && (pkgs.length === 0)) return ''
const rootAlreadySeen = depPaths.has(pkg.path)
depPaths.add(pkg.path)
const allDeps = [
...(pkg.optionalDependencies ?? []),
...(pkg.dependencies ?? []),
...(pkg.devDependencies ?? []),
...(pkg.unsavedDependencies ?? []),
]
const pkgs = sortPackages(flatten(depPaths, allDeps))
if (rootAlreadySeen && pkgs.length === 0) return ''
if (!opts.alwaysPrintRootPackage && pkgs.length === 0 && allDeps.length === 0) return ''
if (opts.long) {
let firstLine = pkg.path
if (pkg.name) {
@@ -54,7 +53,7 @@ function renderParseableForPackage (
}
}
return [
firstLine,
...(rootAlreadySeen ? [] : [firstLine]),
...pkgs.map((pkgNode) => {
const node = pkgNode as DependencyNode
if (node.alias !== node.name) {
@@ -73,7 +72,7 @@ function renderParseableForPackage (
].join('\n')
}
return [
pkg.path,
...(rootAlreadySeen ? [] : [pkg.path]),
...pkgs.map((pkg) => pkg.path),
].join('\n')
}

View File

@@ -971,3 +971,154 @@ test('renderParseable displays file: protocol correctly for aliased packages', a
expect(output).toContain('my-alias my-local-pkg@file:local-pkg')
})
test('renderParseable search: shared dep across packages is not duplicated', async () => {
const output = await renderParseable(
[
{
name: 'pkg-a',
path: '/workspace/packages/pkg-a',
version: '1.0.0',
dependencies: [
{
alias: '@org/shared',
name: '@org/shared',
version: '1.0.0',
path: '/workspace/packages/shared',
isMissing: false,
isPeer: false,
isSkipped: false,
},
],
},
{
name: 'pkg-b',
path: '/workspace/packages/pkg-b',
version: '1.0.0',
dependencies: [
{
alias: '@org/shared',
name: '@org/shared',
version: '1.0.0',
path: '/workspace/packages/shared',
isMissing: false,
isPeer: false,
isSkipped: false,
},
],
},
{
name: '@org/shared',
path: '/workspace/packages/shared',
version: '1.0.0',
},
],
{
alwaysPrintRootPackage: false,
depth: 0,
long: false,
search: true,
}
)
const lines = output.split('\n')
expect(lines).toContain('/workspace/packages/pkg-a')
expect(lines).toContain('/workspace/packages/pkg-b')
expect(lines).toContain('/workspace/packages/shared')
expect(lines.filter((l) => l === '/workspace/packages/shared')).toHaveLength(1)
})
test('renderParseable search: packages unrelated to search are excluded', async () => {
const output = await renderParseable(
[
{
name: 'root',
path: '/workspace',
version: '1.0.0',
},
{
name: 'pkg-a',
path: '/workspace/packages/pkg-a',
version: '1.0.0',
dependencies: [
{
alias: '@org/shared',
name: '@org/shared',
version: '1.0.0',
path: '/workspace/packages/shared',
isMissing: false,
isPeer: false,
isSkipped: false,
},
],
},
{
name: 'unrelated',
path: '/workspace/packages/unrelated',
version: '1.0.0',
},
],
{
alwaysPrintRootPackage: false,
depth: 0,
long: false,
search: true,
}
)
const lines = output.split('\n')
expect(lines).toContain('/workspace/packages/pkg-a')
expect(lines).toContain('/workspace/packages/shared')
expect(lines).not.toContain('/workspace')
expect(lines).not.toContain('/workspace/packages/unrelated')
})
test('renderParseable search long: shared dep across packages is not duplicated', async () => {
const output = await renderParseable(
[
{
name: 'pkg-a',
path: '/workspace/packages/pkg-a',
version: '1.0.0',
dependencies: [
{
alias: '@org/shared',
name: '@org/shared',
version: 'link:../shared',
path: '/workspace/packages/shared',
isMissing: false,
isPeer: false,
isSkipped: false,
},
],
},
{
name: 'pkg-b',
path: '/workspace/packages/pkg-b',
version: '1.0.0',
dependencies: [
{
alias: '@org/shared',
name: '@org/shared',
version: 'link:../shared',
path: '/workspace/packages/shared',
isMissing: false,
isPeer: false,
isSkipped: false,
},
],
},
],
{
alwaysPrintRootPackage: false,
depth: 0,
long: true,
search: true,
}
)
const lines = output.split('\n')
expect(lines).toContain('/workspace/packages/pkg-a:pkg-a@1.0.0')
expect(lines).toContain('/workspace/packages/pkg-b:pkg-b@1.0.0')
expect(lines.filter((l) => l.startsWith('/workspace/packages/shared'))).toHaveLength(1)
})