diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 66cae51..c0e183b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -166,6 +166,49 @@ 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' + # 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: | + actual="$("$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, diff --git a/dist/index.js b/dist/index.js index 528b6d9..f7910dc 100644 Binary files a/dist/index.js and b/dist/index.js differ diff --git a/src/index.ts b/src/index.ts index b3c72cd..c892ded 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) diff --git a/src/install-pnpm/index.ts b/src/install-pnpm/index.ts index 23b2695..abfe9e0 100644 --- a/src/install-pnpm/index.ts +++ b/src/install-pnpm/index.ts @@ -4,13 +4,15 @@ import runSelfInstaller from './run' export { runSelfInstaller } -export async function install(inputs: Inputs) { +export async function install(inputs: Inputs): Promise { 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 diff --git a/src/install-pnpm/run.ts b/src/install-pnpm/run.ts index e20fa60..6b04107 100644 --- a/src/install-pnpm/run.ts +++ b/src/install-pnpm/run.ts @@ -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 { +export interface SelfInstallerResult { + exitCode: number + binDest: string +} + +export async function runSelfInstaller(inputs: Inputs): Promise { 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 { 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 { 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: { diff --git a/src/outputs/index.ts b/src/outputs/index.ts index 1d64199..05ea3dd 100644 --- a/src/outputs/index.ts +++ b/src/outputs/index.ts @@ -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)