From dfc27fd5a2a51a84bd32386e5ebce776163273e8 Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Fri, 31 Jul 2026 15:25:28 +0200 Subject: [PATCH] Shortens deploy_production label, adds a deploy_test toggle for manual runs, and skips backend/frontend gates when that side of the repo has no changes Push-to-master keeps auto-deploying to test unchanged. A new deploy_test input (default false) lets a manual workflow_dispatch run opt into a test deploy instead of it firing by default. A new changes job (dorny/paths-filter) gates the backend and frontend build/test/lint jobs on whether their side of the repo actually changed, shortening runs that only touch one side; publish jobs still run whenever either side changed, and manual runs always run everything since there's no commit to diff against. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VhySVFARKAP89WqQ3Kff8M --- .gitea/workflows/continuous_integration.yaml | 86 +++++++++++++++++--- 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/.gitea/workflows/continuous_integration.yaml b/.gitea/workflows/continuous_integration.yaml index 5c06e5e..f8a0fc2 100644 --- a/.gitea/workflows/continuous_integration.yaml +++ b/.gitea/workflows/continuous_integration.yaml @@ -3,8 +3,12 @@ name: Continuous Integration on: workflow_dispatch: inputs: + deploy_test: + description: 'Deploy to Test' + type: boolean + default: false deploy_production: - description: 'Also deploy to production after a successful build/test (in addition to the automatic test deploy)' + description: 'Deploy to Production' type: boolean default: false pull_request: @@ -62,8 +66,47 @@ jobs: echo "health_check_url_test=${{ env.HEALTH_CHECK_URL_TEST }}" >> "$GITHUB_OUTPUT" echo "health_check_url_production=${{ env.HEALTH_CHECK_URL_PRODUCTION }}" >> "$GITHUB_OUTPUT" + # Path-based skip: keeps the pipeline short when a change only touches one side of the repo. + # fetch-depth: 0 gives paths-filter full local git history to diff against, rather than relying + # on its GitHub-API fallback for missing history, which targets GitHub's REST API and cannot be + # assumed to work against a Gitea remote. Changes under the workflow files themselves count as + # both backend and frontend, so a workflow edit is never left partially untested. A manual + # workflow_dispatch run has no `before` commit to diff against and is always an explicit request + # to run everything, so it force-reports both sides as changed rather than trusting the filter. + changes: + runs-on: ubuntu-latest + outputs: + backend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.backend == 'true' }} + frontend: ${{ github.event_name == 'workflow_dispatch' || steps.filter.outputs.frontend == 'true' }} + steps: + # Skipped on workflow_dispatch: there's no `before` commit to diff against, and a manual run + # always force-reports both sides changed anyway (see the outputs above), so the diff would be + # wasted work. + - uses: actions/checkout@v4 + if: github.event_name != 'workflow_dispatch' + with: + fetch-depth: 0 + - uses: dorny/paths-filter@v3 + if: github.event_name != 'workflow_dispatch' + id: filter + with: + filters: | + workflow: &workflow + - '.gitea/workflows/**' + backend: + - *workflow + - 'src/**' + - 'tests/**' + - '*.sln' + - '**/*.csproj' + frontend: + - *workflow + - 'frontend/**' + # --- Gate 1: backend build --- backend-build: + needs: changes + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -75,7 +118,8 @@ jobs: # --- Gate 2: backend tests --- backend-test: - needs: backend-build + needs: [changes, backend-build] + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -90,6 +134,8 @@ jobs: # step greps its own output and fails deliberately (FR-22, D-12, OPEN-03 — closed by pinning # Microsoft.OpenApi and System.Security.Cryptography.Xml in the relevant .csproj files). vulnerability-scan: + needs: changes + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -106,6 +152,8 @@ jobs: fi frontend-prepare: + needs: changes + if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -135,7 +183,8 @@ jobs: # that environment's Vite variables set and copies the result into wwwroot/admin before # `dotnet publish` runs — see that step's comment for why this isn't an MSBuild target instead. frontend-build: - needs: frontend-prepare + needs: [changes, frontend-prepare] + if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -163,7 +212,8 @@ jobs: # --- Gate 5: frontend tests --- frontend-test: - needs: frontend-prepare + needs: [changes, frontend-prepare] + if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -191,7 +241,8 @@ jobs: # --- Gate 6: frontend lint / format-check --- frontend-lint: - needs: frontend-prepare + needs: [changes, frontend-prepare] + if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -227,7 +278,15 @@ jobs: # gate cannot see the actual runtime CSP configuration; it only catches drift between the frontend # build and what this variable claims the CSP allows. publish-test: - needs: [backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint] + needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint] + # always() bypasses the automatic skip-cascade from a gate job that was itself skipped (because + # its side of the repo didn't change) — !failure() && !cancelled() still blocks a run where a + # gate that DID run actually failed. The remaining changes.outputs check makes sure there's + # something to publish at all: a run that touched neither backend nor frontend has nothing new + # to deploy. + if: | + always() && !failure() && !cancelled() && + (needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -289,8 +348,12 @@ jobs: # a distinct job rather than a parameterized reuse of publish-test, because VITE_APP_ENV is a # Vite build-time value: one dist/ bundle cannot be tagged as both 'test' and 'production' (FR-05). publish-production: - needs: [backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint] - if: github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' + needs: [changes, backend-build, backend-test, vulnerability-scan, frontend-build, frontend-test, frontend-lint] + # See publish-test's comment on the always()/!failure()/!cancelled() combination above. + if: | + always() && !failure() && !cancelled() && + github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' && + (needs.changes.outputs.backend == 'true' || needs.changes.outputs.frontend == 'true') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -342,10 +405,13 @@ jobs: path: ${{ env.ARTIFACT_NAME_PRODUCTION }} retention-days: 1 - # Automatic test deploy on push to master, or on any workflow_dispatch run (FR-03, D-01, D-09). + # Automatic test deploy on every push to master (unchanged — a merge should reach test without + # extra steps). A manual workflow_dispatch run only deploys to test when deploy_test is checked, + # since a dispatch run is often just re-running CI/production, not a new test build (FR-03, D-01, + # D-09). deploy-test: needs: [publish-test, config] - if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/master') + if: (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_test == 'true') || (github.event_name == 'push' && github.ref == 'refs/heads/master') uses: ./.gitea/workflows/deploy-scp.yaml secrets: inherit with: