-
Notifications
You must be signed in to change notification settings - Fork 0
140 lines (122 loc) · 5.4 KB
/
ci.yml
File metadata and controls
140 lines (122 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# CI test gate — run the test suites on every pull request and on push to
# `main`, so a red suite can't merge.
#
# WHY: until now the lint/test pipeline was local-only — `scripts/lint.sh`
# + the pre-commit hooks, run on a contributor's laptop before the Merger
# pulled a PR (the deliberate "no CI in pre-alpha" posture). With many
# agents merging in parallel and only CodeQL gating server-side, test
# regressions slipped onto `main` green (e.g. #401 broke
# `tests/test_logentry.py`, undetected until a later full local run —
# #451). This enforces the suites server-side. Resolves the core of #452
# (run the test suites in CI); the no-CI revisit is recorded in
# `SECURITY.md` §8 / `docs/agents/decisions.md` / OQ-A-001.
#
# SCOPE: the backend job runs the Python lint gate (ruff check + ruff
# format --check + mypy + bandit) and then `pytest`. The frontend job
# runs the full `pnpm` gate — typecheck + lint + test + build. The Python
# lint stack was collapsed onto a single Ruff-based chain in #651/#652
# (black/isort/flake8 removed), which made the gate satisfiable on a
# clean tree and let it be wired in here.
#
# SECURITY POSTURE:
# - Least-privilege: top-level `contents: read`; no job needs write.
# - All third-party actions are pinned to a full commit SHA (a tag can
# be moved, a SHA cannot) — consistent with codeql.yml / publish.yml
# and the supply-chain hardening in #331.
# - This is a *gate*, not a publisher: it has no access to PyPI, no
# `id-token`, and no stored secrets.
#
# NOTE: making these checks *required* (branch protection) is a separate
# owner action in repo Settings → Branches — see #452 / #331.
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
# A newer push to the same ref supersedes an in-flight run — don't burn
# minutes finishing a run whose commit is already stale.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
backend:
name: Backend (pytest py${{ matrix.python }} / Django ${{ matrix.django }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Django 4.2 LTS is supported through April 2026 (#622). Add it
# alongside 5.x and 6.0 so the package keeps working for the
# majority of installed Django by deployment count.
python: ["3.12"]
django: ["4.2", "5.2"]
# 4.2 added Python 3.13 support in 4.2.16 but pip may resolve to
# an earlier 4.2.x; skip 3.13 + 4.2 conservatively. (We only
# exercise py3.12 here today; the matrix is set up for an easy
# future expansion.)
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: ${{ matrix.python }}
- name: Install Poetry
# Poetry is pinned to the version that generated poetry.lock (see
# its header). `poetry install` compares the lock's content-hash
# against the one it computes from pyproject; a different Poetry
# can compute it differently and reject an otherwise-valid lock.
# Bump this in lockstep whenever the lock is regenerated.
run: pipx install poetry==2.1.4
- name: Install dependencies (locked)
run: poetry install --no-interaction
- name: Pin Django to the matrix version
run: poetry run pip install "django~=${{ matrix.django }}.0"
# Python lint gate (#651/#652): a single Ruff-based stack —
# ruff check (lint, incl. `I` import order) + ruff format --check +
# mypy (strict subset on the package) + bandit (security). Black,
# standalone isort, and flake8 were removed. This is the same gate
# scripts/lint.sh runs locally.
- name: Lint (ruff + mypy + bandit)
run: |
poetry run ruff check django_admin_react tests
poetry run ruff format --check django_admin_react tests
poetry run mypy django_admin_react
poetry run bandit -r django_admin_react -c pyproject.toml -q
# pytest with coverage (per pyproject `addopts`), including
# tests/test_security.py. `filterwarnings = ["error"]` means a new
# warning fails the run.
- name: Tests (pytest)
run: poetry run pytest
frontend:
name: Frontend (typecheck + lint + test + build)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up pnpm
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9
- name: Set up Node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile
- name: Typecheck (tsc --noEmit, every package)
run: pnpm -r typecheck
- name: Lint (eslint --max-warnings 0 + stylelint + dark-mode coverage)
run: pnpm lint
- name: Unit tests (vitest)
run: pnpm test
- name: Build (Vite bundle, every package)
run: pnpm -r build