fix: resolve musl pacquet binary on musl-based systems (#12347)

* fix: resolve musl pacquet binary on musl-based systems

The pacquet binary packages are split by libc on linux and only the
matching one is installed, but resolvePacquetBin always asked for the
glibc name. On Alpine and other musl systems the frozen install failed
with: Cannot find module '@pacquet/linux-x64/pacquet'.

fix pnpm/pnpm#12049

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: sort imports in runPacquet.ts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: verify the musl pacquet binary package on musl-based systems

The signature verification hard-coded the glibc platform package name,
so on musl systems it verified a package other than the binary that is
actually spawned. Share one platform-package-name helper between
resolvePacquetBin and collectPacquetPackagesToVerify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kalven Schraut
2026-06-12 02:51:20 -05:00
committed by GitHub
parent f648e9b7c4
commit dfa91df6e8
4 changed files with 20 additions and 2 deletions

View File

@@ -88,6 +88,7 @@
"@zkochan/table": "catalog:",
"chalk": "catalog:",
"ci-info": "catalog:",
"detect-libc": "catalog:",
"get-npm-tarball-url": "catalog:",
"is-subdir": "catalog:",
"load-json-file": "catalog:",

View File

@@ -8,6 +8,7 @@ import type { Writable } from 'node:stream'
import { PnpmError } from '@pnpm/error'
import { logger, streamParser } from '@pnpm/logger'
import chalk from 'chalk'
import { familySync as getLibcFamilySync, MUSL } from 'detect-libc'
// The runtime `streamParser` is a `Transform` stream (split2 + JSON.parse).
// Its public typing only exposes `on`/`removeListener`, so we narrow to the
@@ -178,7 +179,18 @@ export function makeRunPacquet (opts: MakeRunPacquetOpts): (callOpts?: RunPacque
function resolvePacquetBin (lockfileDir: string, packageName: 'pacquet' | '@pnpm/pacquet'): string {
const ext = process.platform === 'win32' ? '.exe' : ''
const pacquetPkg = fs.realpathSync(path.join(lockfileDir, 'node_modules/.pnpm-config', packageName, 'package.json'))
return createRequire(pacquetPkg).resolve(`@pacquet/${process.platform}-${process.arch}/pacquet${ext}`)
return createRequire(pacquetPkg).resolve(`${pacquetPlatformPkgName()}/pacquet${ext}`)
}
/**
* Name of the `@pacquet/<platform>-<arch>[-musl]` package that holds the
* native pacquet binary for the host. On linux the binary packages are
* split by libc and only the matching one is installed, so spawning and
* signature verification must agree on this exact name.
*/
export function pacquetPlatformPkgName (): string {
const libc = process.platform === 'linux' && getLibcFamilySync() === MUSL ? '-musl' : ''
return `@pacquet/${process.platform}-${process.arch}${libc}`
}
/**

View File

@@ -11,6 +11,8 @@ import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header'
import type { CreateFetchFromRegistryOptions, RetryTimeoutOptions } from '@pnpm/network.fetch'
import type { Registries, RegistryConfig } from '@pnpm/types'
import { pacquetPlatformPkgName } from './runPacquet.js'
export interface VerifyPacquetIdentityOptions extends CreateFetchFromRegistryOptions {
lockfileDir: string
rootDir: string
@@ -93,7 +95,7 @@ async function collectPacquetPackagesToVerify (
// Only the host's platform binary is ever spawned, so that's the one whose
// identity matters. If it isn't in the lockfile, pacquet couldn't run here.
const platformPkgName = `@pacquet/${process.platform}-${process.arch}`
const platformPkgName = pacquetPlatformPkgName()
const platformVersion = envLockfile.snapshots[shimKey]?.optionalDependencies?.[platformPkgName]
if (platformVersion == null) return undefined
const platformKey = `${platformPkgName}@${platformVersion}`

3
pnpm-lock.yaml generated
View File

@@ -5399,6 +5399,9 @@ importers:
ci-info:
specifier: 'catalog:'
version: 4.4.0
detect-libc:
specifier: 'catalog:'
version: 2.1.2
get-npm-tarball-url:
specifier: 'catalog:'
version: 2.1.0