diff --git a/.github/actions/setup-pnpm-node/action.yml b/.github/actions/setup-pnpm-node/action.yml new file mode 100644 index 0000000..a19b30d --- /dev/null +++ b/.github/actions/setup-pnpm-node/action.yml @@ -0,0 +1,20 @@ +name: Setup pnpm + Node.js (cached) +description: >- + Install pnpm, then Node.js with the pnpm store cache wired in + (pnpm must be on PATH before actions/setup-node's cache: pnpm probe runs). +inputs: + node-version: + description: Node.js version to install + required: false + default: '22' +runs: + using: composite + steps: + - name: Install pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version: ${{ inputs.node-version }} + cache: pnpm diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 010458b..45a0af3 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -47,17 +47,17 @@ jobs: steps: - id: set # PRs build the cheap linux-x64 target only (catches the vast majority - # of compile errors on the self-hosted runner). Full cross-platform - # validation runs on push to main and on manually pushed - # cachekit-core-ts-v* tags — the validation a (currently manual, see - # header) publish relies on — keeping the macOS/Windows cost off the - # PR review loop without losing coverage where it matters. + # of compile errors fast). Full cross-platform validation runs on + # push to main and on manually pushed cachekit-core-ts-v* tags — the + # validation a (currently manual, see header) publish relies on — + # keeping the macOS/Windows cost off the PR review loop without + # losing coverage where it matters. run: | if [ "${{ github.event_name }}" = "pull_request" ]; then - builds='[{"target":"x86_64-unknown-linux-gnu","os":"cachekit-lean"}]' + builds='[{"target":"x86_64-unknown-linux-gnu","os":"ubuntu-latest"}]' else builds='[ - {"target":"x86_64-unknown-linux-gnu","os":"cachekit-lean"}, + {"target":"x86_64-unknown-linux-gnu","os":"ubuntu-latest"}, {"target":"aarch64-unknown-linux-gnu","os":"ubuntu-latest"}, {"target":"x86_64-apple-darwin","os":"macos-latest"}, {"target":"aarch64-apple-darwin","os":"macos-latest"}, @@ -78,19 +78,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Configure Rust paths (self-hosted) - if: matrix.os == 'cachekit-lean' - run: | - echo "RUSTUP_HOME=/tmp/rustup" >> "$GITHUB_ENV" - echo "CARGO_HOME=/tmp/cargo" >> "$GITHUB_ENV" - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Setup Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable @@ -98,6 +86,12 @@ jobs: toolchain: stable targets: ${{ matrix.target }} + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + workspaces: packages/cachekit-core-ts + shared-key: ${{ matrix.target }} + - name: Install aarch64 cross-compiler if: matrix.target == 'aarch64-unknown-linux-gnu' run: | @@ -125,16 +119,14 @@ jobs: needs: build strategy: matrix: - os: [cachekit-lean] node: [22, 24] - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + - uses: ./.github/actions/setup-pnpm-node with: node-version: ${{ matrix.node }} @@ -144,9 +136,6 @@ jobs: name: bindings-x86_64-unknown-linux-gnu path: packages/cachekit-core-ts - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 - - name: Install dependencies run: pnpm install diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index 88a45af..7da4d92 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -37,23 +37,11 @@ env: jobs: build: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Configure Rust paths (self-hosted) - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - echo "RUSTUP_HOME=/tmp/rustup" >> "$GITHUB_ENV" - echo "CARGO_HOME=/tmp/cargo" >> "$GITHUB_ENV" - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Setup Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e572845..b7793f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,17 +11,11 @@ permissions: jobs: lint: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Install dependencies run: pnpm install --frozen-lockfile @@ -33,7 +27,7 @@ jobs: run: pnpm type-check test: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -42,14 +36,10 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js ${{ matrix.node }} - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + - uses: ./.github/actions/setup-pnpm-node with: node-version: ${{ matrix.node }} - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 - - name: Install dependencies run: pnpm install --frozen-lockfile @@ -60,13 +50,7 @@ jobs: run: pnpm test test-integration: - # Fork PRs fall back to ubuntu-latest (GHA disables self-hosted runners - # for fork PRs by security policy). Internal/push runs use 'cachekit' - # (NOT 'cachekit-lean') because the redis service container below - # requires Docker on the host: cachekit-lean is a locked-down rootless - # pod with no DOCKER_HOST and no docker socket, so services would fail - # to start. ubuntu-latest has Docker preinstalled. - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit' }} + runs-on: ubuntu-latest services: redis: image: redis:7-alpine @@ -81,13 +65,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Install dependencies run: pnpm install --frozen-lockfile @@ -102,17 +80,11 @@ jobs: CI: true coverage: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Install dependencies run: pnpm install --frozen-lockfile @@ -130,17 +102,11 @@ jobs: fail_ci_if_error: false security: - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' + - uses: ./.github/actions/setup-pnpm-node - name: Install dependencies run: pnpm install --frozen-lockfile @@ -198,7 +164,7 @@ jobs: workers: name: Workers lane (wasm + workerd) - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest env: # Must match the wasm-bindgen version pinned in the crate's Cargo.lock; # scripts/build.sh fails loudly on drift. @@ -211,19 +177,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Configure Rust paths (self-hosted) - if: ${{ !github.event.pull_request.head.repo.fork }} - run: | - echo "RUSTUP_HOME=/tmp/rustup" >> "$GITHUB_ENV" - echo "CARGO_HOME=/tmp/cargo" >> "$GITHUB_ENV" - - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Setup Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable @@ -234,6 +188,11 @@ jobs: toolchain: 1.97.1 targets: wasm32-unknown-unknown + - name: Cache Rust build artifacts + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + workspaces: packages/cachekit-core-wasm + - name: Install wasm-bindgen + wasm-opt (pinned prebuilts, checksummed) run: | mkdir -p "$HOME/.local/bin" @@ -264,18 +223,12 @@ jobs: smoke-test: name: Package smoke test (ESM + CJS) - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} + runs-on: ubuntu-latest needs: [test] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '22' - - - name: Install pnpm - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v2 + - uses: ./.github/actions/setup-pnpm-node - name: Install dependencies run: pnpm install --frozen-lockfile @@ -323,10 +276,51 @@ jobs: console.log('CJS: all exports verified'); " + self-hosted-drift-guard: + name: Self-hosted runner drift guard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + # Drift protection for maintainers only: a fork PR runs its own copy of + # this workflow (a fork can't remove its own guard), so this catches an + # accidental self-hosted `runs-on` creeping back onto main/push, not a + # malicious fork PR. The server-side control for fork PRs lives in + # repository and org runner settings, outside this file. + # + # This is a line-oriented heuristic (case-insensitive grep over + # runs-on:/os:/runner: keys and JSON "os": values), not a schema-aware + # YAML parse — a determined bypass via a renamed matrix key or a YAML + # anchor/multi-line block scalar would slip past it. That tradeoff is + # deliberate: closing it properly means defeating a determined bypass, + # which is what the settings-level runner controls are for, not this + # maintainer sanity check. + - name: Fail if any workflow still targets a self-hosted runner + run: | + set -euo pipefail + hits=$(grep -rnE '^[[:space:]]*runs-on:|^[[:space:]]*(os|runner):|"os":' .github/workflows/ \ + | grep -iE 'cachekit|self-hosted' || true) + if [ -n "$hits" ]; then + echo "::error::self-hosted runner reference(s) found in .github/workflows/ (LAB-3503 drift guard):" + echo "$hits" + exit 1 + fi + echo "no self-hosted runner references found in .github/workflows/" + ci-success: name: CI Success - runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'cachekit-lean' }} - needs: [lint, test, test-integration, coverage, security, smoke-test, workers] + runs-on: ubuntu-latest + needs: + [ + lint, + test, + test-integration, + coverage, + security, + smoke-test, + workers, + self-hosted-drift-guard, + ] if: always() steps: - name: Check all jobs succeeded @@ -337,7 +331,8 @@ jobs: [[ "${{ needs.coverage.result }}" != "success" ]] || \ [[ "${{ needs.security.result }}" != "success" ]] || \ [[ "${{ needs.smoke-test.result }}" != "success" ]] || \ - [[ "${{ needs.workers.result }}" != "success" ]]; then + [[ "${{ needs.workers.result }}" != "success" ]] || \ + [[ "${{ needs.self-hosted-drift-guard.result }}" != "success" ]]; then echo "One or more jobs failed" exit 1 fi