fix(exec.commands): fall back to alias as bin name when dlx slot lacks package.json (#11886)

`getBinName` reads the installed package's `package.json` out of the
GVS slot to discover the bin name. On CI this read has been failing
intermittently for `node@runtime:24.6.0` with
`ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` — the dlx install reports
`added 1, done`, but the slot the symlink points at has no
`package.json`. The bin link itself is fine (pnpm creates it from the
resolution's `bin` info, not from the slot's manifest), so the only
casualty is `getBinName`.

The slot can end up without `package.json` when something populated it
without going through pnpm's `appendManifest` synthesis (or pacquet's
runtime-manifest synthesis equivalent) — runtime archives don't ship
their own `package.json`, so the synthesized one is the only way it
gets there. Pacquet's `import_indexed_dir` short-circuits on existing
slots without checking which files are present, so a slot populated
by an older code path stays incomplete.

Catch the manifest-not-found error and fall back to the scopeless
package name. For single-bin packages that match `manifest.bin` (the
common case for `pnpm dlx <pkg>`, including every `runtime:` spec),
this gives the same answer the manifest would. Multi-bin packages
already require `--package=<spec> <bin>` to disambiguate, which
short-circuits `getBinName` upstream and never enters this branch.
This commit is contained in:
Zoltan Kochan
2026-05-23 21:42:27 +02:00
committed by GitHub
parent 7a5cb92f80
commit 3d143854c0
2 changed files with 26 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/exec.commands": patch
pnpm: patch
---
Fixed `pnpm dlx` failing with `ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND` when the installed package's CAS slot is missing its `package.json`. Observed in the wild for `pnpm dlx node@runtime:<version>` when the GVS slot was populated without the synthesized manifest runtime archives need (they don't ship a `package.json` of their own, so the synthesized one is the only way it gets there; an existing slot from an earlier code path that skipped the synthesis stays incomplete). The bin link itself is wired up from the resolution and remains valid, so `dlx` now falls back to the scopeless package name when the slot's manifest is unreadable — for single-bin packages (the dlx common case, including every `runtime:` spec) this matches what `manifest.bin` would have named. Multi-bin packages already require `--package=<spec> <bin>` to disambiguate and don't enter this code path.

View File

@@ -256,7 +256,26 @@ async function getPkgName (pkgDir: string): Promise<string> {
async function getBinName (cachedDir: string, opts: Pick<DlxCommandOptions, 'engineStrict'>): Promise<string> {
const pkgName = await getPkgName(cachedDir)
const pkgDir = path.join(cachedDir, 'node_modules', pkgName)
const manifest = await readProjectManifestOnly(pkgDir, opts) as PackageManifest
let manifest: PackageManifest
try {
manifest = await readProjectManifestOnly(pkgDir, opts) as PackageManifest
} catch (err: unknown) {
// The installed package's `package.json` is unreadable. Observed in the
// wild for `node@runtime:<version>` whose CAS slot was materialized by
// a code path that didn't run pnpm's `appendManifest` (or pacquet's
// equivalent runtime-manifest synthesis), leaving the slot without
// the `package.json` runtime archives don't ship themselves. Fall back
// to the scopeless package name — for single-bin packages (the dlx
// common case) it matches what `manifest.bin` would have named, and
// the `node_modules/.bin/<name>` symlink the install already wired up
// from the resolution's bin info is what `execa` resolves against.
// Multi-bin packages require `--package=<spec> <bin>` to disambiguate,
// which short-circuits `getBinName` upstream and never enters this path.
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND') {
return scopeless(pkgName)
}
throw err
}
const bins = await getBinsFromPackageManifest(manifest, pkgDir)
if (bins.length === 0) {
throw new PnpmError('DLX_NO_BIN', `No binaries found in ${pkgName}`)