Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 111 additions & 13 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ jobs:
matrix:
include:
- os: ubuntu-latest
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
- os: ubuntu-24.04-arm
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
- os: macos-latest
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
- os: windows-latest
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -56,6 +56,104 @@ jobs:
fi
shell: bash

smoke-v11:
# v11's release archive bundles a Node-SEA launcher plus a sibling `dist/`,
# unlike v12's single self-contained binary. Verify the action downloads and
# lays it out correctly and puts pnpm on PATH across OSes/arches. (macos-latest
# is arm64; pnpm v11 ships no Intel-macOS binary, so no macos-13 entry here.)
name: 'Smoke pnpm 11 (${{ matrix.os }})'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v6

- id: pnpm
uses: ./
with:
version: '11.17.0'
install: false

- name: 'Test: pnpm 11 on PATH matches request'
env:
BIN_DEST: ${{ steps.pnpm.outputs.bin-dest }}
run: |
set -e
which pnpm
actual="$(pnpm --version)"
echo "pnpm --version: ${actual}"
if [ "${actual}" != "11.17.0" ]; then
echo "Expected pnpm 11.17.0, got ${actual}"
exit 1
fi
bin_dest_version="$("$BIN_DEST/pnpm" --version)"
if [ "${bin_dest_version}" != "11.17.0" ]; then
echo "Expected 11.17.0 via bin_dest, got ${bin_dest_version}"
exit 1
fi
shell: bash

runtime-node-pnpm11:
# The whole premise of supporting v11 is that `pnpm runtime` works there.
# Install pnpm v11, install a runtime through it, and run `pnpm install`.
name: 'Runtime node + install on pnpm 11'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up a synthetic package.json
# Use a fresh manifest (and drop the repo lockfile) so `pnpm install`
# under v11 resolves cleanly rather than against a v12-authored lockfile.
run: |
rm -f pnpm-lock.yaml
cat > package.json <<'EOF'
{
"dependencies": {
"is-odd": "3.0.1"
}
}
EOF
shell: bash

- id: pnpm
uses: ./
with:
version: '11.17.0'
runtime: node@22

- name: 'Test: pnpm 11, node 22, and install all worked'
env:
OUT_NAME: ${{ steps.pnpm.outputs.runtime-name }}
OUT_VERSION: ${{ steps.pnpm.outputs.runtime-version }}
run: |
set -e
pnpm_version="$(pnpm --version)"
echo "pnpm --version: ${pnpm_version}"
case "${pnpm_version}" in
11.*) ;;
*) echo "Expected pnpm 11.x, got ${pnpm_version}"; exit 1 ;;
esac
which node
node_version="$(node --version)"
echo "node --version: ${node_version}"
case "${node_version}" in
v22.*) ;;
*) echo "Expected node v22.x, got ${node_version}"; exit 1 ;;
esac
if [ "${OUT_NAME}" != "node" ]; then
echo "Expected outputs.runtime-name=node, got ${OUT_NAME}"; exit 1
fi
if [ "${OUT_VERSION}" != "22" ]; then
echo "Expected outputs.runtime-version=22, got ${OUT_VERSION}"; exit 1
fi
if [ ! -d node_modules/is-odd ]; then
echo "Expected pnpm install to populate node_modules/is-odd"; exit 1
fi
shell: bash

version-from-dist-tag:
# `version` may be an npm dist-tag; it is resolved against the main
# `pnpm` package's dist-tags.
Expand Down Expand Up @@ -127,7 +225,7 @@ jobs:
- id: pnpm
uses: ./
with:
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
runtime: node@${{ matrix.major }}

