Compare commits

..

59 Commits
v3 ... fix/252

Author SHA1 Message Date
Zoltan Kochan
b635341b3e chore: rebuild bundle after reverting bootstrap pnpm to 11.0.4 2026-05-11 01:49:47 +02:00
Zoltan Kochan
025de3876f test: assert env var directly instead of relying on pnpm runtime behavior
The previous version of this test self-updated to pnpm 10.33.0 and expected
pnpm --version to fail under devEngines.onFail=error. But pnpm v10 only reads
the packageManager field, not devEngines, so the check never fired and the
test failed.

Switch the assertion to the contract the af8e203 scope fix actually enforces:
when an explicit version: is supplied, pnpm_config_pm_on_fail must not be
exported by the action. That's deterministic and pnpm-version-agnostic.
2026-05-11 01:49:23 +02:00
Zoltan Kochan
5c388c2b3b test: assert onFail=error stays strict when explicit version is supplied
Adds a manifest_pin matrix entry combining `version: 10.33.0` with
`devEngines.packageManager` pinned to 9.15.5/onFail=error. The action
self-updates to 10.33.0, then `pnpm --version` must fail with
BAD_PM_VERSION — confirming pnpm_config_pm_on_fail=download is scoped to
the no-target-version branch and does not silently override the user's
strict policy.
2026-05-11 01:49:23 +02:00
Zoltan Kochan
84fa52c3d7 ci: consolidate test matrix and stop double-firing on PRs
- Limit push trigger to master so PRs run jobs once instead of twice.
- Fold test_default_inputs / test_dest / test_version_respects_request /
  test_bin_dest_output into one cross-OS `smoke` job (5 matrix entries:
  ubuntu × 2 versions + ubuntu custom-dest + macos + windows).
- Fold test_package_manager_field / test_dev_engines /
  test_dev_engines_on_fail_error into one ubuntu-only `manifest_pin` job
  (6 matrix entries) — manifest handling is OS-independent.
- Keep standalone and run_install but ubuntu-only.

