mirror of
https://github.com/Joulenap/joulenap.git
synced 2026-08-11 13:21:43 +02:00
158 lines
5.1 KiB
YAML
158 lines
5.1 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
|
|
# Cancel superseded runs on the same ref (e.g. rapid pushes to a PR).
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# The Node major selects the bundled npm (22 -> npm 10, 24 -> npm 11), and those npm
|
|
# majors disagree on whether a violated *optional* peer dependency is fatal. If the
|
|
# image builds the SPA on a different major than CI validates it on, `npm ci` can pass
|
|
# every job here and still fail `docker build` on the same lockfile. Fail on drift
|
|
# rather than trusting the "keep in sync" comment in the Dockerfile.
|
|
toolchain:
|
|
name: Toolchain (node parity)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Dockerfile node major matches ci.yml node-version
|
|
run: |
|
|
set -euo pipefail
|
|
# `|| true`: a no-match grep exits 1, which under `set -e` would abort the
|
|
# step before the explicit error below could explain what went wrong.
|
|
docker_major="$(grep -oP '^FROM node:\K[0-9]+' Dockerfile | sort -u || true)"
|
|
ci_major="$(grep -oP 'node-version:\s*"\K[0-9]+' .github/workflows/ci.yml | sort -u || true)"
|
|
|
|
echo "Dockerfile: ${docker_major:-<none found>}"
|
|
echo "ci.yml: ${ci_major:-<none found>}"
|
|
|
|
if [ -z "$docker_major" ] || [ -z "$ci_major" ]; then
|
|
echo "::error::could not parse a node major — did the FROM or node-version syntax change?"
|
|
exit 1
|
|
fi
|
|
if [ "$(printf '%s\n' "$docker_major" | wc -l)" -ne 1 ] \
|
|
|| [ "$(printf '%s\n' "$ci_major" | wc -l)" -ne 1 ]; then
|
|
echo "::error::more than one distinct node major declared; unify them first"
|
|
exit 1
|
|
fi
|
|
if [ "$docker_major" != "$ci_major" ]; then
|
|
echo "::error::node major drift — Dockerfile is on $docker_major, ci.yml on $ci_major"
|
|
exit 1
|
|
fi
|
|
echo "OK — both build the SPA on node $docker_major"
|
|
|
|
backend:
|
|
name: Backend (ruff + pytest)
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
python-version: ["3.12", "3.13"]
|
|
defaults:
|
|
run:
|
|
working-directory: backend
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v7
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: backend/pyproject.toml
|
|
|
|
- name: Install dependencies
|
|
run: pip install -e ".[dev]"
|
|
|
|
- name: Lint (ruff)
|
|
run: ruff check .
|
|
|
|
- name: Test (pytest)
|
|
run: pytest
|
|
|
|
frontend:
|
|
name: Frontend (typecheck + build)
|
|
runs-on: ubuntu-latest
|
|
defaults:
|
|
run:
|
|
working-directory: frontend
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: "24"
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build (tsc --noEmit && vite build)
|
|
run: npm run build
|
|
|
|
- name: Test (node --test)
|
|
run: npm test
|
|
|
|
# Dependency CVE scanning of what actually ships (Python + frontend packages). The OS
|
|
# layer of the image is covered separately by the image-scan job below.
|
|
security:
|
|
name: Security (dependency audit)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v7
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
cache-dependency-path: backend/pyproject.toml
|
|
|
|
# Audit runtime deps only (install without the dev extras) so build/test tooling
|
|
# doesn't muddy the shipped-surface signal.
|
|
- name: Audit Python dependencies (pip-audit)
|
|
working-directory: backend
|
|
run: |
|
|
pip install -e .
|
|
pip install pip-audit
|
|
pip-audit
|
|
|
|
- uses: actions/setup-node@v7
|
|
with:
|
|
node-version: "24"
|
|
cache: npm
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
# --omit=dev: the shipped bundle contains only prod deps; esbuild/vite (build-time,
|
|
# dev-server advisories) aren't served. Gate on high+ severity in that tree.
|
|
- name: Audit frontend production dependencies (npm audit)
|
|
working-directory: frontend
|
|
run: npm audit --omit=dev --audit-level=high
|
|
|
|
# Scans the built image, covering what the dependency audits can't: the OS packages in
|
|
# the python:3.12-slim base (glibc, openssl, …). ignore-unfixed so we only fail on CVEs
|
|
# that actually have a fix (a slim base always carries a few unpatched ones); the remedy
|
|
# is usually a rebuild or bumping the base pin.
|
|
image-scan:
|
|
name: Security (image scan)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Build image
|
|
run: docker build -t joulenap:ci .
|
|
|
|
- name: Scan image (Trivy)
|
|
uses: aquasecurity/trivy-action@v0.36.0
|
|
with:
|
|
image-ref: joulenap:ci
|
|
severity: HIGH,CRITICAL
|
|
ignore-unfixed: true
|
|
exit-code: "1"
|