Compare commits

..

6 Commits
v6 ... 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
12 changed files with 246 additions and 352 deletions

1
.github/CODEOWNERS vendored
View File

@@ -1 +0,0 @@
* @zkochan

View File

@@ -194,102 +194,6 @@ jobs:
pnpm add is-odd pnpm add is-odd
shell: bash shell: bash
standalone_windows_self_update:
# Regression guard for the patchPnpmEnv PATH-shadow bug. When
# standalone: true on Windows AND the requested pnpm differs from the
# bootstrap, the previous patchPnpmEnv prepended node_modules/.bin to
# PATH; that directory contains an npm-created pnpm.cmd shim pointing
# at the BOOTSTRAP pnpm, which shadowed the self-updated pnpm at
# $PNPM_HOME/bin and caused `pnpm install` inside the action to run
# under the bootstrap version. Exercising a newer-pnpm-only flag
# (`--no-runtime`, added in 11.1.0) makes the regression assertable:
# if the bootstrap (11.0.4) handles the install, it errors with
# "Unknown option: 'runtime'".
name: 'Standalone Windows self-update (PATH regression)'
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up package.json with a minimal manifest
# run_install needs a manifest to install against. Removing the
# repo's existing pnpm-lock.yaml avoids frozen-lockfile mismatch.
run: |
rm -f pnpm-lock.yaml
echo '{"name":"sw","private":true,"packageManager":"pnpm@11.1.0"}' > package.json
shell: bash
- name: Run the action
uses: ./
with:
version: 11.1.0
standalone: true
run_install: |
args: ['--no-runtime']
- name: 'Test: pnpm install completed under the self-updated pnpm'
# If the bug recurs, the previous step's run_install will have failed
# the job with "Unknown option: 'runtime'", so reaching this step
# implies success. Still verify the version on PATH matches request.
env:
REQUIRED: '11.1.0'
run: |
set -e
actual="$(pnpm --version)"
echo "pnpm --version: ${actual}"
if [ "${actual}" != "${REQUIRED}" ]; then
echo "Expected pnpm ${REQUIRED}, got ${actual}"
exit 1
fi
shell: bash
cache_store_path:
# Regression guard for #233. When package.json pins a pnpm major that
# differs from the bootstrap pnpm's major, the bootstrap reports its
# own STORE_VERSION from `pnpm store path` (the `store` command skips
# pnpm's auto-switch). The user's actual `pnpm install` runs under the
# pinned version and writes to a different STORE_VERSION, so the post
# step's saveCache then fails with "Path Validation Error". The fix is
# to self-update the bootstrap to the pinned version up front.
name: 'Cache store path matches install (#233): ${{ matrix.label }}'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- label: 'packageManager pnpm@10.33.0'
manifest: '{"packageManager":"pnpm@10.33.0","dependencies":{"is-odd":"3.0.1"}}'
- label: 'devEngines exact pnpm@10.33.0'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":"10.33.0"}},"dependencies":{"is-odd":"3.0.1"}}'
- label: 'devEngines range >=10 <11'
manifest: '{"devEngines":{"packageManager":{"name":"pnpm","version":">=10 <11"}},"dependencies":{"is-odd":"3.0.1"}}'
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Set up package.json
run: echo '${{ matrix.manifest }}' > package.json
shell: bash
- id: pnpm
uses: ./
with:
cache: true
run_install: |
- args: [--no-frozen-lockfile]
- name: 'Test: store path computed by the action exists on disk'
run: |
set -e
actual="$(pnpm store path --silent)"
echo "pnpm store path: ${actual}"
if [ ! -d "${actual}" ]; then
echo "Expected store path to exist on disk; cache save would fail"
exit 1
fi
shell: bash
run_install: run_install:
name: 'run_install (${{ matrix.run_install.name }})' name: 'run_install (${{ matrix.run_install.name }})'

View File

