fix: dependency graph hash calculation (#10236)

This commit is contained in:
Zoltan Kochan
2025-11-25 20:36:52 +01:00
committed by GitHub
parent 306d161ccb
commit 1e6de2539b
4 changed files with 46 additions and 5 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/dependency-path": patch
"@pnpm/calc-dep-state": patch
---
Fix dependency graph hash calculation for runtime dependencies (like Node.js, Deno).

View File

@@ -48,7 +48,15 @@ function calcDepGraphHash<T extends string> (
if (cache[depPath]) return cache[depPath]
const node = depsGraph[depPath]
if (!node) return ''
node.fullPkgId ??= createFullPkgId(node.pkgIdWithPatchHash!, node.resolution!)
if (!node.fullPkgId) {
if (!node.pkgIdWithPatchHash) {
throw new Error(`pkgIdWithPatchHash is not defined for ${depPath} in depsGraph`)
}
if (!node.resolution) {
throw new Error(`resolution is not defined for ${depPath} in depsGraph`)
}
node.fullPkgId = createFullPkgId(node.pkgIdWithPatchHash, node.resolution)
}
const deps: Record<string, string> = {}
if (Object.keys(node.children).length && !parents.has(node.fullPkgId)) {
const nextParents = new Set([...Array.from(parents), node.fullPkgId])
@@ -135,6 +143,6 @@ function lockfileDepsToGraphChildren (deps: Record<string, string>): Record<stri
}
function createFullPkgId (pkgIdWithPatchHash: PkgIdWithPatchHash, resolution: LockfileResolution): string {
const res = 'integrity' in resolution ? resolution.integrity : JSON.stringify(resolution)
const res = 'integrity' in resolution ? resolution.integrity : hashObject(resolution)
return `${pkgIdWithPatchHash}:${res}`
}

View File

@@ -66,9 +66,6 @@ export function getPkgIdWithPatchHash (depPath: DepPath): PkgIdWithPatchHash {
if (sepIndex !== -1) {
pkgId = pkgId.substring(0, sepIndex)
}
if (pkgId.includes(':')) {
pkgId = pkgId.substring(pkgId.indexOf('@', 1) + 1)
}
return pkgId as PkgIdWithPatchHash
}

View File

@@ -1,6 +1,7 @@
/// <reference path="../../../__typings__/index.d.ts"/>
import {
depPathToFilename,
getPkgIdWithPatchHash,
isAbsolute,
parse,
refToRelative,
@@ -109,3 +110,32 @@ test('tryGetPackageId', () => {
expect(tryGetPackageId('/@(-.-)/foo@1.0.0(@types/babel__core@7.1.14)' as DepPath)).toBe('/@(-.-)/foo@1.0.0')
expect(tryGetPackageId('foo@1.0.0(patch_hash=xxxx)(@types/babel__core@7.1.14)' as DepPath)).toBe('foo@1.0.0')
})
test('getPkgIdWithPatchHash', () => {
// Runtime dependency
expect(getPkgIdWithPatchHash('node@runtime:24.11.1' as DepPath)).toBe('node@runtime:24.11.1')
// Regular packages
expect(getPkgIdWithPatchHash('foo@1.0.0' as DepPath)).toBe('foo@1.0.0')
// Packages with patch hash
expect(getPkgIdWithPatchHash('foo@1.0.0(patch_hash=xxxx)' as DepPath)).toBe('foo@1.0.0(patch_hash=xxxx)')
// Packages with peer dependencies (should remove peer dependencies)
expect(getPkgIdWithPatchHash('foo@1.0.0(@types/babel__core@7.1.14)' as DepPath)).toBe('foo@1.0.0')
// Packages with both patch hash and peer dependencies (should keep patch hash, remove peer dependencies)
expect(getPkgIdWithPatchHash('foo@1.0.0(patch_hash=xxxx)(@types/babel__core@7.1.14)' as DepPath)).toBe('foo@1.0.0(patch_hash=xxxx)')
// Scoped packages
expect(getPkgIdWithPatchHash('@foo/bar@1.0.0' as DepPath)).toBe('@foo/bar@1.0.0')
// Scoped packages with patch hash
expect(getPkgIdWithPatchHash('@foo/bar@1.0.0(patch_hash=yyyy)' as DepPath)).toBe('@foo/bar@1.0.0(patch_hash=yyyy)')
// Scoped packages with peer dependencies
expect(getPkgIdWithPatchHash('@foo/bar@1.0.0(@types/node@18.0.0)' as DepPath)).toBe('@foo/bar@1.0.0')
// Scoped packages with both patch hash and peer dependencies
expect(getPkgIdWithPatchHash('@foo/bar@1.0.0(patch_hash=zzzz)(@types/node@18.0.0)' as DepPath)).toBe('@foo/bar@1.0.0(patch_hash=zzzz)')
})