ci.yml

Continuous integration

Tier 2engineeringWarns only

A commit made with --no-verify, and a project’s own tests, which the hook is too quick to run. This runs them, and every installed checker, on each pull request and on main, where nobody can skip them quietly.

CI.YMLPRINTS, DOES NOT BLOCK PULL REQUEST PROJECT TESTS INSTALLED CHECKS GREEN ON MAIN FAILING CHECK NAMED
Add this to your project
Add "Continuous integration" from ntent to this repo.

Fetch https://ntent.app/r/f/ci as plain text. Write it verbatim to .github/workflows/ci.yml. Then GitHub Actions. On another host, port the steps. Then make exactly these edits and no others: Nothing for a pnpm repo with a packageManager field in package.json. Without that field, add "packageManager": "pnpm@<version>" there. If ntent was installed in a package folder rather than the repo root, set defaults.run.working-directory to that folder. A checker that is not installed is skipped and named in the log; when you add one, its line is already here.

Then check it: Open a pull request: the project’s test, typecheck and lint scripts run, then each installed checker. Break one of the references in contract.json and push, and the "Installed checkers" step fails naming check-contract-coverage.mjs. The same step recomputes the pins, so a test edited under an accepted journey fails here when it was committed with --no-verify, or arrived by a merge or a rebase, neither of which runs the commit hook. It runs on pull requests and on pushes to main, not on every push to a branch.

Reads https://ntent.app/r/f/ci

Read the code
ci.yml.github/workflows/ci.yml · GitHub Actions. On another host, port the steps
99 lines
# The backstop for the pre-commit hook.
#
# The hook runs the checks locally, fast and offline, and can be bypassed. This
# runs them on every pull request and on main, where a bypass is visible, and it
# is where the project's own tests run, because the hook is too quick for them.
#
# It works for a repo that has not installed every checker: each one runs only
# if its file is there, and the log says which were skipped. A checker that
# exits 2 read nothing; point its CONFIG at the app's folders, which is what the
# hook's note tells you too. A skipped checker is a gap, so read the list.
name: CI

on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  checks:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      # Full history, not the default depth of one: the secrets scan reads every
      # commit, and refuses a shallow clone rather than report clean.
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Reads the pnpm version from packageManager in package.json.
      - uses: pnpm/action-setup@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      # The project's own scripts. --if-present skips one it has not written yet.
      - run: pnpm run --if-present test
      - run: pnpm run --if-present typecheck
      - run: pnpm run --if-present lint

      # Each checker has tests beside it; the hook runs them on a commit that
      # edits a checker, and this runs them all.
      - name: Checker tests
        if: hashFiles('scripts/**/*.test.mjs') != ''
        run: node --test "scripts/**/*.test.mjs"

      # The credential scan over all history needs gitleaks, pinned to a version
      # and to the sha256 the release publishes.
      - name: Install gitleaks
        if: hashFiles('scripts/check-secrets.mjs') != ''
        run: |
          curl -sSfL -o gitleaks.tgz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
          echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb  gitleaks.tgz" | sha256sum -c -
          tar -xzf gitleaks.tgz gitleaks
          sudo install gitleaks /usr/local/bin/gitleaks
          rm gitleaks.tgz gitleaks

      # Every checker the repo installed, all of them run, one list of what failed.
      # The screen check is not here: it needs a running app and a browser.
      - name: Installed checkers
        run: |
          failed=""
          check () {
            local script="scripts/$1"
            shift
            if [ ! -f "$script" ]; then
              echo "not installed, skipped: $script"
              return 0
            fi
            echo "::group::node $script $*"
            node "$script" "$@" || failed="$failed $script"
            echo "::endgroup::"
          }
          check check-env.mjs --check
          check check-secrets.mjs --all
          check check-probes.mjs
          check check-tokens.mjs --check
          check check-contrast.mjs --check
          check check-api-routes.mjs --check
          check verify-fixtures.mjs
          [ -f contract.json ] && check check-contract-coverage.mjs --refs
          check check-rules-doc.mjs --check
          check check-locale-keys.mjs --check
          check check-stray-components.mjs --check
          check check-clip-comments.mjs --check
          check check-backend.mjs --check --allow-empty
          check check-migrations.mjs --check --allow-empty
          if [ -n "$failed" ]; then
            echo "::error::These checkers failed:$failed"
            exit 1
          fi

How to tell it works: Open a pull request: the project’s test, typecheck and lint scripts run, then each installed checker. Break one of the references in contract.json and push, and the "Installed checkers" step fails naming check-contract-coverage.mjs. The same step recomputes the pins, so a test edited under an accepted journey fails here when it was committed with --no-verify, or arrived by a merge or a rebase, neither of which runs the commit hook. It runs on pull requests and on pushes to main, not on every push to a branch.

Included in