# 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
      # Catalogue-vs-code drift gate per ADR-0025 §"Confirmation".
      # Cheap (parses ~workspace .ts files via the TypeScript
      # compiler API, ~1s on a warm cache) so it rides the same
      # `check` job rather than spinning up a new one. The
      # script's own unit tests run first — if they fail the gate
      # itself is broken and the actual catalogue check would be
      # noise.
      - run: pnpm ci:catalogue-drift:test
      - run: pnpm ci:catalogue-drift

  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)."
