# 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 via chrome-launcher, which # probes the PATH / CHROME_PATH for a standard Chrome/Chromium # binary. The Playwright image bundles Chromium under /ms-playwright # with a non-standard name chrome-launcher cannot discover, so we # stay on the base Node image and apt-install Debian's `chromium` # (lands at /usr/bin/chromium). The future ADR-0016 axe-core e2e job # — which drives Playwright directly — will use the Playwright image # instead: the two tools want different things, so they get # different images rather than one image that serves both poorly. image: node:24-bookworm variables: CHROME_PATH: /usr/bin/chromium before_script: - !reference [.node-job, before_script] - apt-get update -qq && apt-get install -y -qq --no-install-recommends chromium # 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