grubble 4.7.2

Automatic semantic versioning based on conventional commits, optimized for AI-generated commit messages
name: 'Grubble - Semantic Version Bump'
description: 'Automatically bump semantic version based on conventional commits'
author: 'David Garvey'

branding:
  icon: 'arrow-up'
  color: 'orange'

inputs:
  push:
    description: 'Push changes to remote'
    required: false
    default: 'false'
  tag:
    description: 'Create git tag for the version'
    required: false
    default: 'false'
  release-notes:
    description: 'Include release notes in the git tag annotation'
    required: false
    default: 'false'
  raw:
    description: 'Output only the new version string (dry run, no changes)'
    required: false
    default: 'false'
  quiet:
    description: 'Suppress commit list output'
    required: false
    default: 'false'
  preset:
    description: 'Versioning strategy (node, rust, git)'
    required: false
    default: ''
  tag-prefix:
    description: 'Prefix for git tags'
    required: false
    default: ''
  commit-prefix:
    description: 'Prefix for commit messages'
    required: false
    default: ''
  package-files:
    description: 'Comma-separated list of files to update'
    required: false
    default: ''
  git-user-name:
    description: 'Git user name for commits'
    required: false
    default: ''
  git-user-email:
    description: 'Git user email for commits'
    required: false
    default: ''
  update-major-tag:
    description: 'Update major version tag (e.g., v4 -> v4.x.x)'
    required: false
    default: 'false'
  update-minor-tag:
    description: 'Update minor version tag (e.g., v4.1 -> v4.1.x)'
    required: false
    default: 'false'
  changelog:
    description: 'Generate and maintain a CHANGELOG.md file'
    required: false
    default: 'false'

outputs:
  version:
    description: 'The new version number'
    value: ${{ steps.bump.outputs.version }}
  previous-version:
    description: 'The previous version number'
    value: ${{ steps.bump.outputs.previous_version }}
  bump-type:
    description: 'The type of version bump (major, minor, patch, none)'
    value: ${{ steps.bump.outputs.bump_type }}

