Skip to content

Commit 91ab88e

Browse files
authored
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
1 parent e578e19 commit 91ab88e

6 files changed

Lines changed: 218 additions & 163 deletions

File tree

.github/workflows/test.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,49 @@ jobs:
166166
fi
167167
shell: bash
168168

169+
test_bin_dest_output:
170+
name: 'Test bin_dest output points to requested version (${{ matrix.version }}, ${{ matrix.os }})'
171+
# Regression test for #247: invoking pnpm via the `bin_dest` output returned the
172+
# bootstrap version because self-update writes the target to `${bin_dest}/bin/`,
173+
# not directly into `${bin_dest}/`.
174+
175+
runs-on: ${{ matrix.os }}
176+
177+
strategy:
178+
fail-fast: false
179+
matrix:
180+
os:
181+
- ubuntu-latest
182+
- macos-latest
183+
- windows-latest
184+
version:
185+
- '9.15.5'
186+
- '10.33.2'
187+
188+
steps:
189+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
190+
191+
- id: pnpm
192+
name: Run the action
193+
uses: ./
194+
with:
195+
version: ${{ matrix.version }}
196+
197+
- name: 'Test: bin_dest/pnpm reports requested version'
198+
# Pass paths via env, not template interpolation, so Windows
199+
# backslashes in `bin_dest` aren't eaten by bash's escape handling.
200+
env:
201+
BIN_DEST: ${{ steps.pnpm.outputs.bin_dest }}
202+
REQUIRED: ${{ matrix.version }}
203+
run: |
204+
actual="$("$BIN_DEST/pnpm" --version)"
205+
echo "pnpm version via bin_dest: ${actual}"
206+
if [ "${actual}" != "${REQUIRED}" ]; then
207+
echo "Expected pnpm version ${REQUIRED}, but got ${actual}"
208+
exit 1
209+
fi
210+
shell: bash
211+
169212
test_package_manager_field:
170213
name: 'Test packageManager field is respected (${{ matrix.version }}, ${{ matrix.os }})'
171214
# Reproduces #227: when `packageManager` is set in package.json and no `version:` input is given,

dist/index.js

Lines changed: 149 additions & 150 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ async function main() {
2020
async function runMain(inputs: Inputs) {
2121
saveState('is_post', 'true')
2222

23-
await installPnpm(inputs)
23+
const binDest = await installPnpm(inputs)
24+
if (binDest === undefined) return
2425
console.log('Installation Completed!')
25-
setOutputs(inputs)
26+
setOutputs(inputs, binDest)
2627

2728
await restoreCache(inputs)
2829

src/install-pnpm/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import runSelfInstaller from './run'
44

55
export { runSelfInstaller }
66

7-
export async function install(inputs: Inputs) {
7+
export async function install(inputs: Inputs): Promise<string | undefined> {
88
startGroup('Running self-installer...')
9-
const status = await runSelfInstaller(inputs)
9+
const { exitCode, binDest } = await runSelfInstaller(inputs)
1010
endGroup()
11-
if (status) {
12-
return setFailed(`Something went wrong, self-installer exits with code ${status}`)
11+
if (exitCode) {
12+
setFailed(`Something went wrong, self-installer exits with code ${exitCode}`)
13+
return undefined
1314
}
15+
return binDest
1416
}
1517

1618
export default install

src/install-pnpm/run.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import exeLock from './bootstrap/exe-lock.json'
1212
const BOOTSTRAP_PNPM_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { pnpm: pnpmLock.packages['node_modules/pnpm'].version } })
1313
const BOOTSTRAP_EXE_PACKAGE_JSON = JSON.stringify({ private: true, dependencies: { '@pnpm/exe': exeLock.packages['node_modules/@pnpm/exe'].version } })
1414

15-
export async function runSelfInstaller(inputs: Inputs): Promise<number> {
15+
export interface SelfInstallerResult {
16+
exitCode: number
17+
binDest: string
18+
}
19+
20+
export async function runSelfInstaller(inputs: Inputs): Promise<SelfInstallerResult> {
1621
const { version, dest, packageJsonFile } = inputs
1722

1823
// pnpm v11 requires Node >= 22.13; use standalone (exe) bootstrap which
@@ -45,7 +50,7 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
4550
const npmEnv = { ...process.env, [pathKey]: currentPath ? currentPath + path.delimiter + nodeDir : nodeDir }
4651
const npmExitCode = await runCommand('npm', ['ci'], { cwd: dest, env: npmEnv })
4752
if (npmExitCode !== 0) {
48-
return npmExitCode
53+
return { exitCode: npmExitCode, binDest: path.join(dest, 'node_modules', '.bin') }
4954
}
5055

5156
// On Windows with standalone mode, npm's .bin shims can't properly
@@ -87,11 +92,18 @@ export async function runSelfInstaller(inputs: Inputs): Promise<number> {
8792
const args = standalone ? ['self-update', targetVersion] : [bootstrapPnpm, 'self-update', targetVersion]
8893
const exitCode = await runCommand(cmd, args, { cwd: dest })
8994
if (exitCode !== 0) {
90-
return exitCode
95+
return { exitCode, binDest: pnpmHome }
9196
}
97+
// self-update writes the target pnpm/pnpx into PNPM_HOME/bin, leaving
98+
// the bootstrap symlinks in pnpmHome pointing at the old version. Use
99+
// PNPM_HOME/bin so consumers of the bin_dest output (e.g.
100+
// `${steps.pnpm.outputs.bin_dest}/pnpm`) invoke the requested version.
101+
return { exitCode: 0, binDest: path.join(pnpmHome, 'bin') }
92102
}
93103

94-
return 0
104+
// No explicit target version: rely on the bootstrap pnpm to switch to
105+
// the version declared in packageManager/devEngines at runtime.
106+
return { exitCode: 0, binDest: pnpmHome }
95107
}
96108

97109
function readTargetVersion(opts: {

src/outputs/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { setOutput } from '@actions/core'
22
import { Inputs } from '../inputs'
3-
import { getBinDest } from '../utils'
43

5-
export function setOutputs(inputs: Inputs) {
6-
const binDest = getBinDest(inputs)
4+
export function setOutputs(inputs: Inputs, binDest: string) {
75
// NOTE: addPath is already called in installPnpm — do not call it again
86
// here, as a second addPath would shadow the correct entry on Windows.
97
setOutput('dest', inputs.dest)

0 commit comments

Comments
 (0)