Skip to content

chore(versioning): Add semantic versioning CI action (#3) #1

chore(versioning): Add semantic versioning CI action (#3)

chore(versioning): Add semantic versioning CI action (#3) #1

Workflow file for this run

name: Release
on:
push:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- '.github/**'
- '!.github/workflows/release.yml'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: false
type: choice
options:
- auto
- major
- minor
- patch
default: 'auto'
# Prevent concurrent releases
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write
pull-requests: read
jobs:
release:
name: Create Release
runs-on: ubuntu-latest
# Only run if not a bot commit (to avoid release loops)
if: ${{ !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, 'chore(release)') }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@v2
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Check if release needed
id: check_release
run: |
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [[ -z "$LAST_TAG" ]]; then
echo "No previous tag found, release needed"
echo "release_needed=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check if there are conventional commits since last tag
COMMITS=$(git log "$LAST_TAG"..HEAD --pretty=format:"%s" --no-merges)
if echo "$COMMITS" | grep -qE "^(feat|fix|perf|refactor)(\([a-z-]+\))?(!)?:"; then
echo "Found conventional commits, release needed"
echo "release_needed=true" >> $GITHUB_OUTPUT
else
echo "No conventional commits found, skipping release"
echo "release_needed=false" >> $GITHUB_OUTPUT
fi
- name: Bump version
if: steps.check_release.outputs.release_needed == 'true'
id: bump_version
run: |
chmod +x scripts/bump-version.sh
# Use manual bump type if provided, otherwise auto-detect
BUMP_TYPE="${{ github.event.inputs.bump_type || 'auto' }}"
if [[ "$BUMP_TYPE" == "auto" ]]; then
./scripts/bump-version.sh
else
./scripts/bump-version.sh "$BUMP_TYPE"
fi
- name: Update CHANGELOG
if: steps.check_release.outputs.release_needed == 'true'
run: |
chmod +x scripts/update-changelog.sh
./scripts/update-changelog.sh "${{ steps.bump_version.outputs.new_version }}"
- name: Update README badge
if: steps.check_release.outputs.release_needed == 'true'
run: |
VERSION="${{ steps.bump_version.outputs.new_version }}"
# Update version badge in README.md
sed -i "s/version-[0-9]\+\.[0-9]\+\.[0-9]\+-blue/version-${VERSION}-blue/" README.md
echo "✓ Updated version badge to ${VERSION}"
- name: Extract release notes
if: steps.check_release.outputs.release_needed == 'true'
id: extract_notes
run: |
# Extract the latest version section from CHANGELOG
VERSION="${{ steps.bump_version.outputs.new_version }}"
# Create release notes file
NOTES_FILE=$(mktemp)
# Extract content between [VERSION] and next [VERSION] or end
awk "/^## \[$VERSION\]/ { flag=1; next } /^## \[[0-9]/ { flag=0 } flag" CHANGELOG.md > "$NOTES_FILE"
# Set output
{
echo 'notes<<EOF'
cat "$NOTES_FILE"
echo 'EOF'
} >> $GITHUB_OUTPUT
- name: Commit version bump
if: steps.check_release.outputs.release_needed == 'true'
run: |
git add VERSION CHANGELOG.md README.md
git commit -m "chore(release): bump version to ${{ steps.bump_version.outputs.new_version }} [skip ci]"
git push origin main
- name: Create and push tag
if: steps.check_release.outputs.release_needed == 'true'
run: |
VERSION="${{ steps.bump_version.outputs.new_version }}"
git tag -a "v$VERSION" -m "Release v$VERSION"
git push origin "v$VERSION"
- name: Create GitHub Release
if: steps.check_release.outputs.release_needed == 'true'
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.bump_version.outputs.new_version }}
release_name: v${{ steps.bump_version.outputs.new_version }}
body: ${{ steps.extract_notes.outputs.notes }}
draft: false
prerelease: false
- name: Release summary
if: steps.check_release.outputs.release_needed == 'true'
run: |
echo "## 🚀 Release v${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Previous version:** ${{ steps.bump_version.outputs.old_version }}" >> $GITHUB_STEP_SUMMARY
echo "**New version:** ${{ steps.bump_version.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "**Bump type:** ${{ steps.bump_version.outputs.bump_type }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Release Notes" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.extract_notes.outputs.notes }}" >> $GITHUB_STEP_SUMMARY
- name: Skip summary
if: steps.check_release.outputs.release_needed != 'true'
run: |
echo "## ⏭️ Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "No conventional commits found since last release." >> $GITHUB_STEP_SUMMARY
echo "Release will be created when feat/fix/perf/refactor commits are pushed." >> $GITHUB_STEP_SUMMARY