chore: scaffold Gitea Actions pipelines per ADR-0015 / ADR-0017
CI / commits (push) Has been skipped
CI / check (push) Failing after 36s
CI / a11y (push) Failing after 2s
CI / perf (push) Failing after 38s
CI / scan (push) Failing after 2m15s

Wire the CI/CD pipeline scaffolding. Implements the level-2 (Gitea
Actions) part of ADR-0015 with the thin-YAML pattern, plus the perf
gate from ADR-0017. The level-1 decisions (gates list, branch model,
secrets policy) are unchanged.

Files:
- .nvmrc pins Node 24 (latest LTS major) for actions/setup-node.
- package.json gains four ci:* scripts that the workflows call:
  - ci:check  - 'nx affected -t format:check lint test build'
  - ci:audit  - 'pnpm audit --audit-level=moderate'
  - ci:commits- 'commitlint --from $COMMIT_LINT_FROM --to HEAD'
  - ci:perf   - 'nx build portal-shell --configuration=production
                 && lhci autorun --config=./lighthouserc.js'
  All four runnable locally; CI workflows are thin wrappers.
- @lhci/cli added as a dev dependency for ci:perf.
- lighthouserc.js encodes the Core Web Vitals thresholds from
  ADR-0017 (LCP <= 2500ms, CLS <= 0.1, TBT <= 200ms, server
  response <= 800ms, Performance >= 0.9). v1 measures only the
  static-served portal-shell bundle (Nx Welcome placeholder); the
  critical-routes list expands as real screens land.
- .gitea/workflows/ci.yml runs five jobs on PR + push to main:
  check, scan (audit + Trivy + gitleaks), commits (PR-only), perf,
  a11y. The a11y job is a placeholder that no-ops with a clear
  message; it wires up for real with the first Playwright e2e suite
  (ADR-0016). All gates are blocking - branch protection on main
  will require all five jobs green.
- .gitea/workflows/security-scheduled.yml runs weekly (Mon 04:00
  UTC) for full-tree Trivy + gitleaks (no severity filter, no
  skip-dirs - broader than per-PR) plus a Lighthouse run against
  the prod URL when vars.LHCI_PROD_URL is set.

Slight deviation from ADR-0015 §'Level 2': Trivy and gitleaks are
binaries (Go) and don't have clean npm wrappers, so they are
invoked through their official Gitea-Actions-compatible actions
inside the YAML rather than via 'pnpm ci:scan'. The high-level
decision (gates: audit + secret-scan + dep-scan) is unchanged; the
script/action boundary is shifted by one tool. Documented here for
traceability; no ADR amendment needed.

Operational TODOs (not blocking the scaffold):
- pnpm audit currently reports 6 moderate transitive vulnerabilities
  (ajv, brace-expansion, yaml, @hono/node-server, follow-redirects,
  uuid) in deep deps of Nx/Angular plugins. CI will fail on this
  gate until upstream updates land or Renovate bumps; expected and
  documented.
- act_runner self-hosted instances are not yet registered against
  the Gitea organisation; the workflows reference [self-hosted,
  on-prem] runner labels per ADR-0015. CI will not actually execute
  until the runners are up - that's an infra task.
- Branch protection rules on main (require all five jobs green) are
  configured in Gitea UI, not in this commit.
- Lighthouse-prod scheduled job runs only when vars.LHCI_PROD_URL
  is set - skipped silently otherwise. To be configured once a prod
  environment exists.
This commit is contained in:
Julien Gautier
2026-04-30 19:48:20 +02:00
parent 25a89bc8b0
commit be7187d5f2
6 changed files with 1862 additions and 4 deletions
+103
View File
@@ -0,0 +1,103 @@
# Per ADR-0015 (CI/CD on Gitea Actions).
# Thin YAML — orchestration lives in package.json scripts (ci:check,
# ci:audit, ci:commits, ci:perf) and Nx targets. Any change to gate
# behaviour belongs in those scripts, not in this file.
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: nrwl/nx-set-shas@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm ci:check
scan:
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm ci:audit
# Dependency vulnerability scan (binary tool, invoked via official
# action — Trivy is a Go binary, not an npm package, so it cannot
# live in package.json scripts as cleanly as audit/lint do).
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
ignore-unfixed: true
skip-dirs: node_modules
exit-code: '1'
severity: 'CRITICAL,HIGH'
# Secret scan, same reasoning (gitleaks is a Go binary).
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
commits:
if: github.event_name == 'pull_request'
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: COMMIT_LINT_FROM=origin/main pnpm ci:commits
perf:
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm ci:perf
- uses: actions/upload-artifact@v4
if: always()
with:
name: lighthouseci-report
path: .lighthouseci/
retention-days: 30
a11y:
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
# Placeholder until the e2e a11y suite (axe-core via Playwright,
# per ADR-0016) is wired with the first real screens. The job
# exists so branch protection can require it from day one - it
# currently no-ops with a clear message.
- run: echo "a11y gate placeholder - axe-core via Playwright wires up with the first real screens (ADR-0016)."
+56
View File
@@ -0,0 +1,56 @@
# Per ADR-0015 (CI/CD on Gitea Actions). Weekly full-tree security
# scans plus a Lighthouse run against the production environment when
# its URL is configured. Complements the per-PR ci.yml workflow with
# broader / longer-running checks that don't fit the per-PR budget.
name: Security and perf — scheduled
on:
schedule:
# Mondays, 04:00 UTC — outside business hours; before the week starts.
- cron: '0 4 * * 1'
workflow_dispatch:
jobs:
full-tree-scan:
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm audit
# Full-tree Trivy (no skip-dirs, no severity filter — the per-PR
# gate filters by severity for speed; this run wants the full
# surface for the security feed).
- uses: aquasecurity/trivy-action@master
with:
scan-type: fs
ignore-unfixed: true
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
lighthouse-prod:
# Skipped silently if the prod URL hasn't been configured yet.
if: vars.LHCI_PROD_URL != ''
runs-on: [self-hosted, on-prem]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm exec lhci collect --url=${{ vars.LHCI_PROD_URL }} --numberOfRuns=3
- run: pnpm exec lhci assert --config=./lighthouserc.js
- uses: actions/upload-artifact@v4
if: always()
with:
name: lighthouseci-prod-report
path: .lighthouseci/
retention-days: 90
+1
View File
@@ -0,0 +1 @@
24
+50
View File
@@ -0,0 +1,50 @@
/**
* Lighthouse CI configuration — per ADR-0017 (Performance budgets).
*
* Thresholds match the Google "Good" Core Web Vitals values plus the
* Lighthouse Performance score floor we set as the project bar.
*
* Critical-routes list grows as features land. v1 only checks the
* static-served portal-shell bundle (which today shows the Nx Welcome
* placeholder); meaningful per-route assertions land alongside the
* features that introduce those routes.
*/
module.exports = {
ci: {
collect: {
// Serve the production bundle statically (no BFF needed for v1
// measurements). When real auth / routes exist, the URL list
// expands to the critical-routes list documented in ADR-0017.
startServerCommand: 'pnpm nx run portal-shell:serve-static',
startServerReadyPattern: 'Available on:',
url: ['http://localhost:4200/'],
numberOfRuns: 3,
settings: {
// Slow-4G profile so scores are stable across CI runners and
// comparable to industry public benchmarks.
preset: 'desktop',
},
},
assert: {
assertions: {
// Core Web Vitals (Google "Good"), per ADR-0017
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'total-blocking-time': ['error', { maxNumericValue: 200 }],
'server-response-time': ['error', { maxNumericValue: 800 }],
// Aggregate score — our gate is "Performance >= 90"
'categories:performance': ['error', { minScore: 0.9 }],
// a11y is enforced by the dedicated a11y gate (axe-core,
// ADR-0016) — keep Lighthouse a11y as a soft signal here.
'categories:accessibility': ['warn', { minScore: 0.9 }],
// Best practices and SEO are advisory in this gate.
'categories:best-practices': ['warn', { minScore: 0.9 }],
'categories:seo': 'off',
},
},
upload: {
target: 'filesystem',
outputDir: './.lighthouseci',
},
},
};
+6 -1
View File
@@ -3,7 +3,11 @@
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"prepare": "husky" "prepare": "husky",
"ci:check": "pnpm exec nx affected -t format:check lint test build",
"ci:audit": "pnpm audit --audit-level=moderate",
"ci:commits": "pnpm exec commitlint --from ${COMMIT_LINT_FROM:-origin/main} --to HEAD --verbose",
"ci:perf": "pnpm exec nx build portal-shell --configuration=production && pnpm exec lhci autorun --config=./lighthouserc.js"
}, },
"private": true, "private": true,
"lint-staged": { "lint-staged": {
@@ -23,6 +27,7 @@
"@commitlint/cli": "^20.5.3", "@commitlint/cli": "^20.5.3",
"@commitlint/config-conventional": "^20.5.3", "@commitlint/config-conventional": "^20.5.3",
"@eslint/js": "^9.8.0", "@eslint/js": "^9.8.0",
"@lhci/cli": "^0.15.1",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.0", "@nestjs/testing": "^11.0.0",
"@nx/angular": "^22.7.1", "@nx/angular": "^22.7.1",
+1646 -3
View File
File diff suppressed because it is too large Load Diff