Compare commits

..

83 Commits

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
Zoltan Kochan
a3252b78c4 docs(README): update version 2024-02-08 11:28:50 +01:00
Erik Burton
1ee9c9d01d feat!: node20 upgrade (#110)
* upgrade versions to latest

* remove usage of ts-schema-autogen

* fix: update pnpm sources

* update build/output

* use node20

* fix: run-install array output

* fix: maintain behaviour for parseRunInstall, error messages

* fix: another edge case for input.args

* fix: use zod for input validation

* fix: use zod.infer for exported RunInstall types

* fix: remove @types/js-yaml

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2024-02-08 11:22:15 +01:00
dependabot[bot]
ebcfd6995d Bump actions/setup-node from 3 to 4 (#103)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-10 21:58:17 +02:00
Steven Yung
d2613e087f docs: update pnpm version in caching example (#94) 2023-09-27 19:17:23 +03:00
dependabot[bot]
d928be8e0c Bump actions/checkout from 3 to 4 (#96)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-27 19:17:06 +03:00
Khải
d882d12c64 feat: standalone binary (#92)
* feat: add an option to install the self-contained binary version of pnpm

* test: add a test about nodejs_bundled

* style: remove an empty line

* chore: use newer pnpm

* chore: update dependencies

* feat: rename `nodejs_bundled` to `standalone`

as @zkochan suggested

* docs: add

---------

Co-authored-by: Takashi Sato <takashi@tks.st>
2023-07-26 14:50:04 +03:00
Khải
0b715c7ebb Merge pull request #75 from ojeytonwilliams/fix/caching-example
docs: update the caching example
2023-07-25 17:00:41 +07:00
Khải
2ed49cbb02 Merge pull request #88 from KengoTODA/make-path-configurable
support projects that have package.json in non-root directory
2023-07-25 16:59:00 +07:00
Khải
218cb35941 Merge pull request #69 from zakuro9715/zakuro9715-patch-2
Remove corepack url in error message
2023-07-25 14:16:42 +07:00
Khải
3723f63bb4 Merge pull request #90 from pnpm/optional-version
feat: make version actually optional
2023-07-25 14:05:04 +07:00
khai96_
849d884800 feat: make version actually optional
closes https://github.com/pnpm/action-setup/issues/85
2023-07-25 14:01:28 +07:00
khai96_
f92eb0edb6 docs: update pnpm version in readme
fixes https://github.com/pnpm/action-setup/issues/89
2023-07-25 13:50:35 +07:00
Kengo TODA
b27f801bf9 feat: add package_json_file option
Signed-off-by: Kengo TODA <skypencil@gmail.com>
2023-07-08 07:02:46 +08:00
Khải
11dd14d0c0 Change sponsor to pnpm's opencollective 2023-04-22 17:39:01 +07:00
Oliver Eyton-Williams
61eb8c655a docs: update the caching example 2023-03-08 00:13:47 +01:00
zakuro
65db188e28 Remove corepack url in error message 2022-11-09 06:54:12 +09:00
Khải
0609f0983b Merge pull request #66 from mrienstra/patch-1
docs: drop minor & patch version from examples
2022-11-02 12:09:43 +07:00
Michael Rienstra
7208fa2733 docs: drop minor & patch version from examples 2022-11-01 21:55:20 -07:00
Khải
1bd00a2972 Merge pull request #64 from mrienstra/patch-1
typo: not latest version
2022-10-26 10:32:07 +07:00
Michael Rienstra
d5601fb603 bump first occurrence of actions/checkout to v3 2022-10-25 10:52:54 -07:00
Michael Rienstra
bc06aa6030 typo: not latest version 2022-10-25 10:42:24 -07:00
Khải
cd2af74528 Merge pull request #62 from melMass/mel/fix-readme
fix: ️ cross-platform example
2022-10-18 17:29:18 +07:00
Mel Massadian
c8a150e137 fix: ️ just use shell:bash
set-output is deprecated see #58
2022-10-18 12:23:15 +02:00
Mel Massadian
ed0172a253 fix: ️ cross-platform example 2022-10-18 12:02:38 +02:00
30 changed files with 2291 additions and 269113 deletions

14
.github/FUNDING.yml vendored
View File

@@ -1,12 +1,2 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: khai96_
open_collective: # Collective unavailable
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # disabled
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom:
- https://opencollective.com/pnpm

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,92 +1,210 @@
name: Test Action
on:
- push
- pull_request
- workflow_dispatch
pull_request:
push:
branches:
- master
workflow_dispatch:
jobs:
test_default_inputs:
name: Test with default inputs
smoke:
# 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 }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
include:
- name: 'ubuntu / v9.15.5'
os: ubuntu-latest
version: '9.15.5'
- name: 'ubuntu / v10.33.0'
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:
- uses: actions/checkout@v3
- 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
uses: ./
with:
version: 4.11.1
version: ${{ matrix.explicit_version }}
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: pnpm reports the pinned version'
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'
run: pnpm install
- name: 'Test: pnpm_config_pm_on_fail not exported (explicit version preserves strict policy)'
if: ${{ matrix.expect_pm_on_fail_unset }}
run: |
if [ -n "${pnpm_config_pm_on_fail:-}" ]; then
echo "Expected pnpm_config_pm_on_fail to be unset, but got: '${pnpm_config_pm_on_fail}'"
exit 1
fi
echo "pnpm_config_pm_on_fail is unset, as expected"
shell: bash
test_explicit_inputs:
name: Test with explicit inputs
standalone:
name: Standalone mode
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Run the action
uses: ./
with:
version: 4.11.1
dest: ~/test/pnpm
version: 9.15.0
standalone: true
- name: 'Test: which'
run: which pnpm && which pnpx
- name: 'Test: pnpm works'
run: |
set -e
which pnpm
actual="$(pnpm --version)"
if [ "${actual}" != "9.15.0" ]; then
echo "Expected 9.15.0, got ${actual}"
exit 1
fi
mkdir /tmp/test-standalone
cd /tmp/test-standalone
pnpm init
pnpm add is-odd
shell: bash
- name: 'Test: install'
run: pnpm install
run_install:
name: 'run_install (${{ matrix.run_install.name }})'
test_run_install:
name: 'Test with run_install (${{ matrix.run_install.name }}, ${{ matrix.os }})'
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pnpm:
- 4.11.1
os:
- ubuntu-latest
- macos-latest
- windows-latest
run_install:
- name: 'null'
value: 'null'
- name: 'empty object'
value: '{}'
- name: 'recursive'
value: |
recursive: true
- name: 'global'
value: |
args:
@@ -94,29 +212,24 @@ jobs:
- --global-dir=./pnpm-global
- npm
- yarn
- pnpm
- name: 'array'
value: |
- {}
- recursive: true
- args:
- --global
- --global-dir=./pnpm-global
- npm
- yarn
- pnpm
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Run the action
uses: ./
with:
version: 4.11.1
version: 9.15.5
run_install: ${{ matrix.run_install.value }}
- name: 'Test: which'
run: which pnpm; which pnpx
- name: 'Test: install'
run: pnpm install
- name: 'Test: pnpm works'
run: |
set -e
which pnpm
which pnpx
actual="$(pnpm --version)"
if [ "${actual}" != "9.15.5" ]; then
echo "Expected 9.15.5, got ${actual}"
exit 1
fi
shell: bash

2
.gitignore vendored
View File

@@ -2,7 +2,6 @@ node_modules
*.log
/dist/*
!/dist/index.js
!/dist/pnpm.js
tmp
temp
*.tmp
@@ -10,3 +9,4 @@ temp
tmp.*
temp.*
.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
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,7 +40,25 @@ 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`/[`package.yaml`](https://github.com/pnpm/pnpm/pull/1799) to read "packageManager" configuration.
### `standalone`
**Optional** (_type:_ `boolean`, _default:_ `false`) When set to true, [@pnpm/exe](https://www.npmjs.com/package/@pnpm/exe), which is a Node.js bundled package, will be installed, enabling using `pnpm` without Node.js.
This is useful when you want to use a incompatible pair of Node.js and pnpm.
## Outputs
@@ -50,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:
@@ -62,9 +86,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v6
with:
version: 6.0.2
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
@@ -79,14 +120,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v2.2.4
- uses: pnpm/action-setup@v6
with:
version: 6.0.2
version: 10
run_install: |
- recursive: true
args: [--frozen-lockfile, --strict-peer-dependencies]
args: [--strict-peer-dependencies]
- args: [--global, gulp, prettier, typescript]
```
@@ -103,32 +144,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16
- uses: pnpm/action-setup@v2.0.1
- uses: pnpm/action-setup@v6
name: Install pnpm
id: pnpm-install
with:
version: 7
run_install: false
- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
version: 10
cache: true
- name: Install dependencies
run: pnpm install
@@ -142,4 +164,4 @@ This action does not setup Node.js for you, use [actions/setup-node](https://git
## 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

@@ -6,6 +6,7 @@ branding:
inputs:
version:
description: Version of pnpm to install
required: false
dest:
description: Where to store pnpm files
required: false
@@ -14,7 +15,28 @@ inputs:
description: If specified, run `pnpm install`
required: false
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:
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.
required: false
default: 'false'
outputs:
dest:
description: Expanded path of inputs#dest
bin_dest:
description: Location of `pnpm` and `pnpx` command
runs:
using: node16
using: node24
main: dist/index.js
post: dist/index.js

299
dist/index.js vendored

File diff suppressed because one or more lines are too long

134031
dist/pnpm.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,26 +1,24 @@
{
"private": true,
"scripts": {
"build:schemas": "ts-schema-autogen generate",
"build:ncc": "ncc build --minify --no-source-map-register --no-cache dist/tsc/index.js --out dist/",
"build": "pnpm run build:schemas && tsc && pnpm run build:ncc && cp src/install-pnpm/pnpm.js dist/pnpm.js",
"start": "pnpm run build && sh ./run.sh"
"build:bundle": "esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --minify --outfile=dist/index.js --loader:.json=json",
"build": "pnpm run build:bundle",
"start": "pnpm run build && sh ./run.sh",
"update-bootstrap": "node scripts/update-bootstrap.mjs"
},
"dependencies": {
"@actions/core": "^1.10.0",
"@types/expand-tilde": "^2.0.0",
"@types/fs-extra": "^9.0.13",
"@types/js-yaml": "^4.0.5",
"@types/node": "^14.18.32",
"@types/node-fetch": "^2.6.2",
"ajv": "^6.12.6",
"@actions/cache": "^4.1.0",
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/glob": "^0.5.0",
"@types/expand-tilde": "^2.0.2",
"@types/node": "^22.0.0",
"expand-tilde": "^2.0.2",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0"
"yaml": "^2.3.4",
"zod": "^3.22.4"
},
"devDependencies": {
"@ts-schema-autogen/cli": "^0.1.2",
"@vercel/ncc": "^0.33.4",
"typescript": "^4.8.4"
"esbuild": "^0.27.4",
"typescript": "^5.3.3"
}
}

1659
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

1
run.sh
View File

@@ -4,4 +4,5 @@ export HOME="$(pwd)"
export INPUT_VERSION=4.11.1
export INPUT_DEST='~/pnpm.temp'
export INPUT_RUN_INSTALL=null
export INPUT_standalone=false
exec node dist/index.js

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 getInputs from './inputs'
import restoreCache from './cache-restore'
import saveCache from './cache-save'
import getInputs, { Inputs } from './inputs'
import installPnpm from './install-pnpm'
import setOutputs from './outputs'
import pnpmInstall from './pnpm-install'
@@ -7,15 +9,32 @@ import pruneStore from './pnpm-store-prune'
async function main() {
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')
await installPnpm(inputs)
const binDest = await installPnpm(inputs)
if (binDest === undefined) return
console.log('Installation Completed!')
setOutputs(inputs)
setOutputs(inputs, binDest)
await restoreCache(inputs)
pnpmInstall(inputs)
}
async function runPost(inputs: Inputs) {
pruneStore(inputs)
await saveCache(inputs)
}
main().catch(error => {
console.error(error)
setFailed(error)

View File

@@ -1,11 +1,15 @@
import { getInput, InputOptions } from '@actions/core'
import { getBooleanInput, getInput, InputOptions } from '@actions/core'
import expandTilde from 'expand-tilde'
import { RunInstall, parseRunInstall } from './run-install'
export interface Inputs {
readonly version?: string
readonly dest: string
readonly cache: boolean
readonly cacheDependencyPath: string
readonly runInstall: RunInstall[]
readonly packageJsonFile: string
readonly standalone: boolean
}
const options: InputOptions = {
@@ -17,7 +21,11 @@ const parseInputPath = (name: string) => expandTilde(getInput(name, options))
export const getInputs = (): Inputs => ({
version: getInput('version'),
dest: parseInputPath('dest'),
cache: getBooleanInput('cache'),
cacheDependencyPath: parseInputPath('cache_dependency_path'),
runInstall: parseRunInstall('run_install'),
packageJsonFile: parseInputPath('package_json_file'),
standalone: getBooleanInput('standalone'),
})
export default getInputs

View File

@@ -1,21 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/ksxnodeapps/ts-schema-autogen/master/packages/schemas/config.schema.json",
"instruction": {
"compilerOptions": {
"strict": true,
"target": "ES2018",
"lib": [
"ES2018",
"ES2019",
"ES2020",
"ESNext"
],
"moduleResolution": "Node",
"esModuleInterop": true,
"resolveJsonModule": true
},
"input": "run-install.ts",
"symbol": "RunInstallInput",
"output": "run-install-input.schema.json"
}
}

View File

@@ -1,39 +0,0 @@
{
"anyOf": [
{
"$ref": "#/definitions/RunInstall"
},
{
"type": "array",
"items": {
"$ref": "#/definitions/RunInstall"
}
},
{
"type": [
"null",
"boolean"
]
}
],
"definitions": {
"RunInstall": {
"type": "object",
"properties": {
"recursive": {
"type": "boolean"
},
"cwd": {
"type": "string"
},
"args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@@ -1,39 +1,41 @@
import { getInput, error, InputOptions } from '@actions/core'
import Ajv from 'ajv'
import { load } from 'js-yaml'
import process from 'process'
import runInstallSchema from './run-install-input.schema.json'
import { getInput, error } from '@actions/core'
import { parse as parseYaml } from 'yaml'
import { z, ZodError } from 'zod'
export interface RunInstall {
readonly recursive?: boolean
readonly cwd?: string
readonly args?: readonly string[]
}
const RunInstallSchema = z.object({
recursive: z.boolean().optional(),
cwd: z.string().optional(),
args: z.array(z.string()).optional(),
})
export type RunInstallInput =
| null
| boolean
| RunInstall
| RunInstall[]
const RunInstallInputSchema = z.union([
z.null(),
z.boolean(),
RunInstallSchema,
z.array(RunInstallSchema),
])
const options: InputOptions = {
required: true,
}
export type RunInstallInput = z.infer<typeof RunInstallInputSchema>
export type RunInstall = z.infer<typeof RunInstallSchema>
export function parseRunInstall(name: string): RunInstall[] {
const result: RunInstallInput = load(getInput(name, options)) as any
const ajv = new Ajv({
allErrors: true,
})
const validate = ajv.compile(runInstallSchema)
if (!validate(result)) {
for (const errorItem of validate.errors!) {
error(`with.run_install${errorItem.dataPath}: ${errorItem.message}`)
export function parseRunInstall(inputName: string): RunInstall[] {
const input = getInput(inputName, { required: true })
const parsedInput: unknown = parseYaml(input)
try {
const result: RunInstallInput = RunInstallInputSchema.parse(parsedInput)
if (!result) return []
if (result === true) return [{ recursive: true }]
if (Array.isArray(result)) return result
return [result]
} catch (exception: unknown) {
error(`Error for input "${inputName}" = ${input}`)
if (exception instanceof ZodError) {
error(`Errors: ${exception.errors}`)
} else {
error(`Exception: ${exception}`)
}
return process.exit(1)
process.exit(1)
}
if (!result) return []
if (result === true) return [{ recursive: true }]
if (Array.isArray(result)) return result
return [result]
}

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 async function install(inputs: Inputs) {
export async function install(inputs: Inputs): Promise<string | undefined> {
startGroup('Running self-installer...')
const status = await runSelfInstaller(inputs)
const { exitCode, binDest } = await runSelfInstaller(inputs)
endGroup()
if (status) {
return setFailed(`Something went wrong, self-installer exits with code ${status}`)
if (exitCode) {
setFailed(`Something went wrong, self-installer exits with code ${exitCode}`)
return undefined
}
return binDest
}
export default install

File diff suppressed because one or more lines are too long

View File

@@ -1,61 +1,202 @@
import { addPath, exportVariable } from '@actions/core'
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 { execPath } from 'process'
import util from 'util'
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 { version, dest } = inputs
const BOOTSTRAP_PNPM_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { pnpm: pnpmLock.packages['node_modules/pnpm'].version } })
const BOOTSTRAP_EXE_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { '@pnpm/exe': exeLock.packages['node_modules/@pnpm/exe'].version } })
// prepare self install
await remove(dest)
const pkgJson = path.join(dest, 'package.json')
await ensureFile(pkgJson)
await writeFile(pkgJson, JSON.stringify({ private: true }))
// prepare target pnpm
const target = await readTarget(version)
const cp = spawn(execPath, [path.join(__dirname, 'pnpm.js'), 'install', target, '--no-lockfile'], {
cwd: dest,
stdio: ['pipe', 'inherit', 'inherit'],
})
const exitCode = await new Promise<number>((resolve, reject) => {
cp.on('error', reject)
cp.on('close', resolve)
})
if (exitCode === 0) {
const pnpmHome = path.join(dest, 'node_modules/.bin')
addPath(pnpmHome)
exportVariable('PNPM_HOME', pnpmHome)
}
return exitCode
export interface SelfInstallerResult {
exitCode: number
binDest: string
}
async function readTarget(version?: string | undefined) {
if (version) return `pnpm@${version}`
export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerResult> {
const { version, dest, packageJsonFile } = inputs
// pnpm v11 requires Node >= 22.13; use standalone (exe) bootstrap which
// bundles its own Node.js when the system Node is too old
const systemNode = await getSystemNodeVersion()
const standalone = inputs.standalone || systemNode.major < 22 || (systemNode.major === 22 && systemNode.minor < 13)
// 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(path.join(pnpmHome, 'bin'))
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)
}
}
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 packageJsonFile: string
}): string | undefined {
const { version, packageJsonFile } = opts
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) {
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.
Otherwise, please specify the pnpm version in the action configuration.`)
}
const { packageManager } = JSON.parse(await readFile(path.join(GITHUB_WORKSPACE, 'package.json'), '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:
- in the GitHub Action config with the key "version"
- in the package.json with the key "packageManager" (See https://nodejs.org/api/corepack.html)`)
}
- in the package.json with the key "packageManager"
- in the package.json with the key "devEngines.packageManager"`)
}
if (!packageManager.startsWith('pnpm@')) {
throw new Error('Invalid packageManager field in package.json')
}
return packageManager
function getSystemNodeVersion(): Promise<{ major: number; minor: number }> {
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 }))
})
}
function runCommand(cmd: string, args: string[], opts: { cwd: string; env?: Record<string, string | undefined> }): Promise<number> {
return new Promise<number>((resolve, reject) => {
const cp = spawn(cmd, args, {
cwd: opts.cwd,
env: opts.env,
stdio: ['pipe', 'inherit', 'inherit'],
shell: process.platform === 'win32',
})
cp.on('error', reject)
cp.on('close', resolve)
})
}
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 { getBinDest } from '../utils'
export function setOutputs(inputs: Inputs) {
const binDest = getBinDest(inputs)
addPath(binDest)
export function setOutputs(inputs: Inputs, binDest: string) {
// NOTE: addPath is already called in installPnpm — do not call it again
// here, as a second addPath would shadow the correct entry on Windows.
setOutput('dest', inputs.dest)
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 => ({
...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,30 +1,19 @@
{
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"moduleResolution": "Node",
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"lib": [
"ES2018",
"ES2019",
"ES2020",
"ESNext"
"ES2023"
],
"outDir": "./dist/tsc",
"preserveConstEnums": true,
"incremental": false,
"declaration": true,
"sourceMap": true,
"importHelpers": false,
"noEmit": true,
"strict": true,
"pretty": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
"esModuleInterop": true
}
}