- name: 'Test: node binary on PATH'
Expand Down Expand Up @@ -168,7 +266,7 @@ jobs:
- uses: actions/checkout@v6
- uses: ./
with:
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
runtime: bun@latest
- name: 'Test: bun on PATH'
run: |
Expand All @@ -184,7 +282,7 @@ jobs:
- uses: actions/checkout@v6
- uses: ./
with:
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
runtime: deno@2
- name: 'Test: deno on PATH'
run: |
Expand All @@ -209,7 +307,7 @@ jobs:
rm -f pnpm-lock.yaml
cat > package.json <<'EOF'
{
"packageManager": "pnpm@12.0.0-alpha.19",
"packageManager": "pnpm@12.0.0-alpha.21",
"devEngines": {
"runtime": { "name": "node", "version": "^22.0.0", "onFail": "download" }
},
Expand Down Expand Up @@ -266,7 +364,7 @@ jobs:
rm -f pnpm-lock.yaml
cat > package.json <<'EOF'
{
"packageManager": "pnpm@12.0.0-alpha.19",
"packageManager": "pnpm@12.0.0-alpha.21",
"devEngines": {
"runtime": { "name": "node", "version": "^20.0.0", "onFail": "download" }
},
Expand Down Expand Up @@ -311,7 +409,7 @@ jobs:
rm -f pnpm-lock.yaml
cat > package.json <<'EOF'
{
"packageManager": "pnpm@12.0.0-alpha.19",
"packageManager": "pnpm@12.0.0-alpha.21",
"devEngines": {
"runtime": { "name": "node", "version": "^20.0.0", "onFail": "download" }
}
Expand Down Expand Up @@ -348,7 +446,7 @@ jobs:
rm -f pnpm-lock.yaml
cat > package.json <<'EOF'
{
"packageManager": "pnpm@12.0.0-alpha.19",
"packageManager": "pnpm@12.0.0-alpha.21",
"dependencies": {
"is-odd": "3.0.1"
}
Expand All @@ -358,7 +456,7 @@ jobs:

- uses: ./
with:
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'
install: false

- name: 'Test: node_modules was not populated'
Expand All @@ -385,7 +483,7 @@ jobs:
- id: pnpm
uses: ./
with:
version: '12.0.0-alpha.19'
version: '12.0.0-alpha.21'

- name: 'Test: pnpm works, runtime outputs are empty'
env:
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@

Install pnpm **and** a JavaScript runtime (Node.js, Bun, or Deno) in a single GitHub Actions step.

Since v12, pnpm is a standalone executable — the action downloads the native binary for the runner's platform directly from the npm registry (no Node.js or npm needed) and then uses `pnpm runtime set` to install the requested runtime. The runtime binary is placed on `PATH` for subsequent steps, replacing the need for `actions/setup-node`, `oven-sh/setup-bun`, or `denoland/setup-deno`. `pnpm install` runs automatically when a `package.json` is present.
pnpm ships a self-contained release binary — the action downloads it for the runner's platform directly from pnpm's GitHub releases (no Node.js or npm needed) and then uses `pnpm runtime set` to install the requested runtime. The runtime binary is placed on `PATH` for subsequent steps, replacing the need for `actions/setup-node`, `oven-sh/setup-bun`, or `denoland/setup-deno`. `pnpm install` runs automatically when a `package.json` is present.

> [!NOTE]
> This action installs pnpm v12 and newer only. To install pnpm 11 or older (which are Node.js programs, not standalone executables), use [`pnpm/action-setup`](https://github.com/pnpm/action-setup) instead.
> This action installs pnpm v11 and newer only — it relies on pnpm's self-contained release binaries and the `pnpm runtime` command, both available from v11. To install pnpm 10 or older, use [`pnpm/action-setup`](https://github.com/pnpm/action-setup) instead.
>
> One caveat: pnpm v11 publishes no binary for Intel macOS (`darwin-x64`); use v12 or newer on Intel macOS runners.

If your `package.json` declares `devEngines.runtime`, the action picks up the runtime and version from there automatically — no inputs required.

## Inputs

| Name | Description |
|------|-------------|
| `version` | Version of pnpm to install: an exact version, a semver range (`^12.0.0`), or a dist-tag (`next-12`). Must resolve to v12 or newer. Optional when `packageManager` or `devEngines.packageManager` is set in `package.json`. |
| `version` | Version of pnpm to install: an exact version, a semver range (`^12.0.0`), or a dist-tag (`next-12`). Must resolve to v11 or newer. Optional when `packageManager` or `devEngines.packageManager` is set in `package.json`. |
| `dest` | Where to store pnpm files. Defaults to `~/setup-pnpm`. |
| `runtime` | Runtime spec, in `<name>` or `<name>@<version>` form (e.g. `node@22`, `node@lts`, `bun@latest`, `deno@2`). Supported names: `node`, `bun`, `deno`. When the version is omitted, falls back to `devEngines.runtime` in `package.json`, then to `lts` (for `node`) / `latest`. If the input itself is omitted, the action reads `devEngines.runtime` from `package.json`. |
| `cache` | Cache the pnpm store directory. Default: `false`. |
| `cache-dependency-path` | Path(s) to the pnpm lockfile, used to compute the cache key. Default: `pnpm-lock.yaml`. |
| `package-json-file` | Path to `package.json` (relative to `GITHUB_WORKSPACE`). Default: `package.json`. |
| `install` | Run `pnpm install` after setup. Default: `true`. Set to `false` for jobs that only need pnpm itself (e.g. `pnpm audit`, lockfile-only regeneration). |
| `token` | GitHub token used to look up the pnpm release and its asset checksum via the GitHub API. Defaults to `${{ github.token }}`, which lifts the low anonymous rate limit. Rarely needs to be set. |

## Outputs

Expand Down Expand Up @@ -107,7 +110,7 @@ For jobs that only need pnpm itself — e.g. `pnpm audit`, lockfile-only regener

## How it works

1. The action resolves the requested version (exact, range, or dist-tag) against the npm registry, downloads the `@pnpm/exe.<os>-<arch>` tarball for the runner's platform, verifies its integrity, and places the `pnpm` executable (plus the `pnpx`, `pn`, and `pnx` aliases) into `dest`. No Node.js or npm is involved.
1. The action resolves the requested version (exact, range, or dist-tag) against the npm registry, then downloads the matching self-contained release archive for the runner's platform (`pnpm-<os>-<arch>.tar.gz`, or `pnpm-win32-<arch>.zip` on Windows) from pnpm's GitHub releases. It verifies the archive against the SHA-256 digest GitHub publishes for the asset, extracts the `pnpm` executable (and, for pnpm builds that need it, its bundled `dist/`), and links the `pnpx`, `pn`, and `pnx` aliases into `dest`. No Node.js or npm is involved.
2. `PNPM_HOME` is exported and `dest` plus `$PNPM_HOME/bin` are added to `PATH`.
3. The action runs `pnpm runtime set <name> <version> -g`, which downloads the requested runtime into `$PNPM_HOME/bin` — making `node`, `bun`, or `deno` available to later workflow steps.
4. If a `package.json` exists in the workspace, the action runs `pnpm install` (unless `install: false` is set). When the `runtime` input is set, `--no-runtime` is appended so the just-installed runtime isn't shadowed by a different version declared in `devEngines.runtime`.
Expand Down
11 changes: 9 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ inputs:
description: |
Version of pnpm to install: an exact version (`12.0.0-alpha.17`), a
semver range (`^12.0.0`), or an npm dist-tag (`next-12`). Must resolve
to pnpm v12 or newer — since v12, pnpm is a standalone executable, and
the action downloads it directly without needing Node.js or npm.
to pnpm v11 or newer — the action downloads pnpm's native per-platform
executable directly, without needing Node.js or npm.

When omitted, the version is read from `devEngines.packageManager` or
`packageManager` in the project's package.json.
Expand Down Expand Up @@ -59,6 +59,13 @@ inputs:
need pnpm itself (e.g. `pnpm audit`, lockfile-only regeneration).
required: false
default: 'true'
token:
description: |
GitHub token used to look up the pnpm release (and its asset checksum)
via the GitHub API. Defaults to the workflow's automatic token, which
lifts the low anonymous API rate limit. Rarely needs to be set.
required: false
default: ${{ github.token }}
outputs:
dest:
description: Expanded path of inputs#dest
Expand Down
Loading