name: CI - Reactor packages

on:
  pull_request:
    paths:
      - "packages/reactor/**"
      - "packages/reactor-cli/**"
      - "packages/reactor-devtools/**"
      - "package.json"
      - "pnpm-lock.yaml"
      - "pnpm-workspace.yaml"
      - "turbo.json"
      - ".github/workflows/ci-reactor-package.yml"
  push:
    branches: [main]
    tags:
      - "reactor-v*"

concurrency:
  group: ci-reactor-package-${{ github.head_ref || github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

jobs:
  ci:
    # NOTE: this job name is a required status check on the release PR — do not rename.
    name: CI - Reactor Package
    runs-on: ubuntu-latest
    timeout-minutes: 25

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"
          cache-dependency-path: "pnpm-lock.yaml"

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build reactor packages
        run: |
          pnpm --filter @openprose/reactor build
          pnpm --filter @openprose/reactor-cli build
          pnpm --filter @openprose/reactor-devtools build

      - name: Test reactor packages (offline, zero model spend)
        run: pnpm test:reactor:offline

      - name: Pack reactor packages (validates publishability)
        id: pack
        run: |
          set -euo pipefail
          pack_dir="${RUNNER_TEMP}/reactor-pack"
          mkdir -p "${pack_dir}"
          # pnpm pack rewrites each package's `workspace:*` dependency on
          # @openprose/reactor to the concrete published version; npm pack does not.
          pnpm --dir packages/reactor pack --pack-destination "${pack_dir}"
          pnpm --dir packages/reactor-cli pack --pack-destination "${pack_dir}"
          pnpm --dir packages/reactor-devtools pack --pack-destination "${pack_dir}"
          echo "Packed tarballs:"
          ls -1 "${pack_dir}"
          echo "pack_dir=${pack_dir}" >> "$GITHUB_OUTPUT"

      - name: Upload reactor tarballs
        if: ${{ startsWith(github.ref, 'refs/tags/reactor-v') }}
        uses: actions/upload-artifact@v4
        with:
          name: reactor-tarballs
          path: ${{ steps.pack.outputs.pack_dir }}/*.tgz
          if-no-files-found: error
          retention-days: 7

  publish:
    name: Publish Reactor Packages
    needs: ci
    if: ${{ startsWith(github.ref, 'refs/tags/reactor-v') }}
    runs-on: ubuntu-latest
    timeout-minutes: 15
    permissions:
      contents: read
      id-token: write

    env:
      NPM_CONFIG_PROVENANCE: "true"

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v4

      - name: Setup Node.js for trusted publishing
        uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: "pnpm"
          cache-dependency-path: "pnpm-lock.yaml"

      - name: Upgrade npm for trusted publishing
        run: npm install -g npm@latest

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build reactor packages
        # Build up front so cross-package types resolve during each `pnpm pack`'s
        # prepack build — even when a package's publish is SKIPPED (idempotent re-run).
        # Without this, an already-published reactor short-circuits before its pack
        # builds `reactor/dist`, and reactor-cli / reactor-devtools then fail to resolve
        # `@openprose/reactor` (TS2307). Order matters: the SDK first.
        run: |
          pnpm --filter @openprose/reactor build
          pnpm --filter @openprose/reactor-cli build
          pnpm --filter @openprose/reactor-devtools build

      - name: Publish reactor packages
        # OIDC trusted publishing: npm (>=11.5.1) exchanges the GitHub Actions id-token
        # for a short-lived npm token — no NPM_TOKEN secret needed. pnpm has NO OIDC
        # support, so we pnpm-PACK each package (which rewrites its `workspace:*` SDK
        # dependency to the concrete version and runs the prepack build) and then
        # `npm publish` the resulting tarball.
        # Each package publishes at its own package.json version (they diverge: reactor
        # 0.2.0, reactor-cli 0.1.0, reactor-devtools 0.1.0); already-published versions are
        # skipped so re-runs are idempotent. Order matters — reactor first, because cli +
        # devtools depend on the concrete reactor version.
        run: |
          set -euo pipefail
          pack_dir="${RUNNER_TEMP}/reactor-publish"
          mkdir -p "${pack_dir}"
          publish_pkg() {
            local dir="$1"
            local name version slug tarball
            name="$(node -p "require('./${dir}/package.json').name")"
            version="$(node -p "require('./${dir}/package.json').version")"
            if npm view "${name}@${version}" version >/dev/null 2>&1; then
              echo "Skipping ${name}@${version} — already published."
              return 0
            fi
            echo "Packing ${name}@${version}"
            pnpm --dir "${dir}" pack --pack-destination "${pack_dir}"
            slug="$(printf '%s' "${name}" | sed 's#@##; s#/#-#g')"
            tarball="$(find "${pack_dir}" -maxdepth 1 -name "${slug}-${version}.tgz" -print -quit)"
            if [ -z "${tarball}" ]; then
              echo "::error::No tarball produced for ${name}@${version}."
              exit 1
            fi
            echo "Publishing ${tarball} (OIDC trusted publishing + provenance)"
            npm publish "${tarball}" --access public --provenance
          }
          publish_pkg packages/reactor
          publish_pkg packages/reactor-cli
          publish_pkg packages/reactor-devtools
