diff --git a/.github/actions/setup-workspace/action.yml b/.github/actions/setup-workspace/action.yml index e9b025aeb6..e363407887 100644 --- a/.github/actions/setup-workspace/action.yml +++ b/.github/actions/setup-workspace/action.yml @@ -6,7 +6,7 @@ inputs: description: Node version to install required: true pnpm-version: - description: pnpm version to install. Needs a matching pnpm-.sha256 file beside this action. + description: pnpm version to install. Must match the root packageManager and have a pnpm-.sha256 file beside this action. required: true cache-prefix: description: Names this job's Turbo cache entry (e.g. checks, test-server). Every job reads every prefix; this only decides which entry the job writes back. diff --git a/.github/actions/setup-workspace/install-pnpm.sh b/.github/actions/setup-workspace/install-pnpm.sh index e255d097eb..63bf47b0e2 100755 --- a/.github/actions/setup-workspace/install-pnpm.sh +++ b/.github/actions/setup-workspace/install-pnpm.sh @@ -13,6 +13,12 @@ set -euo pipefail version="${PNPM_VERSION:?PNPM_VERSION is required}" action_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" checksums="${action_dir}/pnpm-${version}.sha256" +package_manager="$(node --print 'require(process.argv[1]).packageManager' "${GITHUB_WORKSPACE:?GITHUB_WORKSPACE is required}/package.json")" + +if [[ "${package_manager}" != "pnpm@${version}" ]]; then + echo "::error::pnpm version mismatch: package.json declares ${package_manager#pnpm@}, but the action requested ${version}." + exit 1 +fi if [[ ! -f "${checksums}" ]]; then echo "::error::No checksums for pnpm ${version}. Download the release assets from https://github.com/pnpm/pnpm/releases/tag/v${version}, run sha256sum on them, and commit the output as ${checksums}." diff --git a/packages/scripts/test/ci-workflow.test.ts b/packages/scripts/test/ci-workflow.test.ts index 113dfa1847..05be52a2b0 100644 --- a/packages/scripts/test/ci-workflow.test.ts +++ b/packages/scripts/test/ci-workflow.test.ts @@ -1,7 +1,16 @@ -import { readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import { + chmodSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { expect, it } from "vitest"; +import { expect, it, onTestFinished } from "vitest"; const testDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(testDir, "..", "..", ".."); @@ -15,3 +24,36 @@ it("limits concurrent Turbo test tasks to the CI runner CPU count", () => { expect(testStep).toContain("--concurrency=4"); }); + +it("rejects a pnpm version that disagrees with the root manifest", () => { + const fixture = mkdtempSync(join(tmpdir(), "bb-pnpm-version-")); + onTestFinished(() => rmSync(fixture, { force: true, recursive: true })); + const fakeBin = resolve(fixture, "bin"); + mkdirSync(fakeBin); + writeFileSync( + resolve(fixture, "package.json"), + '{"packageManager":"pnpm@9.15.1"}\n', + ); + writeFileSync(resolve(fakeBin, "curl"), "#!/bin/sh\nexit 23\n"); + chmodSync(resolve(fakeBin, "curl"), 0o755); + + const result = spawnSync( + "bash", + [resolve(repoRoot, ".github/actions/setup-workspace/install-pnpm.sh")], + { + cwd: fixture, + encoding: "utf8", + env: { + ...process.env, + GITHUB_WORKSPACE: fixture, + PATH: `${fakeBin}:${process.env.PATH}`, + PNPM_VERSION: "9.15.0", + }, + }, + ); + + expect(result.status).toBe(1); + expect(result.stdout).toContain( + "pnpm version mismatch: package.json declares 9.15.1, but the action requested 9.15.0", + ); +}); diff --git a/turbo.json b/turbo.json index 3dcf8a84d7..df9371f8d6 100644 --- a/turbo.json +++ b/turbo.json @@ -454,6 +454,7 @@ "dependsOn": ["//#ensure-native-modules", "@bb/cli#build", "topo"], "inputs": [ "$TURBO_DEFAULT$", + "$TURBO_ROOT$/.github/actions/setup-workspace/**", "$TURBO_ROOT$/.github/APPROVED_CONTRIBUTORS", "$TURBO_ROOT$/.github/workflows/approve-contributor.yml", "$TURBO_ROOT$/.github/workflows/pr-gate.yml",