# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
# See: https://github.com/JulianCataldo/gh-actions

# For matrix setup:
# https://github.com/withastro/astro/blob/main/.github/workflows/ci.yml
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow

name: CI / Release

on:
  workflow_dispatch:
  push:
    paths-ignore:
      - .github/**
      - '!.github/workflows/release.yaml'
      - '**/*.md'
    branches:
      - '([0-9])?(.{+([0-9]),x}).x'
      - main
      - next
      - next-major
      - alpha
      - beta
      - 'feat/*'
      - 'fix/*'
      # - to-integrate
      # - to-integrate-next

permissions:
  contents: read # for checkout
  # TODO: Trusted Publishers
  # id-token: write # Required for OIDC

jobs:
  release:
    name: CI / Release

    timeout-minutes: 30

    permissions:
      contents: write # to be able to publish a GitHub release
      issues: write # to be able to comment on released issues
      pull-requests: write # to be able to comment on released pull requests
      id-token: write # to enable use of OIDC for npm provenance

    runs-on: ubuntu-latest
    # TODO:
    # runs-on: ${{ matrix.os }}
    # timeout-minutes: 25
    # # needs: build
    # strategy:
    #   matrix:
    #     OS: [ubuntu-latest]
    #     NODE_VERSION: [18, 20]
    #     include:
    #       - os: macos-14
    #         NODE_VERSION: 18
    #       - os: windows-latest
    #         NODE_VERSION: 18
    #   fail-fast: false
    # env:
    #   NODE_VERSION: ${{ matrix.NODE_VERSION }}

    steps:
      # MARK: Setup GH Action

      - name: 'Harden Runner'
        uses: 'step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b' # v2.15.0
        with:
          egress-policy: 'audit'

      - name: Git checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 0

      # - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
      #   persist-credentials: false
      # env:
      #   GIT_COMMITTER_NAME: "GitHub Actions Shell"
      #   GIT_AUTHOR_NAME: "GitHub Actions Shell"
      #   EMAIL: "github-actions[bot]@users.noreply.github.com"

      # MARK: Setup Node env.

      - name: Setup PNPM
        uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0

      - name: Use Node.js 24
        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
        with:
          registry-url: 'https://registry.npmjs.org'
          node-version: 24
          cache: pnpm

      - name: Install packages
        shell: bash
        run: pnpm install --frozen-lockfile

      # TODO: More tests
      # - name: Syncpack Lint
      #   shell: bash
      #   run: node --run syncpack:lint

      - name:
          'Verify the integrity of provenance attestations and registry
          signatures for installed [prod] dependencies'
        run: node --run audit

      # MARK: Lint/Checks pre-build

      - name: Lint last commit — Commitlint
        shell: bash
        run: node --run lint:commit

      # - name: Lint CSS — Stylelint
      #   shell: bash
      #   run: node --run lint:css

      - name: Check all formatting — Prettier
        shell: bash
        run: node --run format

      # MARK: Build packages

      - name: Setup Turbo cache
        uses: dtinth/setup-github-actions-caching-for-turbo@cc723b4600e40a6b8815b65701d8614b91e2669e # v1.3.0

      - name: Build libraries
        shell: bash
        run: node --run build:libs

      # MARK: Lint/Checks post-build

      - name: Prepare docs content (Typedoc, READMEs)
        shell: bash
        run: node --run docs:prepare

      - name: Cache ESLint results
        uses: actions/cache@v4
        with:
          path: .eslintcache
          key: eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.js') }}

      - name: Lint JS/TS — ESLint
        shell: bash
        run: node --run lint:es

      # MARK: Tests

      - name: Tests — Units
        shell: bash
        run: node --run test:unit

      # MARK: Playwright — single install for all test suites
      # All packages use the same @playwright/test version (catalog:).
      # Browsers land in ~/.cache/ms-playwright regardless of working directory.
      - name: Cache Playwright browsers
        uses: actions/cache@v4
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ hashFiles('pnpm-lock.yaml') }}

      - name: Install Playwright browsers
        shell: bash
        run: npx playwright install --with-deps chromium
        working-directory: starter-projects

      - name: Tests — Integration
        shell: bash
        run: node --run test:integration

      - name: Tests — E2E
        shell: bash
        run: node --run test:e2e

      - name: Build docs website
        shell: bash
        env:
          VITE_DOCS_IS_NEXT:
            ${{ github.ref == 'refs/heads/next' && 'true' || 'false' }}
        run: node --run build:docs

      # MARK: Starter Projects — Smoke Test (pre-publish, workspace-linked)

      - name: 'Regenerate starter-project templates'
        run: pnpm --prefix starter-projects run generate

      - name: Tests — Starter projects smoke
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next'
        shell: bash
        run: pnpm --prefix starter-projects run test:smoke

      - name: Tests — Docs smoke
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next'
        shell: bash
        run: pnpm --prefix docs/website run test:smoke

      # MARK: Publish packages
      # NOTE: Auth is configured by setup-node's registry-url above.
      # The NPM_TOKEN secret MUST be a Granular Access Token (or classic Automation
      # token) to bypass 2FA/OTP requirements in CI.

      - name: Git user configuration
        run: |
          git config --global user.name "${{ github.actor }}"
          git config --global user.email "${{ github.actor }}@users.noreply.github.com"

      # MARK: [MAIN]
      # --force-git-tag: a prior interrupted CI run may have left orphan tags
      # (e.g. @gracile/client@0.5.0) that collide with graduation.
      - name: 'Lerna publish [main]'
        if: github.ref == 'refs/heads/main'
        # https://github.com/lerna/lerna/issues/2532
        id: graduateRelease
        continue-on-error: true
        env:
          GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
          NPM_CONFIG_PROVENANCE: 'true'
        run: |
          pnpm lerna publish --message 'chore: publish [main] release [skip ci]' --create-release=github --conventional-graduate --force-git-tag --yes

      # Fallback for https://github.com/lerna/lerna/issues/2532 (newly introduced
      # packages that fail --conventional-graduate). Fail explicitly if neither
      # the graduate nor the fallback actually published anything.
      - name: 'Lerna publish [main] — fallback (non-graduate)'
        if:
          ${{ always() && github.ref == 'refs/heads/main' &&
          steps.graduateRelease.outcome == 'failure' }}
        env:
          GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
          NPM_CONFIG_PROVENANCE: 'true'
        run: |
          echo "Graduate publish failed — attempting recovery"
          git stash || true

          # Recovery 1: from-package (handles Sigstore/Rekor TLOG_CREATE_ENTRY_ERROR 409)
          # Queries npm directly and publishes any package versions not yet in the registry,
          # regardless of git tags — safe to run even if some packages are already published.
          echo "==> from-package: recovering partial publish"
          FROM_PKG_OUT=$(pnpm lerna publish from-package --yes 2>&1)
          FROM_PKG_EXIT=$?
          echo "$FROM_PKG_OUT"

          # Recovery 2: non-graduate lerna publish (handles lerna#2532: newly introduced
          # packages that have no pre-release version to graduate from).
          echo "==> non-graduate: publishing new or changed packages"
          OUTPUT=$(pnpm lerna publish --message 'chore: publish [main] release [skip ci]' --create-release=github --yes 2>&1) || true
          echo "$OUTPUT"

          # Fail explicitly only when BOTH approaches published nothing useful.
          if [ "$FROM_PKG_EXIT" -ne 0 ] && echo "$OUTPUT" | grep -q 'No changed packages to publish'; then
            echo "::error::Neither from-package nor conventional fallback published anything"
            exit 1
          fi

      # MARK: [NEXT]
      - name: 'Lerna publish [next]'
        if: github.ref == 'refs/heads/next'
        env:
          GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
          NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}'
          NPM_CONFIG_PROVENANCE: 'true'
        run: |
          pnpm lerna publish --message 'chore: publish [next] pre-release' --conventional-prerelease --pre-dist-tag=next --preid=next --yes

      # MARK: Commit regenerated starter-project templates
      # Templates were generated pre-publish (rsync + README) and keep
      # workspace:/catalog: refs — create-gracile resolves at scaffold time.
      - name: 'Commit starter-project templates'
        if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next'
        run: |
          git add starter-projects/templates
          git commit -m 'chore: regenerate starter-project templates [skip ci]' || echo 'No template changes to commit'
          git push

      - name: Merge back main into next
        if: github.ref == 'refs/heads/main'
        continue-on-error: true
        env:
          GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
        run: |
          git checkout next
          git merge main --no-ff -m 'chore: auto-merge main into next [skip ci]'
          git push

      - name: 'Deploy docs website [main → production]'
        if: github.ref == 'refs/heads/main'
        env:
          NETLIFY_AUTH_TOKEN: '${{ secrets.NETLIFY_AUTH_TOKEN }}'
          NETLIFY_SITE_ID: '${{ secrets.NETLIFY_SITE_ID }}'
        shell: bash
        run: pnpm --prefix ./docs/website deploy:main

      - name: 'Deploy docs website [next → preview]'
        if: github.ref == 'refs/heads/next'
        env:
          NETLIFY_AUTH_TOKEN: '${{ secrets.NETLIFY_AUTH_TOKEN }}'
          NETLIFY_SITE_ID: '${{ secrets.NETLIFY_SITE_ID }}'
        shell: bash
        run: pnpm --prefix ./docs/website deploy:next
