name: "π Auto Release on Main"
# π― Workflow Logic:
# 1. Triggers ONLY when tests pass successfully on main branch
# 2. Skips release for version bump commits to avoid infinite loops
# 3. Supports manual triggering via workflow_dispatch
# 4. Auto-increments version and creates GitHub release
on:
workflow_run:
workflows: ["π§ͺ Tests & Quality with UV"]
types:
- completed
branches: [ main ]
workflow_dispatch:
inputs:
version_type:
description: 'Version bump type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
# Permissions needed for auto-release
permissions:
contents: write # Required to push commits and create tags
actions: write # Required to trigger workflows
issues: read # Required for workflow_run events
# Cancel previous runs when new commits are pushed
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# Check if tests passed and determine if we should release
check-prerequisites:
name: "π Check Prerequisites"
runs-on: ubuntu-latest
# Only run if tests passed successfully OR manual trigger
if: github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch'
outputs:
should_release: ${{ steps.check.outputs.should_release }}
is_manual: ${{ steps.check.outputs.is_manual }}
trigger_type: ${{ steps.check.outputs.trigger_type }}
steps:
- name: "π₯ Checkout repository"
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: "π Check prerequisites"
id: check
run: |
EVENT_NAME="${{ github.event_name }}"
IS_MANUAL="${{ github.event_name == 'workflow_dispatch' }}"
echo "Event: $EVENT_NAME"
echo "Is manual: $IS_MANUAL"
if [[ "$IS_MANUAL" == "true" ]]; then
echo "should_release=true" >> $GITHUB_OUTPUT
echo "trigger_type=manual" >> $GITHUB_OUTPUT
echo "π Manual release triggered"
elif [[ "$EVENT_NAME" == "workflow_run" ]]; then
# Triggered by test workflow completion
WORKFLOW_CONCLUSION="${{ github.event.workflow_run.conclusion }}"
COMMIT_SHA="${{ github.event.workflow_run.head_sha }}"
echo "Tests workflow conclusion: $WORKFLOW_CONCLUSION"
echo "Commit SHA: $COMMIT_SHA"
# Get commit message from the triggering commit
git fetch origin $COMMIT_SHA
COMMIT_MSG=$(git log --format=%B -n 1 $COMMIT_SHA)
echo "Commit message: $COMMIT_MSG"
# Skip if this is a version bump commit or release commit
if [[ "$COMMIT_MSG" =~ ^(chore|bump|release):.*version.* ]] || [[ "$COMMIT_MSG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "should_release=false" >> $GITHUB_OUTPUT
echo "trigger_type=workflow_run" >> $GITHUB_OUTPUT
echo "βοΈ Skipping auto-release (version bump or release commit)"
elif [[ "$WORKFLOW_CONCLUSION" == "success" ]]; then
echo "should_release=true" >> $GITHUB_OUTPUT
echo "trigger_type=workflow_run" >> $GITHUB_OUTPUT
echo "β
Tests passed on main branch, proceeding with auto-release"
else
echo "should_release=false" >> $GITHUB_OUTPUT
echo "trigger_type=workflow_run" >> $GITHUB_OUTPUT
echo "β Tests failed, skipping auto-release"
fi
echo "is_manual=$IS_MANUAL" >> $GITHUB_OUTPUT
# Version bump and release
auto-release:
name: "π Auto Version Bump & Release"
runs-on: ubuntu-latest
needs: check-prerequisites
if: needs.check-prerequisites.outputs.should_release == 'true'
outputs:
new_version: ${{ steps.bump.outputs.new_version }}
tag_name: ${{ steps.bump.outputs.tag_name }}
steps:
- name: "π₯ Checkout repository"
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true
- name: "β‘ Install UV"
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
export PATH="$HOME/.cargo/bin:$PATH"
- name: "π Set up Python"
run: |
export PATH="$HOME/.cargo/bin:$PATH"
uv python install 3.10
- name: "π¦ Install dependencies"
run: |
export PATH="$HOME/.cargo/bin:$PATH"
uv sync --all-groups
- name: "π Determine version bump type"
id: version-type
run: |
if [[ "${{ needs.check-prerequisites.outputs.is_manual }}" == "true" ]]; then
VERSION_TYPE="${{ github.event.inputs.version_type }}"
else
# Get commit SHA from workflow_run event or current HEAD
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
COMMIT_SHA="${{ github.event.workflow_run.head_sha }}"
git fetch origin $COMMIT_SHA
COMMIT_MSG=$(git log --format=%B -n 1 $COMMIT_SHA)
else
COMMIT_MSG=$(git log -1 --pretty=%B)
fi
echo "Analyzing commit: $COMMIT_MSG"
# Auto-determine based on commit message
if echo "$COMMIT_MSG" | grep -i "breaking\|major"; then
VERSION_TYPE="major"
elif echo "$COMMIT_MSG" | grep -i "feat\|feature\|minor"; then
VERSION_TYPE="minor"
else
VERSION_TYPE="patch"
fi
fi
echo "version_type=$VERSION_TYPE" >> $GITHUB_OUTPUT
echo "π Version bump type: $VERSION_TYPE"
- name: "π Bump version"
id: bump
run: |
export PATH="$HOME/.cargo/bin:$PATH"
# Get current version using tomli (compatible with Python 3.10)
CURRENT_VERSION=$(uv run python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "Current version: $CURRENT_VERSION"
# Calculate new version
VERSION_TYPE="${{ steps.version-type.outputs.version_type }}"
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
case $VERSION_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
TAG_NAME="v$NEW_VERSION"
echo "New version: $NEW_VERSION"
echo "Tag name: $TAG_NAME"
# Update pyproject.toml
sed -i "s/version = \"$CURRENT_VERSION\"/version = \"$NEW_VERSION\"/" pyproject.toml
# Verify update using tomli
UPDATED_VERSION=$(uv run python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")
echo "Updated version: $UPDATED_VERSION"
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
- name: "π Commit version bump"
run: |
git config --global user.email "action@github.com"
git config --global user.name "GitHub Action"
git add pyproject.toml
git commit -m "chore: bump version to ${{ steps.bump.outputs.new_version }}"
git push origin HEAD:main
- name: "π·οΈ Create and push tag"
run: |
git tag ${{ steps.bump.outputs.tag_name }}
git push origin ${{ steps.bump.outputs.tag_name }}
echo "β
Created and pushed tag: ${{ steps.bump.outputs.tag_name }}"
- name: "π Trigger release workflow via repository_dispatch"
uses: actions/github-script@v7
with:
script: |
console.log('π Triggering release workflow for tag: ${{ steps.bump.outputs.tag_name }}');
// Use repository_dispatch as fallback if workflow_dispatch fails
try {
const response = await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'release.yml',
ref: 'main',
inputs: {
tag_name: '${{ steps.bump.outputs.tag_name }}',
version: '${{ steps.bump.outputs.new_version }}'
}
});
console.log('β
Release workflow triggered via workflow_dispatch');
} catch (error) {
console.log('β οΈ workflow_dispatch failed, trying repository_dispatch...');
const dispatchResponse = await github.rest.repos.createDispatchEvent({
owner: context.repo.owner,
repo: context.repo.repo,
event_type: 'release-trigger',
client_payload: {
tag_name: '${{ steps.bump.outputs.tag_name }}',
version: '${{ steps.bump.outputs.new_version }}',
ref: 'main'
}
});
console.log('β
Release workflow triggered via repository_dispatch');
}
console.log('π¦ Release will be created for version ${{ steps.bump.outputs.new_version }}');
# Summary
summary:
name: "π Auto-Release Summary"
runs-on: ubuntu-latest
needs: [check-prerequisites, auto-release]
if: always()
steps:
- name: "π Summary"
run: |
echo "## π Auto-Release Summary"
echo "**Should Release:** ${{ needs.check-prerequisites.outputs.should_release }}"
echo "**Release Result:** ${{ needs.auto-release.result }}"
if [[ "${{ needs.auto-release.result }}" == "success" ]]; then
echo "β
**Success!** New version ${{ needs.auto-release.outputs.new_version }} created"
echo "π·οΈ **Tag:** ${{ needs.auto-release.outputs.tag_name }}"
echo "π¦ **Release workflow triggered automatically**"
elif [[ "${{ needs.check-prerequisites.outputs.should_release }}" == "false" ]]; then
echo "βοΈ **Skipped** - Prerequisites not met (version bump commit or tests failed)"
else
echo "β **Failed** - Auto-release encountered an error"
fi