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 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
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
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
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