zipatch-rs 1.0.2

Parser for FFXIV ZiPatch patch files
Documentation
name: Release

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: write

concurrency:
  group: release
  cancel-in-progress: false

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    env:
      CRATE_NAME: zipatch-rs
    steps:
      - uses: actions/checkout@v6.0.2
        with:
          fetch-depth: 0

      - name: Extract version from Cargo.toml
        id: version
        run: |
          version=$(grep '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/')
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "tag=v$version" >> "$GITHUB_OUTPUT"

      - name: Check git tag
        id: tag-check
        run: |
          tag="v${{ steps.version.outputs.version }}"
          if git rev-parse "$tag" >/dev/null 2>&1; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
            echo "Tag $tag already exists."
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Check GitHub release
        id: release-check
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          tag="v${{ steps.version.outputs.version }}"
          if gh release view "$tag" >/dev/null 2>&1; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
            echo "GitHub release $tag already exists."
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Check crates.io
        id: crates-check
        run: |
          version="${{ steps.version.outputs.version }}"
          # crates.io requires a descriptive User-Agent with contact info,
          # otherwise the API returns 403 (data-access policy).
          status=$(curl -sS -o /tmp/crate.json -w "%{http_code}" \
            -A "${CRATE_NAME}-release-workflow (https://github.com/${{ github.repository }})" \
            "https://crates.io/api/v1/crates/${CRATE_NAME}/${version}")
          if [ "$status" = "200" ]; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
            echo "crates.io ${CRATE_NAME} ${version} already published."
          elif [ "$status" = "404" ]; then
            echo "exists=false" >> "$GITHUB_OUTPUT"
          else
            echo "Unexpected status $status from crates.io API:"
            cat /tmp/crate.json
            exit 1
          fi

      - name: Summarise plan
        run: |
          echo "tag v${{ steps.version.outputs.version }}: exists=${{ steps.tag-check.outputs.exists }}"
          echo "release v${{ steps.version.outputs.version }}: exists=${{ steps.release-check.outputs.exists }}"
          echo "crates.io ${CRATE_NAME} ${{ steps.version.outputs.version }}: exists=${{ steps.crates-check.outputs.exists }}"

      - name: Set up Rust
        if: steps.crates-check.outputs.exists == 'false'
        uses: dtolnay/rust-toolchain@stable

      - name: Cache Rust
        if: steps.crates-check.outputs.exists == 'false'
        uses: Swatinem/rust-cache@v2.9.1

      - name: Verify build with dry-run
        if: steps.crates-check.outputs.exists == 'false'
        run: cargo publish --dry-run --all-features

      - name: Extract changelog section
        if: steps.release-check.outputs.exists == 'false'
        id: changelog
        run: |
          version="${{ steps.version.outputs.version }}"
          start=$(grep -n "^## \[$version\]" CHANGELOG.md | head -1 | cut -d: -f1)
          if [ -z "$start" ]; then
            echo "Error: Could not find version $version in CHANGELOG.md"
            exit 1
          fi
          end=$(tail -n +$((start + 1)) CHANGELOG.md | grep -n "^## \[" | head -1 | cut -d: -f1)
          if [ -z "$end" ]; then
            sed -n "${start},\$p" CHANGELOG.md > /tmp/release_notes.txt
          else
            end=$((start + end - 1))
            sed -n "${start},$((end - 1))p" CHANGELOG.md > /tmp/release_notes.txt
          fi
          if [ ! -s /tmp/release_notes.txt ]; then
            echo "Error: Could not extract changelog section for version $version"
            exit 1
          fi
          echo "notes_file=/tmp/release_notes.txt" >> "$GITHUB_OUTPUT"

      - name: Create git tag
        if: steps.tag-check.outputs.exists == 'false'
        run: |
          git tag "${{ steps.version.outputs.tag }}"
          git push origin "${{ steps.version.outputs.tag }}"

      - name: Create GitHub release
        if: steps.release-check.outputs.exists == 'false'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create \
            "${{ steps.version.outputs.tag }}" \
            --notes-file "${{ steps.changelog.outputs.notes_file }}"

      - name: Publish to crates.io
        if: steps.crates-check.outputs.exists == 'false'
        run: cargo publish --all-features
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}