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