Direct GitHub-expression interpolation of `${{ steps.pnpm.outputs.bin_dest }}`
into the bash script let bash eat the backslashes in the Windows path
(`C:Usersrunneradminsetup-pnpmnode_modules.binbin/pnpm`), failing with
"No such file or directory". Forward the value via env so the path
reaches bash unmangled.
`pnpm self-update <version>` writes the target binary to
`${PNPM_HOME}/bin/`, leaving the bootstrap symlink at `${PNPM_HOME}/pnpm`
untouched. The `bin_dest` output was set to `${PNPM_HOME}`, so consumers
invoking `${{ steps.pnpm.outputs.bin_dest }}/pnpm` got the bootstrap
version (currently 11.0.4) instead of the version they requested.
PATH lookup hid the bug: `${PNPM_HOME}/bin` was prepended ahead of
`${PNPM_HOME}`, so `pnpm` resolved from PATH was the right one. Existing
version-respect tests only checked `pnpm --version`, not `bin_dest`.
Resolve `binDest` inside `runSelfInstaller` (target lives in
`${PNPM_HOME}/bin` after self-update, otherwise stays at `${PNPM_HOME}`)
and plumb it through to `setOutputs`. Add a regression test that invokes
`${bin_dest}/pnpm --version` directly across Linux/macOS/Windows.
* fix: use npm co-located with the action node binary
* fix: resolve npm by absolute path; guard against unset PATH
Follow-up to 5a9e198. Two refinements to the GHE self-hosted runner fix:
- Spawn npm via `path.join(dirname(process.execPath), 'npm[.cmd]')`
instead of relying on PATH lookup. This matches the original PR
description and is robust against PATH-shadowed npm installations.
- Avoid `"<dir>:undefined"` leaking into PATH when `process.env.PATH`
is unset (rare, but possible in stripped environments).
PATH still has the node directory prepended so npm's
`#!/usr/bin/env node` shebang can resolve node on Linux/macOS.
* fix: revert npm to PATH lookup; runner externals lacks npm
Revert 42e75a1's switch to absolute-path npm resolution. The premise
that npm is co-located with the action's node binary is false on
GitHub-hosted runners: `process.execPath` points into
`runner/externals/node24/bin/`, which contains node only — not npm.
The absolute-path spawn produced ENOENT on Linux/macOS and
"not recognized" on Windows.
Go back to spawning `'npm'` and relying on PATH lookup, which works
on standard runners (npm is on PATH from the runner image) and on
the GHE self-hosted setup that motivated the original fix. Keep the
node-directory prepend so npm's `#!/usr/bin/env node` shebang
resolves, and keep the unset-PATH guard.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
The existing version tests only check output format via regex, which is
why the PATH-shadowing bug (#230) slipped through — the bootstrap pnpm's
version string matched the regex just as well as the requested version.
- test_version_respects_request: runs the action with `version: 9.15.5`
and `version: 10.33.0` (both differ from the bootstrap) and asserts
that `pnpm --version` matches exactly. Regression test for #225/#230.
- test_package_manager_field: writes a `packageManager: pnpm@<v>` entry
into package.json, runs the action with no `version:` input, and
asserts exact match. Reproduces #227; currently expected to fail
since `packageManager` extraction was intentionally not added.
Problem
pnpm self-update installs the target version to PNPM_HOME/bin/pnpm, but the bootstrap binary at PNPM_HOME/pnpm has higher PATH precedence because addPath(pnpmHome) was called after addPath(pnpmHome/bin). @actions/core's addPath prepends, so the later call wins — the bootstrap version shadows the self-updated binary.
Fix
Swap the addPath call order so PNPM_HOME/bin (where self-update puts the target binary) has higher PATH precedence. The bootstrap pnpm is invoked via absolute path, so this doesn't affect the bootstrap step.
* fix: overwrite npm .cmd wrappers for @pnpm/exe on Windows
npm creates .cmd wrappers that invoke bin entries through `node`,
but @pnpm/exe bins are native executables, not JavaScript files.
This causes pnpm commands to silently fail on Windows.
* fix: copy pnpm.exe to .bin/ on Windows for standalone mode
The .cmd wrapper approach didn't work because CMD doesn't properly
wait for extensionless PE binaries. Instead, copy the actual .exe
(and .cmd for pnpx) from @pnpm/exe into .bin/ so PATHEXT finds
pnpm.exe directly, bypassing npm's broken node-wrapping shim.
* fix: add @pnpm/exe dir to PATH on Windows instead of .bin shims
On Windows, npm's .bin shims can't properly execute the extensionless
native binaries from @pnpm/exe. Instead of trying to fix the shims,
add the @pnpm/exe directory directly to PATH where pnpm.exe lives.
* test: validate pnpm --version output in CI
All version checks now capture output and assert it matches a semver
pattern. Previously, a silently failing pnpm (exit 0, no output)
would pass the tests.
* debug: log pnpm --version output during setup
* fix: remove duplicate addPath in setOutputs that shadowed pnpm.exe
setOutputs called addPath(node_modules/.bin) AFTER installPnpm had
already added the correct path (@pnpm/exe on Windows). Since
GITHUB_PATH entries are prepended, .bin ended up first in PATH,
causing PowerShell to find npm's broken shims instead of pnpm.exe.
* fix: add PNPM_HOME/bin to PATH on all platforms
* fix: address review feedback — PATH ordering and regex anchoring
- Swap addPath order so pnpmHome (with pnpm.exe) is prepended last
and has highest precedence over pnpmHome/bin.
- Anchor version regex with $ and allow prerelease suffixes.
* feat: read pnpm version from devEngines.packageManager field
When no version is specified in the action config or the packageManager
field of package.json, fall back to devEngines.packageManager.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: skip self-update for devEngines.packageManager and add CI tests
pnpm auto-switches to the right version when devEngines.packageManager
is set, so self-update is unnecessary. This also enables range support
(e.g. ">=9.15.0") which self-update doesn't handle.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
When packageManager is set to e.g. "pnpm@9.1.0+sha...", strip the
"pnpm@" prefix and any "+sha..." hash suffix so the action installs
the correct version. Previously returning undefined caused failures
on Windows.
* feat!: replace bundled pnpm binary with npm + lockfile bootstrap
Remove the 9MB bundled pnpm.cjs/worker.js and instead use npm ci with
committed package-lock.json files (~5KB) to install a bootstrap pnpm,
which then installs the target version with integrity verification via
the project's pnpm-lock.yaml.
Also switch from ncc to esbuild and modernize to ESM.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bundle as CJS to support @actions/* packages
The @actions/* packages use CJS require() for Node.js builtins,
which fails with "Dynamic require of 'os' is not supported" when
bundled as ESM. Switch esbuild output to CJS format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove "type": "module" from package.json
Node.js treats dist/index.js as ESM due to "type": "module",
but the bundle uses CJS require() calls. Remove the field so
Node.js defaults to CJS for .js files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove packageManager field and fix Windows npm spawn
- Remove packageManager from package.json to avoid version conflict
when the action tests against itself (uses: ./)
- Use shell: true on Windows so spawn can find npm.cmd
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: always use pnpm (not @pnpm/exe) for bootstrap and update lockfile
The bootstrap only needs regular pnpm to install the target package.
@pnpm/exe requires install scripts which we skip with --ignore-scripts.
Also regenerate pnpm-lock.yaml to match current package.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use --no-lockfile for target install
--lockfile-dir pointing to GITHUB_WORKSPACE causes the bootstrap pnpm
to use the project's pnpm-lock.yaml (which tracks project deps, not
pnpm itself), corrupting the install. Revert to --no-lockfile for now.
Lockfile-based integrity verification can be added when pnpm v11 has
proper support for verifying the pnpm package itself.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: run bootstrap pnpm via node instead of bin shim
Use `node .../pnpm/bin/pnpm.cjs` to run the bootstrap pnpm, matching
the approach used by the old bundled pnpm.cjs. This avoids issues with
the .bin symlink on different platforms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use pnpm self-update instead of installing target separately
- Bootstrap pnpm via npm ci (verified by lockfile)
- Use `pnpm self-update <version>` for explicit version
- Let pnpm handle packageManager field automatically
- Remove standalone/exe-specific install logic (pnpm handles this)
- Update tests to not run pnpm install against the action repo itself
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support standalone mode with @pnpm/exe bootstrap
- When standalone=true, bootstrap with @pnpm/exe via npm ci
- When standalone=false, bootstrap with pnpm via npm ci
- Both use pnpm self-update to reach the target version
- Remove --ignore-scripts from npm ci so @pnpm/exe install scripts run
- Add standalone test back to CI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* debug: add logging to diagnose pnpm not found on PATH
Log .bin directory contents after npm ci to understand why
pnpm binary is not found in subsequent CI steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: ensure pnpm bin link exists after npm ci
npm ci sometimes doesn't create the .bin/pnpm symlink for
@pnpm/exe (observed on Linux CI). Manually create the symlink
if it's missing after npm ci completes.
This fixes the case where standalone=true with no explicit version
(relying on packageManager field) — pnpm self-update wouldn't run,
leaving .bin empty and pnpm not found on PATH.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add PNPM_HOME/bin to PATH for pnpm v11
pnpm v11 moved global binaries from PNPM_HOME to PNPM_HOME/bin.
Add the new bin subdirectory to PATH so that pnpm's global bin
directory check passes. This is backwards compatible — the extra
PATH entry is harmless for older pnpm versions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add packages field to pnpm-workspace.yaml
pnpm v9 requires the packages field in pnpm-workspace.yaml.
Without it, `pnpm --version` fails with "packages field missing or empty".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix pnpm-workspace.yaml
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* add pnpm store caching
* style: format
* no semicolons
* no star imports
* import order
* style: no star imports
---------
Co-authored-by: khai96_ <hvksmr1996@gmail.com>
* Clarify that package_json_file is relative to GITHUB_WORKSPACE
Clarify the description for package_json_file parameter to specify that the path must be relative to the repository root.
* Apply suggestion from @zkochan
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
When a project has both npm and pnpm, using pnpm/action-setup will result in an error: `Multiple versions of pnpm specified`.
The previous implementation was only meant to avoid the "ERR_PNPM_BAD_PM_VERSION" error, but it did not take into account the situation of multiple different package managers.
Signed-off-by: Kevin Cui <bh@bugs.cc>
> The v2 version of this action [has stopped working](https://github.com/pnpm/action-setup/issues/135) with newer Node.js versions. Please, upgrade to the latest version to fix any issues.
# Setup pnpm
Install pnpm package manager.
@@ -10,7 +14,7 @@ Version of pnpm to install.
**Optional** when there is a [`packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html).
otherwise, this field is **required** It supports npm versioning scheme, it could be an exact version (such as `6.24.1`), or a version range (such as `6`, `6.x.x`, `6.24.x`, `^6.24.1`, `*`, etc.), or `latest`.
otherwise, this field is **required** It supports npm versioning scheme, it could be an exact version (such as `10.9.8`), or a version range (such as `10`, `10.x.x`, `10.9.x`, `^10.9.8`, `*`, etc.), or `latest`.
### `dest`
@@ -36,11 +40,19 @@ If `run_install` is a YAML string representation of either an object or an array
#### `run_install.args`
**Optional** (_type:_ `string[]`) Additional arguments after `pnpm [recursive] install`, e.g. `[--frozen-lockfile, --strict-peer-dependencies]`.
**Optional** (_type:_ `string[]`) Additional arguments after `pnpm [recursive] install`, e.g. `[--ignore-scripts, --strict-peer-dependencies]`.
### `cache`
**Optional** (_type:_ `boolean`, _default:_`false`) Whether to cache the pnpm store directory.
### `cache_dependency_path`
**Optional** (_type:_ `string|string[]`, _default:_`pnpm-lock.yaml`) File path to the pnpm lockfile, which contents hash will be used as a cache key.
### `package_json_file`
**Optional** (_type:_ `string`, _default:_`package.json`) File path to the `package.json` to read "packageManager" configuration.
**Optional** (_type:_ `string`, _default:_`package.json`) File path to the `package.json`/[`package.yaml`](https://github.com/pnpm/pnpm/pull/1799) to read "packageManager" configuration.
### `standalone`
@@ -60,7 +72,9 @@ Location of `pnpm` and `pnpx` command.
## Usage example
### Just install pnpm
### Install only pnpm without `packageManager`
This works when the repo either doesn't have a `package.json` or has a `package.json` but it doesn't specify `packageManager`.
```yaml
on:
@@ -72,9 +86,26 @@ jobs:
runs-on:ubuntu-latest
steps:
- uses:pnpm/action-setup@v2
- uses:pnpm/action-setup@v6
with:
version:8
version:10
```
### Install only pnpm with `packageManager`
Omit `version` input to use the version in the [`packageManager` field in the `package.json`](https://nodejs.org/api/corepack.html).
description:Whether to cache the pnpm store directory
required:false
default:'false'
cache_dependency_path:
description:File path to the pnpm lockfile, which contents hash will be used as a cache key
required:false
default:'pnpm-lock.yaml'
package_json_file:
description:File path to the package.json to read "packageManager" configuration
description:File path to the package.json to read "packageManager" configuration. This path must be relative to the repository root (GITHUB_WORKSPACE).
required:false
default:'package.json'
standalone:
description:When set to true, @pnpm/exe, which is a Node.js bundled package, will be installed, enabling using pnpm without Node.js.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.