Total runs per PR push: ~88 → 14.
2026-05-11 01:49:22 +02:00
Zoltan Kochan
07ad39cba4 refactor: scope pm_on_fail override to the no-target-version branch
Only export `pnpm_config_pm_on_fail=download` when the action is relying on
bootstrap pnpm's runtime switch. When the user passes an explicit `version:`
input, self-update aligns the binary and we should not silently override their
`devEngines.packageManager.onFail = "error"` policy.
2026-05-11 01:49:22 +02:00
Zoltan Kochan
052c4ffb7d fix: honor devEngines.packageManager.onFail=error (#252)
Export pnpm_config_pm_on_fail=download so the bootstrap pnpm switches to
the version pinned in devEngines.packageManager instead of throwing
BAD_PM_VERSION on the user's first invocation.
2026-05-11 01:49:22 +02:00
Zoltan Kochan
91ab88e261 fix: bin_dest output points to self-updated pnpm, not bootstrap (#249)
* fix: bin_dest output points to self-updated pnpm, not bootstrap (#247)

`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.

* test(ci): pass bin_dest via env to survive Windows backslashes

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.

* build: rebuild dist with clean lockfile-matched deps
2026-05-07 12:58:58 +02:00
Zoltan Kochan
e578e19d19 fix: update pnpm to 11.0.4 2026-05-04 12:08:02 +02:00
Zoltan Kochan
8912a9102a fix: append (not prepend) action node dir to PATH for npm bootstrap (#241) 2026-05-02 16:10:46 +02:00
Ben Quarmby
26f6d4f2c5 fix: use npm co-located with the action node binary (#239)
* 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>
2026-04-30 22:28:41 +02:00
Zoltan Kochan
903f9c1a6e fix: update pnpm to 11.0.0-rc.5 2026-04-21 20:27:39 +02:00
Zoltan Kochan
bdf0af2a9d test: add strict version-match jobs to reproduce #225 / #227
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.
2026-04-18 15:20:30 +02:00
oniani1
71c92474e7 fix: pnpm self-update binary shadowed by bootstrap on PATH (#230)
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.
2026-04-18 15:00:23 +02:00
Zoltan Kochan
078e9d4164 fix: update pnpm to 11.0.0-rc.2 2026-04-18 00:36:06 +02:00
Zoltan Kochan
08c4be7e2e docs(README): update action-setup version 2026-04-10 23:08:33 +02:00
Zoltan Kochan
579891461a chore: update .gitignore 2026-04-10 23:03:10 +02:00
Zoltan Kochan
ddffd66754 fix: remove accidentally committed file 2026-04-10 23:02:48 +02:00
Zoltan Kochan
b43f991918 fix: update pnpm to 11.0.0-rc.0 2026-04-10 23:00:38 +02:00
Andreas Deininger
3852509c9e README.md: bring versions up-to-date (#222) 2026-04-10 22:48:51 +02:00
Zoltan Kochan
6e7bdbda5f chore: bump bootstrap pnpm to 11.0.0-beta.4-1 and add update script
Add `pnpm run update-bootstrap [version]` to regenerate the bootstrap
lockfiles. Bumps from 11.0.0-beta.3 to 11.0.0-beta.4-1.
2026-03-30 00:09:19 +02:00
Zoltan Kochan
6b87c4621a fix: Windows standalone mode — bypass broken npm shims (#217)
* 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.
2026-03-27 20:42:10 +01:00
Zoltan Kochan
994d756a33 feat: read pnpm version from devEngines.packageManager (#211)
* 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>
2026-03-27 11:10:47 +01:00
Zoltan Kochan
738f428026 docs: upgrade pnpm/action-setup from v4 to v5 2026-03-26 22:31:06 +01:00
Zoltan Kochan
62bce64275 fix: extract pnpm version from packageManager field instead of returning undefined (#216)
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.
2026-03-25 13:59:54 +01:00
Zoltan Kochan
58e6119fe4 feat!: replace bundled pnpm binary with npm + lockfile bootstrap (#212)
* 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>
2026-03-21 14:02:31 +01:00
axel7083
2e223e0f0d chore(workflows): adding pr-check.yaml to validate dist folder (#213)
* chore(workflows): adding pr-check.yaml to validate dist/index.js

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

* fix: update dist/index.js

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>

---------

Signed-off-by: axel7083 <42176370+axel7083@users.noreply.github.com>
2026-03-19 14:52:48 +01:00
Zoltan Kochan
fc06bc1257 feat!: run the action on Node.js 24 (#205) 2026-03-13 11:30:26 +01:00
Zoltan Kochan
b906affcce Revert "feat!: run the action on Node.js 24 (#205)"
This reverts commit 9b5745cdf0.
2026-03-11 15:54:42 +01:00
Zoltan Kochan
9b5745cdf0 feat!: run the action on Node.js 24 (#205) 2026-02-17 13:30:54 +01:00
Boosted-Bonobo
1e1c8eafbd ci: pin github actions (#199) 2025-12-15 14:31:35 +01:00
Khải
b9e1dbc72f fix(ci): exclude macos (#197) 2025-12-10 13:54:29 +01:00
Khải
61bc82c7df refactor: remove star imports (#196) 2025-12-10 13:14:52 +01:00
Jeremiasz Major
e94b270858 feat: store caching (#188)
* add pnpm store caching

* style: format

* no semicolons
* no star imports
* import order

* style: no star imports

---------

Co-authored-by: khai96_ <hvksmr1996@gmail.com>
2025-12-07 22:16:49 +01:00
Chris Martin
ee7b8711bd Clarify that package_json_file is relative to GITHUB_WORKSPACE (#184)
* 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>
2025-12-07 01:36:02 +01:00
silverwind
3a0024f066 Remove unused @types/node-fetch dependency (#186) 2025-12-05 16:50:59 +01:00
Roman Usherenko
72f04517b7 Update README.md (#175)
fix the string run_install example
2025-12-05 15:32:46 +01:00
Adrian Riedel
41ff726559 feat: support installation from custom NPM registry (#179)
copy .npmrc from GitHub workspace if it exists so that PNPM respects custom
registry configurations when self-installing
2025-10-08 10:48:14 +02:00
Matthias
f2b2b233b5 Remove --frozen-lockfile from examples (#171) 2025-07-11 00:08:35 +02:00
Matthias
77504a59bc Fix multiline run_install example in README.md (#167) 2025-06-25 18:10:02 +02:00
Kevin Cui
d648c2dd06 fix: not allow install multiple package manager (#161)
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>
2025-03-28 08:37:14 +08:00
Zoltan Kochan
a7487c7e89 feat: update dist 2025-02-06 22:30:07 +01:00
Zoltan Kochan
fff70888d0 test: update pnpm to v9 2025-02-06 22:24:21 +01:00
Daniel Bayley
6e3017af18 docs: support package.yaml (#157) 2025-02-06 22:13:04 +01:00
Daniel Bayley
0cb0538c33 feat: support package.yaml (#156) 2025-02-06 22:04:41 +01:00
Azat S.
e303250a24 docs: update pnpm version in readme examples (#154) 2025-01-09 00:42:18 +01:00
Karl Horky
ac5bf11548 Update examples to use pnpm v9 (#142) 2024-08-14 16:07:48 +02:00
its-monotype
18ac635edf docs: remove redundant manual cache due to setup-node cache (#131) 2024-07-05 15:24:26 +02:00
Zoltan Kochan
0d0b43217a docs: add warning about v2 2024-07-05 14:37:28 +02:00
Karl Horky
0eb0e97082 Add readme example for omitting version (#134)
* Add readme example for omitting `version`

* docs: more detailed explanations

---------

Co-authored-by: Khải <hvksmr1996@gmail.com>
2024-07-04 10:37:24 +02:00
hyesung oh
23657c8550 docs: change order of setup node and pnpm (#129) 2024-06-16 14:33:23 +02:00
Linda_pp
5d79380f29 fix: add missing outputs metadata to action.yml (#127) 2024-06-12 16:45:37 +02:00
Ben McCann
562dbbf611 fix: correct typo in error message (#125) 2024-06-12 16:44:03 +02:00
Ben McCann
00884bcdc5 docs: use cached pnpm installation (#128) 2024-06-12 16:38:46 +02:00
Zoltan Kochan
fe02b34f77 docs: bump action-setup version in README 2024-05-07 15:16:48 +02:00
Karl Horky
bee1f099e5 feat: throw error when multiple versions specified (#122)
* Throw error when multiple versions specified

* fix: fmt

* fix: fmt

* Swallow error on ENOENT

* Match versions

* refactor: install pnpm

---------

Co-authored-by: Khải <hvksmr1996@gmail.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
2024-05-06 23:24:46 +02:00
Sukka
ce859e384f refactor: replace fs-extra with Node.js built-in fs methods (#120) 2024-04-16 11:26:31 +02:00
Zoltan Kochan
2ab6dce4f5 docs(README): fix link to LICENSE 2024-03-27 22:17:57 +01:00
Ethan Neff
e280758d01 docs(README): update dependency versions (#117) 2024-03-27 22:12:51 +01:00
dependabot[bot]
129abb77bf Bump undici from 5.28.2 to 5.28.3 (#115)
Bumps [undici](https://github.com/nodejs/undici) from 5.28.2 to 5.28.3.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.2...v5.28.3)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:59:33 +01:00
26 changed files with 2259 additions and 237841 deletions

28
.github/workflows/pr-check.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: pr-check
on: [ pull_request ]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: true
version: 9
- name: Update dist/index.js
run: pnpm run build
- name: Check for uncommitted changes in dist
run: git diff --exit-code dist/index.js

View File

@@ -1,147 +1,210 @@
name: Test Action name: Test Action
on: on:
- push pull_request:
- pull_request push:
- workflow_dispatch branches:
- master
workflow_dispatch:
jobs: jobs:
test_default_inputs: smoke:
name: Test with default inputs # Cross-OS coverage. Exercises the bootstrap install + PATH on each platform,
# the version-respects-request regression (#225 / #230 — Windows PATH shadow),
# and the bin_dest output regression (#247). Multi-version coverage on Linux
# so we don't pay 3x for major-version differences.
name: 'Smoke (${{ matrix.name }})'
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
pnpm: include:
- 4.11.1 - name: 'ubuntu / v9.15.5'
os: os: ubuntu-latest
- ubuntu-latest version: '9.15.5'
- macos-latest - name: 'ubuntu / v10.33.0'
- windows-latest os: ubuntu-latest
version: '10.33.0'
- name: 'ubuntu / v9.15.5 / custom-dest'
os: ubuntu-latest
version: '9.15.5'
dest: '~/test/pnpm'
- name: 'macos / v9.15.5'
os: macos-latest
version: '9.15.5'
- name: 'windows / v9.15.5'
os: windows-latest
version: '9.15.5'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- id: pnpm
name: Run the action
uses: ./
with:
version: ${{ matrix.version }}
dest: ${{ matrix.dest || '~/setup-pnpm' }}
- name: 'Test: pnpm/pnpx on PATH report the requested version (incl. via bin_dest)'
# Pass paths via env, not template interpolation, so Windows
# backslashes in `bin_dest` aren't eaten by bash's escape handling.
env:
BIN_DEST: ${{ steps.pnpm.outputs.bin_dest }}
REQUIRED: ${{ matrix.version }}
run: |
set -e
which pnpm
which pnpx
actual="$(pnpm --version)"
echo "pnpm --version: ${actual}"
if [ "${actual}" != "${REQUIRED}" ]; then
echo "Expected pnpm version ${REQUIRED}, but got ${actual}"
exit 1
fi
bin_dest_version="$("$BIN_DEST/pnpm" --version)"
echo "bin_dest pnpm --version: ${bin_dest_version}"
if [ "${bin_dest_version}" != "${REQUIRED}" ]; then
echo "Expected ${REQUIRED} via bin_dest, but got ${bin_dest_version}"
exit 1
fi
shell: bash
- name: 'Test: install in a fresh project'
run: |
mkdir /tmp/test-project
cd /tmp/test-project
pnpm init
pnpm add is-odd
shell: bash
manifest_pin:
# Folds the old test_package_manager_field, test_dev_engines, and
# test_dev_engines_on_fail_error jobs. The action's manifest handling is
# OS-independent, so ubuntu-only is sufficient.
name: 'Manifest pin: ${{ matrix.label }}'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: 'packageManager pnpm@9.15.5 (#227)'
manifest: '{"packageManager":"pnpm@9.15.5"}'
version: '9.15.5'
- label: 'packageManager pnpm@10.33.0'
manifest: '{"packageManager":"pnpm@10.33.0"}'
version: '10.33.0'
- label: 'devEngines onFail=download, exact'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"download"}}}'
version: '9.15.5'
- label: 'devEngines onFail=download, range'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=9.15.0","onFail":"download"}}}'
version: '>=9.15.0'
- label: 'devEngines onFail=error, exact (#252)'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"error"}}}'
version: '9.15.5'
- label: 'devEngines onFail=error, range (#252)'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=9.15.0","onFail":"error"}}}'
version: '>=9.15.0'
- label: 'explicit version: pnpm_config_pm_on_fail not exported'
# Regression guard for the af8e203 scope fix: when the user passes an
# explicit `version:` input, the action must NOT export
# pnpm_config_pm_on_fail=download, so the user's strict onFail policy
# is preserved. Asserted directly on the env var rather than pnpm
# runtime behavior — different pnpm majors read devEngines
# differently (v10 ignores it, v11+ honors it).
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"9.15.5","onFail":"error"}}}'
explicit_version: '10.33.0'
expect_pm_on_fail_unset: true
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up package.json
run: echo '${{ matrix.manifest }}' > package.json
shell: bash
- name: Run the action - name: Run the action
uses: ./ uses: ./
with: with:
version: 4.11.1 version: ${{ matrix.explicit_version }}
- name: 'Test: which' - name: 'Test: pnpm reports the pinned version'
run: which pnpm; which pnpx if: ${{ !matrix.expect_pm_on_fail_unset }}
env:
REQUIRED: ${{ matrix.version }}
run: |
set -e
actual="$(pnpm --version)"
echo "pnpm version: ${actual}"
if [ "${REQUIRED}" = ">=9.15.0" ]; then
min="9.15.0"
if [ "$(printf '%s\n' "${min}" "${actual}" | sort -V | head -n1)" != "${min}" ]; then
echo "Expected pnpm version >= ${min}, but got ${actual}"
exit 1
fi
else
if [ "${actual}" != "${REQUIRED}" ]; then
echo "Expected pnpm version ${REQUIRED}, but got ${actual}"
exit 1
fi
fi
shell: bash
- name: 'Test: install' - name: 'Test: pnpm_config_pm_on_fail not exported (explicit version preserves strict policy)'
run: pnpm install if: ${{ matrix.expect_pm_on_fail_unset }}
run: |
test_dest: if [ -n "${pnpm_config_pm_on_fail:-}" ]; then
name: Test with dest echo "Expected pnpm_config_pm_on_fail to be unset, but got: '${pnpm_config_pm_on_fail}'"
exit 1
runs-on: ${{ matrix.os }} fi
echo "pnpm_config_pm_on_fail is unset, as expected"
strategy: shell: bash
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
steps:
- uses: actions/checkout@v4
- name: Run the action
uses: ./
with:
version: 4.11.1
dest: ~/test/pnpm
- name: 'Test: which'
run: which pnpm && which pnpx
- name: 'Test: install'
run: pnpm install
test_standalone:
name: Test with standalone
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
standalone: standalone:
- true name: Standalone mode
- false
runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Run the action - name: Run the action
uses: ./ uses: ./
with: with:
version: 7.0.0 version: 9.15.0
standalone: ${{ matrix.standalone }} standalone: true
- name: install Node.js - name: 'Test: pnpm works'
uses: actions/setup-node@v4
with:
# pnpm@7.0.0 is not compatible with Node.js 12
node-version: 12.22.12
- name: 'Test: which (pnpm)'
run: which pnpm
- name: 'Test: which (pnpx)'
if: matrix.standalone == false
run: which pnpx
- name: 'Test: install when standalone is true'
if: matrix.standalone
run: pnpm install
- name: 'Test: install when standalone is false'
if: matrix.standalone == false
# Since the default shell on windows runner is pwsh, we specify bash explicitly
shell: bash
run: | run: |
if pnpm install; then set -e
echo "pnpm install should fail" which pnpm
actual="$(pnpm --version)"
if [ "${actual}" != "9.15.0" ]; then
echo "Expected 9.15.0, got ${actual}"
exit 1 exit 1
else
echo "pnpm install failed as expected"
fi fi
mkdir /tmp/test-standalone
cd /tmp/test-standalone
pnpm init
pnpm add is-odd
shell: bash
test_run_install: run_install:
name: 'Test with run_install (${{ matrix.run_install.name }}, ${{ matrix.os }})' name: 'run_install (${{ matrix.run_install.name }})'
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
run_install: run_install:
- name: 'null' - name: 'null'
value: 'null' value: 'null'
- name: 'empty object'
value: '{}'
- name: 'recursive'
value: |
recursive: true
- name: 'global' - name: 'global'
value: | value: |
args: args:
@@ -149,29 +212,24 @@ jobs:
- --global-dir=./pnpm-global - --global-dir=./pnpm-global
- npm - npm
- yarn - yarn
- pnpm
- name: 'array'
value: |
- {}
- recursive: true
- args:
- --global
- --global-dir=./pnpm-global
- npm
- yarn
- pnpm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Run the action - name: Run the action
uses: ./ uses: ./
with: with:
version: 4.11.1 version: 9.15.5
run_install: ${{ matrix.run_install.value }} run_install: ${{ matrix.run_install.value }}
- name: 'Test: which' - name: 'Test: pnpm works'
run: which pnpm; which pnpx run: |
set -e
- name: 'Test: install' which pnpm
run: pnpm install which pnpx
actual="$(pnpm --version)"
if [ "${actual}" != "9.15.5" ]; then
echo "Expected 9.15.5, got ${actual}"
exit 1
fi
shell: bash

3
.gitignore vendored
View File

@@ -2,8 +2,6 @@ node_modules
*.log *.log
/dist/* /dist/*
!/dist/index.js !/dist/index.js
!/dist/pnpm.cjs
!/dist/worker.js
tmp tmp
temp temp
*.tmp *.tmp
@@ -11,3 +9,4 @@ temp
tmp.* tmp.*
temp.* temp.*
.pnpm-store .pnpm-store
.claude

View File

@@ -1,3 +1,7 @@
> ## :warning: Upgrade from v2!
>
> 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 # Setup pnpm
Install pnpm package manager. 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). **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` ### `dest`
@@ -36,11 +40,19 @@ If `run_install` is a YAML string representation of either an object or an array
#### `run_install.args` #### `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` ### `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` ### `standalone`
@@ -60,7 +72,9 @@ Location of `pnpm` and `pnpx` command.
## Usage example ## 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 ```yaml
on: on:
@@ -72,9 +86,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v6
with: 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).
```yaml
on:
- push
- pull_request
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: pnpm/action-setup@v6
``` ```
### Install pnpm and a few npm packages ### Install pnpm and a few npm packages
@@ -89,14 +120,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v6
- uses: pnpm/action-setup@v3 - uses: pnpm/action-setup@v6
with: with:
version: 8 version: 10
run_install: | run_install: |
- recursive: true - recursive: true
args: [--frozen-lockfile, --strict-peer-dependencies] args: [--strict-peer-dependencies]
- args: [--global, gulp, prettier, typescript] - args: [--global, gulp, prettier, typescript]
``` ```
@@ -113,31 +144,13 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v6
- name: Install Node.js - uses: pnpm/action-setup@v6
uses: actions/setup-node@v3
with:
node-version: 16
- uses: pnpm/action-setup@v3
name: Install pnpm name: Install pnpm
with: with:
version: 8 version: 10
run_install: false cache: true
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies - name: Install dependencies
run: pnpm install run: pnpm install
@@ -151,4 +164,4 @@ This action does not setup Node.js for you, use [actions/setup-node](https://git
## License ## License
[MIT](https://git.io/JfclH) © [Hoàng Văn Khải](https://github.com/KSXGitHub/) [MIT](https://github.com/pnpm/action-setup/blob/master/LICENSE.md) © [Hoàng Văn Khải](https://github.com/KSXGitHub/)

View File

@@ -15,15 +15,28 @@ inputs:
description: If specified, run `pnpm install` description: If specified, run `pnpm install`
required: false required: false
default: 'null' default: 'null'
cache:
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: 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 required: false
default: 'package.json' default: 'package.json'
standalone: standalone:
description: When set to true, @pnpm/exe, which is a Node.js bundled package, will be installed, enabling using pnpm without Node.js. description: When set to true, @pnpm/exe, which is a Node.js bundled package, will be installed, enabling using pnpm without Node.js.
required: false required: false
default: 'false' default: 'false'
outputs:
dest:
description: Expanded path of inputs#dest
bin_dest:
description: Location of `pnpm` and `pnpx` command
runs: runs:
using: node20 using: node24
main: dist/index.js main: dist/index.js
post: dist/index.js post: dist/index.js

289
dist/index.js vendored

File diff suppressed because one or more lines are too long

220780
dist/pnpm.cjs vendored

File diff suppressed because one or more lines are too long

16625
dist/worker.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,24 @@
{ {
"private": true, "private": true,
"scripts": { "scripts": {
"build:ncc": "ncc build --minify --no-source-map-register --no-cache dist/tsc/index.js --out dist/", "build:bundle": "esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --minify --outfile=dist/index.js --loader:.json=json",
"build": "tsc && pnpm run build:ncc", "build": "pnpm run build:bundle",
"start": "pnpm run build && sh ./run.sh", "start": "pnpm run build && sh ./run.sh",
"update-pnpm-dist": "pnpm install && cp ./node_modules/pnpm/dist/pnpm.cjs ./dist/pnpm.cjs && cp ./node_modules/pnpm/dist/worker.js ./dist/worker.js" "update-bootstrap": "node scripts/update-bootstrap.mjs"
}, },
"dependencies": { "dependencies": {
"@actions/cache": "^4.1.0",
"@actions/core": "^1.10.1", "@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.5.0",
"@types/expand-tilde": "^2.0.2", "@types/expand-tilde": "^2.0.2",
"@types/fs-extra": "^11.0.4", "@types/node": "^22.0.0",
"@types/node": "^20.11.5",
"@types/node-fetch": "^2.6.11",
"expand-tilde": "^2.0.2", "expand-tilde": "^2.0.2",
"fs-extra": "^11.2.0",
"yaml": "^2.3.4", "yaml": "^2.3.4",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@vercel/ncc": "^0.38.1", "esbuild": "^0.27.4",
"pnpm": "^8.14.3",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }
} }

1081
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
packages:
- '.'
allowBuilds:
esbuild: true

View File

@@ -0,0 +1,47 @@
#!/usr/bin/env node
// Usage: node scripts/update-bootstrap.mjs [version]
// If version is omitted, fetches the latest next-11 tag from npm.
// Regenerates the bootstrap lockfiles used by action-setup to install pnpm via npm.
import { execSync } from 'child_process'
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
const BOOTSTRAP_DIR = new URL('../src/install-pnpm/bootstrap/', import.meta.url).pathname
const version = process.argv[2] || resolveLatestVersion()
console.log(`Updating bootstrap lockfiles to pnpm@${version} ...`)
generateLock('pnpm-lock.json', { pnpm: version }, 'bootstrap-pnpm')
generateLock('exe-lock.json', { '@pnpm/exe': version }, 'bootstrap-exe')
console.log('Done!')
function resolveLatestVersion() {
const json = execSync('npm view @pnpm/exe dist-tags --json', { encoding: 'utf8' })
const tags = JSON.parse(json)
const version = tags['next-11'] || tags['latest']
if (!version) {
console.error('Could not determine latest pnpm version from npm dist-tags')
process.exit(1)
}
return version
}
function generateLock(filename, dependencies, name) {
const tmp = mkdtempSync(join(tmpdir(), 'pnpm-bootstrap-'))
try {
writeFileSync(join(tmp, 'package.json'), JSON.stringify({ private: true, dependencies }))
execSync('npm install --package-lock-only --ignore-scripts', { cwd: tmp, stdio: 'pipe' })
const lock = readFileSync(join(tmp, 'package-lock.json'), 'utf8')
const parsed = JSON.parse(lock)
parsed.name = name
writeFileSync(join(BOOTSTRAP_DIR, filename), JSON.stringify(parsed, null, 2) + '\n')
console.log(` ${filename} -> ${Object.values(dependencies)[0]}@${version}`)
} finally {
rmSync(tmp, { recursive: true, force: true })
}
}

View File

@@ -0,0 +1,19 @@
import { isFeatureAvailable } from '@actions/cache'
import { endGroup, startGroup, warning } from '@actions/core'
import { Inputs } from '../inputs'
import { runRestoreCache } from './run'
export async function restoreCache(inputs: Inputs) {
if (!inputs.cache) return
if (!isFeatureAvailable()) {
warning('Cache is not available, skipping cache restoration')
return
}
startGroup('Restoring cache...')
await runRestoreCache(inputs)
endGroup()
}
export default restoreCache

39
src/cache-restore/run.ts Normal file
View File

@@ -0,0 +1,39 @@
import { restoreCache } from '@actions/cache'
import { debug, info, saveState, setOutput } from '@actions/core'
import { getExecOutput } from '@actions/exec'
import { hashFiles } from '@actions/glob'
import os from 'os'
import { Inputs } from '../inputs'
export async function runRestoreCache(inputs: Inputs) {
const cachePath = await getCacheDirectory()
saveState('cache_path', cachePath)
const fileHash = await hashFiles(inputs.cacheDependencyPath)
if (!fileHash) {
throw new Error('Some specified paths were not resolved, unable to cache dependencies.')
}
const primaryKey = `pnpm-cache-${process.env.RUNNER_OS}-${os.arch()}-${fileHash}`
debug(`Primary key is ${primaryKey}`)
saveState('cache_primary_key', primaryKey)
let cacheKey = await restoreCache([cachePath], primaryKey)
setOutput('cache-hit', Boolean(cacheKey))
if (!cacheKey) {
info(`Cache is not found`)
return
}
saveState('cache_restored_key', cacheKey)
info(`Cache restored from key: ${cacheKey}`)
}
async function getCacheDirectory() {
const { stdout } = await getExecOutput('pnpm store path --silent')
const cacheFolderPath = stdout.trim()
debug(`Cache folder is set to "${cacheFolderPath}"`)
return cacheFolderPath
}

15
src/cache-save/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import { setFailed } from '@actions/core'
import { Inputs } from '../inputs'
import { runSaveCache } from './run'
export async function saveCache(inputs: Inputs) {
if (!inputs.cache) return
try {
await runSaveCache()
} catch (error) {
setFailed((error as Error).message)
}
}
export default saveCache

18
src/cache-save/run.ts Normal file
View File

@@ -0,0 +1,18 @@
import { saveCache } from '@actions/cache'
import { getState, info } from '@actions/core'
export async function runSaveCache() {
const state = getState('cache_restored_key')
const primaryKey = getState('cache_primary_key')
const cachePath = getState('cache_path')
if (primaryKey === state) {
info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`)
return
}
const cacheId = await saveCache([cachePath], primaryKey)
if (cacheId == -1) return
info(`Cache saved with the key: ${primaryKey}`)
}