@@ -48,7 +48,7 @@ If `run_install` is a YAML string representation of either an object or an array
### `cache_dependency_path` ### `cache_dependency_path`
**Optional** (_type:_ `string`, _default:_ `pnpm-lock.yaml`) File path to the pnpm lockfile, whose contents hash will be used as a cache key. Accepts multiple paths delimited by newlines. **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`
@@ -158,33 +158,6 @@ jobs:
**Note:** You don't need to run `pnpm store prune` at the end; post-action has already taken care of that. **Note:** You don't need to run `pnpm store prune` at the end; post-action has already taken care of that.
### Cache dependencies from multiple lockfiles
```yaml
on:
- push
- pull_request
jobs:
cache-and-install-multiple:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: 10
cache: true
cache_dependency_path: |
one/pnpm-lock.yaml
two/pnpm-lock.yaml
run_install: |
- cwd: one
- cwd: two
```
## Notes ## Notes
This action does not setup Node.js for you, use [actions/setup-node](https://github.com/actions/setup-node) yourself. This action does not setup Node.js for you, use [actions/setup-node](https://github.com/actions/setup-node) yourself.

View File

@@ -20,7 +20,7 @@ inputs:
required: false required: false
default: 'false' default: 'false'
cache_dependency_path: cache_dependency_path:
description: File path to the pnpm lockfile, whose contents hash will be used as a cache key. Accepts multiple paths delimited by newlines. description: File path to the pnpm lockfile, which contents hash will be used as a cache key
required: false required: false
default: 'pnpm-lock.yaml' default: 'pnpm-lock.yaml'
package_json_file: package_json_file:

270
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -8,16 +8,16 @@ import pnpmInstall from './pnpm-install'
import pruneStore from './pnpm-store-prune' import pruneStore from './pnpm-store-prune'
async function main() { async function main() {
const inputs = getInputs()
if (getState('is_post') === 'true') { if (getState('is_post') === 'true') {
await runPost() await runPost(inputs)
} else { } else {
await runMain() await runMain(inputs)
} }
} }
async function runMain() { async function runMain(inputs: Inputs) {
const inputs = getInputs()
saveState('inputs', inputs)
saveState('is_post', 'true') saveState('is_post', 'true')
const binDest = await installPnpm(inputs) const binDest = await installPnpm(inputs)
@@ -30,8 +30,7 @@ async function runMain() {
pnpmInstall(inputs) pnpmInstall(inputs)
} }
async function runPost() { async function runPost(inputs: Inputs) {
const inputs = JSON.parse(getState('inputs')) as Inputs
pruneStore(inputs) pruneStore(inputs)
await saveCache(inputs) await saveCache(inputs)
} }

View File

@@ -5,13 +5,13 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@pnpm/exe": "11.1.1" "@pnpm/exe": "11.0.4"
} }
}, },
"node_modules/@pnpm/exe": { "node_modules/@pnpm/exe": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/exe/-/exe-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/exe/-/exe-11.0.4.tgz",
"integrity": "sha512-5mQnDW1NCBRRWA+cnGhQO+tIrfSfWm3/IyGxU88LnT+tzNW5UrwwKfjsnnYJToyAjIfdfEJtJKUxCvP+mhA+nQ==", "integrity": "sha512-3OwYqbbj1KtuUqoMo5OEkY8nU/WutZ7L5ADFl0bbW9oyqU55U37aDqA3NJNSk28CyszNARfrjerAF2DW2TsV7w==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -28,19 +28,20 @@
"url": "https://opencollective.com/pnpm" "url": "https://opencollective.com/pnpm"
}, },
"optionalDependencies": { "optionalDependencies": {
"@pnpm/linux-arm64": "11.1.1", "@pnpm/linux-arm64": "11.0.4",
"@pnpm/linux-x64": "11.1.1", "@pnpm/linux-x64": "11.0.4",
"@pnpm/linuxstatic-arm64": "11.1.1", "@pnpm/linuxstatic-arm64": "11.0.4",
"@pnpm/linuxstatic-x64": "11.1.1", "@pnpm/linuxstatic-x64": "11.0.4",
"@pnpm/macos-arm64": "11.1.1", "@pnpm/macos-arm64": "11.0.4",
"@pnpm/win-arm64": "11.1.1", "@pnpm/macos-x64": "11.0.4",
"@pnpm/win-x64": "11.1.1" "@pnpm/win-arm64": "11.0.4",
"@pnpm/win-x64": "11.0.4"
} }
}, },
"node_modules/@pnpm/linux-arm64": { "node_modules/@pnpm/linux-arm64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linux-arm64/-/linux-arm64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/linux-arm64/-/linux-arm64-11.0.4.tgz",
"integrity": "sha512-u9hs51XV0/gU5LLfNLoQsozGKIxNjxsh/0xPr+8Hny0M38psa4lBtwFvarL2bLToPIrtueQYi65LdlzRxITRyg==", "integrity": "sha512-Bz7V2sFypoGHX/t5w/w7jnCw5DCK3C8s5q8whHJJ3iS5kRznX3Q1F4LwSjjy+lsi777fHyNIvD7qtNmdt9IKoA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -54,9 +55,9 @@
} }
}, },
"node_modules/@pnpm/linux-x64": { "node_modules/@pnpm/linux-x64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linux-x64/-/linux-x64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/linux-x64/-/linux-x64-11.0.4.tgz",
"integrity": "sha512-yQO9i57oyJmIG22BjV7sqLUT2syKQohiku8yNZRgp7M6wsVkikpVLLVSpBifQnrI/P/roueKnWSUEESH1aPaoA==", "integrity": "sha512-u0Yn1gytR1vKdPk6fYF500H8ZWQlj0cTuIQPp+5GYVPkMmA5bSw41RNIDPBfjDlE8ERmQWaQcrgmTcmTZ+n22A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -70,9 +71,9 @@
} }
}, },
"node_modules/@pnpm/linuxstatic-arm64": { "node_modules/@pnpm/linuxstatic-arm64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-arm64/-/linuxstatic-arm64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-arm64/-/linuxstatic-arm64-11.0.4.tgz",
"integrity": "sha512-FUZB8L9Z8L5m88G0RTx5AsHFr5yUQPW+28zQdTNUWxiLwj11FW/fOLodYdcNYHdNJFepsZyqt3aRnpiqIdZb2g==", "integrity": "sha512-0aitEcfhWNXNZhfJGt/kJaRvfcdtJzXZpV+toJN94kfawSJnhuawfnUSXMi/3m0G97HkJc7BH8rOz3sojUKt0g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -89,9 +90,9 @@
} }
}, },
"node_modules/@pnpm/linuxstatic-x64": { "node_modules/@pnpm/linuxstatic-x64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-x64/-/linuxstatic-x64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/linuxstatic-x64/-/linuxstatic-x64-11.0.4.tgz",
"integrity": "sha512-I/z56hfa1zM5F/Unup/1NrgsA+dcptsKQ2TjJLFz3wHKDx0RLrfF7DB0Rkpnr5IoAZ33v0GFZjlGhkOtc9VFGw==", "integrity": "sha512-xDJdeJ7D2YvDBy2/IH9lEqMKiSuZiV8190XKWOgQgxUGGeuW4z3j6Ewpl0S5bXsWuNjAgC+uCKp7Qp3P7cXAvw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -108,9 +109,9 @@
} }
}, },
"node_modules/@pnpm/macos-arm64": { "node_modules/@pnpm/macos-arm64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/macos-arm64/-/macos-arm64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/macos-arm64/-/macos-arm64-11.0.4.tgz",
"integrity": "sha512-YQu6fC27F4jTIpXhF+4PdzOV7uSnVVG9KUxj5W+AFj0XFlUvBw+I1NsoPCY6uV1nccxWpIAZOTZtSj8+hWPb8w==", "integrity": "sha512-dNR69jUARtGFuyyLE9VuyxhRUKC8MO/7/xIyAdeIMZAD5ej0Y/Ct0DYCa/FLbgFL1nXaXmp4+gRMfJBkkrKfQQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -123,10 +124,26 @@
"url": "https://opencollective.com/pnpm" "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": { "node_modules/@pnpm/win-arm64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/win-arm64/-/win-arm64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/win-arm64/-/win-arm64-11.0.4.tgz",
"integrity": "sha512-2HvZut3IcKPxzIfOjBJ4677PaLIh57mWccL86O+q71QhO5emnQvht0CE19IoEyUIOEe1WjlN+Su/dD5k6CuGyg==", "integrity": "sha512-fOQEv8b9KxZlUAxPPXSQQUUIrt2nY24Qwd4RzCPpatacBnsE4JIadlr/B4V5z2zFxmV7FdHr7nYUhv2RqTlY/w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -140,9 +157,9 @@
} }
}, },
"node_modules/@pnpm/win-x64": { "node_modules/@pnpm/win-x64": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/@pnpm/win-x64/-/win-x64-11.1.1.tgz", "resolved": "https://registry.npmjs.org/@pnpm/win-x64/-/win-x64-11.0.4.tgz",
"integrity": "sha512-QXBIBErgPhGLovOVzTRIpHsejFKebyqlcF3fea/TfH87gkhN5yWH0WuTPRBoOWvpk6aNhjDW4RPUMx8RaPqxjw==", "integrity": "sha512-pErHAV8m3NZuPSeCmH3senTSHX0nwkH5lLzQSpiFuyt08hq8sqL3jDymT4ri9N7ixPN9RFZghZIiT3h+Croaew==",
"cpu": [ "cpu": [
"x64" "x64"
], ],

View File

@@ -5,13 +5,13 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"pnpm": "11.1.1" "pnpm": "11.0.4"
} }
}, },
"node_modules/pnpm": { "node_modules/pnpm": {
"version": "11.1.1", "version": "11.0.4",
"resolved": "https://registry.npmjs.org/pnpm/-/pnpm-11.1.1.tgz", "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-11.0.4.tgz",
"integrity": "sha512-0f319zxhe2T6GlaoHDyN/g6WbjOmAQqiVrUXrne+Idk+Ba/8DeGoOw5PKdVp9otEaujwaM1yR8C7PfD7TXvfmg==", "integrity": "sha512-CjlxZQB6AU7VKRmmHl9GxIubyohATDA+yuzGP2Le9WOJjTxril1epYEes5jP4DqwXaGlzpY/Em1erUwC+TuDww==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"pn": "bin/pnpm.mjs", "pn": "bin/pnpm.mjs",

View File

@@ -84,31 +84,37 @@ export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerRes
? path.join(dest, 'node_modules', '@pnpm', 'exe', process.platform === 'win32' ? 'pnpm.exe' : 'pnpm') ? path.join(dest, 'node_modules', '@pnpm', 'exe', process.platform === 'win32' ? 'pnpm.exe' : 'pnpm')
: path.join(dest, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs') : path.join(dest, 'node_modules', 'pnpm', 'bin', 'pnpm.mjs')
// Self-update the bootstrap to the requested pnpm version. readTargetVersion // Determine the target version
// either returns a value or throws, so this always runs.
const targetVersion = readTargetVersion({ version, packageJsonFile }) const targetVersion = readTargetVersion({ version, packageJsonFile })
const cmd = standalone ? bootstrapPnpm : process.execPath
const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion] if (targetVersion) {
const exitCode = await runCommand(cmd, args, { cwd: dest }) const cmd = standalone ? bootstrapPnpm : process.execPath
if (exitCode !== 0) { const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion]
return { exitCode, binDest: pnpmHome } 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') }
} }
// self-update writes the target pnpm/pnpx into PNPM_HOME/bin, leaving
// the bootstrap symlinks in pnpmHome pointing at the old version. Use // No explicit target version: rely on the bootstrap pnpm to switch to
// PNPM_HOME/bin so consumers of the bin_dest output (e.g. // the version declared in packageManager/devEngines at runtime. Force
// `${steps.pnpm.outputs.bin_dest}/pnpm`) invoke the requested version. // `pmOnFail=download` so a project that pins
// // `devEngines.packageManager.onFail = "error"` doesn't trip BAD_PM_VERSION
// When the requested version resolves to the bootstrap version, self-update // before the switch can happen (issue #252). Scoped to this branch so users
// is a no-op and PNPM_HOME/bin is not created — fall back to pnpmHome, // who pass an explicit `version:` input keep strict onFail behavior.
// whose symlinks already point at the right version. exportVariable('pnpm_config_pm_on_fail', 'download')
const updatedBinDir = path.join(pnpmHome, 'bin') return { exitCode: 0, binDest: pnpmHome }
return { exitCode: 0, binDest: existsSync(updatedBinDir) ? updatedBinDir : pnpmHome }
} }
function readTargetVersion(opts: { function readTargetVersion(opts: {
readonly version?: string | undefined readonly version?: string | undefined
readonly packageJsonFile: string readonly packageJsonFile: string
}): string { }): string | undefined {
const { version, packageJsonFile } = opts const { version, packageJsonFile } = opts
const { GITHUB_WORKSPACE } = process.env const { GITHUB_WORKSPACE } = process.env
@@ -129,15 +135,12 @@ function readTargetVersion(opts: {
} }
} }
// packageManager is always exact `pnpm@<version>[+<integrity>]` per spec.
// Strip the integrity hash for self-update.
const packageManagerVersion =
typeof packageManager === 'string' && packageManager.startsWith('pnpm@')
? packageManager.slice('pnpm@'.length).split('+')[0]
: undefined
if (version) { if (version) {
if (packageManagerVersion && packageManagerVersion !== version) { if (
typeof packageManager === 'string' &&
packageManager.startsWith('pnpm@') &&
packageManager.replace('pnpm@', '') !== version
) {
throw new Error(`Multiple versions of pnpm specified: throw new Error(`Multiple versions of pnpm specified:
- version ${version} in the GitHub Action config with the key "version" - version ${version} in the GitHub Action config with the key "version"
- version ${packageManager} in the package.json with the key "packageManager" - version ${packageManager} in the package.json with the key "packageManager"
@@ -147,22 +150,13 @@ Remove one of these versions to avoid version mismatch errors like ERR_PNPM_BAD_
return version return version
} }
// Self-update the bootstrap pnpm to the version pinned in package.json so // pnpm will automatically download and switch to the right version
// PATH-resolved `pnpm` (and the bin_dest output) reflect the target if (typeof packageManager === 'string' && packageManager.startsWith('pnpm@')) {
// version. Without this, `pnpm store path` runs as the bootstrap and return undefined
// reports a different STORE_VERSION than the one the user's actual
// install writes to — breaking cache: true and actions/setup-node's
// `cache: pnpm` on cold caches (issue #233).
//
// devEngines.packageManager takes priority over packageManager, matching
// pnpm's getWantedPackageManager logic. `pnpm self-update` accepts both
// exact versions and semver ranges, so we pass either through directly.
if (devEngines?.packageManager?.name === 'pnpm' && devEngines.packageManager.version) {
return devEngines.packageManager.version
} }
if (packageManagerVersion) { if (devEngines?.packageManager?.name === 'pnpm' && devEngines.packageManager.version) {
return packageManagerVersion return undefined
} }
if (!GITHUB_WORKSPACE) { if (!GITHUB_WORKSPACE) {

View File

@@ -1,8 +1,11 @@
import { setFailed, startGroup, endGroup } from '@actions/core' import { setFailed, startGroup, endGroup } from '@actions/core'
import { spawnSync } from 'child_process' import { spawnSync } from 'child_process'
import { Inputs } from '../inputs' import { Inputs } from '../inputs'
import { patchPnpmEnv } from '../utils'
export function runPnpmInstall(inputs: Inputs) { export function runPnpmInstall(inputs: Inputs) {
const env = patchPnpmEnv(inputs)
for (const options of inputs.runInstall) { for (const options of inputs.runInstall) {
const args = ['install'] const args = ['install']
if (options.recursive) args.unshift('recursive') if (options.recursive) args.unshift('recursive')
@@ -11,16 +14,11 @@ export function runPnpmInstall(inputs: Inputs) {
const cmdStr = ['pnpm', ...args].join(' ') const cmdStr = ['pnpm', ...args].join(' ')
startGroup(`Running ${cmdStr}...`) startGroup(`Running ${cmdStr}...`)
// spawnSync inherits process.env, which already has $PNPM_HOME/bin and
// $PNPM_HOME prepended via addPath() in install-pnpm. Do NOT pass a
// hand-patched env that adds node_modules/.bin to the front — on
// Windows standalone, .bin/pnpm.cmd is an npm shim pointing at the
// BOOTSTRAP pnpm, which would shadow the self-updated one and break
// newer-pnpm-only behavior.
const { error, status } = spawnSync('pnpm', args, { const { error, status } = spawnSync('pnpm', args, {
stdio: 'inherit', stdio: 'inherit',
cwd: options.cwd, cwd: options.cwd,
shell: true, shell: true,
env,
}) })
endGroup() endGroup()

View File

@@ -1,6 +1,7 @@
import { warning, startGroup, endGroup } from '@actions/core' import { warning, startGroup, endGroup } from '@actions/core'
import { spawnSync } from 'child_process' import { spawnSync } from 'child_process'
import { Inputs } from '../inputs' import { Inputs } from '../inputs'
import { patchPnpmEnv } from '../utils'
export function pruneStore(inputs: Inputs) { export function pruneStore(inputs: Inputs) {
if (inputs.runInstall.length === 0) { if (inputs.runInstall.length === 0) {
@@ -9,11 +10,10 @@ export function pruneStore(inputs: Inputs) {
} }
startGroup('Running pnpm store prune...') startGroup('Running pnpm store prune...')
// spawnSync inherits process.env (which has the right PATH from addPath
// in install-pnpm). See pnpm-install/index.ts for the rationale.
const { error, status } = spawnSync('pnpm', ['store', 'prune'], { const { error, status } = spawnSync('pnpm', ['store', 'prune'], {
stdio: 'inherit', stdio: 'inherit',
shell: true, shell: true,
env: patchPnpmEnv(inputs),
}) })
endGroup() endGroup()

10
src/utils/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import path from 'path'
import process from 'process'
import { Inputs } from '../inputs'
export const getBinDest = (inputs: Inputs): string => path.join(inputs.dest, 'node_modules', '.bin')
export const patchPnpmEnv = (inputs: Inputs): NodeJS.ProcessEnv => ({
...process.env,
PATH: path.join(getBinDest(inputs), 'bin') + path.delimiter + getBinDest(inputs) + path.delimiter + process.env.PATH,
})