5
0
mirror of https://github.com/pnpm/action-setup.git synced 2026-05-08 01:48:28 +00:00

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.
This commit is contained in:
Zoltan Kochan
2026-05-07 08:55:16 +02:00
parent e578e19d19
commit 624e28f5d0
6 changed files with 172 additions and 120 deletions

View File

@@ -166,6 +166,45 @@ jobs:
fi
shell: bash
test_bin_dest_output:
name: 'Test bin_dest output points to requested version (${{ matrix.version }}, ${{ matrix.os }})'
# Regression test for #247: invoking pnpm via the `bin_dest` output returned the
# bootstrap version because self-update writes the target to `${bin_dest}/bin/`,
# not directly into `${bin_dest}/`.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
version:
- '9.15.5'
- '10.33.2'
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- id: pnpm
name: Run the action
uses: ./
with:
version: ${{ matrix.version }}
- name: 'Test: bin_dest/pnpm reports requested version'
run: |
required='${{ matrix.version }}'
actual="$(${{ steps.pnpm.outputs.bin_dest }}/pnpm --version)"
echo "pnpm version via bin_dest: ${actual}"
if [ "${actual}" != "${required}" ]; then
echo "Expected pnpm version ${required}, but got ${actual}"
exit 1
fi
shell: bash
test_package_manager_field:
name: 'Test packageManager field is respected (${{ matrix.version }}, ${{ matrix.os }})'
# Reproduces #227: when `packageManager` is set in package.json and no `version:` input is given,

214
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -20,9 +20,10 @@ async function main() {
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)

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

View File

@@ -12,7 +12,12 @@ import exeLock from './bootstrap/exe-lock.json'
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 } })
export async function runSelfInstaller(inputs: Inputs): Promise<number> {
export interface SelfInstallerResult {
exitCode: number
binDest: string
}
export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerResult> {
const { version, dest, packageJsonFile } = inputs
// pnpm v11 requires Node >= 22.13; use standalone (exe) bootstrap which
@@ -45,7 +50,7 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
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 npmExitCode
return { exitCode: npmExitCode, binDest: path.join(dest, 'node_modules', '.bin') }
}
// On Windows with standalone mode, npm's .bin shims can't properly
@@ -87,11 +92,18 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion]
const exitCode = await runCommand(cmd, args, { cwd: dest })
if (exitCode !== 0) {
return exitCode
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') }
}
return 0
// No explicit target version: rely on the bootstrap pnpm to switch to
// the version declared in packageManager/devEngines at runtime.
return { exitCode: 0, binDest: pnpmHome }
}
function readTargetVersion(opts: {

View File

@@ -1,9 +1,7 @@
import { setOutput } from '@actions/core'
import { Inputs } from '../inputs'
import { getBinDest } from '../utils'
export function setOutputs(inputs: Inputs) {
const binDest = getBinDest(inputs)
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)