View File

@@ -1,5 +1,7 @@
import { setFailed, saveState, getState } from '@actions/core' import { setFailed, saveState, getState } from '@actions/core'
import getInputs from './inputs' import restoreCache from './cache-restore'
import saveCache from './cache-save'
import getInputs, { Inputs } from './inputs'
import installPnpm from './install-pnpm' import installPnpm from './install-pnpm'
import setOutputs from './outputs' import setOutputs from './outputs'
import pnpmInstall from './pnpm-install' import pnpmInstall from './pnpm-install'
@@ -7,15 +9,32 @@ import pruneStore from './pnpm-store-prune'
async function main() { async function main() {
const inputs = getInputs() const inputs = getInputs()
const isPost = getState('is_post')
if (isPost === 'true') return pruneStore(inputs) if (getState('is_post') === 'true') {
await runPost(inputs)
} else {
await runMain(inputs)
}
}
async function runMain(inputs: Inputs) {
saveState('is_post', 'true') saveState('is_post', 'true')
await installPnpm(inputs)
const binDest = await installPnpm(inputs)
if (binDest === undefined) return
console.log('Installation Completed!') console.log('Installation Completed!')
setOutputs(inputs) setOutputs(inputs, binDest)
await restoreCache(inputs)
pnpmInstall(inputs) pnpmInstall(inputs)
} }
async function runPost(inputs: Inputs) {
pruneStore(inputs)
await saveCache(inputs)
}
main().catch(error => { main().catch(error => {
console.error(error) console.error(error)
setFailed(error) setFailed(error)

View File

@@ -5,6 +5,8 @@ import { RunInstall, parseRunInstall } from './run-install'
export interface Inputs { export interface Inputs {
readonly version?: string readonly version?: string
readonly dest: string readonly dest: string
readonly cache: boolean
readonly cacheDependencyPath: string
readonly runInstall: RunInstall[] readonly runInstall: RunInstall[]
readonly packageJsonFile: string readonly packageJsonFile: string
readonly standalone: boolean readonly standalone: boolean
@@ -19,6 +21,8 @@ const parseInputPath = (name: string) => expandTilde(getInput(name, options))
export const getInputs = (): Inputs => ({ export const getInputs = (): Inputs => ({
version: getInput('version'), version: getInput('version'),
dest: parseInputPath('dest'), dest: parseInputPath('dest'),
cache: getBooleanInput('cache'),
cacheDependencyPath: parseInputPath('cache_dependency_path'),
runInstall: parseRunInstall('run_install'), runInstall: parseRunInstall('run_install'),
packageJsonFile: parseInputPath('package_json_file'), packageJsonFile: parseInputPath('package_json_file'),
standalone: getBooleanInput('standalone'), standalone: getBooleanInput('standalone'),

View File

@@ -1,5 +1,5 @@
import { getInput, error } from '@actions/core' import { getInput, error } from '@actions/core'
import * as yaml from 'yaml' import { parse as parseYaml } from 'yaml'
import { z, ZodError } from 'zod' import { z, ZodError } from 'zod'
const RunInstallSchema = z.object({ const RunInstallSchema = z.object({
@@ -20,7 +20,7 @@ export type RunInstall = z.infer<typeof RunInstallSchema>
export function parseRunInstall(inputName: string): RunInstall[] { export function parseRunInstall(inputName: string): RunInstall[] {
const input = getInput(inputName, { required: true }) const input = getInput(inputName, { required: true })
const parsedInput: unknown = yaml.parse(input) const parsedInput: unknown = parseYaml(input)
try { try {
const result: RunInstallInput = RunInstallInputSchema.parse(parsedInput) const result: RunInstallInput = RunInstallInputSchema.parse(parsedInput)

View File

@@ -0,0 +1,344 @@
{
"name": "bootstrap-exe",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@pnpm/exe": "11.0.4"
}
},
"node_modules/@pnpm/exe": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/exe/-/exe-11.0.4.tgz",
"integrity": "sha512-3OwYqbbj1KtuUqoMo5OEkY8nU/WutZ7L5ADFl0bbW9oyqU55U37aDqA3NJNSk28CyszNARfrjerAF2DW2TsV7w==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@reflink/reflink": "0.1.19",
"detect-libc": "^2.0.3"
},
"bin": {
"pn": "pn",
"pnpm": "pnpm",
"pnpx": "pnpx",
"pnx": "pnx"
},
"funding": {
"url": "https://opencollective.com/pnpm"
},
"optionalDependencies": {
"@pnpm/linux-arm64": "11.0.4",
"@pnpm/linux-x64": "11.0.4",
"@pnpm/linuxstatic-arm64": "11.0.4",
"@pnpm/linuxstatic-x64": "11.0.4",
"@pnpm/macos-arm64": "11.0.4",
"@pnpm/macos-x64": "11.0.4",
"@pnpm/win-arm64": "11.0.4",
"@pnpm/win-x64": "11.0.4"
}
},
"node_modules/@pnpm/linux-arm64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linux-arm64/-/linux-arm64-11.0.4.tgz",
"integrity": "sha512-Bz7V2sFypoGHX/t5w/w7jnCw5DCK3C8s5q8whHJJ3iS5kRznX3Q1F4LwSjjy+lsi777fHyNIvD7qtNmdt9IKoA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/linux-x64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linux-x64/-/linux-x64-11.0.4.tgz",
"integrity": "sha512-u0Yn1gytR1vKdPk6fYF500H8ZWQlj0cTuIQPp+5GYVPkMmA5bSw41RNIDPBfjDlE8ERmQWaQcrgmTcmTZ+n22A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/linuxstatic-arm64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-arm64/-/linuxstatic-arm64-11.0.4.tgz",
"integrity": "sha512-0aitEcfhWNXNZhfJGt/kJaRvfcdtJzXZpV+toJN94kfawSJnhuawfnUSXMi/3m0G97HkJc7BH8rOz3sojUKt0g==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/linuxstatic-x64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-x64/-/linuxstatic-x64-11.0.4.tgz",
"integrity": "sha512-xDJdeJ7D2YvDBy2/IH9lEqMKiSuZiV8190XKWOgQgxUGGeuW4z3j6Ewpl0S5bXsWuNjAgC+uCKp7Qp3P7cXAvw==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/macos-arm64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/macos-arm64/-/macos-arm64-11.0.4.tgz",
"integrity": "sha512-dNR69jUARtGFuyyLE9VuyxhRUKC8MO/7/xIyAdeIMZAD5ej0Y/Ct0DYCa/FLbgFL1nXaXmp4+gRMfJBkkrKfQQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/macos-x64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/macos-x64/-/macos-x64-11.0.4.tgz",
"integrity": "sha512-RfyrxSBajeEU16dZsgFjbdagDV9F4HNCJfbBgm8IbGjL0+J95naM/VmCDLd6S3+1tISeI2MxtcyCxqjKJsD/BA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/win-arm64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/win-arm64/-/win-arm64-11.0.4.tgz",
"integrity": "sha512-fOQEv8b9KxZlUAxPPXSQQUUIrt2nY24Qwd4RzCPpatacBnsE4JIadlr/B4V5z2zFxmV7FdHr7nYUhv2RqTlY/w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@pnpm/win-x64": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/win-x64/-/win-x64-11.0.4.tgz",
"integrity": "sha512-pErHAV8m3NZuPSeCmH3senTSHX0nwkH5lLzQSpiFuyt08hq8sqL3jDymT4ri9N7ixPN9RFZghZIiT3h+Croaew==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"funding": {
"url": "https://opencollective.com/pnpm"
}
},
"node_modules/@reflink/reflink": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink/-/reflink-0.1.19.tgz",
"integrity": "sha512-DmCG8GzysnCZ15bres3N5AHCmwBwYgp0As6xjhQ47rAUTUXxJiK+lLUxaGsX3hd/30qUpVElh05PbGuxRPgJwA==",
"license": "MIT",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@reflink/reflink-darwin-arm64": "0.1.19",
"@reflink/reflink-darwin-x64": "0.1.19",
"@reflink/reflink-linux-arm64-gnu": "0.1.19",
"@reflink/reflink-linux-arm64-musl": "0.1.19",
"@reflink/reflink-linux-x64-gnu": "0.1.19",
"@reflink/reflink-linux-x64-musl": "0.1.19",
"@reflink/reflink-win32-arm64-msvc": "0.1.19",
"@reflink/reflink-win32-x64-msvc": "0.1.19"
}
},
"node_modules/@reflink/reflink-darwin-arm64": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-darwin-arm64/-/reflink-darwin-arm64-0.1.19.tgz",
"integrity": "sha512-ruy44Lpepdk1FqDz38vExBY/PVUsjxZA+chd9wozjUH9JjuDT/HEaQYA6wYN9mf041l0yLVar6BCZuWABJvHSA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-darwin-x64": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-darwin-x64/-/reflink-darwin-x64-0.1.19.tgz",
"integrity": "sha512-By85MSWrMZa+c26TcnAy8SDk0sTUkYlNnwknSchkhHpGXOtjNDUOxJE9oByBnGbeuIE1PiQsxDG3Ud+IVV9yuA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-arm64-gnu": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-arm64-gnu/-/reflink-linux-arm64-gnu-0.1.19.tgz",
"integrity": "sha512-7P+er8+rP9iNeN+bfmccM4hTAaLP6PQJPKWSA4iSk2bNvo6KU6RyPgYeHxXmzNKzPVRcypZQTpFgstHam6maVg==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-arm64-musl": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-arm64-musl/-/reflink-linux-arm64-musl-0.1.19.tgz",
"integrity": "sha512-37iO/Dp6m5DDaC2sf3zPtx/hl9FV3Xze4xoYidrxxS9bgP3S8ALroxRK6xBG/1TtfXKTvolvp+IjrUU6ujIGmA==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-x64-gnu": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-gnu/-/reflink-linux-x64-gnu-0.1.19.tgz",
"integrity": "sha512-jbI8jvuYCaA3MVUdu8vLoLAFqC+iNMpiSuLbxlAgg7x3K5bsS8nOpTRnkLF7vISJ+rVR8W+7ThXlXlUQ93ulkw==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-linux-x64-musl": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-linux-x64-musl/-/reflink-linux-x64-musl-0.1.19.tgz",
"integrity": "sha512-e9FBWDe+lv7QKAwtKOt6A2W/fyy/aEEfr0g6j/hWzvQcrzHCsz07BNQYlNOjTfeytrtLU7k449H1PI95jA4OjQ==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-win32-arm64-msvc": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-win32-arm64-msvc/-/reflink-win32-arm64-msvc-0.1.19.tgz",
"integrity": "sha512-09PxnVIQcd+UOn4WAW73WU6PXL7DwGS6wPlkMhMg2zlHHG65F3vHepOw06HFCq+N42qkaNAc8AKIabWvtk6cIQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@reflink/reflink-win32-x64-msvc": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@reflink/reflink-win32-x64-msvc/-/reflink-win32-x64-msvc-0.1.19.tgz",
"integrity": "sha512-E//yT4ni2SyhwP8JRjVGWr3cbnhWDiPLgnQ66qqaanjjnMiu3O/2tjCPQXlcGc/DEYofpDc9fvhv6tALQsMV9w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
}
}
}

View File

@@ -0,0 +1,30 @@
{
"name": "bootstrap-pnpm",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"pnpm": "11.0.4"
}
},
"node_modules/pnpm": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/pnpm/-/pnpm-11.0.4.tgz",
"integrity": "sha512-CjlxZQB6AU7VKRmmHl9GxIubyohATDA+yuzGP2Le9WOJjTxril1epYEes5jP4DqwXaGlzpY/Em1erUwC+TuDww==",
"license": "MIT",
"bin": {
"pn": "bin/pnpm.mjs",
"pnpm": "bin/pnpm.mjs",
"pnpx": "bin/pnpx.mjs",
"pnx": "bin/pnpx.mjs"
},
"engines": {
"node": ">=22.13"
},
"funding": {
"url": "https://opencollective.com/pnpm"
}
}
}
}

View File

@@ -4,13 +4,15 @@ import runSelfInstaller from './run'
export { runSelfInstaller } export { runSelfInstaller }
export async function install(inputs: Inputs) { export async function install(inputs: Inputs): Promise<string | undefined> {
startGroup('Running self-installer...') startGroup('Running self-installer...')
const status = await runSelfInstaller(inputs) const { exitCode, binDest } = await runSelfInstaller(inputs)
endGroup() endGroup()
if (status) { if (exitCode) {
return setFailed(`Something went wrong, self-installer exits with code ${status}`) setFailed(`Something went wrong, self-installer exits with code ${exitCode}`)
return undefined
} }
return binDest
} }
export default install export default install

View File

@@ -1,72 +1,202 @@
import { addPath, exportVariable } from '@actions/core' import { addPath, exportVariable } from '@actions/core'
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { remove, ensureFile, writeFile, readFile } from 'fs-extra' import { rm, writeFile, mkdir, symlink } from 'fs/promises'
import { readFileSync, existsSync } from 'fs'
import path from 'path' import path from 'path'
import { execPath } from 'process' import util from 'util'
import { Inputs } from '../inputs' import { Inputs } from '../inputs'
import { parse as parseYaml } from 'yaml'
import pnpmLock from './bootstrap/pnpm-lock.json'
import exeLock from './bootstrap/exe-lock.json'
export async function runSelfInstaller(inputs: Inputs): Promise<number> { const BOOTSTRAP_PNPM_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { pnpm: pnpmLock.packages['node_modules/pnpm'].version } })
const { version, dest, packageJsonFile, standalone } = inputs const BOOTSTRAP_EXE_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { '@pnpm/exe': exeLock.packages['node_modules/@pnpm/exe'].version } })
// prepare self install export interface SelfInstallerResult {
await remove(dest) exitCode: number
const pkgJson = path.join(dest, 'package.json') binDest: string
await ensureFile(pkgJson) }
await writeFile(pkgJson, JSON.stringify({ private: true }))
// prepare target pnpm export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerResult> {
const target = await readTarget({ version, packageJsonFile, standalone }) const { version, dest, packageJsonFile } = inputs
const cp = spawn(execPath, [path.join(__dirname, 'pnpm.cjs'), 'install', target, '--no-lockfile'], {
cwd: dest,
stdio: ['pipe', 'inherit', 'inherit'],
})
const exitCode = await new Promise<number>((resolve, reject) => { // pnpm v11 requires Node >= 22.13; use standalone (exe) bootstrap which
cp.on('error', reject) // bundles its own Node.js when the system Node is too old
cp.on('close', resolve) const systemNode = await getSystemNodeVersion()
}) const standalone = inputs.standalone || systemNode.major < 22 || (systemNode.major === 22 && systemNode.minor < 13)
if (exitCode === 0) {
const pnpmHome = path.join(dest, 'node_modules/.bin') // Install bootstrap pnpm via npm (integrity verified by committed lockfile)
await rm(dest, { recursive: true, force: true })
await mkdir(dest, { recursive: true })
const lockfile = standalone ? exeLock : pnpmLock
const packageJson = standalone ? BOOTSTRAP_EXE_PACKAGE_JSON : BOOTSTRAP_PNPM_PACKAGE_JSON
await writeFile(path.join(dest, 'package.json'), packageJson)
await writeFile(path.join(dest, 'package-lock.json'), JSON.stringify(lockfile))
// Append the action's node directory to PATH so npm's
// `#!/usr/bin/env node` shebang resolves on runners (e.g. GHE
// self-hosted) where node isn't already on PATH. Append (not
// prepend) so a user-installed toolchain on PATH — e.g. from a
// prior `setup-node` step — keeps precedence; otherwise the
// runner-bundled node would shadow it and pair the user's npm
// with a mismatched node version. npm itself is resolved via
// PATH — on the GitHub Actions runner it is not co-located with
// `process.execPath`.
const nodeDir = path.dirname(process.execPath)
// On Windows, the PATH key casing varies; search case-insensitively.
const pathKey = Object.keys(process.env).find(k => k.toUpperCase() === 'PATH') ?? 'PATH'
const currentPath = process.env[pathKey]
const npmEnv = { ...process.env, [pathKey]: currentPath ? currentPath + path.delimiter + nodeDir : nodeDir }
const npmExitCode = await runCommand('npm', ['ci'], { cwd: dest, env: npmEnv })
if (npmExitCode !== 0) {
return { exitCode: npmExitCode, binDest: path.join(dest, 'node_modules', '.bin') }
}
// On Windows with standalone mode, npm's .bin shims can't properly
// execute the extensionless @pnpm/exe native binaries. Add the
// @pnpm/exe directory directly to PATH so pnpm.exe is found natively.
const pnpmHome = standalone && process.platform === 'win32'
? path.join(dest, 'node_modules', '@pnpm', 'exe')
: path.join(dest, 'node_modules', '.bin')
// PNPM_HOME/bin is where `pnpm self-update` places the target version
// binary. It must have higher PATH precedence than pnpmHome (which
// contains the bootstrap binary) so the self-updated version is found
// first. The bootstrap pnpm is invoked via absolute path, not PATH,
// so this ordering does not affect the bootstrap step.
addPath(pnpmHome) addPath(pnpmHome)
addPath(path.join(pnpmHome, 'bin'))
exportVariable('PNPM_HOME', pnpmHome) exportVariable('PNPM_HOME', pnpmHome)
// Ensure pnpm bin link exists — npm ci sometimes doesn't create it
if (process.platform !== 'win32') {
const pnpmBinLink = path.join(dest, 'node_modules', '.bin', 'pnpm')
if (!existsSync(pnpmBinLink)) {
await mkdir(path.join(dest, 'node_modules', '.bin'), { recursive: true })
const target = standalone
? path.join('..', '@pnpm', 'exe', 'pnpm')
: path.join('..', 'pnpm', 'bin', 'pnpm.mjs')
await symlink(target, pnpmBinLink)
} }
return exitCode
} }
async function readTarget(opts: { const bootstrapPnpm = standalone
? path.join(dest, 'node_modules', '@pnpm', 'exe', process.platform === 'win32' ? 'pnpm.exe' : 'pnpm')
: path.join(dest, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs')
// Determine the target version
const targetVersion = readTargetVersion({ version, packageJsonFile })
if (targetVersion) {
const cmd = standalone ? bootstrapPnpm : process.execPath
const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion]
const exitCode = await runCommand(cmd, args, { cwd: dest })
if (exitCode !== 0) {
return { exitCode, binDest: pnpmHome }
}
// self-update writes the target pnpm/pnpx into PNPM_HOME/bin, leaving
// the bootstrap symlinks in pnpmHome pointing at the old version. Use
// PNPM_HOME/bin so consumers of the bin_dest output (e.g.
// `${steps.pnpm.outputs.bin_dest}/pnpm`) invoke the requested version.
return { exitCode: 0, binDest: path.join(pnpmHome, 'bin') }
}
// No explicit target version: rely on the bootstrap pnpm to switch to
// the version declared in packageManager/devEngines at runtime. Force
// `pmOnFail=download` so a project that pins
// `devEngines.packageManager.onFail = "error"` doesn't trip BAD_PM_VERSION
// before the switch can happen (issue #252). Scoped to this branch so users
// who pass an explicit `version:` input keep strict onFail behavior.
exportVariable('pnpm_config_pm_on_fail', 'download')
return { exitCode: 0, binDest: pnpmHome }
}
function readTargetVersion(opts: {
readonly version?: string | undefined readonly version?: string | undefined
readonly packageJsonFile: string readonly packageJsonFile: string
readonly standalone: boolean }): string | undefined {
}) { const { version, packageJsonFile } = opts
const { version, packageJsonFile, standalone } = opts
if (version) return `${ standalone ? '@pnpm/exe' : 'pnpm' }@${version}`
const { GITHUB_WORKSPACE } = process.env const { GITHUB_WORKSPACE } = process.env
let packageManager: string | undefined
let devEngines: { packageManager?: { name?: string; version?: string } } | undefined
if (GITHUB_WORKSPACE) {
try {
const content = readFileSync(path.join(GITHUB_WORKSPACE, packageJsonFile), 'utf8');
const manifest = packageJsonFile.endsWith(".yaml")
? parseYaml(content, { merge: true })
: JSON.parse(content)
packageManager = manifest.packageManager
devEngines = manifest.devEngines
} catch (error: unknown) {
// Swallow error if package.json doesn't exist in root
if (!util.types.isNativeError(error) || !('code' in error) || error.code !== 'ENOENT') throw error
}
}
if (version) {
if (
typeof packageManager === 'string' &&
packageManager.startsWith('pnpm@') &&
packageManager.replace('pnpm@', '') !== version
) {
throw new Error(`Multiple versions of pnpm specified:
- version ${version} in the GitHub Action config with the key "version"
- version ${packageManager} in the package.json with the key "packageManager"
Remove one of these versions to avoid version mismatch errors like ERR_PNPM_BAD_PM_VERSION`)
}
return version
}
// pnpm will automatically download and switch to the right version
if (typeof packageManager === 'string' && packageManager.startsWith('pnpm@')) {
return undefined
}
if (devEngines?.packageManager?.name === 'pnpm' && devEngines.packageManager.version) {
return undefined
}
if (!GITHUB_WORKSPACE) { if (!GITHUB_WORKSPACE) {
throw new Error(`No workspace is found. throw new Error(`No workspace is found.
If you're intended to let pnpm/action-setup read preferred pnpm version from the "packageManager" field in the package.json file, If you've intended to let pnpm/action-setup read preferred pnpm version from the "packageManager" field in the package.json file,
please run the actions/checkout before pnpm/action-setup. please run the actions/checkout before pnpm/action-setup.
Otherwise, please specify the pnpm version in the action configuration.`) Otherwise, please specify the pnpm version in the action configuration.`)
} }
const { packageManager } = JSON.parse(await readFile(path.join(GITHUB_WORKSPACE, packageJsonFile), 'utf8'))
if (typeof packageManager !== 'string') {
throw new Error(`No pnpm version is specified. throw new Error(`No pnpm version is specified.
Please specify it by one of the following ways: Please specify it by one of the following ways:
- in the GitHub Action config with the key "version" - in the GitHub Action config with the key "version"
- in the package.json with the key "packageManager"`) - in the package.json with the key "packageManager"
- in the package.json with the key "devEngines.packageManager"`)
} }
if (!packageManager.startsWith('pnpm@')) { function getSystemNodeVersion(): Promise<{ major: number; minor: number }> {
throw new Error('Invalid packageManager field in package.json') return new Promise((resolve) => {
const cp = spawn('node', ['--version'], { stdio: ['pipe', 'pipe', 'pipe'], shell: process.platform === 'win32' })
let output = ''
cp.stdout.on('data', (data: Buffer) => { output += data.toString() })
cp.on('close', () => {
const match = output.match(/^v(\d+)\.(\d+)/)
resolve(match ? { major: parseInt(match[1], 10), minor: parseInt(match[2], 10) } : { major: 0, minor: 0 })
})
cp.on('error', () => resolve({ major: 0, minor: 0 }))
})
} }
if(standalone){ function runCommand(cmd: string, args: string[], opts: { cwd: string; env?: Record<string, string | undefined> }): Promise<number> {
return packageManager.replace('pnpm@', '@pnpm/exe@') return new Promise<number>((resolve, reject) => {
} const cp = spawn(cmd, args, {
cwd: opts.cwd,
return packageManager env: opts.env,
stdio: ['pipe', 'inherit', 'inherit'],
shell: process.platform === 'win32',
})
cp.on('error', reject)
cp.on('close', resolve)
})
} }
export default runSelfInstaller export default runSelfInstaller

