name: CD on: push: branches: [main] tags: ["v*"] pull_request: branches: [main] env: IMAGE: spatial-agent-app jobs: guard: name: Detect unsubstituted template runs-on: self-hosted outputs: is_template: ${{ steps.detect.outputs.is_template }} deploy_enabled: ${{ steps.deploy.outputs.deploy_enabled }} steps: - uses: actions/checkout@v4 - id: detect # The detection lives in scripts/is-template.sh so it can be TESTED. # It was inline here twice and wrong both times (#9, #10): the grep # named a placeholder literally, and the scaffolder substitutes every # file including this one, so in a generated repo the guard matched # forever and check/build/deploy all skipped while the run went green. # # An inline shell block in a workflow cannot be run by a test, which is # why the second fix repeated the first bug under a comment warning # about it. scripts/tests/is-template_test.sh now substitutes the guard # and asserts it still answers correctly. run: | echo "is_template=$(bash scripts/is-template.sh go.mod)" >> "$GITHUB_OUTPUT" - id: deploy # build and deploy run only when .deploy-enabled is committed. Before # that there is no service: mathias/infra has no # k3s/apps/spatial-agent-app, so deploy failed on every push to main and # build pushed images for nothing. To deploy later: create the infra # manifest first (an infra-lane issue), then commit .deploy-enabled. # The logic is in scripts/deploy-enabled.sh so a test can run it. # A missing or misspelt output compares unequal to 'true' below, so # this fails closed. The script runs on its own line under set -e, not # inside the echo: a command substitution there swallows its exit 2 and # a missing directory would look like a deliberate "false". run: | set -euo pipefail enabled="$(bash scripts/deploy-enabled.sh .)" echo "deploy_enabled=${enabled}" >> "$GITHUB_OUTPUT" check: name: Lint / Test / Vet # Deliberately NOT gated on the guard (#12). build and deploy are, because # they genuinely cannot work here -- placeholder module path, no image name, # no namespace. But linting and testing the template is meaningful, and # gating it meant this repo had no working gate at all: #8 sat here with CI # green throughout, and was found by a child rather than by us. # # `task check` includes scripts/tests/generated-child_test.sh, which covers # the entrypoint that ./... cannot see -- ungating without that would run a # gate that still skips the package every child is built around. needs: guard runs-on: self-hosted steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Install toolchain run: | go version # Pin the GENERATOR to the same version as the templ LIBRARY in # go.mod. Installing @latest let them drift: the generator emitted # calls (templruntime.WriteString) that the pinned library did not # have, which is template-go-web#8. Nothing caught it because the # guard skipped `check` in this repo entirely (#12). templ_version="$(go list -m -f '{{.Version}}' github.com/a-h/templ)" echo "templ library is $templ_version — installing the matching generator" go install "github.com/a-h/templ/cmd/templ@$templ_version" curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \ | sh -s -- -b "$(go env GOPATH)/bin" v2.11.4 - name: Run checks run: task check build: name: Build & Import needs: [guard, check] if: needs.guard.outputs.is_template != 'true' && needs.guard.outputs.deploy_enabled == 'true' && github.event_name != 'pull_request' runs-on: self-hosted outputs: image-tag: ${{ steps.meta.outputs.sha-tag }} steps: - uses: actions/checkout@v4 - name: Derive image tags id: meta run: | SHA=$(git rev-parse --short HEAD) echo "sha-tag=${SHA}" >> "$GITHUB_OUTPUT" # infra#309: the implementation lives in .gitea/build-image.sh, a # byte-identical copy of mathias/infra's ci-templates/build-image.sh. # Do not edit it here -- edit the canonical file, run `task ci:sync`, # and infra's drift gate will name any repo still carrying an old copy. # # This matters more in a template than anywhere else: every repo # generated from here inherits its CI, so a template that carries the # build LOGIC manufactures a new copy of every bug in it. That is # precisely how rootless buildah (infra#241) reached a dozen repos, and # why the fix then had to be rediscovered here weeks later. A template # that carries a CALL manufactures nothing. # # NEW REPOS: add yourself to mathias/infra's ci-templates/adopters.txt so # the gate keeps this copy current. Until you do, the copy is unmanaged # and will silently fall behind. # # `bash