name: PR build (required check) # Monorepo port of the app repos' pr-build.yml: ONE workflow, ONE stable # required-check name (`gate`), any number of domains. A `changes` job diffs # the PR against its base; per-domain jobs run only for domains the PR # touches; `gate` reduces their results into the single status branch # protection requires. # # Deliberately NOT path-filtered at the workflow level: a path-filtered # required check that never fires leaves the PR stuck "expected -- waiting for # status" forever, so auto-merge never proceeds. An always-running gate whose # domain jobs skip cleanly gives the same compute savings without the # deadlock. # # One-time web UI setup (Settings -> Branches, rules for `dev` and `main`): # -> enable "Enable Status Check", list this workflow's "gate" job # -> merge style: squash # Then on a ready PR: "Auto merge when checks succeed". on: pull_request: branches: [dev, main, release] concurrency: group: pr-build-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: changes: runs-on: docker outputs: backend: ${{ steps.diff.outputs.backend }} frontend: ${{ steps.diff.outputs.frontend }} observability: ${{ steps.diff.outputs.observability }} steps: - uses: actions/checkout@v4 with: # The PR base commit isn't reachable from a depth-1 clone; full # history keeps the diff computable however far the branch diverged. fetch-depth: 0 - name: Detect changed domains id: diff run: | set -euo pipefail base="${{ github.event.pull_request.base.sha }}" changed=$(git diff --name-only "$base" "$GITHUB_SHA") echo "changed files:"; printf '%s\n' "$changed" for d in backend frontend observability; do if printf '%s\n' "$changed" | grep -q "^$d/"; then echo "$d=true" >> "$GITHUB_OUTPUT" else echo "$d=false" >> "$GITHUB_OUTPUT" fi done build-backend: needs: changes if: needs.changes.outputs.backend == 'true' uses: ./.forgejo/workflows/build.yml with: domain: backend build-frontend: needs: changes if: needs.changes.outputs.frontend == 'true' uses: ./.forgejo/workflows/build.yml with: domain: frontend validate-observability: needs: changes if: needs.changes.outputs.observability == 'true' uses: ./.forgejo/workflows/observability-validate.yml gate: name: gate # always(): skipped domain jobs (nothing changed there) must not skip the # gate itself -- the required check has to report on EVERY PR. if: always() needs: [changes, build-backend, build-frontend, validate-observability] runs-on: docker steps: - name: Reduce domain results run: | set -euo pipefail ok=1 for pair in \ "backend=${{ needs.build-backend.result }}" \ "frontend=${{ needs.build-frontend.result }}" \ "observability=${{ needs.validate-observability.result }}"; do echo "$pair" case "${pair#*=}" in success|skipped) ;; *) ok=0 ;; esac done [ "$ok" = 1 ] || { echo "a changed domain's check failed"; exit 1; }