rustdupe 0.2.0

Smart duplicate file finder with interactive TUI
Documentation
name: Release

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      tag_name:
        description: 'Tag to release (e.g. v0.1.0)'
        required: true
        default: 'v0.1.0'

permissions:
  contents: write

env:
  BIN_NAME: rustdupe
  PROJECT_NAME: rustdupe

jobs:
  prepare:
    name: Prepare Release
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get_version.outputs.version }}
      clean_version: ${{ steps.get_version.outputs.clean_version }}
    steps:
      - uses: actions/checkout@v4
      - name: Get version
        id: get_version
        shell: bash
        run: |
          if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
            TAG_NAME="${{ github.event.inputs.tag_name }}"
          else
            TAG_NAME="${GITHUB_REF#refs/tags/}"
          fi
          echo "version=${TAG_NAME}" >> $GITHUB_OUTPUT
          echo "clean_version=${TAG_NAME#v}" >> $GITHUB_OUTPUT
      - name: Create and Push Tag
        if: github.event_name == 'workflow_dispatch'
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag ${{ steps.get_version.outputs.version }} || echo "Tag already exists"
          git push origin ${{ steps.get_version.outputs.version }} || echo "Tag already exists"

  build:
    name: Build - ${{ matrix.target }}
    needs: prepare
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            suffix: ""
            name: linux-x86_64
          - target: x86_64-unknown-linux-musl
            os: ubuntu-latest
            suffix: ""
            name: linux-x86_64-musl
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            suffix: ".exe"
            name: windows-x86_64
          - target: x86_64-apple-darwin
            os: macos-latest
            suffix: ""
            name: macos-x86_64
          - target: aarch64-apple-darwin
            os: macos-latest
            suffix: ""
            name: macos-arm64

    steps:
      - uses: actions/checkout@v4

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

      - name: Install cross-compilation tools
        if: matrix.target == 'x86_64-unknown-linux-musl'
        run: sudo apt-get update && sudo apt-get install -y musl-tools

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

      - name: Package
        shell: bash
        run: |
          VERSION=${{ needs.prepare.outputs.clean_version }}
          ASSET_NAME="${PROJECT_NAME}-${VERSION}-${{ matrix.name }}"
          BINARY_PATH="target/${{ matrix.target }}/release/${BIN_NAME}${{ matrix.suffix }}"
          
          mkdir -p dist staging
          
          # Copy binary to staging area
          cp "$BINARY_PATH" "staging/${BIN_NAME}${{ matrix.suffix }}"
          
          # Also copy raw binary to dist (some users prefer this)
          cp "$BINARY_PATH" "dist/${ASSET_NAME}${{ matrix.suffix }}"
          
          # Create archive
          cd staging
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            # Windows: create .zip
            7z a -tzip "../dist/${ASSET_NAME}.zip" "${BIN_NAME}${{ matrix.suffix }}"
          else
            # Unix: create .tar.gz
            tar -czvf "../dist/${ASSET_NAME}.tar.gz" "${BIN_NAME}${{ matrix.suffix }}"
          fi
          cd ../dist
          
          # Generate checksums for all files
          for file in *; do
            if [[ ! "$file" == *.sha256 ]]; then
              if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
                sha256sum "$file" > "${file}.sha256"
              else
                shasum -a 256 "$file" > "${file}.sha256"
              fi
            fi
          done

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: binaries-${{ matrix.target }}
          path: dist/*

  release:
    name: Create Release
    needs: [build, prepare]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Download Artifacts
        uses: actions/download-artifact@v4
        with:
          path: artifacts
          pattern: binaries-*
          merge-multiple: true

      - name: Generate Release Body
        id: release_body
        shell: bash
        run: |
          VERSION="${{ needs.prepare.outputs.clean_version }}"
          
          # Create a nice header
          echo "## Release ${{ needs.prepare.outputs.version }}" > release_notes.md
          echo "" >> release_notes.md
          
          # Add Installation section
          echo "### Installation" >> release_notes.md
          echo "" >> release_notes.md
          echo "#### Linux" >> release_notes.md
          echo '```bash' >> release_notes.md
          echo "# Download (choose glibc or musl version)" >> release_notes.md
          echo "curl -LO https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.version }}/rustdupe-${VERSION}-linux-x86_64.tar.gz" >> release_notes.md
          echo "" >> release_notes.md
          echo "# Extract and make executable" >> release_notes.md
          echo "tar -xzf rustdupe-${VERSION}-linux-x86_64.tar.gz" >> release_notes.md
          echo "chmod +x rustdupe" >> release_notes.md
          echo "" >> release_notes.md
          echo "# Run" >> release_notes.md
          echo "./rustdupe --help" >> release_notes.md
          echo '```' >> release_notes.md
          echo "" >> release_notes.md
          echo "#### macOS" >> release_notes.md
          echo '```bash' >> release_notes.md
          echo "# Download (use macos-arm64 for Apple Silicon, macos-x86_64 for Intel)" >> release_notes.md
          echo "curl -LO https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.version }}/rustdupe-${VERSION}-macos-arm64.tar.gz" >> release_notes.md
          echo "" >> release_notes.md
          echo "# Extract and make executable" >> release_notes.md
          echo "tar -xzf rustdupe-${VERSION}-macos-arm64.tar.gz" >> release_notes.md
          echo "chmod +x rustdupe" >> release_notes.md
          echo "" >> release_notes.md
          echo "# Remove quarantine attribute (required for unsigned binaries)" >> release_notes.md
          echo "xattr -d com.apple.quarantine rustdupe" >> release_notes.md
          echo "" >> release_notes.md
          echo "# Run" >> release_notes.md
          echo "./rustdupe --help" >> release_notes.md
          echo '```' >> release_notes.md
          echo "" >> release_notes.md
          echo "#### Windows" >> release_notes.md
          echo '```powershell' >> release_notes.md
          echo "# Download and extract" >> release_notes.md
          echo "Invoke-WebRequest -Uri https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare.outputs.version }}/rustdupe-${VERSION}-windows-x86_64.zip -OutFile rustdupe.zip" >> release_notes.md
          echo "Expand-Archive rustdupe.zip -DestinationPath ." >> release_notes.md
          echo "" >> release_notes.md
          echo "# Run" >> release_notes.md
          echo ".\\rustdupe.exe --help" >> release_notes.md
          echo '```' >> release_notes.md
          echo "" >> release_notes.md
          
          # Add "What's Changed" section
          echo "### What's Changed" >> release_notes.md
          
          # Try to get changes since last tag, or all if no tags
          PREV_TAG=$(git describe --tags --abbrev=0 "${{ needs.prepare.outputs.version }}^" 2>/dev/null || echo "")
          if [ -z "$PREV_TAG" ]; then
            git log --pretty=format:"* %s by @${{ github.actor }} in %h" >> release_notes.md
          else
            git log "${PREV_TAG}..${{ needs.prepare.outputs.version }}" --pretty=format:"* %s by @${{ github.actor }} in %h" >> release_notes.md
          fi
          echo "" >> release_notes.md
          echo "" >> release_notes.md
          
          # Add Asset table
          echo "### Assets" >> release_notes.md
          echo "| Filename | Type |" >> release_notes.md
          echo "| -------- | ---- |" >> release_notes.md
          for file in artifacts/*; do
            filename=$(basename "$file")
            if [[ "$filename" == *.sha256 ]]; then
              echo "| $filename | Checksum |" >> release_notes.md
            elif [[ "$filename" == *.exe ]]; then
              echo "| **$filename** | Windows Binary |" >> release_notes.md
            elif [[ "$filename" == *.zip ]]; then
              echo "| **$filename** | Windows Archive |" >> release_notes.md
            elif [[ "$filename" == *.tar.gz ]]; then
              echo "| **$filename** | Unix Archive |" >> release_notes.md
            else
              echo "| **$filename** | Binary |" >> release_notes.md
            fi
          done
          
          # Append Full Changelog link
          echo "" >> release_notes.md
          echo "**Full Changelog**: ${{ github.server_url }}/${{ github.repository }}/commits/${{ needs.prepare.outputs.version }}" >> release_notes.md

      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ needs.prepare.outputs.version }}
          body_path: release_notes.md
          files: artifacts/*
          draft: false
          prerelease: false
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}