Skip to content

Cut Release Tag

Cut Release Tag #13

name: Cut Release Tag
on:
workflow_run:
workflows:
- CI
types:
- completed
workflow_dispatch:
inputs:
bump:
description: SemVer bump type
required: false
default: auto
type: choice
options:
- auto
- patch
- minor
- major
permissions:
actions: write
contents: write
jobs:
cut-tag:
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
- name: Compute next version tag
id: next_tag
env:
BUMP: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.bump || 'auto' }}
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
shell: bash
run: |
set -euo pipefail
EXISTING_TAG="$(git tag --points-at "${TARGET_SHA}" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)"
if [[ -n "${EXISTING_TAG}" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "latest_tag=${EXISTING_TAG}" >> "$GITHUB_OUTPUT"
echo "tag=${EXISTING_TAG}" >> "$GITHUB_OUTPUT"
echo "Target commit already tagged with ${EXISTING_TAG}"
exit 0
fi
LATEST_TAG="$(git tag --list 'v*' --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)"
if [[ -z "${LATEST_TAG}" ]]; then
LATEST_TAG="v0.0.0"
fi
VERSION="${LATEST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<<"${VERSION}"
EFFECTIVE_BUMP="${BUMP}"
BUMP_REASON="Manual bump input"
if [[ "${BUMP}" == "auto" ]]; then
RANGE_SPEC="${LATEST_TAG}..${TARGET_SHA}"
if [[ "${LATEST_TAG}" == "v0.0.0" ]]; then
RANGE_SPEC="${TARGET_SHA}"
elif ! git merge-base --is-ancestor "${LATEST_TAG}" "${TARGET_SHA}"; then
RANGE_SPEC="${TARGET_SHA}"
fi
SUBJECTS="$(git log --format='%s' ${RANGE_SPEC} || true)"
BODIES="$(git log --format='%b' ${RANGE_SPEC} || true)"
if [[ -z "${SUBJECTS}" ]]; then
EFFECTIVE_BUMP="patch"
BUMP_REASON="No commit subjects found in range; defaulted to patch"
elif grep -Eq '^[a-zA-Z]+(\([^)]+\))?!:' <<<"${SUBJECTS}" || grep -Eq 'BREAKING CHANGE:|BREAKING-CHANGE:' <<<"${BODIES}"; then
EFFECTIVE_BUMP="major"
BUMP_REASON="Detected breaking changes in commit messages"
elif grep -Eq '^feat(\([^)]+\))?:' <<<"${SUBJECTS}"; then
EFFECTIVE_BUMP="minor"
BUMP_REASON="Detected feat commits"
elif grep -Eq '^(fix|perf|revert)(\([^)]+\))?:' <<<"${SUBJECTS}"; then
EFFECTIVE_BUMP="patch"
BUMP_REASON="Detected fix/perf/revert commits"
else
EFFECTIVE_BUMP="patch"
BUMP_REASON="No release commit type found; defaulted to patch"
fi
fi
case "${EFFECTIVE_BUMP}" in
patch)
PATCH=$((PATCH + 1))
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
*)
echo "Unsupported bump type: ${EFFECTIVE_BUMP}"
exit 1
;;
esac
NEXT_TAG="v${MAJOR}.${MINOR}.${PATCH}"
if git rev-parse "${NEXT_TAG}" >/dev/null 2>&1; then
echo "Tag ${NEXT_TAG} already exists."
exit 1
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "latest_tag=${LATEST_TAG}" >> "$GITHUB_OUTPUT"
echo "tag=${NEXT_TAG}" >> "$GITHUB_OUTPUT"
echo "bump=${EFFECTIVE_BUMP}" >> "$GITHUB_OUTPUT"
echo "bump_reason=${BUMP_REASON}" >> "$GITHUB_OUTPUT"
- name: Create and push tag
if: steps.next_tag.outputs.skip != 'true'
env:
TARGET_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
shell: bash
run: |
set -euo pipefail
NEXT_TAG="${{ steps.next_tag.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${NEXT_TAG}" "${TARGET_SHA}" -m "Release ${NEXT_TAG}"
git push origin "${NEXT_TAG}"
- name: Trigger release workflows for new tag
if: steps.next_tag.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail
NEXT_TAG="${{ steps.next_tag.outputs.tag }}"
gh workflow run "Release" --ref "${NEXT_TAG}"
gh workflow run "NPM Publish" --ref "${NEXT_TAG}"
- name: Summary
run: |
echo "Created tag: ${{ steps.next_tag.outputs.tag }}"
echo "Previous tag: ${{ steps.next_tag.outputs.latest_tag }}"
echo "Bump: ${{ steps.next_tag.outputs.bump }}"
echo "Reason: ${{ steps.next_tag.outputs.bump_reason }}"