From c47aeffd722a0afd9e9669b54eead09f8334ff8e Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Mon, 22 Dec 2025 21:59:07 +0100 Subject: [PATCH] fix: linking commands of engines close #10244 --- .changeset/thirty-drinks-sin.md | 7 +++ exec/build-modules/src/index.ts | 2 +- pkg-manager/link-bins/src/index.ts | 77 +++++++++++++++++------------- 3 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 .changeset/thirty-drinks-sin.md diff --git a/.changeset/thirty-drinks-sin.md b/.changeset/thirty-drinks-sin.md new file mode 100644 index 0000000000..3a7d6dfad2 --- /dev/null +++ b/.changeset/thirty-drinks-sin.md @@ -0,0 +1,7 @@ +--- +"@pnpm/link-bins": patch +"@pnpm/build-modules": patch +"pnpm": patch +--- + +Binaries of runtime engines (Node.js, Deno, Bun) are written to `node_modules/.bin` before lifecycle scripts (install, postinstall, prepare) are executed [#10244](https://github.com/pnpm/pnpm/issues/10244). diff --git a/exec/build-modules/src/index.ts b/exec/build-modules/src/index.ts index 041b45e4c8..90dfe007cb 100644 --- a/exec/build-modules/src/index.ts +++ b/exec/build-modules/src/index.ts @@ -258,7 +258,7 @@ export async function linkBinsOfDependencies ( const pkgs = await Promise.all(pkgNodes .map(async (dep) => ({ location: dep.dir, - manifest: (await dep.fetching?.())?.bundledManifest ?? (await safeReadPackageJsonFromDir(dep.dir) as DependencyManifest) ?? {}, + manifest: (await dep.fetching?.())?.bundledManifest ?? (await safeReadPackageJsonFromDir(dep.dir) as DependencyManifest), })) ) diff --git a/pkg-manager/link-bins/src/index.ts b/pkg-manager/link-bins/src/index.ts index a0de854620..96cb508a87 100644 --- a/pkg-manager/link-bins/src/index.ts +++ b/pkg-manager/link-bins/src/index.ts @@ -99,7 +99,7 @@ function preferDirectCmds (allCmds: Array, @@ -111,7 +111,12 @@ export async function linkBinsOfPackages ( const allCmds = unnest( (await Promise.all( pkgs - .map(async (pkg) => getPackageBinsFromManifest(pkg.manifest, pkg.location, pkg.nodeExecPath)) + .map(async (pkg) => { + if (!pkg.manifest) { + return getBinsForKnownRuntimes(pkg.location) + } + return getPackageBinsFromManifest(pkg.manifest, pkg.location, pkg.nodeExecPath) + }) )) .filter((cmds: Command[]) => cmds.length) ) @@ -204,38 +209,7 @@ async function getPackageBins ( if (manifest == null) { // There is a probably a better way to do this. // It isn't good to have these hardcoded here. - switch (path.basename(target)) { - case 'node': - return [{ - name: 'node', - path: path.join(target, getNodeBinLocationForCurrentOS()), - ownName: true, - pkgName: '', - pkgVersion: '', - makePowerShellShim: false, - }] - case 'deno': - return [{ - name: 'deno', - path: path.join(target, getDenoBinLocationForCurrentOS()), - ownName: true, - pkgName: '', - pkgVersion: '', - makePowerShellShim: false, - }] - case 'bun': - return [{ - name: 'bun', - path: path.join(target, getBunBinLocationForCurrentOS()), - ownName: true, - pkgName: '', - pkgVersion: '', - makePowerShellShim: false, - }] - } - // There's a directory in node_modules without package.json: ${target}. - // This used to be a warning but it didn't really cause any issues. - return [] + return getBinsForKnownRuntimes(target) } if (isEmpty(manifest.bin) && !await isFromModules(target)) { @@ -249,6 +223,41 @@ async function getPackageBins ( return getPackageBinsFromManifest(manifest, target, nodeExecPath) } +function getBinsForKnownRuntimes (target: string): CommandInfo[] { + switch (path.basename(target)) { + case 'node': + return [{ + name: 'node', + path: path.join(target, getNodeBinLocationForCurrentOS()), + ownName: true, + pkgName: '', + pkgVersion: '', + makePowerShellShim: false, + }] + case 'deno': + return [{ + name: 'deno', + path: path.join(target, getDenoBinLocationForCurrentOS()), + ownName: true, + pkgName: '', + pkgVersion: '', + makePowerShellShim: false, + }] + case 'bun': + return [{ + name: 'bun', + path: path.join(target, getBunBinLocationForCurrentOS()), + ownName: true, + pkgName: '', + pkgVersion: '', + makePowerShellShim: false, + }] + } + // There's a directory in node_modules without package.json: ${target}. + // This used to be a warning but it didn't really cause any issues. + return [] +} + async function getPackageBinsFromManifest (manifest: DependencyManifest, pkgDir: string, nodeExecPath?: string): Promise { const cmds = await getBinsFromPackageManifest(manifest, pkgDir) if (manifest.engines?.runtime && runtimeHasNodeDownloaded(manifest.engines.runtime) && !nodeExecPath) {