Skip to content

CI/CD Pipeline

This guide covers the continuous integration workflows under .forgejo/workflows/. Each scope (backend, frontend, android, web, cli) has its own path-filtered ci-*.yml file, so a change to one scope only runs that scope’s checks.

ci-security.yml is unscoped and always runs, since a leaked secret or a vulnerable dependency can land in any scope.

Release workflows (backend.yml, frontend.yml, web.yml, android.yml) reuse these same ci-*.yml files as their pre-publish gate — see Releases.


Property Value
Triggers push to main, pull_request targeting main — path-filtered per scope (ci-security.yml is unscoped)
Runner Self-hosted rocky (ships Node.js 24 natively — no per-job install step needed), except ci-android.yml which runs on ubuntu for the Android SDK
Concurrency group ci-<scope>-${{ github.ref }}, one per workflow file
Cancel in progress true — a new push to the same ref cancels the previous run

Path-filtered to backend/**, frontend/container/nginx.conf.template (pinned by tests/test_security_headers.py), android/app/src/main/java/com/patrimoine/app/data/remote/ApiErrorCodes.kt (tests/test_android_error_contract.py) and the workflow file. Four jobs: lint, test (three shards, matrix shard: [1, 2, 3], each running the test files assigned to it, no coverage), coverage (whole suite with --cov=app, pushes to main only, non-blocking, failures announced through ntfy; the fail_under gate in backend/pyproject.toml applies here) and schema (OpenAPI check and migration round-trip), each with its own checkout and dependency install.

Step Command
Install uv pip install uv
Install dependencies uv sync --frozen --python 3.13 --extra dev
Lint uv run ruff check .
Test TMPDIR=/dev/shm uv run pytest $files -v -rfE -n 4 on the shard’s test files — four workers (not one per core) so a loaded runner does not lose one; no coverage
Coverage (coverage job) uv run pytest tests/ -q -n 4 --cov=app --cov-report=term on the whole suite
OpenAPI schema check uv run python scripts/export_openapi.py --check
Migration test (upgrade) uv run alembic upgrade head against a scratch sqlite+aiosqlite DB
Migration test (downgrade) uv run alembic downgrade base against the same scratch DB

The migration round-trip test is a CI-only sanity check — it does not touch the real database and is not the same operation as a production rollback (see Updating a Deployment).

Path-filtered to frontend/**. Five jobs run in parallel: static (lint and type check), test-1, test-2 and test-3 (the vitest suite split with --shard=N/3) and build (build, size limit and bundle checks). The shards are explicit jobs rather than a matrix, and run without coverage; ci-local.sh runs the whole suite with coverage.

Step Command
Install dependencies npm ci
Generate version metadata node scripts/generate-version.mjs
Lint oxlint
Type check tsc -b --noEmit
Test vitest run --shard=N/3 — one third of the suite per job
Build npm run build
Bundle size check size-limit
Bundle checks node scripts/verify-bundle.mjs dist — no PDF or non-default language chunk in the service worker precache, no dnd-kit preload on the entry page, the language preload map present, and the gzip size of the eagerly loaded login files within budget

Path-filtered to android/** and backend/openapi.json (the schema the DTO contract test reads). Runs on ubuntu for the Android SDK.

Step Command
Set up JDK 25 + Android SDK actions/setup-java, android-actions/setup-android
Release script tests bash android/scripts/release.test.sh && bash android/scripts/release-notes.test.sh
Lint ./gradlew :app:lintDebug
Test ./gradlew :app:testDebugUnitTest — includes the OpenAPI contract test
Build ./gradlew :app:assembleDebug — a debug APK, never signed or published

Path-filtered to web/documentation/**, web/container/** (the nginx config and entrypoint the site is served with), backend/openapi.json (the API reference source) and frontend/src/locales/** (the bold-UI-label parity check). A single docs job on the one Astro project.

Job Steps
docs (web/documentation) install, npm test (the node --test suites for the nginx redirects, the locale tooling and the build output checks), verify:locale-parity, check (astro check), build, verify:build, verify:no-leakage

Path-filtered to cli/**, backend/openapi.json and the workflow file itself. The schema is in the filter because cli/tests/test_contract.py checks every endpoint the command-line client calls against it, so an API change that breaks the client fails here. A single check job. There is no release workflow: the client is installed from a checkout with uv tool install ./cli.

Step Command
Install uv pip install uv
Install dependencies cd cli && uv sync --frozen --python 3.13 --extra dev
Lint cd cli && uv run ruff check .
Test cd cli && uv run pytest -q

Unscoped — no path filter, runs on every push to main and every pull request targeting main.

Job Step Command
security Backend audit pip-audit --strict (against the locked backend dependency set, dev extra included; a known vulnerability or a package that cannot be audited fails the job)
security Frontend audit npm audit --omit=dev
security Secret scan gitleaks detect --source . --no-banner (full git history); the gitleaks download is verified against a pinned SHA-256
stale-deps (PR only) Backend outdated check uv lock --upgrade --dry-run — warns (non-blocking ::warning::) if updates are available
stale-deps (PR only) Frontend outdated check npm outdated --omit=dev — non-blocking (|| true)