name: npm-release
run-name: >-
  ${{ format('npm publish {0}',
       case(github.event_name == 'release', github.event.release.tag_name,
            inputs.tag)) }}

on:
  release: { types: [published] }
  workflow_dispatch:
    inputs:
      tag:
        description: Release tag to publish (e.g. v0.5.0)
        required: true
        type: string
      run-id:
        description: |-
          release.yml run ID that produced the npm-dist artifact for this tag.
          Find it via `gh run list --workflow=release.yml --branch=<tag> --status=success`.
        required: true
        type: string
      dist-tag:
        description: |-
          npm dist-tag (latest, next, etc.).
          Leave empty to infer from the tag (prerelease → next, otherwise → latest).
        required: false
        options: [latest, next]
        type: choice
      dry-run:
        description: Dry run only
        required: true
        default: false
        type: boolean

permissions:
  contents: read

env:
  NODE_VERSION: latest
  REGISTRY: https://registry.npmjs.org/
  # RELEASE_TAG comes from trusted trigger metadata only — never from
  # anything build-time scripts could influence.
  RELEASE_TAG: ${{ case(github.event_name == 'release', github.event.release.tag_name, inputs.tag) }}
  EVENT_NAME: ${{ github.event_name }}
  INPUT_DIST_TAG: ${{ inputs.dist-tag }}
  INPUT_DRY_RUN: ${{ inputs.dry-run || false }}

jobs:
  publish:
    name: publish to npm
    runs-on: ubuntu-latest
    # Triggered by release:published (real flow) or workflow_dispatch (manual
    # republish). Reject anything else — no workflow_run, no schedule.
    if: >-
      github.event_name == 'workflow_dispatch' ||
      (github.event_name == 'release' && startsWith(github.event.release.tag_name, 'v'))
    permissions:
      contents: read
      id-token: write # for npm provenance
      actions: read # for cross-workflow artifact download
    environment: { name: npm, url: "${{ steps.publish.outputs.package-url }}" }
    outputs:
      dry-run: ${{ steps.derive.outputs.dry-run }}
    steps:
      # Sparse-checkout from the DEFAULT BRANCH gets us the trusted publish
      # scripts only. No tag-supplied code on disk → no untrusted code
      # execution → no cache-poisoning vector.
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event.repository.default_branch }}
          persist-credentials: false
          sparse-checkout: |
            .github/scripts
            npm/targets.json
          sparse-checkout-cone-mode: false

      - uses: actions/setup-node@v6
        with:
          node-version: ${{ env.NODE_VERSION }}
          registry-url: ${{ env.REGISTRY }}

      # Cross-workflow artifact download: npm-dist lives in the release.yml
      # run that built this tag. For workflow_dispatch the user supplies the
      # run-id explicitly. For release:published we look it up by querying
      # the most recent successful release.yml run for this tag — release.yml
      # only marks success after publish-release flips draft→published, so
      # by the time release:published fires the run is already success.
      - name: resolve build run-id
        id: resolve
        env:
          GH_TOKEN: ${{ github.token }}
          INPUT_RUN_ID: ${{ inputs.run-id }}
        run: |
          set -euo pipefail
          if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then
            run_id="${INPUT_RUN_ID}"
          else
            run_id=$(gh run list \
              --repo "${GITHUB_REPOSITORY}" \
              --workflow=release.yml \
              --branch="${RELEASE_TAG}" \
              --status=success \
              --limit=1 \
              --json databaseId \
              --jq '.[0].databaseId')
          fi
          if [[ -z "${run_id}" || "${run_id}" == "null" ]]; then
            echo "error: no run-id resolvable for ${RELEASE_TAG}" >&2
            exit 1
          fi
          # Numeric guard — refuses anything that could inject extra GITHUB_OUTPUT
          # lines (newlines, '=', etc.). gh API returns a bare integer; a
          # workflow_dispatch input from a trusted dispatcher should match too.
          if ! [[ "${run_id}" =~ ^[0-9]+$ ]]; then
            echo "error: run-id '${run_id}' is not a positive integer" >&2
            exit 1
          fi
          echo "run-id=${run_id}" >> "${GITHUB_OUTPUT}"

      - name: download npm-dist artifact
        uses: actions/download-artifact@v8
        with:
          name: npm-dist
          path: npm/dist/
          run-id: ${{ steps.resolve.outputs.run-id }}
          github-token: ${{ github.token }}

      - name: derive dist-tag and dry-run
        id: derive
        run: bash "${GITHUB_WORKSPACE}/.github/scripts/build/derive-dist-dry.sh"

      - name: publish to npm
        id: publish
        env:
          DIST_TAG: ${{ steps.derive.outputs.dist-tag }}
          DRY_RUN: ${{ steps.derive.outputs.dry-run }}
        run: bash "${GITHUB_WORKSPACE}/.github/scripts/publish/npm.sh"
