CI/CD workflows¶
All workflows live in .github/workflows/ and run with a read-only GITHUB_TOKEN by
default; jobs that publish request only the permissions they need.
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
push and PR to main / develop |
Quality gate (below) |
dependency-review.yml |
PR to main / develop |
Blocks new dependencies with high/critical vulnerabilities or a GPL/AGPL license |
docs.yml |
PR touching the docs, push to develop, manual |
Builds and publishes this site and the coverage badge |
release.yml |
tag v*.*.*, manual |
Publishes Docker images, npm package and GitHub release: see Release process |
| CodeQL (default setup) | push, PR, weekly | Code scanning of JavaScript/TypeScript and workflows |
| Dependabot | weekly | Updates npm packages, GitHub Actions, Docker images and Compose files, against develop |
Workflow hardening¶
Every workflow follows the same rules; keep them when adding a job or an action:
- Actions pinned to a commit SHA, with the release as a comment
(
uses: actions/checkout@<40-char sha> # v7.0.1), first-partyactions/*included: a tag can be moved to other code, a commit cannot. Dependabot'sgithub-actionsecosystem understands this form and bumps both the SHA and the comment. To pin a new action, take the commit of its latest release (gh api repos/<owner>/<repo>/git/ref/tags/<tag>, thengh api repos/<owner>/<repo>/git/tags/<sha>when the tag is annotated). - Least-privilege token:
permissions: contents: readat the top of each workflow; only the jobs that publish get more (packages: write,contents: write,pages: write,id-token: write). persist-credentials: falseon everyactions/checkout: no job pushes with git (releases, Pages and registries go through their APIs), so the token is not left in.git/configwhere later steps or scripts could read it.timeout-minuteson every job (5 to 20 minutes, 60 for the multi-architecture image build of a release), instead of GitHub's six-hour default.- Concurrency: in
ci.yml, a new commit on a pull request cancels the run in progress for that pull request; pushes tomainanddevelopare never cancelled.docs.ymlqueues Pages deployments and never cancels one midway. - Untrusted inputs (such as the
workflow_dispatchversion ofrelease.yml) are passed throughenv:and validated before use, never interpolated into a script.
Check workflow changes locally with actionlint
(run actionlint at the repository root).
Continuous integration¶
ci.yml jobs, all on Node.js 26:
| Job | Steps |
|---|---|
| ๐งช Tests & Linting | npm ci, npm run lint, npm run build, npm run test:ci (coverage thresholds enforced), coverage summary and report artifact |
| ๐ Security Audit | npm audit --audit-level high |
| ๐ณ Docker Build Test | builds docker/Dockerfile, runs scripts/test-docker.sh (container start and health check, read-only and without capabilities) and scans the image with Trivy (fails on fixable high/critical vulnerabilities) |
| ๐ End-to-End Tests | with a Redis service: npm run test:e2e, then npx playwright install --with-deps chromium and npm run test:e2e:browser; Playwright traces uploaded on failure |
| โ Quality Gate | fails unless the four jobs above succeeded |
A pull request is merged only when the quality gate is green. See Tests & quality to run the same checks locally.
Documentation¶
docs.yml builds this site with mkdocs build --strict:
- on pull requests that touch
docs/,mkdocs.yml,config/*.ymlor the workflow, as a check; - on pushes to
developand on manual runs, it also deploys the site to GitHub Pages (https://ltoinel.github.io/amw/) with the official Pages actions; only the deploy job getspages: writeandid-token: write.
Before deploying, it runs the Jest suites with coverage and publishes
badges/coverage.json, a shields.io endpoint
document built from the line coverage in coverage/coverage-summary.json. The README
coverage badge reads it, so no third-party coverage service is needed. Test failures
never block the documentation: if no coverage summary is produced, the badge shows
unknown.