Skip to content

Prepare Release

Prepare Release #14

name: Prepare Release
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to release from'
required: true
default: 'main'
version:
description: 'Version to release (e.g. 0.58.0) — leave blank for minor bump'
required: false
default: ''
jobs:
prepare-release:
name: Prepare Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
fetch-depth: 0
- name: Compute version and generate release notes
id: release
env:
VERSION_INPUT: ${{ github.event.inputs.version }}
run: |
pip install packaging -q
# ── Current version from pyproject.toml ─────────────────────────────
CURRENT_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)
echo "Current version: $CURRENT_VERSION"
# ── Resolve target version ───────────────────────────────────────────
if [[ -n "$VERSION_INPUT" ]]; then
python3 .github/scripts/validate_version_input.py
python3 .github/scripts/check_version_bump.py "$CURRENT_VERSION" "$VERSION_INPUT"
NEW_VERSION="$VERSION_INPUT"
else
# Default: minor bump
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
NEW_VERSION="${MAJOR}.$((MINOR + 1)).0"
fi
TAG="v${NEW_VERSION}"
echo "Version: $CURRENT_VERSION → $NEW_VERSION"
# ── Latest tag for commit log baseline ──────────────────────────────
LATEST_TAG=$(git tag --merged HEAD --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$' | head -1)
if [[ -z "$LATEST_TAG" ]]; then
echo "No semver tag found — using initial commit as baseline."
BASELINE=$(git rev-list --max-parents=0 HEAD)
else
BASELINE="$LATEST_TAG"
fi
LOG=$(git log "$BASELINE"..HEAD --pretty=format:"%H %s" --reverse)
if [[ -z "$LOG" ]]; then
echo "ERROR: No commits since $BASELINE. Nothing to release."
exit 1
fi
echo "Commits since $BASELINE:"
echo "$LOG"
# ── Generate release notes ───────────────────────────────────────────
FEATURES=""
FIXES=""
IMPROVEMENTS=""
BREAKING=""
while IFS= read -r line; do
SHA="${line%% *}"
SHORT_SHA="${SHA:0:7}"
SUBJECT="${line#* }"
if echo "$SUBJECT" | grep -qE '(\w+)(\(.+\))?!:|BREAKING CHANGE'; then
SCOPE=$(echo "$SUBJECT" | sed -E 's/^[a-z]+\(([^)]+)\)!:.*/\1/' | sed -E 's/^[a-z]+!:.*//')
DESC=$(echo "$SUBJECT" | sed -E 's/^[a-z]+(\([^)]+\))?!: //')
if [[ -n "$SCOPE" ]]; then
BREAKING+="- **[$SCOPE]**: $DESC (commit $SHORT_SHA)"$'\n'
else
BREAKING+="- $DESC (commit $SHORT_SHA)"$'\n'
fi
elif echo "$SUBJECT" | grep -qE '^feat(\(.+\))?:'; then
SCOPE=$(echo "$SUBJECT" | sed -E 's/^feat\(([^)]+)\):.*/\1/')
DESC=$(echo "$SUBJECT" | sed -E 's/^feat(\([^)]+\))?: //')
if [[ "$SCOPE" != "$SUBJECT" ]]; then
FEATURES+="- **[$SCOPE]**: $DESC (commit $SHORT_SHA)"$'\n'
else
FEATURES+="- $DESC (commit $SHORT_SHA)"$'\n'
fi
elif echo "$SUBJECT" | grep -qE '^fix(\(.+\))?:'; then
SCOPE=$(echo "$SUBJECT" | sed -E 's/^fix\(([^)]+)\):.*/\1/')
DESC=$(echo "$SUBJECT" | sed -E 's/^fix(\([^)]+\))?: //')
if [[ "$SCOPE" != "$SUBJECT" ]]; then
FIXES+="- **[$SCOPE]**: $DESC (commit $SHORT_SHA)"$'\n'
else
FIXES+="- $DESC (commit $SHORT_SHA)"$'\n'
fi
elif echo "$SUBJECT" | grep -qE '^(refactor|perf|chore|docs|test|ci|style|build|revert)(\(.+\))?:'; then
SCOPE=$(echo "$SUBJECT" | sed -E 's/^[a-z]+\(([^)]+)\):.*/\1/')
DESC=$(echo "$SUBJECT" | sed -E 's/^[a-z]+(\([^)]+\))?: //')
if [[ "$SCOPE" != "$SUBJECT" ]]; then
IMPROVEMENTS+="- **[$SCOPE]**: $DESC (commit $SHORT_SHA)"$'\n'
else
IMPROVEMENTS+="- $DESC (commit $SHORT_SHA)"$'\n'
fi
fi
done <<< "$LOG"
# ── Build release notes markdown ─────────────────────────────────────
NOTES=""
if [[ -n "$BREAKING" ]]; then
NOTES+="### Breaking Changes"$'\n'
NOTES+="> ⚠️ **Important**: Review migration steps before upgrading from \`$BASELINE\`."$'\n'
NOTES+="$BREAKING"$'\n'
fi
if [[ -n "$FEATURES" ]]; then
NOTES+="### What's New"$'\n'
NOTES+="$FEATURES"$'\n'
fi
if [[ -n "$FIXES" ]]; then
NOTES+="### Bug Fixes"$'\n'
NOTES+="$FIXES"$'\n'
fi
if [[ -n "$IMPROVEMENTS" ]]; then
NOTES+="### Improvements"$'\n'
NOTES+="$IMPROVEMENTS"$'\n'
fi
printf '%s' "$NOTES" > /tmp/release_notes.md
echo "Release notes:"
cat /tmp/release_notes.md
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Create draft GitHub Release
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.release.outputs.version }}
TAG: ${{ steps.release.outputs.tag }}
BRANCH: ${{ github.event.inputs.branch }}
run: |
IS_PRERELEASE=$(python3 .github/scripts/check_prerelease.py)
PRERELEASE_FLAG=""
if [[ "$IS_PRERELEASE" == "true" ]]; then
PRERELEASE_FLAG="--prerelease"
fi
gh release create "$TAG" \
--repo "${{ github.repository }}" \
--title "v${VERSION}" \
--notes-file /tmp/release_notes.md \
--draft \
$PRERELEASE_FLAG \
--target "$BRANCH"
echo "Draft release created: $TAG (branch: $BRANCH, prerelease: $IS_PRERELEASE)"
echo "Review and edit at: https://github.com/${{ github.repository }}/releases"
- name: Generate bot token
id: bot_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLOUD_SDK_BOT_APP_ID }}
private-key: ${{ secrets.CLOUD_SDK_BOT_PRIVATE_KEY }}
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Bump version and open auto-merge PR
env:
GH_TOKEN: ${{ steps.bot_token.outputs.token }}
VERSION: ${{ steps.release.outputs.version }}
TAG: ${{ steps.release.outputs.tag }}
BRANCH: ${{ github.event.inputs.branch }}
run: |
RELEASE_BRANCH="release-action-${TAG}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$RELEASE_BRANCH"
CURRENT=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)
python .github/scripts/check_version_bump.py "$CURRENT" "$VERSION"
sed -i "s/^version = \"${CURRENT}\"/version = \"${VERSION}\"/" pyproject.toml
grep '^version = ' pyproject.toml
echo "Updated pyproject.toml → ${VERSION}"
uv lock
git add pyproject.toml uv.lock
git commit -m "chore(release): bump version to ${VERSION}"
git push origin "$RELEASE_BRANCH"
PR_URL=$(gh pr create \
--repo "${{ github.repository }}" \
--base "$BRANCH" \
--head "$RELEASE_BRANCH" \
--title "chore(release): bump version to ${VERSION}" \
--body "Automated version bump for [v${VERSION}](https://github.com/${{ github.repository }}/releases/tag/${TAG}). Merges automatically.")
echo "PR created: $PR_URL"
gh pr merge "$PR_URL" \
--repo "${{ github.repository }}" \
--squash \
--admin \
--delete-branch
echo "PR merged and release branch deleted."