name: Release

on:
  push:
    branches:
      - main
  workflow_dispatch:
    inputs:
      publish-only:
        description: "Skip versioning, just publish current versions"
        type: boolean
        default: false

concurrency: ${{ github.workflow }}-${{ github.ref }}

# Default-deny at the workflow level. Each job scopes its own permissions:
# - `release` declares contents/id-token/pull-requests below.
# - `sync-templates` calls a reusable workflow whose own job-level
#   permissions block (contents: read) takes precedence over the caller's
#   inherited permissions.
permissions: {}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    outputs:
      published: ${{ steps.changesets.outputs.published }}
    permissions:
      contents: write
      id-token: write
      pull-requests: write
    steps:
      - name: Generate token
        id: app-token
        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
        with:
          app-id: ${{ secrets.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}
          # changesets/action pushes release commits/tags and opens the
          # release PR (contents + pull-requests). npm publish auth is the
          # separate NODE_AUTH_TOKEN; OIDC provenance is the job's id-token.
          permission-contents: write
          permission-pull-requests: write

      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          fetch-depth: 0
          token: ${{ steps.app-token.outputs.token }}
          # Intentional: changesets/action pushes the release PR / tags
          # using this credential.
          persist-credentials: true

      - name: Setup pnpm
        uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8

      - name: Setup Node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: 24
          cache: pnpm
          registry-url: https://registry.npmjs.org

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

      - name: Build packages
        run: pnpm build

      - name: Block 1.x releases (we are in 0.x)
        run: node .github/scripts/check-no-major.mjs

      - name: Create Release Pull Request or Publish
        if: ${{ !inputs.publish-only }}
        id: changesets
        uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0
        with:
          version: node .github/scripts/release.mjs version
          publish: node .github/scripts/release.mjs publish
          commit: "ci: release"
          title: "ci: release"
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}

      # Attach sandboxed-plugin tarballs to the releases changesets just
      # created, so the decentralized registry has a stable public URL to
      # point each release record at. See the script header for details.
      # Only the changesets path is covered: the publish-only recovery path
      # below neither runs changesets/action nor creates GitHub releases, so
      # assets for that path must be backfilled manually.
      - name: Attach plugin tarballs to releases
        if: ${{ steps.changesets.outputs.published == 'true' }}
        run: node .github/scripts/attach-plugin-tarballs.mjs
        env:
          GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}

      - name: Publish (manual)
        if: ${{ inputs.publish-only }}
        run: node .github/scripts/release.mjs publish

  sync-templates:
    name: Sync Templates
    needs: release
    if: >-
      needs.release.outputs.published == 'true' ||
      inputs.publish-only
    permissions:
      contents: read
    uses: ./.github/workflows/sync-templates.yml
    # Pass only the secrets sync-templates actually uses, not the full set
    # available to this release workflow.
    secrets:
      APP_ID: ${{ secrets.APP_ID }}
      APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
