mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-04 22:44:56 -04:00
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:
@@ -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.
|
||||
@@ -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}`)
|
||||
|
||||
Reference in New Issue
Block a user