name: Release Preflight

# Zero-touch auth dry-run for every external publish surface.
# Run on demand before cutover and after any secret rotation.
# Publishes nothing. Any failure here should be fixed before a real tag is pushed.

on:
  workflow_dispatch:

permissions:
  contents: read
  id-token: write  # required for npm OIDC provenance dry-run

jobs:
  preflight:
    name: Release preflight
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - uses: actions/checkout@v4

      # --- crates.io -----------------------------------------------------
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2

      - name: cargo publish --dry-run
        id: cargo-check
        continue-on-error: true
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish --dry-run

      # --- npm OIDC ------------------------------------------------------
      - uses: actions/setup-node@v4
        with:
          node-version: "24"

      - name: Configure npm registry
        run: npm config set registry https://registry.npmjs.org

      - name: npm publish --dry-run --provenance (meta package)
        id: npm-check
        continue-on-error: true
        working-directory: npm/dynoxide
        # The package.json carries a placeholder prerelease version
        # (0.0.0-development) that publish.sh rewrites at real publish time.
        # npm requires --tag for prerelease dry-runs; the value is inert since
        # nothing is published.
        run: npm publish --dry-run --provenance --tag preflight

      # --- Homebrew tap --------------------------------------------------
      - name: Probe Homebrew tap access
        id: homebrew-check
        continue-on-error: true
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
        # Probe the exact endpoint the formula push uses so a fine-grained PAT
        # scoped to the Formula path still passes this check. A bare
        # "gh api repos/..." call rejects fine-grained tokens that have file-
        # scoped access without metadata read.
        run: |
          set -euo pipefail
          if [ -z "${GH_TOKEN:-}" ]; then
            echo "::error::HOMEBREW_TAP_TOKEN is not set on this repo"
            exit 1
          fi
          HTTP_STATUS=$(gh api -i repos/nubo-db/homebrew-tap/contents/Formula/dynoxide.rb 2>&1 | head -1 || true)
          echo "response: $HTTP_STATUS"
          if ! gh api repos/nubo-db/homebrew-tap/contents/Formula/dynoxide.rb --jq '.sha' >/dev/null 2>&1; then
            echo "::error::HOMEBREW_TAP_TOKEN cannot access Formula/dynoxide.rb in nubo-db/homebrew-tap (auth rejected, expired, or not set)"
            exit 1
          fi
          echo "Homebrew tap access confirmed"

      # --- Summary and final gate ---------------------------------------
      - name: Preflight summary
        if: always()
        env:
          CARGO_OUTCOME: ${{ steps.cargo-check.outcome }}
          NPM_OUTCOME: ${{ steps.npm-check.outcome }}
          HOMEBREW_OUTCOME: ${{ steps.homebrew-check.outcome }}
        run: |
          {
            echo "## Release Preflight"
            echo ""
            echo "| Check | Result |"
            echo "|-------|--------|"
            echo "| cargo publish --dry-run | $CARGO_OUTCOME |"
            echo "| npm publish --dry-run --provenance (meta) | $NPM_OUTCOME |"
            echo "| Homebrew tap write-access probe | $HOMEBREW_OUTCOME |"
            echo ""
            echo "Run: ${{ github.run_id }}"
            echo "Triggered by: ${{ github.actor }}"
          } >> "$GITHUB_STEP_SUMMARY"

          if [ "$CARGO_OUTCOME" != "success" ] || \
             [ "$NPM_OUTCOME" != "success" ] || \
             [ "$HOMEBREW_OUTCOME" != "success" ]; then
            echo "::error::One or more preflight checks failed. See summary for details."
            exit 1
          fi
          echo "All preflight checks passed."
