workbloom 0.9.1

A Git worktree management tool with automatic file copying
Documentation
name: Auto Release

on:
  workflow_run:
    workflows:
      - Build
    types:
      - completed
  workflow_dispatch:
    inputs:
      tag:
        description: 'Tag to release (e.g., v0.1.7)'
        required: true
        type: string

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always

jobs:
  check-and-tag:
    name: Check Version and Create Tag
    runs-on: ubuntu-latest
    if: |
      github.event_name == 'workflow_dispatch' ||
      (
        github.event_name == 'workflow_run' &&
        github.event.workflow_run.conclusion == 'success' &&
        github.event.workflow_run.event == 'push' &&
        github.event.workflow_run.head_branch == 'main'
      )
    outputs:
      version: ${{ steps.get_version.outputs.VERSION }}
      tag: ${{ steps.get_version.outputs.TAG }}
      should_release: ${{ steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch' }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0  # Fetch all history for tags
          ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }}
      
      - name: Get version
        id: get_version
        run: |
          if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
            # Manual trigger - use input tag
            VERSION="${{ github.event.inputs.tag }}"
            VERSION="${VERSION#v}"  # Remove 'v' prefix if present
          else
            # Auto trigger - get from Cargo.toml
            VERSION=$(grep '^version = ' Cargo.toml | head -1 | cut -d'"' -f2)
          fi
          echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
          echo "TAG=v$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"
      
      - name: Check if tag exists
        id: tag_exists
        run: |
          if git rev-parse "${{ steps.get_version.outputs.TAG }}" >/dev/null 2>&1; then
            echo "EXISTS=true" >> $GITHUB_OUTPUT
            echo "Tag ${{ steps.get_version.outputs.TAG }} already exists"
          else
            echo "EXISTS=false" >> $GITHUB_OUTPUT
            echo "Tag ${{ steps.get_version.outputs.TAG }} does not exist"
          fi
      
      - name: Extract changelog for this version
        if: steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch'
        id: changelog
        run: |
          VERSION="${{ steps.get_version.outputs.VERSION }}"
          # Escape dots in version for regex
          VERSION_REGEX=$(echo "$VERSION" | sed 's/\./\\./g')
          
          # Extract the section for this version from CHANGELOG.md
          CHANGELOG=$(awk "/^## \[$VERSION_REGEX\]/{flag=1; next} /^## \[/{flag=0} flag" CHANGELOG.md | sed '/^$/d')
          
          # If changelog is empty, use a default message
          if [ -z "$CHANGELOG" ]; then
            CHANGELOG="Release v$VERSION"
          fi
          
          # Save to file to handle multiline content properly
          echo "$CHANGELOG" > changelog_content.txt
          
          # Set output using delimiter
          echo "CONTENT<<EOF" >> $GITHUB_OUTPUT
          cat changelog_content.txt >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT
      
      - name: Create and push tag
        if: steps.tag_exists.outputs.EXISTS == 'false'
        run: |
          VERSION="${{ steps.get_version.outputs.VERSION }}"
          
          # Configure git
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          
          # Create annotated tag with changelog content from file
          git tag -a "v$VERSION" -m "Release v$VERSION" -m "$(cat changelog_content.txt)"
          
          # Push the tag
          git push origin "v$VERSION"
          
          echo "Created and pushed tag v$VERSION"
      
      - name: Create GitHub Release
        if: steps.tag_exists.outputs.EXISTS == 'false'
        run: |
          VERSION="${{ steps.get_version.outputs.VERSION }}"
          
          # Poll for tag availability (max 30 seconds)
          for i in {1..6}; do
            if git ls-remote --tags origin | grep -q "refs/tags/v$VERSION"; then
              echo "Tag v$VERSION is available"
              break
            fi
            echo "Waiting for tag to be available... (attempt $i/6)"
            sleep 5
          done
          
          # Create release JSON with proper escaping
          RELEASE_JSON=$(jq -n \
            --arg tag "v$VERSION" \
            --arg name "Release v$VERSION" \
            --arg body "See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md) for details." \
            '{
              tag_name: $tag,
              name: $name,
              body: $body,
              draft: false,
              prerelease: false,
              generate_release_notes: true
            }')
          
          # Create a release event with error handling
          HTTP_STATUS=$(curl -s -w "%{http_code}" -o /tmp/release_response.json \
            -X POST \
            -H "Authorization: token ${{ github.token }}" \
            -H "Accept: application/vnd.github.v3+json" \
            https://api.github.com/repos/${{ github.repository }}/releases \
            -d "$RELEASE_JSON")
          
          if [ "$HTTP_STATUS" -eq 201 ]; then
            echo "✅ Release created successfully"
            cat /tmp/release_response.json | jq -r '.html_url'
          elif [ "$HTTP_STATUS" -eq 422 ]; then
            echo "⚠️ Release already exists (this is OK)"
            cat /tmp/release_response.json | jq -r '.errors[].code'
          else
            echo "❌ Failed to create release (HTTP $HTTP_STATUS)"
            cat /tmp/release_response.json
            exit 1
          fi
      
      - name: Install Rust
        # Need Rust for cargo publish regardless of tag existence
        if: steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch'
        uses: dtolnay/rust-toolchain@stable

      - name: Check code quality
        if: steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch'
        run: |
          echo "Running cargo check..."
          cargo check --all-targets
          echo "Running cargo test..."
          cargo test
          echo "Code quality checks passed ✅"

      - name: Verify registry token
        # Need to verify token for cargo publish
        if: steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch'
        run: |
          if [ -z "${{ secrets.CARGO_REGISTRY_TOKEN }}" ]; then
            echo "❌ CARGO_REGISTRY_TOKEN secret is not set"
            exit 1
          fi
          echo "✅ CARGO_REGISTRY_TOKEN is configured"

      - name: Publish to crates.io
        # Always try to publish - cargo will handle if already published
        if: steps.tag_exists.outputs.EXISTS == 'false' || github.event_name == 'workflow_dispatch'
        run: |
          echo "Publishing to crates.io..."
          PUBLISH_OUTPUT=$(cargo publish --allow-dirty --token ${{ secrets.CARGO_REGISTRY_TOKEN }} 2>&1 || true)
          echo "$PUBLISH_OUTPUT"
          
          if echo "$PUBLISH_OUTPUT" | grep -q "already uploaded"; then
            echo "⚠️ Version already published (this is OK)"
            exit 0
          elif echo "$PUBLISH_OUTPUT" | grep -q "Uploading"; then
            echo "✅ Successfully published to crates.io"
            exit 0
          elif echo "$PUBLISH_OUTPUT" | grep -q "error:"; then
            echo "❌ Failed to publish to crates.io"
            echo "$PUBLISH_OUTPUT"
            exit 1
          else
            echo "⚠️ Publish completed with unknown status"
            echo "$PUBLISH_OUTPUT"
          fi

  build-and-upload:
    name: Build and Upload
    needs: check-and-tag
    if: needs.check-and-tag.outputs.should_release == 'true'
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            artifact_name: workbloom
            asset_name: workbloom-linux-amd64
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact_name: workbloom
            asset_name: workbloom-darwin-amd64
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact_name: workbloom
            asset_name: workbloom-darwin-arm64
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || (github.event_name == 'workflow_run' && github.event.workflow_run.head_sha) || github.ref }}
      
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      
      - name: Build
        run: cargo build --release --target ${{ matrix.target }}
      
      - name: Compress binary
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ${{ matrix.asset_name }}.tar.gz ${{ matrix.artifact_name }}
          mv ${{ matrix.asset_name }}.tar.gz ../../../
      
      - name: Upload Release Asset
        uses: softprops/action-gh-release@v1
        with:
          tag_name: ${{ needs.check-and-tag.outputs.tag }}
          files: ./${{ matrix.asset_name }}.tar.gz
          fail_on_unmatched_files: false