ghr-cli 0.7.2

A fast terminal dashboard for GitHub pull requests, issues, and notifications.
name: Release

on:
  push:
    tags:
      - "v*"

env:
  CARGO_TERM_COLOR: always
  BIN_NAME: ghr
  CRATE_NAME: ghr-cli

permissions:
  contents: write

jobs:
  verify:
    name: Verify release
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Verify tag matches Cargo.toml
        id: version
        run: |
          crate_version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == env.CRATE_NAME) | .version')"
          tag_version="${GITHUB_REF_NAME#v}"
          if [ "$crate_version" != "$tag_version" ]; then
            echo "tag $GITHUB_REF_NAME does not match crate version $crate_version" >&2
            exit 1
          fi
          echo "version=$crate_version" >> "$GITHUB_OUTPUT"

      - name: Check formatting
        run: cargo fmt --all -- --check

      - name: Check all targets
        run: cargo check --locked --all-targets

      - name: Clippy
        run: cargo clippy --locked --all-targets -- -D warnings

      - name: Test
        run: cargo test --locked

      - name: Package crate
        run: cargo package --locked

  build:
    name: Build ${{ matrix.target }}
    runs-on: ${{ matrix.os }}
    needs:
      - verify
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            archive: tar
            exe_suffix: ""
          - os: ubuntu-24.04-arm
            target: aarch64-unknown-linux-gnu
            archive: tar
            exe_suffix: ""
          - os: macos-15-intel
            target: x86_64-apple-darwin
            archive: tar
            exe_suffix: ""
          - os: macos-15
            target: aarch64-apple-darwin
            archive: tar
            exe_suffix: ""
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            archive: zip
            exe_suffix: ".exe"
          - os: windows-11-arm
            target: aarch64-pc-windows-msvc
            archive: zip
            exe_suffix: ".exe"
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2
        with:
          key: release-${{ matrix.target }}

      - name: Build binary
        run: cargo build --locked --release --target "${{ matrix.target }}"

      - name: Package tar archive
        if: matrix.archive == 'tar'
        shell: bash
        env:
          TARGET: ${{ matrix.target }}
        run: |
          asset="${BIN_NAME}-${GITHUB_REF_NAME}-${TARGET}"
          staging="dist/${asset}"
          binary_path="target/${TARGET}/release/${BIN_NAME}"

          mkdir -p "$staging"
          cp "$binary_path" "$staging/"
          cp README.md LICENSE "$staging/"

          archive_path="dist/${asset}.tar.gz"
          tar -C dist -czf "$archive_path" "$asset"

          if command -v sha256sum >/dev/null 2>&1; then
            sha256sum "$archive_path" > "${archive_path}.sha256"
          else
            shasum -a 256 "$archive_path" > "${archive_path}.sha256"
          fi
          rm -rf "$staging"

      - name: Package zip archive
        if: matrix.archive == 'zip'
        shell: pwsh
        env:
          TARGET: ${{ matrix.target }}
          EXE_SUFFIX: ${{ matrix.exe_suffix }}
        run: |
          $asset = "${env:BIN_NAME}-${env:GITHUB_REF_NAME}-${env:TARGET}"
          $staging = "dist/${asset}"
          $binaryPath = "target/${env:TARGET}/release/${env:BIN_NAME}${env:EXE_SUFFIX}"
          $archivePath = "dist/${asset}.zip"

          New-Item -ItemType Directory -Force -Path $staging | Out-Null
          Copy-Item $binaryPath $staging
          Copy-Item README.md, LICENSE $staging
          Compress-Archive -Force -Path "${staging}/*" -DestinationPath $archivePath

          $hash = Get-FileHash -Algorithm SHA256 $archivePath
          "$($hash.Hash.ToLowerInvariant())  $archivePath" | Set-Content -NoNewline "${archivePath}.sha256"
          Remove-Item -Recurse -Force $staging

      - name: Upload release artifact
        uses: actions/upload-artifact@v7
        with:
          name: ghr-${{ matrix.target }}
          path: dist/*
          if-no-files-found: error

  publish:
    name: Publish crate and GitHub release
    runs-on: ubuntu-latest
    needs:
      - verify
      - build
    environment: crates-io
    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo
        uses: Swatinem/rust-cache@v2

      - name: Publish to crates.io
        shell: bash
        run: |
          version="${GITHUB_REF_NAME#v}"
          crate_url="https://crates.io/api/v1/crates/${CRATE_NAME}/${version}"
          if curl -fsS -H "User-Agent: ${GITHUB_REPOSITORY} release workflow" "$crate_url" >/dev/null; then
            echo "${CRATE_NAME} ${version} is already published; skipping cargo publish"
          else
            publish_log="$(mktemp)"
            if cargo publish --locked --token "$CARGO_REGISTRY_TOKEN" 2>"$publish_log"; then
              cat "$publish_log"
            elif grep -Eiq "already (uploaded|exists)|version .* is already uploaded" "$publish_log"; then
              cat "$publish_log"
              echo "${CRATE_NAME} ${version} is already published; continuing"
            else
              cat "$publish_log" >&2
              exit 1
            fi
          fi
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

      - name: Download release artifacts
        uses: actions/download-artifact@v8
        with:
          path: dist
          merge-multiple: true

      - name: Publish GitHub release
        run: |
          notes_file="$(mktemp)"
          release_notes=".github/release-notes/${GITHUB_REF_NAME}.md"
          if [ -f "$release_notes" ]; then
            cat "$release_notes" > "$notes_file"
            printf '\n\n' >> "$notes_file"
          fi
          printf 'Published to crates.io: https://crates.io/crates/%s/%s\n' "$CRATE_NAME" "${GITHUB_REF_NAME#v}" >> "$notes_file"
          if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
            gh release edit "$GITHUB_REF_NAME" \
              --title "ghr $GITHUB_REF_NAME" \
              --notes-file "$notes_file"
            gh release upload "$GITHUB_REF_NAME" dist/* --clobber
          else
            gh release create "$GITHUB_REF_NAME" dist/* \
              --verify-tag \
              --title "ghr $GITHUB_REF_NAME" \
              --notes-file "$notes_file"
          fi
        env:
          GH_TOKEN: ${{ github.token }}