diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index f488c5e..18f566d 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,8 +1,12 @@ # CI runs first; build only proceeds if all checks pass. # -# Push to any branch: typecheck + lint + test -# Push to dev: gates + build :dev + : -# Tag v* (release): gates + build :latest + : + : +# Push to dev: typecheck + lint + test + build :dev + : +# Tag v* (release): typecheck + lint + test + build :latest + : + : +# +# main pushes are NOT gated here: a merge to main only happens after +# dev has already passed CI, and the release tag is the sole trigger +# for a production image. Re-running CI on the merge commit just burns +# runner time without changing the outcome. # # To cut a release: # Create a release via the Forgejo UI on main with a v* tag name. @@ -11,6 +15,13 @@ # PRs aren't triggered on purpose — this is a solo dev→main flow, so # gating on branch push is already enough. # +# NOTE on the `if:` guards below: Forgejo Actions does not consistently +# honor `on.push.branches` as a filter — merge commits landing on main +# still trigger the workflow, producing redundant runs on the same SHA +# that was already gated on dev. Every job therefore repeats the ref +# check so main pushes trigger the workflow but every job skips +# immediately (no runner time, no duplicate work). +# # Required secrets (repo → Settings → Secrets → Actions): # REGISTRY_USER — your Forgejo username # REGISTRY_TOKEN — Forgejo PAT with write:packages scope @@ -18,7 +29,7 @@ name: CI & Build on: push: - branches: [dev, main] + branches: [dev] tags: ["v*"] paths: - "src/**" @@ -51,6 +62,8 @@ env: jobs: typecheck: name: TypeScript typecheck + # Skip on main merge-commit pushes — see workflow header comment. + if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') runs-on: ci-runner steps: - uses: actions/checkout@v6 @@ -77,6 +90,7 @@ jobs: lint: name: Python lint + if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') runs-on: ci-runner steps: - uses: actions/checkout@v6 @@ -88,6 +102,7 @@ jobs: test: name: Python tests + if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') runs-on: ci-runner steps: - uses: actions/checkout@v6 @@ -118,11 +133,10 @@ jobs: name: Build & push image needs: [typecheck, lint, test] # Build on dev branch pushes and version tag pushes only. - # main branch pushes run the gates for safety but do not build — - # the release tag (v*) is the sole trigger for a production image. - if: | - github.event_name == 'push' && - (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) + # Mirrors the ref guard on the gate jobs above — main merge-commit + # pushes skip here too, so no production image is ever built from a + # raw main push (only from the v* tag the release creates). + if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') runs-on: ci-runner permissions: contents: read