Skip to content

fix(exe): hardlink binary as extensionless file on Windows#11090

Merged
zkochan merged 1 commit into
mainfrom
windows-test
Mar 25, 2026
Merged

fix(exe): hardlink binary as extensionless file on Windows#11090
zkochan merged 1 commit into
mainfrom
windows-test

Conversation

@zkochan

@zkochan zkochan commented Mar 25, 2026

Copy link
Copy Markdown
Member

Summary

  • On Windows, npm's .cmd/.ps1 bin shims reference the extensionless pnpm file from the published package.json bin entry. Previously, setup.js and linkExePlatformBinary wrote a dummy text file ("This file intentionally left blank") at that path, causing the shim to silently fail — PowerShell's $LASTEXITCODE stays $null, so exit $LASTEXITCODE exits with code 0, making all pnpm commands appear to succeed while doing nothing.
  • Fix by hardlinking the real platform binary as both pnpm.exe and pnpm (no extension), so the shim executes the actual binary.
  • Fixes both the published @pnpm/exe install script (setup.js) and pnpm's internal linkExePlatformBinary (used for manage-package-manager-versions version switching).

Test plan

  • Windows CI tests should actually execute instead of silently succeeding with no output

Copilot AI review requested due to automatic review settings March 25, 2026 13:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a Windows-specific issue in the @pnpm/exe distribution where npm-generated .cmd/.ps1 shims could point at an extensionless pnpm placeholder file, causing commands to silently no-op. The PR ensures the extensionless path is the actual binary and refreshes workflow usage of pnpm/action-setup.

Changes:

  • Hardlink the real Windows platform binary as both pnpm.exe/pn.exe and extensionless pnpm/pn during @pnpm/exe install (setup.js).
  • Update pnpm’s internal linkExePlatformBinary() to also create an extensionless pnpm hardlink on Windows.
  • Bump the pinned pnpm/action-setup commit SHA across multiple GitHub workflows.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pnpm/artifacts/exe/setup.js On Windows, creates extensionless hardlinks (pnpm, pn) to the real binary so npm shims execute correctly.
engine/pm/commands/src/self-updater/installPnpm.ts Updates linkExePlatformBinary() to create an extensionless pnpm hardlink on Windows for scriptless installs/version switching.
.github/workflows/update-lockfile.yml Updates pinned pnpm/action-setup SHA.
.github/workflows/test.yml Updates pinned pnpm/action-setup SHA.
.github/workflows/release.yml Updates pinned pnpm/action-setup SHA.
.github/workflows/ci.yml Updates pinned pnpm/action-setup SHA.
.github/workflows/benchmark.yml Updates pinned pnpm/action-setup SHA.
.github/workflows/audit.yml Updates pinned pnpm/action-setup SHA.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +350 to +362
// Also hardlink the binary as 'pnpm' (no extension).
// npm's bin shims and pnpm's linkBins may reference the extensionless
// name from the published package.json bin entry. The extensionless
// file must be the real binary so it can be executed.
const extlessLink = path.join(exePkgDir, 'pnpm')
try {
fs.unlinkSync(extlessLink)
} catch (err: unknown) {
if (!util.types.isNativeError(err) || !('code' in err) || err.code !== 'ENOENT') {
throw err
}
}
fs.linkSync(src, extlessLink)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows this links only the extensionless pnpm file, but @pnpm/exe also publishes a pn bin (and setup.js hardlinks both pnpm and pn). In scriptless installs/version switching, the pn shim may still resolve to the placeholder pn file and fail. Consider also hardlinking src to pn.exe and/or an extensionless pn alongside pnpm here, and updating exePkg.bin.pn accordingly (mirroring setup.js).

Copilot uses AI. Check for mistakes.
Comment on lines +350 to +362
// Also hardlink the binary as 'pnpm' (no extension).
// npm's bin shims and pnpm's linkBins may reference the extensionless
// name from the published package.json bin entry. The extensionless
// file must be the real binary so it can be executed.
const extlessLink = path.join(exePkgDir, 'pnpm')
try {
fs.unlinkSync(extlessLink)
} catch (err: unknown) {
if (!util.types.isNativeError(err) || !('code' in err) || err.code !== 'ENOENT') {
throw err
}
}
fs.linkSync(src, extlessLink)

Copilot AI Mar 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds new Windows-only behavior (creating an extensionless hardlink) but the existing linkExePlatformBinary tests only assert that pnpm.exe is replaced. Please extend the test suite to assert that, on Windows, node_modules/@pnpm/exe/pnpm is also created/replaced with the platform binary content (and similarly for pn if you add that link).

Copilot uses AI. Check for mistakes.
@zkochan zkochan force-pushed the windows-test branch 7 times, most recently from fbba6c7 to a87ce5d Compare March 25, 2026 21:52
On Windows, npm's .cmd/.ps1 bin shims reference the extensionless
`pnpm` file from the published package.json bin entry. Previously,
setup.js and linkExePlatformBinary wrote a dummy text file ("This file
intentionally left blank") at that path, causing the shim to silently
fail — PowerShell's $LASTEXITCODE stays $null, so `exit $LASTEXITCODE`
exits with code 0, making all pnpm commands appear to succeed while
doing nothing.

Fix by hardlinking the real platform binary as both `pnpm.exe` and
`pnpm` (no extension), so the shim executes the actual binary.
@zkochan zkochan merged commit dd8efdb into main Mar 25, 2026
10 of 12 checks passed
@zkochan zkochan deleted the windows-test branch March 25, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants