name: Release

on:
  push:
    tags:
      - "v*"

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: false

jobs:
  guard:
    name: Release guard
    runs-on: ubuntu-latest
    permissions:
      contents: read
    environment:
      name: release-guard
      deployment: false
    steps:
      - run: echo "Release guard passed"

  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    needs: guard
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            archive: tar.gz
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
            archive: tar.gz
          - os: macos-15-intel
            target: x86_64-apple-darwin
            archive: tar.gz
          - os: macos-14
            target: aarch64-apple-darwin
            archive: tar.gz
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            archive: zip
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Rust
        shell: bash
        env:
          TARGET: ${{ matrix.target }}
        run: |
          rustup default stable
          if ! rustup target list --installed | grep -q "^${TARGET}$"; then
            rustup target add "$TARGET"
          fi

      - name: Cache Cargo
        uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          lookup-only: true

      - name: Test
        run: cargo test

      - name: Build release
        env:
          TARGET: ${{ matrix.target }}
        run: cargo build --release --target "$TARGET"

      - name: Package artifact
        shell: bash
        env:
          TARGET: ${{ matrix.target }}
          ARCHIVE: ${{ matrix.archive }}
        run: |
          set -euo pipefail
          version="${GITHUB_REF_NAME#v}"
          name="actioneer-${version}-${TARGET}"
          bin="target/${TARGET}/release/actioneer"

          mkdir -p dist
          if [ "${ARCHIVE}" = "zip" ]; then
            bin="${bin}.exe"
            mkdir -p "${name}"
            cp LICENSE "${name}/"
            cp "${bin}" "${name}/"
            cd "${name}"
            zip -r "../dist/${name}.zip" .
          else
            mkdir -p "${name}"
            cp LICENSE "${name}/"
            cp "${bin}" "${name}/"
            tar -C . -czf "dist/${name}.tar.gz" "${name}"
          fi

      - name: Upload artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: bin-${{ matrix.target }}
          path: dist/*
          retention-days: 1

  checksums:
    name: Generate checksums
    runs-on: ubuntu-latest
    needs: build
    permissions:
      contents: read
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: bin-*
          merge-multiple: true
          path: dist

      - name: Generate SHA256
        run: |
          set -euo pipefail
          cd dist
          sha256sum * > SHA256SUMS

      - name: Upload checksums
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: checksums
          path: dist/SHA256SUMS
          retention-days: 1

  publish-crate:
    name: Publish to crates.io
    runs-on: ubuntu-latest
    needs: guard
    permissions:
      contents: read
      id-token: write
    environment:
      name: release
      deployment: false
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Rust
        run: rustup default stable

      - name: Cache Cargo
        uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
        with:
          lookup-only: true

      - uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4
        id: auth

      - name: Publish
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}

  create-release:
    name: Create GitHub Release
    runs-on: ubuntu-latest
    needs: [checksums, publish-crate]
    permissions:
      # create and upload release assets
      contents: write
    environment:
      name: release
      deployment: false
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Download all artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: bin-*
          merge-multiple: true
          path: dist

      - name: Download checksums
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: checksums
          path: dist

      - name: generate app token
        id: app-token
        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
        with:
          client-id: ${{ secrets.RELEASE_APP_ID }}
          private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
          repositories: ${{ github.event.repository.name }}
          permission-contents: write

      - name: Create release
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
        run: |
          set -euo pipefail
          tag="${GITHUB_REF_NAME}"
          gh release create "$tag" dist/* \
            --title "$tag" \
            --generate-notes \
            --verify-tag