runs:
  using: 'composite'
  steps:
    - name: Detect OS and Architecture
      id: detect
      shell: bash
      run: |
        OS="${RUNNER_OS}"
        ARCH="${RUNNER_ARCH}"
        
        if [ "$OS" = "Linux" ]; then
          if [ "$ARCH" = "X64" ]; then
            BINARY="grubble-linux-x86_64"
          elif [ "$ARCH" = "ARM64" ]; then
            BINARY="grubble-linux-aarch64"
          else
            echo "::error::Unsupported architecture: $ARCH"
            exit 1
          fi
          ARCHIVE="${BINARY}.tar.gz"
        elif [ "$OS" = "macOS" ]; then
          if [ "$ARCH" = "X64" ]; then
            BINARY="grubble-macos-x86_64"
          elif [ "$ARCH" = "ARM64" ]; then
            BINARY="grubble-macos-aarch64"
          else
            echo "::error::Unsupported architecture: $ARCH"
            exit 1
          fi
          ARCHIVE="${BINARY}.tar.gz"
        elif [ "$OS" = "Windows" ]; then
          if [ "$ARCH" = "X64" ]; then
            BINARY="grubble-windows-x86_64.exe"
          else
            echo "::error::Unsupported architecture: $ARCH"
            exit 1
          fi
          ARCHIVE="${BINARY}.zip"
        else
          echo "::error::Unsupported operating system: $OS"
          exit 1
        fi
        
        echo "binary=$BINARY" >> $GITHUB_OUTPUT
        echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT
        echo "os=$OS" >> $GITHUB_OUTPUT

    - name: Get latest release
      id: release
      shell: bash
      run: |
        RELEASE_URL="https://api.github.com/repos/davegarvey/grubble/releases/latest"
        RELEASE_DATA=$(curl -sS "$RELEASE_URL")
        VERSION=$(echo "$RELEASE_DATA" | jq -r '.tag_name')
        
        if [ "$VERSION" = "null" ] || [ -z "$VERSION" ]; then
          echo "::error::Failed to fetch latest release version"
          exit 1
        fi
        
        echo "version=$VERSION" >> $GITHUB_OUTPUT
        echo "Using grubble version: $VERSION"

    - name: Download binary
      shell: bash
      run: |
        echo "::group::Download grubble binary"
        DOWNLOAD_URL="https://github.com/davegarvey/grubble/releases/download/${{ steps.release.outputs.version }}/${{ steps.detect.outputs.archive }}"
        
        if ! curl -sSL -f -o "${{ steps.detect.outputs.archive }}" "$DOWNLOAD_URL"; then
          echo "::error::Failed to download binary from $DOWNLOAD_URL"
          exit 1
        fi
        
        echo "Downloaded ${{ steps.detect.outputs.archive }}"
        echo "::endgroup::"

    - name: Verify checksum
      shell: bash
      run: |
        echo "::group::Verify checksum"
        CHECKSUM_URL="https://github.com/davegarvey/grubble/releases/download/${{ steps.release.outputs.version }}/${{ steps.detect.outputs.archive }}.sha256"
        
        if ! curl -sSL -f -o "${{ steps.detect.outputs.archive }}.sha256" "$CHECKSUM_URL"; then
          echo "::warning::Failed to download checksum file, skipping verification"
        else
          if [ "${{ steps.detect.outputs.os }}" = "Windows" ]; then
            # Windows verification
            EXPECTED=$(cat "${{ steps.detect.outputs.archive }}.sha256" | awk '{print $1}')
            ACTUAL=$(certutil -hashfile "${{ steps.detect.outputs.archive }}" SHA256 | findstr /v "hash" | findstr /v "CertUtil")
            if [ "$EXPECTED" != "$ACTUAL" ]; then
              echo "::error::Checksum verification failed"
              exit 1
            fi
          else
            # Unix verification
            if ! shasum -a 256 -c "${{ steps.detect.outputs.archive }}.sha256" 2>&1 | grep -v "OK$"; then
              echo "::error::Checksum verification failed"
              exit 1
            fi
          fi
          echo "✓ Checksum verified"
        fi
        echo "::endgroup::"

    - name: Extract binary (Unix)
      if: steps.detect.outputs.os != 'Windows'
      shell: bash
      run: |
        echo "::group::Extract binary"
        tar xzf "${{ steps.detect.outputs.archive }}"
        chmod +x grubble
        echo "✓ Binary ready"
        echo "::endgroup::"

    - name: Extract binary (Windows)
      if: steps.detect.outputs.os == 'Windows'
      shell: pwsh
      run: |
        Write-Output "::group::Extract binary"
        Expand-Archive -Path "${{ steps.detect.outputs.archive }}" -DestinationPath . -Force
        Write-Output "✓ Binary ready"
        Write-Output "::endgroup::"

    - name: Get current version
      id: current
      shell: bash
      run: |
        if [ "${{ steps.detect.outputs.os }}" = "Windows" ]; then
          CURRENT_VERSION=$(./grubble.exe --raw 2>/dev/null || echo "0.0.0")
        else
          CURRENT_VERSION=$(./grubble --raw 2>/dev/null || echo "0.0.0")
        fi
        echo "previous_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT

    - name: Run bump
      id: bump
      shell: bash
      run: |
        ARGS=""
        
        if [ "${{ inputs.push }}" = "true" ]; then
          ARGS="$ARGS --push"
        fi
        
        if [ "${{ inputs.tag }}" = "true" ]; then
          ARGS="$ARGS --tag"
        fi
        
        if [ "${{ inputs.release-notes }}" = "true" ]; then
          ARGS="$ARGS --release-notes"
        fi
        
        if [ "${{ inputs.raw }}" = "true" ]; then
          ARGS="$ARGS --raw"
        fi
        
        if [ "${{ inputs.quiet }}" = "true" ]; then
          ARGS="$ARGS --quiet"
        fi
        
        if [ -n "${{ inputs.preset }}" ]; then
          ARGS="$ARGS --preset ${{ inputs.preset }}"
        fi
        
        if [ -n "${{ inputs.tag-prefix }}" ]; then
          ARGS="$ARGS --tag-prefix ${{ inputs.tag-prefix }}"
        fi
        
        if [ -n "${{ inputs.commit-prefix }}" ]; then
          ARGS="$ARGS --commit-prefix \"${{ inputs.commit-prefix }}\""
        fi
        
        if [ -n "${{ inputs.package-files }}" ]; then
          ARGS="$ARGS --package-files ${{ inputs.package-files }}"
        fi
        
        if [ -n "${{ inputs.git-user-name }}" ]; then
          ARGS="$ARGS --git-user-name \"${{ inputs.git-user-name }}\""
        fi
        
        if [ -n "${{ inputs.git-user-email }}" ]; then
          ARGS="$ARGS --git-user-email \"${{ inputs.git-user-email }}\""
        fi
        
        if [ "${{ inputs.update-major-tag }}" = "true" ]; then
          ARGS="$ARGS --update-major-tag"
        fi
        
        if [ "${{ inputs.update-minor-tag }}" = "true" ]; then
          ARGS="$ARGS --update-minor-tag"
        fi
        
        if [ "${{ inputs.changelog }}" = "true" ]; then
          ARGS="$ARGS --changelog"
        fi
        
        if [ "${{ steps.detect.outputs.os }}" = "Windows" ]; then
          OUTPUT=$(./grubble.exe $ARGS 2>&1) || {
            echo "::error::Grubble command failed: $OUTPUT"
            exit 1
          }
        else
          OUTPUT=$(./grubble $ARGS 2>&1) || {
            echo "::error::Grubble command failed: $OUTPUT"
            exit 1
          }
        fi
        
        echo "$OUTPUT"
        
        # Get the new version
        if [ "${{ steps.detect.outputs.os }}" = "Windows" ]; then
          NEW_VERSION=$(./grubble.exe --raw 2>/dev/null || echo "${{ steps.current.outputs.previous_version }}")
        else
          NEW_VERSION=$(./grubble --raw 2>/dev/null || echo "${{ steps.current.outputs.previous_version }}")
        fi
        
        echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
        
        # Determine bump type
        PREV="${{ steps.current.outputs.previous_version }}"
        if [ "$NEW_VERSION" != "$PREV" ]; then
          PREV_MAJOR=$(echo $PREV | cut -d. -f1)
          PREV_MINOR=$(echo $PREV | cut -d. -f2)
          PREV_PATCH=$(echo $PREV | cut -d. -f3)
          
          NEW_MAJOR=$(echo $NEW_VERSION | cut -d. -f1)
          NEW_MINOR=$(echo $NEW_VERSION | cut -d. -f2)
          NEW_PATCH=$(echo $NEW_VERSION | cut -d. -f3)
          
          if [ "$NEW_MAJOR" != "$PREV_MAJOR" ]; then
            echo "bump_type=major" >> $GITHUB_OUTPUT
          elif [ "$NEW_MINOR" != "$PREV_MINOR" ]; then
            echo "bump_type=minor" >> $GITHUB_OUTPUT
          elif [ "$NEW_PATCH" != "$PREV_PATCH" ]; then
            echo "bump_type=patch" >> $GITHUB_OUTPUT
          else
            echo "bump_type=none" >> $GITHUB_OUTPUT
          fi
        else
          echo "bump_type=none" >> $GITHUB_OUTPUT
        fi