From c24795f1d66e233436958c09a3a5680b6143f442 Mon Sep 17 00:00:00 2001 From: Julien Gautier Date: Wed, 27 May 2026 17:53:39 +0200 Subject: [PATCH] ci(gitlab): land .gitlab-ci.yml alongside gitea workflow (ADR-0028 phase 2) (#241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Bump the existing `tmp` security override from `>=0.2.4` to `>=0.2.6` to cover [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — a path-traversal vulnerability via unsanitized prefix/postfix in `tmp@<0.2.6`. Surfaced by `pnpm ci:audit` on the next CI run; both Gitea and the in-flight GitLab Phase 2 pipeline fail without this. ## Why this isn't an upstream upgrade `tmp` is a transitive dependency. The two consuming paths today: | Path | Latest available | | --- | --- | | `.>nx>tmp` | `nx@22.7.4` still declares `tmp@^0.2.4` | | `.>@lhci/cli>tmp` | `@lhci/cli@0.15.1` (latest) still declares `tmp@^0.1.0` | Upgrading `nx` or `@lhci/cli` does not move `tmp` to 0.2.6. The pre-existing `pnpm.overrides` entry was already pinning `tmp@<0.2.4` → `>=0.2.4` against the prior advisory; this PR just widens the lower bound to match the new one. Same pattern, one line. ## What lands | File | Change | | --- | --- | | `package.json` | `"tmp@<0.2.4": ">=0.2.4"` → `"tmp@<0.2.6": ">=0.2.6"` (one line). | | `pnpm-lock.yaml` | `tmp@0.2.4` → `tmp@0.2.6` resolved; `@scalar/nestjs-api-reference@1.1.16 → 1.1.19` picked up as a natural transitive resolution during `pnpm install` (no semver-major, both are within the existing `^1.1.14` range and match Renovate's "patch + auto-merge" policy). | ## Risk Patch bump (0.2.4 → 0.2.6) on a tiny library with a stable public API since v0.2.0. The release notes for 0.2.5 and 0.2.6 are sanitization-only fixes — no API surface change. Risk of regression in nx / lhci consumers: negligible. ## Test plan - [x] `pnpm audit --audit-level=moderate` returns `No known vulnerabilities found` (exit 0). - [x] `pnpm why tmp` shows `tmp@0.2.6` as the single resolved version (no duplicate). - [ ] CI green on Gitea (`check` + `scan` jobs). - [ ] Once merged, rebase `ci/gitlab-pipeline-phase2` on top and retry the GitLab `audit` job — expected green. ## Related - [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) — the advisory. - Unblocks [ADR-0028](../docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) Phase 2 pipeline parity validation (see `ci/gitlab-pipeline-phase2` branch). --------- Co-authored-by: Julien Gautier Reviewed-on: https://git.unespace.com/julien/apf_portal/pulls/241 --- .gitlab-ci.yml | 149 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..f193c2c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,149 @@ +# Per ADR-0028 (CI/CD migration from Gitea Actions to GitLab CE). +# Thin YAML — orchestration lives in package.json scripts (ci:check, +# ci:catalogue-drift, ci:audit, ci:commits, ci:perf, ci:gzip-budgets) +# and Nx targets. Any change to gate behaviour belongs in those +# scripts, not in this file — see ADR-0015 §"Thin pipeline YAML…" +# (principle preserved by ADR-0028 at the migration boundary). +# +# Phase 2 of ADR-0028: ships alongside .gitea/workflows/ci.yml. Both +# pipelines must remain in parity until cutover (Phase 3), at which +# point the Gitea workflow gets deleted. + +include: + # GitLab built-in scanners — replace the manual Trivy + gitleaks + # install in .gitea/workflows/ci.yml. Both add their own jobs to + # the pipeline; their default stage is `test`, which is mapped in + # `stages:` below. + - template: Jobs/SAST.gitlab-ci.yml + - template: Jobs/Secret-Detection.gitlab-ci.yml + +stages: + - check + - test + - commits + - perf + - a11y + +workflow: + rules: + # MR pipelines — covers GitLab MR review flow once it lands. + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + # Direct pushes — covers post-merge runs on main today, and Phase 2 + # parity testing on feature branches mirrored from Gitea. To be + # tightened to `$CI_COMMIT_BRANCH == "main"` at the Phase 3 cutover + # once GitLab is the source of truth. + - if: $CI_PIPELINE_SOURCE == "push" + +# Shared shape for jobs that need Node + pnpm. Hidden (`.` prefix) so +# GitLab does not try to execute it. Inheriting jobs `extends: .node-job`. +.node-job: + image: node:24-bookworm + cache: + key: + files: + - pnpm-lock.yaml + paths: + - .pnpm-store/ + before_script: + - corepack enable + - corepack prepare pnpm@10.33.4 --activate + - pnpm config set store-dir "$CI_PROJECT_DIR/.pnpm-store" + +check: + extends: .node-job + stage: check + # `nx affected` needs a base SHA to diff against. Mirrors the + # equivalent step in .gitea/workflows/ci.yml — same logic, GitLab + # variable names. Replaces nrwl/nx-set-shas (GitHub-only). + script: + - | + if [ "$CI_PIPELINE_SOURCE" = "merge_request_event" ]; then + git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --depth=100 + export NX_BASE=$(git merge-base HEAD "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME") + else + export NX_BASE="HEAD~1" + fi + export NX_HEAD="HEAD" + - pnpm install --frozen-lockfile + - pnpm ci:check + # Catalogue-vs-code drift gate per ADR-0025 §"Confirmation". The + # script's own unit tests run first — if they fail the gate itself + # is broken and the actual catalogue check would be noise. + - pnpm ci:catalogue-drift:test + - pnpm ci:catalogue-drift + +audit: + extends: .node-job + stage: test + # npm-advisory check against pnpm-lock.yaml. Trivy's dependency-vuln + # role from the Gitea workflow is now covered by the SAST include + # (which includes Dependency Scanning analyzers); `pnpm audit` stays + # in-pipeline as defense in depth against the npm advisory DB + # specifically. + script: + - pnpm install --frozen-lockfile + - pnpm ci:audit + +commits: + extends: .node-job + stage: commits + # MRs opened by Renovate (apf-portal-bot) carry commit messages from + # a vetted Conventional-Commits template — running commitlint on + # them is tautological. Per ADR-0017 amendment. + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $GITLAB_USER_LOGIN != "apf-portal-bot" + variables: + # Default `GIT_DEPTH: 20` is too shallow to diff against + # origin/main on long-running branches. Full clone so commitlint + # can walk all the way back. + GIT_DEPTH: 0 + script: + - git fetch origin main + - pnpm install --frozen-lockfile + - COMMIT_LINT_FROM=origin/main pnpm ci:commits + +perf: + extends: .node-job + stage: perf + # Lighthouse CI drives a real Chrome instance. Playwright's image is + # the canonical "Node + browsers" CI image and is already on our + # roadmap for the ADR-0016 axe-core e2e — single image covers both + # uses once the a11y suite lands. + image: mcr.microsoft.com/playwright:v1.55.0-jammy + # Skip on Renovate MRs (same rationale as commits + the perf signal + # on a dep bump is essentially zero). Push events on `main` still + # run perf — we catch regressions immediately post-merge, not + # pre-merge. Per ADR-0017 amendment. + rules: + - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main" + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $GITLAB_USER_LOGIN != "apf-portal-bot" + script: + - pnpm install --frozen-lockfile + - pnpm ci:perf + artifacts: + when: always + paths: + - .lighthouseci/ + expire_in: 30 days + +a11y: + extends: .node-job + stage: a11y + # 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 — currently no-ops + # with a clear message. + script: + - echo "a11y gate placeholder - axe-core via Playwright wires up with the first real screens (ADR-0016)." + +# Place SAST + Secret Detection in the `test` stage to mirror the +# Gitea workflow's grouping (where Trivy + gitleaks + audit all +# lived in the `scan` job). Override does not change behaviour — +# only stage placement. The included templates set sensible defaults +# (run on default branch + MRs, full repo on default branch / diff +# on MRs); revisited in Phase 3 if pipeline duration becomes a pain. +sast: + stage: test + +secret_detection: + stage: test