View File

@@ -1,10 +1,9 @@
import { setOutput, addPath } from '@actions/core' import { setOutput } from '@actions/core'
import { Inputs } from '../inputs' import { Inputs } from '../inputs'
import { getBinDest } from '../utils'
export function setOutputs(inputs: Inputs) { export function setOutputs(inputs: Inputs, binDest: string) {
const binDest = getBinDest(inputs) // NOTE: addPath is already called in installPnpm — do not call it again
addPath(binDest) // here, as a second addPath would shadow the correct entry on Windows.
setOutput('dest', inputs.dest) setOutput('dest', inputs.dest)
setOutput('bin_dest', binDest) setOutput('bin_dest', binDest)
} }

View File

@@ -6,5 +6,5 @@ export const getBinDest = (inputs: Inputs): string => path.join(inputs.dest, 'no
export const patchPnpmEnv = (inputs: Inputs): NodeJS.ProcessEnv => ({ export const patchPnpmEnv = (inputs: Inputs): NodeJS.ProcessEnv => ({
...process.env, ...process.env,
PATH: getBinDest(inputs) + path.delimiter + process.env.PATH, PATH: path.join(getBinDest(inputs), 'bin') + path.delimiter + getBinDest(inputs) + path.delimiter + process.env.PATH,
}) })

View File

@@ -1,26 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"module": "Node16", "module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"lib": [ "lib": [
"ES2023" "ES2023"
], ],
"outDir": "./dist/tsc", "noEmit": true,
"preserveConstEnums": true,
"incremental": false,
"declaration": true,
"sourceMap": true,
"importHelpers": false,
"strict": true, "strict": true,
"pretty": true, "pretty": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"esModuleInterop": true, "esModuleInterop": true
"experimentalDecorators": true,
"emitDecoratorMetadata": true
} }
} }