Skip to content

Instantly share code, notes, and snippets.

@o6uoq
Created March 30, 2026 10:11
Show Gist options
  • Select an option

  • Save o6uoq/15f15e86a5eb4ffceb10bc8e9a5517af to your computer and use it in GitHub Desktop.

Select an option

Save o6uoq/15f15e86a5eb4ffceb10bc8e9a5517af to your computer and use it in GitHub Desktop.
Conventional Commits → Semver Bump Calculator
# ============================================================================
# Conventional Commits → Semver Bump Calculator
# ============================================================================
# Drop this into your workflow. On PR merge to main, it reads the commits
# from the merged PR and outputs the bump type (major|minor|patch) plus
# the next version string.
#
# Follows the Conventional Commits v1.0.0 spec:
# - fix: → patch
# - feat: → minor
# - BREAKING CHANGE: (footer) or feat!:/fix!:/etc. → major
# - Anything else (chore, docs, ci, etc.) → patch (safe default)
#
# Outputs:
# steps.semver.outputs.bump → major | minor | patch
# steps.semver.outputs.version → e.g. 1.3.0
# steps.semver.outputs.previous → e.g. 1.2.4
#
# Prerequisites:
# - CURRENT_VERSION env var: set this to your artifact's current version.
# Pull it from wherever you track it — a VERSION file, your last Nexus
# tag, a git tag, whatever. The snippet doesn't care where it comes from.
# ============================================================================
name: Build on PR Merge
on:
pull_request:
types: [closed]
branches: [main]
jobs:
build:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Fetch full history so we can read the PR's commits
fetch-depth: 0
# ----------------------------------------------------------------
# Set your current version here. Replace this with however you
# track it — VERSION file, last Nexus tag, git tag, etc.
# ----------------------------------------------------------------
# Examples:
# CURRENT_VERSION=$(cat VERSION)
# CURRENT_VERSION=$(curl -s https://nexus.example.com/api/latest/my-image | jq -r .version)
# CURRENT_VERSION=$(git tag -l 'my-service-v*' --sort=-v:refname | head -1 | sed 's/my-service-v//')
# ----------------------------------------------------------------
- name: Calculate semver bump from PR commits
id: semver
env:
CURRENT_VERSION: "0.0.0" # ← replace with your lookup
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
echo "::group::PR Commits"
# Get commit messages from this PR only (base...head)
COMMITS=$(git log --format="%s%n%b" "${PR_BASE_SHA}..${PR_HEAD_SHA}")
echo "$COMMITS"
echo "::endgroup::"
# Determine bump type — highest wins
BUMP="patch"
while IFS= read -r line; do
# Skip empty lines
[[ -z "$line" ]] && continue
# Major: BREAKING CHANGE footer or ! after type
if echo "$line" | grep -qE '^BREAKING CHANGE:|^BREAKING-CHANGE:'; then
BUMP="major"
break
fi
if echo "$line" | grep -qE '^[a-z]+(\(.+\))?!:'; then
BUMP="major"
break
fi
# Minor: feat
if echo "$line" | grep -qE '^feat(\(.+\))?:'; then
[[ "$BUMP" != "major" ]] && BUMP="minor"
fi
# patch: fix, or anything else with a type — already the default
done <<< "$COMMITS"
# Apply bump to current version
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
MAJOR=${MAJOR:-0}; MINOR=${MINOR:-0}; PATCH=${PATCH:-0}
case "$BUMP" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
NEXT_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Previous : $CURRENT_VERSION"
echo "Bump : $BUMP"
echo "Next : $NEXT_VERSION"
# Set outputs for downstream steps
echo "bump=$BUMP" >> "$GITHUB_OUTPUT"
echo "version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
echo "previous=$CURRENT_VERSION" >> "$GITHUB_OUTPUT"
# ----------------------------------------------------------------
# Use the outputs downstream, e.g.:
# ----------------------------------------------------------------
# - name: Build and push image
# run: |
# docker build -t my-registry/my-image:${{ steps.semver.outputs.version }} .
# docker push my-registry/my-image:${{ steps.semver.outputs.version }}
# ----------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment