204727b1a7
CI / commits (pull_request) Successful in 3m57s
CI / scan (pull_request) Successful in 4m20s
CI / check (pull_request) Successful in 6m13s
CI / a11y (pull_request) Successful in 2m53s
Docs site / build (pull_request) Successful in 4m0s
CI / perf (pull_request) Successful in 7m41s
The NODE_OPTIONS=--use-system-ca workaround merged in #199 did not clear the failure — the same TLS-chain rejection still trips the runner. Either the runner's OS CA store also lacks the missing intermediate, or `--use-system-ca` does not propagate down to the node-fetch@2 used by node-pre-gyp. Without runner shell access the exact reason is not worth investigating. Different angle: grpc-tools is never invoked in CI. The protoc binary it downloads is only used by `pnpm run grpc:codegen`, which runs on a developer's machine when a proto file changes. The generated TypeScript stubs in apps/portal-bff/src/grpc/gen/ are committed (per ADR-0024 §"Sub-decision 3 — vendored protos"), so CI only needs to type-check them — protoc is dead weight there. Move grpc-tools from devDependencies to optionalDependencies. Per pnpm semantics, an optional dependency whose install (including postinstall) fails is logged as a warning and the overall install continues. On the runner: postinstall still fails on TLS, pnpm emits a warning, ci:check proceeds. On a developer machine: TLS validates, postinstall runs, protoc binary lands, `pnpm grpc:codegen` works exactly as before. No dev-side friction; no workflow env vars to maintain. Reverts the workflow `env:` blocks added in #199 — they did not help and adding noise to the workflow YAML for a non-working workaround is worse than removing it.
215 lines
9.2 KiB
YAML
215 lines
9.2 KiB
YAML
# 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@v6
|
|
with:
|
|
fetch-depth: 0
|
|
# Derive NX_BASE / NX_HEAD for `nx affected`. Replaces
|
|
# nrwl/nx-set-shas@v4, which is GitHub-only (it queries the GitHub
|
|
# API to find the last successful workflow run, returning 404 on
|
|
# Gitea). HEAD~1 is a reasonable approximation for push events on
|
|
# a squash-merge trunk; pull_request uses the merge-base with the
|
|
# target branch.
|
|
- name: Derive Nx affected base and head
|
|
shell: bash
|
|
run: |
|
|
# `actions/checkout@v4` with fetch-depth: 0 already pulls every
|
|
# branch and tag, so origin/<base_ref> is present locally — no
|
|
# extra `git fetch` is needed (and `--depth=0` is invalid: git
|
|
# requires a positive integer).
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
echo "NX_BASE=$(git merge-base HEAD origin/${{ github.base_ref }})" >> "$GITHUB_ENV"
|
|
else
|
|
echo "NX_BASE=HEAD~1" >> "$GITHUB_ENV"
|
|
fi
|
|
echo "NX_HEAD=HEAD" >> "$GITHUB_ENV"
|
|
- uses: pnpm/action-setup@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm ci:check
|
|
|
|
scan:
|
|
runs-on: [self-hosted, on-prem]
|
|
# Step ordering matters here: Trivy and gitleaks BOTH run before
|
|
# `pnpm install`. Reason: gitleaks scans the working tree
|
|
# (`--no-git --source .`), and after install, `node_modules/`
|
|
# and `.pnpm-store/` are full of upstream packages whose READMEs
|
|
# and test fixtures contain demo RSA keys / fake API tokens —
|
|
# gitleaks then false-positives on them by the hundreds (caught
|
|
# the hard way: 381 hits on the first run). Trivy reads
|
|
# `pnpm-lock.yaml` for its vuln scan, not `node_modules`, so it
|
|
# also doesn't need install. `pnpm ci:audit` does the same — it
|
|
# queries the advisory DB against the lockfile.
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: pnpm/action-setup@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
# Dependency vulnerability scan. Trivy is a Go binary, not an npm
|
|
# package, so it cannot live in package.json scripts as cleanly
|
|
# as audit/lint do.
|
|
#
|
|
# We deliberately avoid `aquasecurity/trivy-action`. On cache
|
|
# miss the action falls back to `git clone github.com/aquasecurity/
|
|
# trivy` to fetch its install script, using `actions/checkout`
|
|
# which defaults `with.token` to `${{ github.token }}` (Gitea's
|
|
# auto-token, useless for github.com). The clone hits the
|
|
# anonymous github.com rate limit and fails with "could not
|
|
# read Username". Passing GITHUB_TOKEN as an env var doesn't
|
|
# help — actions/checkout reads it from `inputs.token`, not env.
|
|
#
|
|
# Direct curl + tar is simpler, predictable, and gives us an
|
|
# explicit version pin instead of `@master`. GITHUBCOM_TOKEN is
|
|
# passed to handle the github.com rate limit on the release
|
|
# download in the worst case (release artefacts are usually
|
|
# unmetered, but auth is free insurance).
|
|
- name: Install Trivy
|
|
env:
|
|
# renovate: datasource=github-releases depName=aquasecurity/trivy
|
|
TRIVY_VERSION: '0.70.0'
|
|
GITHUB_TOKEN: ${{ secrets.GITHUBCOM_TOKEN }}
|
|
run: |
|
|
curl -sfL \
|
|
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
-o /tmp/trivy.tar.gz \
|
|
"https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
|
|
tar -xzf /tmp/trivy.tar.gz -C /usr/local/bin trivy
|
|
trivy --version
|
|
- name: Run Trivy
|
|
# `--scanners vuln`: limit Trivy to vulnerability scanning. Its
|
|
# secret scanner false-positives on demo RSA keys embedded in
|
|
# the README/fixtures of cryptographic npm packages (which
|
|
# land under .pnpm-store/), and we already have gitleaks below
|
|
# as the dedicated secret-scan gate. Trivy's intent in this
|
|
# job, per ADR-0015, was always "dependency vulnerability
|
|
# scan" — restoring that scope.
|
|
run: |
|
|
trivy fs \
|
|
--scanners vuln \
|
|
--ignore-unfixed \
|
|
--skip-dirs node_modules \
|
|
--exit-code 1 \
|
|
--severity CRITICAL,HIGH \
|
|
.
|
|
# Secret scan. Same install pattern as Trivy: gitleaks is a Go
|
|
# binary, and the official `gitleaks/gitleaks-action@v2` wrapper
|
|
# is now paywalled for organisations (a GITLEAKS_LICENSE secret
|
|
# from gitleaks.io is required, otherwise the action errors out
|
|
# with `🛑 missing gitleaks license`). The binary itself stays
|
|
# MIT-licensed and free — installing it directly bypasses the
|
|
# wrapper and gives us version pinning for free.
|
|
- name: Install gitleaks
|
|
env:
|
|
# renovate: datasource=github-releases depName=gitleaks/gitleaks
|
|
GITLEAKS_VERSION: '8.30.1'
|
|
GITHUB_TOKEN: ${{ secrets.GITHUBCOM_TOKEN }}
|
|
run: |
|
|
curl -sfL \
|
|
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
-o /tmp/gitleaks.tar.gz \
|
|
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
|
|
tar -xzf /tmp/gitleaks.tar.gz -C /usr/local/bin gitleaks
|
|
gitleaks version
|
|
- name: Run gitleaks
|
|
# `--no-git --source .` scans the working tree only. The scan
|
|
# job uses a shallow checkout, so a git-history scan would not
|
|
# see beyond HEAD anyway; the weekly security-scheduled
|
|
# workflow does the deep history scan with a full clone.
|
|
# `--redact` masks any matched secret in the log output so we
|
|
# do not leak it via the CI logs themselves.
|
|
run: |
|
|
gitleaks detect \
|
|
--no-git \
|
|
--source . \
|
|
--redact \
|
|
--exit-code 1
|
|
# npm-advisory check (against pnpm-lock.yaml). Run last so
|
|
# `pnpm install` does not pollute the working tree before the
|
|
# scanners above.
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm ci:audit
|
|
|
|
commits:
|
|
# PRs opened by Renovate (apf-portal-bot) carry commit messages
|
|
# generated from a vetted Conventional-Commits template — running
|
|
# commitlint on them is tautological. Per ADR-0017 amendment.
|
|
if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'apf-portal-bot'
|
|
runs-on: [self-hosted, on-prem]
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
- uses: pnpm/action-setup@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: COMMIT_LINT_FROM=origin/main pnpm ci:commits
|
|
|
|
perf:
|
|
# Skip the Lighthouse run on PRs opened by Renovate (apf-portal-bot):
|
|
# the per-PR perf signal on a dep bump is essentially zero (no
|
|
# routes yet, bundle is the static placeholder), and the Lighthouse
|
|
# round-trip burns several minutes per PR. Push events on `main`
|
|
# still run perf — we catch regressions immediately post-merge,
|
|
# not pre-merge. Per ADR-0017 amendment.
|
|
if: github.event_name != 'pull_request' || github.event.pull_request.user.login != 'apf-portal-bot'
|
|
runs-on: [self-hosted, on-prem]
|
|
# Lighthouse CI drives a real Chrome instance; the default act runner
|
|
# image (catthehacker/ubuntu:act-22.04) ships without one. The :full
|
|
# variant adds Chrome, Firefox, and the GUI-test toolchain — pinned
|
|
# to the same Ubuntu 22.04 base as the default labels for parity.
|
|
container:
|
|
image: catthehacker/ubuntu:full-22.04
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: pnpm/action-setup@v6
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'pnpm'
|
|
- run: pnpm install --frozen-lockfile
|
|
- run: pnpm ci:perf
|
|
- uses: actions/upload-artifact@v7
|
|
if: always()
|
|
with:
|
|
name: lighthouseci-report
|
|
path: .lighthouseci/
|
|
retention-days: 30
|
|
|
|
a11y:
|
|
runs-on: [self-hosted, on-prem]
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
- uses: pnpm/action-setup@v6
|
|
- uses: actions/setup-node@v6
|
|
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)."
|