fix(dependencies-hierarchy): handle undefined pkgSnapshot in pnpm why -r (#10705)

* fix(dependencies-hierarchy): handle undefined pkgSnapshot in pnpm why -r

Running pnpm why -r crashes with TypeError due to undefined pkgSnapshot during
invokation of pkgSnapshotToResolution.

This commit wraps resolution and integrity steps with if (pkgSnapshot).
Also, added unit test for the same.

close #10700

* fix: handle undefined pkgSnapshot in pnpm why -r

* Apply suggestions from code review

#10700

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Ishan Gupta
2026-02-27 21:58:55 +05:30
committed by GitHub
parent fc25fefac5
commit d3a0765bea
3 changed files with 47 additions and 6 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/reviewing.dependencies-hierarchy": patch
"pnpm": patch
---
Handle undefined pkgSnapshot in `pnpm why -r` [#10700](https://github.com/pnpm/pnpm/issues/10700).

View File

@@ -68,7 +68,7 @@ export function getPkgInfo (opts: GetPkgInfoOpts): { pkgInfo: PackageInfo, readM
let integrity: string | undefined
const depPath = refToRelative(opts.ref, opts.alias)
if (depPath) {
let pkgSnapshot!: PackageSnapshot
let pkgSnapshot: PackageSnapshot | undefined
if (opts.currentPackages[depPath]) {
pkgSnapshot = opts.currentPackages[depPath]
const parsed = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
@@ -87,12 +87,14 @@ export function getPkgInfo (opts: GetPkgInfoOpts): { pkgInfo: PackageInfo, readM
isMissing = true
isSkipped = opts.skipped.has(depPath)
}
resolved = (pkgSnapshotToResolution(depPath, pkgSnapshot, opts.registries) as TarballResolution).tarball
depType = opts.depTypes[depPath]
optional = pkgSnapshot.optional
if ('integrity' in pkgSnapshot.resolution) {
integrity = pkgSnapshot.resolution.integrity as string
if (pkgSnapshot) {
resolved = (pkgSnapshotToResolution(depPath, pkgSnapshot, opts.registries) as TarballResolution).tarball
optional = pkgSnapshot.optional
if ('integrity' in pkgSnapshot.resolution) {
integrity = pkgSnapshot.resolution.integrity as string
}
}
depType = opts.depTypes[depPath]
} else {
name = opts.alias
version = opts.ref

View File

@@ -0,0 +1,33 @@
import { getPkgInfo, type GetPkgInfoOpts } from '../src/getPkgInfo.js'
import path from 'path'
test('getPkgInfo handles missing pkgSnapshot without crashing', () => {
const opts: GetPkgInfoOpts = {
alias: 'missing-pkg',
ref: 'missing-pkg@1.0.0',
currentPackages: {},
wantedPackages: {},
depTypes: {},
skipped: new Set<string>(),
registries: {
default: 'https://registry.npmjs.org/',
},
virtualStoreDirMaxLength: 120,
modulesDir: '',
linkedPathBaseDir: '',
}
const result = getPkgInfo(opts)
expect(result.pkgInfo).toEqual({
alias: 'missing-pkg',
name: 'missing-pkg',
version: 'missing-pkg@1.0.0',
isMissing: true,
isPeer: false,
isSkipped: false,
path: path.join('.pnpm/missing-pkg@1.0.0/node_modules/missing-pkg'),
})
expect(result.pkgInfo.resolved).toBeUndefined()
expect(result.pkgInfo.optional).toBeUndefined()
})