Skip to content

feat!: install pnpm v12 as a standalone native executable#11

Merged
zkochan merged 5 commits into
mainfrom
upgrade-pnpm-to-v12
Jul 23, 2026
Merged

feat!: install pnpm v12 as a standalone native executable#11
zkochan merged 5 commits into
mainfrom
upgrade-pnpm-to-v12

Conversation

@zkochan

@zkochan zkochan commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Since v12, pnpm is a Rust rewrite distributed as a standalone native executable — no Node.js or npm required. This PR reworks the action to take advantage of that, deleting the entire npm-based bootstrap.

Before: write a synthetic package.json + committed lockfile → npm ci to install @pnpm/exe (with a node-on-PATH hack so npm's shebang resolves) → fix up .bin shims on Windows → pnpm self-update <target>.

After: resolve the requested version against the npm registry → download the @pnpm/exe.<os>-<arch> tarball → verify its sha512 integrity → drop the pnpm binary (plus pnpx/pn/pnx aliases, which work via argv[0] dispatch) into dest.

Details

  • Version resolution (new src/install-pnpm/download.ts): exact versions are used directly; semver ranges resolve via maxSatisfying over the published platform-package versions (stable preferred, prerelease fallback so ^12.0.0 works during the alpha); anything else is treated as a dist-tag (e.g. next-12) and resolved against the main pnpm package's tags — the per-platform packages carry stale tags (latest → 11.0.0-rc.3).
  • v12+ only: versions below 12 fail with a clear error pointing to pnpm/action-setup. The version-source precedence is unchanged (version input → devEngines.packageManagerpackageManager).
  • Deleted: exe-lock.json, scripts/update-bootstrap.mjs (no more periodic bootstrap bumps), the npm ci step, the node-on-PATH hack, Windows .bin shim workarounds, the self-update step and its no-op edge case, and the --no-runtime version probe. Dropped the unused zod dependency; added semver and @actions/http-client (proxy-aware downloads).
  • Rust CLI adjustments (verified against the real 12.0.0-alpha.17 binary):
    • pnpm store path no longer accepts --silent
    • $PNPM_HOME/bin is created upfront — pnpm runtime set -g fails with ERR_PNPM_PNPM_DIR_NOT_WRITABLE when it's missing (arguably a pnpm alpha bug worth fixing separately)
  • Layout: dest doubles as PNPM_HOME; the binary lives in dest, while runtimes and any later pnpm self-update land in dest/bin, which gets higher PATH precedence.
  • CI: test matrix moved to 12.0.0-alpha.17, new jobs for dist-tag and semver-range resolution, and an ubuntu-24.04-arm smoke entry to cover the arm64 binary.
  • run.sh fix: hyphenated inputs are encoded by the runner as e.g. INPUT_CACHE-DEPENDENCY-PATH, so the underscore-named exports never matched; inputs are now passed via env.

Testing

  • Ran the compiled bundle end-to-end locally (isolated HOME, no node/npm on PATH beyond the action runtime): exact version, next-12 tag, ^12.0.0-0 range, and the sub-12 error path all behave; runtime set node@24 lands node in $PNPM_HOME/bin; pnpm/pnpx/node all resolve on the simulated PATH; outputs are correct.
  • Confirmed pnpm v12 reads this repo's v11-format lockfile, so the smoke jobs that run pnpm install on the repo itself still pass.
  • tsc clean; dist/index.js rebuilt.

Open question

With version omitted and no packageManager/devEngines.packageManager in the manifest, the action still errors asking for a version — kept rather than defaulting to next-12 while v12 is prerelease.

Summary by CodeRabbit

  • New Features

    • Added support for installing pnpm v12+ as a standalone executable.
    • Added version resolution from exact versions, semver ranges, and npm dist-tags.
    • Added integrity verification and platform-specific executable installation.
  • Documentation

    • Updated setup guidance and input documentation for pnpm v12+.
    • Clarified automatic version detection from project configuration.
  • Bug Fixes

    • Improved runtime executable selection across platforms.
    • Ensured installations consistently disable pnpm-managed runtimes when a runtime is configured.
    • Expanded coverage for pnpm 12 prerelease resolution.

Since v12, pnpm is a Rust rewrite shipped as a native executable that
needs neither Node.js nor npm. The action now resolves the requested
version (exact, semver range, or dist-tag) against the npm registry,
downloads the @pnpm/exe.<os>-<arch> tarball for the runner's platform,
verifies its sha512 integrity, and places the pnpm binary (plus the
pnpx, pn, and pnx aliases) directly into dest.

This removes the whole npm-based bootstrap: the committed exe-lock.json,
the update-bootstrap script, the npm ci step, the node-on-PATH hack, the
Windows .bin shim workarounds, and the pnpm self-update step.

Versions below 12 are rejected with a pointer to pnpm/action-setup.

Adjustments for the Rust CLI:
- 'pnpm store path' no longer accepts --silent
- $PNPM_HOME/bin is created upfront: 'pnpm runtime set -g' fails with
  ERR_PNPM_PNPM_DIR_NOT_WRITABLE when it is missing
- the --no-runtime version probe is gone (always supported in v12)
Comment thread .github/workflows/test.yaml Fixed
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@zkochan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 24140114-770a-4c15-a24f-c000b9556cb5

📥 Commits

Reviewing files that changed from the base of the PR and between ba8ec1b and e182dc1.

⛔ Files ignored due to path filters (1)
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (3)
  • .github/workflows/test.yaml
  • README.md
  • src/install-pnpm/download.ts
📝 Walkthrough

Walkthrough

The action now installs pnpm v12+ as a verified standalone executable, resolves exact versions, ranges, and dist-tags, updates runtime integration, and expands workflow and documentation coverage for the new installation model.

Changes

pnpm v12 installer migration

Layer / File(s) Summary
Native executable resolution and download
src/install-pnpm/download.ts, package.json
Resolves platform-specific pnpm v12+ packages, downloads and verifies tarballs, extracts executables, and creates aliases.
Installer orchestration and command integration
src/install-pnpm/..., src/index.ts, src/install-runtime/index.ts, src/pnpm-install/index.ts, src/cache-restore/run.ts, run.sh
Replaces bootstrap installation, validates the installed binary, configures PATH and PNPM_HOME, invokes platform-specific binaries directly, and always passes --no-runtime when a runtime is configured.
v12 behavior validation and documentation
.github/workflows/test.yaml, README.md, action.yml
Adds dist-tag and semver-range tests, updates runtime scenarios to pnpm v12, and documents supported version resolution and native installation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Action
  participant npmRegistry
  participant Installer
  participant pnpm
  Action->>Installer: resolve requested pnpm version
  Installer->>npmRegistry: fetch package metadata and tarball
  npmRegistry-->>Installer: version, executable, integrity
  Installer->>Installer: verify and extract executable
  Installer->>pnpm: run --version
  pnpm-->>Action: installed version
Loading

Poem

I’m a rabbit with a fresh binary bright,
Hopping through tarballs by moonlight.
Tags and ranges now lead the way,
Checksums guard each hop and stay.
v12 trails sparkle in the run—
A tidy setup, and tests are done!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: pnpm v12 is now installed as a standalone native executable.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch upgrade-pnpm-to-v12

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/install-pnpm/download.ts Fixed
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Install pnpm v12+ by downloading the native executable from npm

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Install pnpm v12+ by downloading the platform executable tarball from npm.
• Resolve exact/range/dist-tag versions and verify sha512 integrity before use.
• Update PATH/PNPM_HOME layout, docs, and CI matrix for pnpm v12 behavior.
Diagram

graph TD
  A["Action entry (src/index.ts)"] --> B["Resolve pnpm version (download.ts)"] --> C{{"npm registry"}}
  C --> D["Download + verify + extract (download.ts)"] --> E["Install + PATH/PNPM_HOME (install-pnpm/run.ts)"] --> F["Install runtime (install-runtime)"] --> G["Restore cache (cache-restore)"] --> H["pnpm install (pnpm-install)"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use GitHub Releases as the binary source
  • ➕ Avoid npm packument/dist-tag edge cases and registry metadata drift
  • ➕ Potentially simpler version resolution semantics for users
  • ➕ Could leverage GitHub-provided checksums/signatures if available
  • ➖ Requires mapping pnpm versions to release assets per OS/arch (often more bespoke)
  • ➖ Proxy/mirror setups may prefer npm registry access over GitHub
  • ➖ Loses npm dist.integrity sha512 verification path used here
2. Use a JS tar extraction library instead of spawning `tar`
  • ➕ Removes dependency on tar being present/compatible on runners
  • ➕ More consistent behavior across environments; easier to unit test
  • ➖ Adds dependency weight and potentially slower extraction in JS
  • ➖ Still needs careful path handling for Windows and symlinks/hardlinks
3. Use npm client libraries (e.g., pacote) for resolution/download/integrity
  • ➕ Battle-tested npm metadata handling, semver/dist-tag resolution, and integrity verification
  • ➕ Less custom registry plumbing and fewer edge cases
  • ➖ Significant dependency surface area and bundle size increase for the action
  • ➖ May pull in Node/npm ecosystem assumptions the PR intentionally removes

Recommendation: Keep the PR’s approach (direct npm registry + sha512 integrity + simple extraction). It aligns with pnpm v12’s platform packages, remains proxy-aware via @actions/http-client, and preserves a strong integrity check using npm’s published dist.integrity. The main follow-up to consider is whether spawning tar is acceptable long-term; if runner portability issues arise, switch to a JS extraction library.

Files changed (13) +358 / -184

Enhancement (2) +221 / -90
download.tsAdd npm-registry resolution + download + sha512 verification for pnpm binaries +179/-0

Add npm-registry resolution + download + sha512 verification for pnpm binaries

• Implements resolution of exact versions, semver ranges (stable-preferred, prerelease fallback), and dist-tags (via the main pnpm package). Downloads the platform-specific '@pnpm/exe.<os>-<arch>' tarball, verifies sha512 integrity from npm metadata, extracts the executable, and creates pnpx/pn/pnx aliases.

src/install-pnpm/download.ts

run.tsReplace npm bootstrap + self-update with direct pnpm v12 binary install +42/-90

Replace npm bootstrap + self-update with direct pnpm v12 binary install

• Removes the entire npm-based bootstrap (package.json/lockfile generation, node-on-PATH hack, Windows shim workarounds, and 'pnpm self-update'). Resolves the requested spec, downloads and validates the native binary into 'dest', creates 'dest/bin' upfront, verifies 'pnpm --version', and sets PATH/PNPM_HOME so 'dest/bin' has higher precedence.

src/install-pnpm/run.ts

Bug fix (3) +14 / -15
run.shFix input env var passing for hyphenated action inputs +12/-7

Fix input env var passing for hyphenated action inputs

• Stops exporting invalid shell identifiers for hyphenated inputs and instead passes them via 'env' with the runner-encoded names (e.g. INPUT_CACHE-DEPENDENCY-PATH). Updates defaults to v12-oriented inputs (next-12) and includes INPUT_INSTALL.

run.sh

run.tsRemove unsupported '--silent' from 'pnpm store path' +1/-1

Remove unsupported '--silent' from 'pnpm store path'

• Updates cache directory discovery to call 'pnpm store path' without '--silent', matching pnpm v12 CLI behavior.

src/cache-restore/run.ts

index.tsUse explicit pnpm executable path on Windows +1/-7

Use explicit pnpm executable path on Windows

• Adjusts runtime installation to call 'pnpm.exe' on Windows rather than relying on 'shell: true' PATH/PATHEXT resolution, matching the new direct-binary install layout.

src/install-runtime/index.ts

Refactor (3) +13 / -45
index.tsAssume pnpm installation always returns a result +0/-1

Assume pnpm installation always returns a result

• Removes the early-return path for an undefined install result, reflecting that installation now throws on failure rather than returning an error code.

src/index.ts

index.tsSimplify install wrapper and rely on exceptions for failures +7/-9

Simplify install wrapper and rely on exceptions for failures

• Changes the installer to always return a 'SelfInstallerResult' and uses a try/finally to ensure the log group is closed, removing 'setFailed'-based exit-code handling.

src/install-pnpm/index.ts

index.tsAlways pass '--no-runtime' when runtime input is set +6/-35

Always pass '--no-runtime' when runtime input is set

• Removes version-gating and warnings for '--no-runtime', since the action now only supports pnpm v12+. Simplifies comments to reflect PATH precedence coming from the new install layout.

src/pnpm-install/index.ts

Tests (1) +65 / -14
test.yamlMove CI to pnpm v12 alpha and add dist-tag/range resolution coverage +65/-14

Move CI to pnpm v12 alpha and add dist-tag/range resolution coverage

• Updates the smoke/runtime test matrix to pnpm 12.0.0-alpha.17 and adds an arm64 smoke run. Adds dedicated jobs to assert that dist-tags (e.g. next-12) and semver ranges (e.g. ^12.0.0-0) resolve to pnpm 12.x.

.github/workflows/test.yaml

Documentation (1) +9 / -6
README.mdDocument v12-only native binary install and version spec formats +9/-6

Document v12-only native binary install and version spec formats

• Updates the README to reflect that pnpm v12 is downloaded as a native executable from the npm registry without Node.js/npm. Documents that the action supports exact versions, semver ranges, and dist-tags and is v12+ only (with a pointer to pnpm/action-setup for older pnpm).

README.md

Other (3) +36 / -14
action.ymlClarify 'version' input semantics and v12+ requirement +8/-1

Clarify 'version' input semantics and v12+ requirement

• Expands the 'version' input documentation to describe exact/range/dist-tag formats and explicitly states the action only supports pnpm v12+ installed as a standalone executable.

action.yml

package.jsonReplace bootstrap tooling deps with semver + proxy-aware HTTP client +6/-5

Replace bootstrap tooling deps with semver + proxy-aware HTTP client

• Removes the bootstrap update script and stops special-casing json loader in the bundle command. Drops 'zod' and adds 'semver', '@types/semver', and '@actions/http-client' to support version resolution and proxy-aware downloads.

package.json

pnpm-lock.yamlLockfile update for added/removed dependencies +22/-8

Lockfile update for added/removed dependencies

• Adds lock entries for '@actions/http-client', 'semver', and '@types/semver', and removes 'zod'. Updates transitive metadata accordingly.

pnpm-lock.yaml

@qodo-code-review

qodo-code-review Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. External tar dependency ✓ Resolved 🐞 Bug ☼ Reliability
Description
The pnpm installer now shells out to a system tar executable to extract the downloaded
@pnpm/exe.* tarball, which will fail on self-hosted/minimal runner images that don’t have a
compatible tar on PATH. It also extracts the entire archive into dest/.download even though only
package/pnpm[.exe] is needed, increasing I/O and exposure to unexpected archive contents.
Code

src/install-pnpm/download.ts[R163-176]

+function extractTarball(tarball: string, destDir: string): Promise<void> {
+  // A tar executable is available on all GitHub-hosted runners, including
+  // Windows (bsdtar ships with Windows since 2019). Backslashes are converted
+  // to forward slashes because MSYS-based tar implementations misread them.
+  const args = ['-xzf', tarball.replace(/\\/g, '/'), '-C', destDir.replace(/\\/g, '/')]
+  return new Promise<void>((resolve, reject) => {
+    const cp = spawn('tar', args, { stdio: ['ignore', 'inherit', 'inherit'] })
+    cp.on('error', reject)
+    cp.on('close', (code) => {
+      if (code === 0) {
+        resolve()
+      } else {
+        reject(new Error(`tar exited with code ${code} while extracting ${tarball}`))
+      }
Evidence
downloadPnpm() downloads the tarball, verifies integrity, then calls extractTarball() which
spawns tar to unpack the archive; afterward it only uses tmpDir/package/pnpm[.exe], indicating
full extraction is unnecessary and will fail if tar is unavailable.

src/install-pnpm/download.ts[64-96]
src/install-pnpm/download.ts[163-179]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The installer extracts pnpm by spawning `tar`, creating a hard runtime dependency on an external tool and unpacking the full archive when only a single file is required.

### Issue Context
This action is likely to be used on self-hosted runners as well as GitHub-hosted runners. Depending on `tar` being present/compatible is a common portability footgun; and extracting the whole archive is unnecessary work.

### Fix Focus Areas
- src/install-pnpm/download.ts[64-96]
- src/install-pnpm/download.ts[163-179]

### Suggested fix
- Replace `extractTarball()` with a Node-based extractor (e.g., a small tar reader library) and extract only `package/pnpm` (or `package/pnpm.exe` on Windows) into the temp directory.
- If you keep the system `tar` approach as a fallback, at minimum:
 - preflight-check that `tar` exists and emit a clear actionable error when missing
 - extract only the expected entry (`tar -xzf <tgz> -C <dir> package/<exe>`) instead of the entire archive.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/install-pnpm/download.ts Outdated
zkochan added 2 commits July 23, 2026 10:00
The new release includes the pacquet parity fixes from pnpm/pnpm#13194:
runtime set -g now creates a missing global bin dir, and the --silent
shorthand is accepted again. The upfront dest/bin creation stays for
users pinning alpha.17 or older.
zkochan added 2 commits July 23, 2026 10:57
- add a least-privilege permissions block to the test workflow (CodeQL:
  workflow does not limit GITHUB_TOKEN permissions)
- use replaceAll when encoding the scoped package name for the registry
  URL (CodeQL: replace only rewrites the first occurrence)
- extract only the pnpm binary entry from the downloaded tarball instead
  of the whole archive, and fail with an actionable message when no tar
  executable is on PATH (self-hosted minimal runners)
Includes the fresh-install --no-runtime fix (pnpm/pnpm#13225), which
unblocks the devEngines test jobs that install without a lockfile.
@zkochan
zkochan merged commit 6bfbb82 into main Jul 23, 2026
21 checks passed
zkochan added a commit to pnpm/pnpm that referenced this pull request Jul 23, 2026
* ci: update pnpm/setup to the v12 standalone-executable revision

Since pnpm/setup#11 the action downloads the standalone pnpm v12
executable directly from the registry — no npm bootstrap, no system
Node.js dependency. Every workflow moves to the new revision except
update-latest.yml: its RELEASE_TOOL_PNPM_VERSION deliberately pins a
proven pnpm 11, which only the pre-v12 action revision can install, so
it keeps the old pin until that version moves to v12.

* ci: operate the Tag workflow with pnpm v12

RELEASE_TOOL_PNPM_VERSION moves to 12.0.0-alpha.19 and the pnpm/setup
pins to the v12 revision. The commands the workflow runs are rewritten
to forms both CLIs accept, since the Rust CLI does not (yet) expose
--registry, --allow-build on add, or a post-subcommand --dir:

- --registry becomes --config.registry (read by both stacks)
- the add build-script approval moves from `--allow-build=@pnpm/exe` to
  an allowBuilds entry in the scratch dir pnpm-workspace.yaml
- --dir moves ahead of the subcommand

Each rewritten command was verified against the 12.0.0-alpha.19 binary,
including the verify-upgrade sequence end to end (add of
`@pnpm/exe@11.16.0` with the build approval, bin shim run) and the
config set/get/delete round trip for the auth token.

* revert: operate the Tag workflow with pnpm v11 again

Moving the operator to v12 required rewriting commands around three
CLI surfaces pacquet is missing (--registry, --allow-build on add,
post-subcommand --dir). The Rust CLI must gain those for parity instead
of the workflow papering over them — tracked in
#13242. The workflow keeps the
pre-v12 pnpm/setup pin and pnpm 11.13.1 until that lands.
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