From 8db12f9aeb5a78bd3592a279e78e7a830ca2b039 Mon Sep 17 00:00:00 2001 From: feledori Date: Fri, 2 Oct 2026 12:48:00 +0200 Subject: [PATCH] feat(ci-gate): pass for a repository whose ruleset requires its own checks --- .github/workflows/ci-gate.yml | 33 +++++++++++++++++++++++++++++++++ README.md | 7 +++++++ 2 files changed, 40 insertions(+) diff --git a/.github/workflows/ci-gate.yml b/.github/workflows/ci-gate.yml index f27b58d..ac83c46 100644 --- a/.github/workflows/ci-gate.yml +++ b/.github/workflows/ci-gate.yml @@ -4,6 +4,10 @@ name: ci-gate # It runs the project's own `check` script (or `typecheck`) for Bun projects and # passes with a notice for everything else. Production builds stay with Vercel # previews because they need per-project environment variables. +# +# A repository whose own ruleset already requires a status check on the default +# branch has its own enforced CI. The gate passes for it with a notice and does +# not install or run anything. on: pull_request: @@ -22,12 +26,34 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: + # A required status check from any active ruleset on the default branch + # counts, except `check`, the name of this job. When the API call fails, + # the gate runs as usual. + - name: Detect own CI + id: own + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.event.repository.default_branch }} + run: | + checks=$(gh api --paginate --slurp "/repos/$REPO/rules/branches/$BRANCH" | jq -r \ + '[.[][] | select(.type == "required_status_checks") | + .parameters.required_status_checks[].context | select(. != "check")] | + unique | join(", ")' || true) + if [ -n "$checks" ]; then + echo "::notice::The default branch already requires its own checks ($checks); nothing to gate." + echo "enforced=true" >> "$GITHUB_OUTPUT" + fi + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + if: steps.own.outputs.enforced != 'true' with: persist-credentials: false - name: Detect project id: detect + if: steps.own.outputs.enforced != 'true' shell: bash run: | if [ ! -f package.json ] || { [ ! -f bun.lock ] && [ ! -f bun.lockb ]; }; then @@ -56,6 +82,13 @@ jobs: with: bun-version: ${{ steps.detect.outputs.bun-version }} + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + if: steps.detect.outputs.kind == 'bun' + with: + path: ~/.bun/install/cache + key: ci-gate-bun-${{ runner.os }}-${{ hashFiles('bun.lock', 'bun.lockb') }} + restore-keys: ci-gate-bun-${{ runner.os }}- + - name: Install dependencies if: steps.detect.outputs.kind == 'bun' run: bun install --frozen-lockfile diff --git a/README.md b/README.md index f9d2ab0..28704e6 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,13 @@ and runs the `check` script, or `typecheck` when `check` is absent. It warns whe Other repositories pass with a notice. Production builds stay with Vercel previews, which carry each project's environment variables. +A repository with its own CI makes that CI a required status check in its own +ruleset for the default branch. The gate then passes with a notice and installs +and runs nothing. A file in the repository does not count: only a required check +does, so a repository cannot leave the gate without enforced CI. The gate still +starts a job, and GitHub bills each job as at least one minute. To save that +minute too, an owner names the repository in the ruleset's exclusions. + Every change to `ci-gate.yml` changes the gate for the whole organization. ## Dependabot merge policy