Skip to content

release

release #2

Workflow file for this run

name: release
# Manual trigger: Actions → release → Run workflow. Always runs against main
# (the workflow validates GITHUB_REF). Steps:
# 1. Compute the new version from the `version` input (patch/minor/major
# or explicit semver). The current version is read from __init__.py.
# 2. Update __version__ in __init__.py and version in plugin.yaml.
# 3. Commit as `chore(release): vX.Y.Z`, tag, push to main + push the tag.
# 4. Publish a GitHub Release. Body is the matching `## [X.Y.Z]` block from
# CHANGELOG.md when present; otherwise auto-generated release notes.
#
# Recommended flow: land a PR that adds a `## [X.Y.Z]` section to CHANGELOG.md
# first, then run this workflow with the matching version so the release notes
# are the hand-written changelog instead of commit-message-derived notes.
on:
workflow_dispatch:
inputs:
version:
description: "Version bump (`patch`, `minor`, `major`) or explicit semver (`0.3.0`)"
required: true
default: "patch"
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
name: Tag and Publish GitHub Release
runs-on: ubuntu-latest
steps:
- name: Validate trigger is main
run: |
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
echo "::error::release must run against main. Got $GITHUB_REF"
exit 1
fi
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute new version
id: bump
run: |
set -euo pipefail
VERSION_INPUT="${{ github.event.inputs.version }}"
# Source of truth for the current version is __init__.py.
CURRENT=$(grep -E '^__version__ = ' __init__.py \
| sed -E 's/^__version__ = "([^"]+)".*/\1/')
if [ -z "$CURRENT" ]; then
echo "::error::Could not read __version__ from __init__.py"
exit 1
fi
echo "Current version: $CURRENT"
if [[ "$VERSION_INPUT" =~ ^(patch|minor|major)$ ]]; then
IFS=. read -r MAJ MIN PAT <<< "$CURRENT"
case "$VERSION_INPUT" in
major) MAJ=$((MAJ + 1)); MIN=0; PAT=0 ;;
minor) MIN=$((MIN + 1)); PAT=0 ;;
patch) PAT=$((PAT + 1)) ;;
esac
NEW="${MAJ}.${MIN}.${PAT}"
elif [[ "$VERSION_INPUT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
NEW="$VERSION_INPUT"
else
echo "::error::Invalid version input: '$VERSION_INPUT'"
echo "::error::Use patch|minor|major or explicit X.Y.Z"
exit 1
fi
if [ "$NEW" = "$CURRENT" ]; then
echo "::error::New version equals current ($NEW). Pick a different version."
exit 1
fi
echo "New version: $NEW"
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
echo "version=$NEW" >> "$GITHUB_OUTPUT"
echo "tag=v$NEW" >> "$GITHUB_OUTPUT"
- name: Refuse if tag already exists
run: |
set -euo pipefail
TAG="${{ steps.bump.outputs.tag }}"
if git rev-parse --verify "refs/tags/$TAG" >/dev/null 2>&1; then
echo "::error::Tag $TAG already exists locally. Pick a different version."
exit 1
fi
if git ls-remote --tags origin "$TAG" | grep -q "refs/tags/$TAG$"; then
echo "::error::Tag $TAG already exists on origin. Pick a different version."
exit 1
fi
- name: Update version files
run: |
set -euo pipefail
NEW="${{ steps.bump.outputs.version }}"
sed -i -E "s/^__version__ = \"[^\"]+\"/__version__ = \"${NEW}\"/" __init__.py
sed -i -E "s/^version: .*/version: ${NEW}/" plugin.yaml
# Verify both files changed and that the new version is present.
grep -q "^__version__ = \"${NEW}\"" __init__.py
grep -q "^version: ${NEW}$" plugin.yaml
echo "--- diff ---"
git --no-pager diff -- __init__.py plugin.yaml
- name: Configure Git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Commit and tag
run: |
set -euo pipefail
TAG="${{ steps.bump.outputs.tag }}"
git add __init__.py plugin.yaml
git commit -m "chore(release): ${TAG}"
git tag -a "${TAG}" -m "${TAG}"
- name: Push commit and tag
run: |
set -euo pipefail
git push origin HEAD:main
git push origin "${{ steps.bump.outputs.tag }}"
- name: Extract CHANGELOG section for this version
id: changelog
run: |
set -euo pipefail
VERSION="${{ steps.bump.outputs.version }}"
# Pull lines between `## [VERSION]` and the next `## [` heading.
SECTION=$(awk -v ver="$VERSION" '
$0 ~ "^## \\[" ver "\\]" { found=1; next }
found && /^## \[/ { exit }
found { print }
' CHANGELOG.md)
if [ -z "$SECTION" ]; then
echo "::warning::No CHANGELOG.md section found for v${VERSION} — falling back to auto-generated release notes."
echo "has_section=false" >> "$GITHUB_OUTPUT"
else
echo "has_section=true" >> "$GITHUB_OUTPUT"
{
echo 'body<<EOF_CHANGELOG'
echo "$SECTION"
echo 'EOF_CHANGELOG'
} >> "$GITHUB_OUTPUT"
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.bump.outputs.tag }}
name: ${{ steps.bump.outputs.tag }}
body: ${{ steps.changelog.outputs.body }}
generate_release_notes: ${{ steps.changelog.outputs.has_section == 'false' }}
token: ${{ secrets.GITHUB_TOKEN }}