diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 8e3b446..ba50226 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,12 +1,19 @@ # CI runs first; build only proceeds if all checks pass. # -# Push to dev: typecheck + lint + test + build :dev + : -# Tag v* (release): typecheck + lint + test + build :latest + : + : +# Push to dev: typecheck + lint + test + build :dev + : +# Push to main: typecheck + lint + test + build : (no moving tag) +# 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. +# Both dev and main are gated AND built. dev pushes move the :dev tag; main +# pushes publish only the immutable : image — no :main tag, because +# :latest (release-only) is the single production pointer and a :main alias +# would just duplicate it. Running CI on the main merge commit is intentional: +# main is validated and its : image is the rollback point. The v* release +# tag is the ONLY trigger that publishes :latest plus the immutable :. +# +# Successive pushes to the SAME ref supersede each other (see concurrency +# below), so rapid pushes don't stack identical work; dev and main runs are +# independent refs and never cancel one another. # # To cut a release: # Create a release via the Forgejo UI on main with a v* tag name. @@ -16,11 +23,8 @@ # 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). +# honor `on.push.branches` as a filter, so every job repeats the ref check +# explicitly — permitting dev, main, and v* tags, rejecting anything else. # # Required secrets (repo → Settings → Secrets → Actions): # REGISTRY_USER — your Forgejo username @@ -29,7 +33,7 @@ name: CI & Build on: push: - branches: [dev] + branches: [dev, main] tags: ["v*"] paths: - "src/**" @@ -67,8 +71,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') + # Gate dev, main, and v* tags; reject any other ref (see header note). + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: python-ci container: image: git.fabledsword.com/bvandeusen/ci-python:3.14 @@ -92,7 +96,7 @@ jobs: lint: name: Python lint - if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: python-ci container: image: git.fabledsword.com/bvandeusen/ci-python:3.14 @@ -106,7 +110,7 @@ jobs: test: name: Python tests - if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v') + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: python-ci container: image: git.fabledsword.com/bvandeusen/ci-python:3.14 @@ -138,11 +142,9 @@ jobs: build: name: Build & push image needs: [typecheck, lint, test] - # Build on dev branch pushes and version tag pushes only. - # 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') + # Build on dev, main, and v* tag pushes. dev → :dev, main → (sha only), + # tag → :latest + :; every build also gets an immutable :. + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: python-ci container: image: git.fabledsword.com/bvandeusen/ci-python:3.14 @@ -168,6 +170,11 @@ jobs: refs/heads/dev) TAGS="$TAGS,${{ env.IMAGE }}:dev" ;; + refs/heads/main) + # main publishes only the immutable : image (set above) — + # no :main tag; :latest (release-only) is the production pointer. + BUILD_VERSION="main" + ;; refs/tags/*) TAGS="$TAGS,${{ env.IMAGE }}:latest,${{ env.IMAGE }}:${{ github.ref_name }}" BUILD_VERSION="${{ github.ref_name }}"