Skip to content

Release

Release #13

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
version-bump:
description: 'Version bump type (major, minor, or patch)'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
issues: write
jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run all tests
run: ./tests/run-all-tests.sh
release:
name: Create Release
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git-cliff
- name: Set up Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine next version
id: version
run: |
# Get the latest tag
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
latest_version=${latest_tag#v}
# Split version into components
IFS='.' read -r -a version_parts <<< "$latest_version"
major=${version_parts[0]}
minor=${version_parts[1]}
patch=${version_parts[2]}
# Bump version based on input
case "${{ github.event.inputs.version-bump }}" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
esac
new_version="v$major.$minor.$patch"
echo "new_version=$new_version" >> $GITHUB_OUTPUT
- name: Generate release notes
id: release_notes
run: |
# Get the latest tag
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
# Generate release notes from commits since last tag
echo "## What's Changed" > release_notes.md
echo "" >> release_notes.md
# Get commits since last tag
if [ "$latest_tag" != "v0.0.0" ]; then
git log --pretty=format:"- %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" ${latest_tag}..HEAD >> release_notes.md
else
git log --pretty=format:"- %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" >> release_notes.md
fi
echo "" >> release_notes.md
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${latest_tag}...${{ steps.version.outputs.new_version }}" >> release_notes.md
# Set output
{
echo 'content<<EOF'
cat release_notes.md
echo EOF
} >> $GITHUB_OUTPUT
- name: Generate and update CHANGELOG.md
run: |
#!/bin/bash
set -euo pipefail
# Get the latest tag for comparison
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
echo "Generating changelog for ${{ steps.version.outputs.new_version }} (since $latest_tag)"
# Create temporary files for categorizing commits
added_file=$(mktemp)
changed_file=$(mktemp)
fixed_file=$(mktemp)
security_file=$(mktemp)
deprecated_file=$(mktemp)
removed_file=$(mktemp)
other_file=$(mktemp)
# Get commits since last tag
if [ "$latest_tag" != "v0.0.0" ]; then
commits=$(git log --pretty=format:"%s" ${latest_tag}..HEAD)
else
commits=$(git log --pretty=format:"%s")
fi
# Process each commit and categorize based on conventional commits
echo "$commits" | while IFS= read -r commit; do
case "$commit" in
feat\(*|feat:*)
echo "- ${commit#feat*: }" >> "$added_file"
;;
fix\(*|fix:*)
echo "- ${commit#fix*: }" >> "$fixed_file"
;;
security\(*|security:*)
echo "- ${commit#security*: }" >> "$security_file"
;;
deprecate\(*|deprecate:*|deprecated\(*|deprecated:*)
echo "- ${commit#deprecat*: }" >> "$deprecated_file"
;;
remove\(*|remove:*|removed\(*|removed:*)
echo "- ${commit#remov*: }" >> "$removed_file"
;;
docs\(*|docs:*|chore\(*|chore:*|refactor\(*|refactor:*|perf\(*|perf:*|style\(*|style:*|test\(*|test:*)
echo "- ${commit}" >> "$changed_file"
;;
*)
echo "- ${commit}" >> "$other_file"
;;
esac
done
# Create the new changelog entry
{
echo "## [${{ steps.version.outputs.new_version }}] - $(date +%Y-%m-%d)"
echo ""
} > new_entry.md
# Add categorized changes following Keep a Changelog format
for category in "Added:$added_file" "Changed:$changed_file" "Deprecated:$deprecated_file" "Removed:$removed_file" "Fixed:$fixed_file" "Security:$security_file"; do
category_name="${category%:*}"
file_path="${category#*:}"
if [ -s "$file_path" ]; then
echo "### $category_name" >> new_entry.md
echo "" >> new_entry.md
cat "$file_path" >> new_entry.md
echo "" >> new_entry.md
fi
done
# Add other changes if any
if [ -s "$other_file" ]; then
echo "### Other Changes" >> new_entry.md
echo "" >> new_entry.md
cat "$other_file" >> new_entry.md
echo "" >> new_entry.md
fi
# Create or update the complete changelog
{
echo "# Changelog"
echo ""
echo "All notable changes to this project will be documented in this file."
echo ""
echo "The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),"
echo "and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)."
echo ""
} > CHANGELOG.md
# Add the new entry
cat new_entry.md >> CHANGELOG.md
# Preserve existing changelog entries if they exist
if git show HEAD:CHANGELOG.md >/dev/null 2>&1; then
# Get existing entries (skip header and current version if it exists)
git show HEAD:CHANGELOG.md | sed -n '/^## \[/,$p' | grep -v "^## \[${{ steps.version.outputs.new_version }}\]" >> CHANGELOG.md 2>/dev/null || true
fi
# Add version links at the bottom
echo "" >> CHANGELOG.md
# Get all tags for version links
all_tags=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true)
if [ -n "$all_tags" ]; then
prev_tag=""
echo "$all_tags" | while IFS= read -r tag; do
version="${tag#v}"
if [ -z "$prev_tag" ]; then
# First tag (latest) - compare with HEAD
echo "[$version]: https://github.com/${{ github.repository }}/compare/$tag...HEAD" >> CHANGELOG.md
else
# Compare with previous tag
echo "[$version]: https://github.com/${{ github.repository }}/compare/$tag...$prev_tag" >> CHANGELOG.md
fi
prev_tag="$tag"
done
# Add link for the oldest version
if [ -n "$prev_tag" ]; then
oldest_version="${prev_tag#v}"
echo "[$oldest_version]: https://github.com/${{ github.repository }}/releases/tag/$prev_tag" >> CHANGELOG.md
fi
fi
# Cleanup temporary files
rm -f "$added_file" "$changed_file" "$fixed_file" "$security_file" "$deprecated_file" "$removed_file" "$other_file" new_entry.md
echo "CHANGELOG.md generated successfully"
- name: Commit and push CHANGELOG.md
run: |
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG.md for ${{ steps.version.outputs.new_version }}"
git push
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ steps.version.outputs.new_version }}
name: ${{ steps.version.outputs.new_version }}
body: ${{ steps.release_notes.outputs.content }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Process and close issues
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NOTES: ${{ steps.release_notes.outputs.content }}
RELEASE_VERSION: ${{ steps.version.outputs.new_version }}
run: |
#!/bin/bash
set -euo pipefail
# Extract issue numbers from release notes (using basic grep instead of -P for compatibility)
issue_numbers=$(echo "$RELEASE_NOTES" | grep -o '#[0-9]\+' | sed 's/#//' | sort -u || true)
if [ -z "$issue_numbers" ]; then
echo "No issues found in release notes to process."
echo "This is normal for releases that don't reference specific issues."
exit 0
fi
echo "Found issues: $issue_numbers"
for issue_number in $issue_numbers; do
echo "Processing issue #$issue_number"
# Check if issue exists and is open
if ! gh issue view "$issue_number" --json state -q .state >/dev/null 2>&1; then
echo "Issue #$issue_number does not exist, skipping."
continue
fi
issue_state=$(gh issue view "$issue_number" --json state -q .state)
if [ "$issue_state" != "OPEN" ]; then
echo "Issue #$issue_number is not open, skipping."
continue
fi
# Add label, comment and close issue
gh issue edit "$issue_number" --add-label "released" || echo "Failed to add label to issue #$issue_number"
gh issue comment "$issue_number" --body "🎉 This issue has been released in version $RELEASE_VERSION." || echo "Failed to comment on issue #$issue_number"
gh issue close "$issue_number" || echo "Failed to close issue #$issue_number"
echo "Processed issue #$issue_number"
done
echo "Issue processing completed."