CI/CD Pipeline — GitHub Actions
This document describes the project's continuous integration (CI) pipeline in GitHub Actions. It focuses on fast feedback, trunk-based development, and safe defaults when parts of the stack are not present yet (Python and/or frontend).
- Workflow file:
.github/workflows/ci.yml - Triggers: push to any branch, pull requests to
main, and manualworkflow_dispatch - Permissions: minimal (
contents: read) - Concurrency: uses
ci-prefix withgithub.refto avoid overlapping runs on the same ref (cancel-in-progress enabled)
Jobs
Python — Lint and Tests
- Version: Python 3.12 on
ubuntu-latest - Step-level detection for Python files to avoid running setup and tooling when the repo has no Python yet
- Caching: pip cache keyed by
requirements*.txt,pyproject.toml,setup.cfg - Tooling: ruff (lint), black (format check), pytest (tests)
Key steps:
- Detect Python files
- Uses
git ls-files -- '*.py'to set an outputhas_python=true|false
- Uses
- Set up Python and cache pip only when
has_python == 'true' - Install project deps if
requirements*.txtorpyproject.tomlexist - Install dev tools (ruff, black, pytest)
- Run:
ruff check .,black --check .,pytest -q(tests only iftests/**/*.pyexists)
Why step-level detection? GitHub Actions expressions like hashFiles() are not supported at the job level. Guarding steps with conditional expressions on steps.detect_py.outputs.has_python avoids context warnings and keeps the job defined while doing no work when Python isn't present.
Frontend — Build and Tests
- Step-level detection for Node project by checking for
package.json - Node setup:
actions/setup-node@v4with LTS and npm caching - Commands:
npm ci,npm run build --if-present,npm test --if-present
Key steps:
- Detect Node project
- Uses
git ls-files -- 'package.json'to sethas_node=true|false
- Uses
- Set up Node only when
has_node == 'true' - Install, build, and test with
--if-presentto stay no-op-safe in early stages
Troubleshooting
- Job-level
if:supportshashFiles()and similar contexts syntactically, but these are evaluated before checkout and will not work as expected; keep file-system checks at the step level, or useon.pathsfilters inon.push/on.pull_requestif you want to avoid triggering the workflow at all. - If your repo has no Python yet, the Python job will run with steps skipped — this is expected and fast. Same for frontend when there’s no
package.json. - Cache misses on first run are normal; subsequent runs will warm the cache.
Optional enhancements (future)
- Add a docs build job (e.g., VuePress) once the docs site exists.
- Add coverage reporting (e.g.,
pytest-cov+ Codecov) and upload artifacts. - Add
pathsfilters to narrow triggers when the repo grows.
Publishing container images
Publishing images is handled by a dedicated workflow (to be added) that runs on merges to main and on releases. See docs/devops/registry.md for the phased GHCR strategy (naming, tagging, access, retention) and for where the publish workflow lives under .github/workflows/.
Skipping CI runs
To preserve build minutes you can opt out per change:
- Include the token "[skip ci]" in the commit message (for push events), or in the PR title (for pull_request events). Jobs in
.github/workflows/ci.ymlwill be skipped when this token is present. - You can also trigger runs manually via the Actions tab using "Run workflow" (workflow_dispatch).
See .github/workflows/ci.yml for the exact conditions.