From b5a8608e02d01fe7bb5fa13705354321243f598e Mon Sep 17 00:00:00 2001 From: Ovidiu Galatan Date: Mon, 27 Jul 2026 17:14:46 +0300 Subject: [PATCH] ci: split checks into lint and test jobs, harden the workflow WHAT: - Split the single `verify` job into `lint` and `test`, so a formatting failure and a test failure are distinguishable and run in parallel. - Deny-by-default `permissions: {}` at workflow level, granting `contents: read` per job. - Pin actions to full commit SHAs with the version in a trailing comment. - `persist-credentials: false` on checkout so the token is not left behind in .git/config. - Pin the runner to ubuntu-24.04 rather than ubuntu-latest, add per-job timeouts, name the explicit pull_request types, and add workflow_dispatch. - Document each job with its step list. The `test` job builds before running jest. This is load-bearing, not incidental: tests/integration/bundle-version-interpolation.test.ts asserts against dist/proxy.js and skips itself when the bundle is missing, so a test job without a build would report green while silently dropping it. WHY: SHA-pinned actions and deny-by-default permissions remove a supply-chain and token-scope risk that the tag-based version carried. Splitting the jobs makes a failure attributable to the thing that failed. Note: the required status check names change from `verify` to `Lint and typecheck` and `Unit and integration tests`. --- .github/workflows/ci.yml | 103 ++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3adcf28..849bd39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,47 +1,65 @@ -name: CI +name: Test + +# Disable permissions for all available scopes by default. +# Any needed permissions should be configured at the job level. +permissions: {} on: - pull_request: + workflow_dispatch: push: - branches: [trunk] + branches: + - trunk + pull_request: + types: + - opened + - synchronize -# A new push to a PR makes the in-flight run obsolete; don't burn minutes on it. +# Cancels all previous workflow runs for pull requests that have not completed. concurrency: - group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # The concurrency group contains the workflow name and the branch name for pull requests + # or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true jobs: - verify: - name: verify - runs-on: ubuntu-latest + # Runs the formatting and type checks. + # + # Performs the following steps: + # - Checks out the repository. + # - Sets up Node.js. + # - Installs npm dependencies. + # - Runs Prettier over src and typechecks the whole project. + # - Verifies the version constant agrees with package.json. + lint: + name: Lint and typecheck + runs-on: ubuntu-24.04 permissions: contents: read + timeout-minutes: 10 + steps: - - name: Check out - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: + # Matches the version release.yml publishes with. There is no .nvmrc. node-version: '22' cache: npm - - name: Install dependencies + - name: Install npm dependencies run: npm ci - # Formatting plus a whole-project typecheck. tsc must keep --noEmit here: - # tsconfig sets outDir to ./dist, so a bare tsc would overwrite the tsup - # bundle with per-file output. + # `npm run check` is `prettier --check src && tsc --noEmit`. The --noEmit + # matters: tsconfig sets outDir to ./dist, so a bare tsc overwrites the + # tsup bundle with per-file output. - name: Check formatting and types run: npm run check - - name: Build - run: npm run build - - # Both suites. tests/unit/ alone silently skips the integration tests. - - name: Run tests - run: npx jest tests/unit/ tests/integration/ --no-coverage - # package.json and the constant are independent sources of truth, and # drifting apart once shipped a token directory named # wordpress-remote-undefined. @@ -58,3 +76,44 @@ jobs: exit 1 fi echo "Version constant matches package.json: ${PKG_VERSION}" + + # Runs the unit and integration suites against a real build. + # + # Performs the following steps: + # - Checks out the repository. + # - Sets up Node.js. + # - Installs npm dependencies. + # - Builds the tsup bundle. + # - Runs the unit and integration suites. + test: + name: Unit and integration tests + runs-on: ubuntu-24.04 + permissions: + contents: read + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + show-progress: ${{ runner.debug == '1' && 'true' || 'false' }} + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '22' + cache: npm + + - name: Install npm dependencies + run: npm ci + + # Must run before the suites. tests/integration/bundle-version-interpolation + # asserts against dist/proxy.js and silently skips itself when the bundle is + # absent, so without this the suite would report green while dropping it. + - name: Build + run: npm run build + + # Both suites. tests/unit/ alone silently skips the integration tests. + - name: Run tests + run: npx jest tests/unit/ tests/integration/ --no-coverage