# CluPilot CI — runs on Gitea Actions (GitHub-Actions syntax, own runner).
#
# Deliberately not mirrored to GitHub: this repository describes how our
# infrastructure is provisioned, and the same workflow runs at home. If we ever
# move, this file goes along unchanged.
name: tests

on:
  push:
    branches: [main, 'feat/**']
  pull_request:

jobs:
  pest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4.2.2

      - name: Set up PHP
        # Pinned: the floating v2 tag now requires node24, which the runner
        # matching Gitea 1.20 cannot execute. 2.34.1 is the newest that still
        # declares node20.
        uses: shivammathur/setup-php@2.34.1
        with:
          php-version: '8.4'
          extensions: mbstring, pdo_sqlite, sodium, redis, bcmath, gd, zip
          coverage: none

      - name: Install PHP dependencies
        # Source clones, not dist archives: dist downloads go through GitHub's
        # API, which throttles anonymous callers and left a half-installed
        # vendor/ behind — the tests then failed with 500s that had nothing to
        # do with the code. Cloning is slower and does not need anyone's quota.
        # Swap back to --prefer-dist once a GitHub token is configured.
        run: composer install --no-interaction --prefer-source --no-progress

      - name: Prepare environment
        run: |
          cp .env.example .env
          php artisan key:generate

      - name: Tests
        # phpunit.xml pins its own sqlite/array drivers, so no services needed.
        run: ./vendor/bin/pest --colors=always

  assets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4.2.2

      - uses: actions/setup-node@v4.1.0
        with:
          node-version: '22'

      - name: Install JS dependencies
        run: npm ci --no-fund --no-audit

      # A build failure here is what used to surface as "the design is broken"
      # only after deploying — catch it before it ships.
      - name: Build assets
        run: npm run build

  release:
    # Only a green run produces the tag the console offers as an update.
    needs: [pest, assets]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4.2.2
        with:
          fetch-depth: 0

      - name: Tag this commit as tested
        run: |
          tag="tested-$(date -u +%Y%m%d-%H%M)-$(git rev-parse --short HEAD)"
          git tag "$tag"
          git push origin "$tag"

      # A release is cut by editing VERSION and merging it — nothing else. The
      # tag is created here, only after the suite is green, and only if it does
      # not already exist. Never moved: servers are pinned to these, and a tag
      # that changes underneath them means two machines claiming one version.
      - name: Tag the release when VERSION changed
        run: |
          version="$(tr -d ' \n\r' < VERSION)"
          # Anchored, because a `case` glob does not anchor: `[0-9]*.[0-9]*`
          # happily accepts 1x.2y.3garbage and would tag it.
          printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' \
            || { echo "VERSION is not MAJOR.MINOR.PATCH: '$version'" >&2; exit 1; }

          # Only the commit that RAISED the version is the release. Without
          # this, any later green push finds the tag missing and claims it —
          # and the tag would then name code the bump never described. Runs
          # overlap and finish out of order; that is enough for it to happen.
          previous="$(git show 'HEAD^:VERSION' 2>/dev/null | tr -d ' \n\r' || true)"
          if [ "$previous" = "$version" ]; then
            echo "::notice::VERSION is unchanged ($version) — nothing to release."
            exit 0
          fi

          tag="v${version}"
          git fetch --tags --force origin

          if existing="$(git rev-parse -q --verify "refs/tags/${tag}^{commit}")"; then
            # Already released. The invariant is that the tag points at the
            # commit VERSION was raised in — if a later commit still carries
            # that number, that is fine, but the tag must not be re-pointed.
            if [ "$existing" != "$(git rev-parse HEAD)" ]; then
              echo "::notice::${tag} already exists at ${existing}; leaving it alone."
            fi
            exit 0
          fi

          git config user.name  "CluPilot CI"
          git config user.email "ci@clupilot.local"
          git tag -a "$tag" -m "CluPilot ${version}"
          git push origin "$tag"
          echo "::notice::Released ${tag}"
