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
Continuous Integration / config (pull_request) Successful in 10s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 5m17s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m30s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m32s
Continuous Integration / backend-test (pull_request) Successful in 5m31s
Continuous Integration / frontend-build (pull_request) Successful in 2m10s
Continuous Integration / frontend-test (pull_request) Successful in 4m26s
Continuous Integration / frontend-lint (pull_request) Successful in 2m12s
Continuous Integration / publish-test (pull_request) Canceled after 0s
Continuous Integration / publish-production (pull_request) Canceled after 0s
Continuous Integration / deploy-test (pull_request) Canceled after 0s
Continuous Integration / deploy-production (pull_request) Canceled after 0s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VhySVFARKAP89WqQ3Kff8M
This commit is contained in:
2026-07-31 15:25:28 +02:00
co-authored by Claude Sonnet 5
parent 66c0d768cf
commit dfc27fd5a2
+76 -10
View File
@@ -3,8 +3,12 @@ name: Continuous Integration
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
deploy_test:
description: 'Deploy to Test'
type: boolean
default: false
deploy_production: 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 type: boolean
default: false default: false
pull_request: pull_request:
@@ -62,8 +66,47 @@ jobs:
echo "health_check_url_test=${{ env.HEALTH_CHECK_URL_TEST }}" >> "$GITHUB_OUTPUT" echo "health_check_url_test=${{ env.HEALTH_CHECK_URL_TEST }}" >> "$GITHUB_OUTPUT"
echo "health_check_url_production=${{ env.HEALTH_CHECK_URL_PRODUCTION }}" >> "$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 --- # --- Gate 1: backend build ---
backend-build: backend-build:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -75,7 +118,8 @@ jobs:
# --- Gate 2: backend tests --- # --- Gate 2: backend tests ---
backend-test: backend-test:
needs: backend-build needs: [changes, backend-build]
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - 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 # 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). # Microsoft.OpenApi and System.Security.Cryptography.Xml in the relevant .csproj files).
vulnerability-scan: vulnerability-scan:
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -106,6 +152,8 @@ jobs:
fi fi
frontend-prepare: frontend-prepare:
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -135,7 +183,8 @@ jobs:
# that environment's Vite variables set and copies the result into wwwroot/admin before # 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. # `dotnet publish` runs — see that step's comment for why this isn't an MSBuild target instead.
frontend-build: frontend-build:
needs: frontend-prepare needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -163,7 +212,8 @@ jobs:
# --- Gate 5: frontend tests --- # --- Gate 5: frontend tests ---
frontend-test: frontend-test:
needs: frontend-prepare needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -191,7 +241,8 @@ jobs:
# --- Gate 6: frontend lint / format-check --- # --- Gate 6: frontend lint / format-check ---
frontend-lint: frontend-lint:
needs: frontend-prepare needs: [changes, frontend-prepare]
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -227,7 +278,15 @@ jobs:
# gate cannot see the actual runtime CSP configuration; it only catches drift between the frontend # gate cannot see the actual runtime CSP configuration; it only catches drift between the frontend
# build and what this variable claims the CSP allows. # build and what this variable claims the CSP allows.
publish-test: 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 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - 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 # 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). # Vite build-time value: one dist/ bundle cannot be tagged as both 'test' and 'production' (FR-05).
publish-production: publish-production:
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]
if: github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_production == 'true' # 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 runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -342,10 +405,13 @@ jobs:
path: ${{ env.ARTIFACT_NAME_PRODUCTION }} path: ${{ env.ARTIFACT_NAME_PRODUCTION }}
retention-days: 1 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: deploy-test:
needs: [publish-test, config] 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 uses: ./.gitea/workflows/deploy-scp.yaml
secrets: inherit secrets: inherit
with: with: