name: Shared - Semantic Versioning
on: workflow_call: inputs: runner: description: 'Runner type' required: false type: string default: 'ubuntu-latest' update_changelog: description: 'Generate and commit CHANGELOG.md' required: false type: boolean default: false tag_prefix: description: 'Prefix for version tags' required: false type: string default: 'v' major_pattern: description: 'Regex pattern for major version bump' required: false type: string default: '(MAJOR|BREAKING CHANGE)' minor_pattern: description: 'Regex pattern for minor version bump' required: false type: string default: '(feat)'
outputs: version: description: 'Semantic version (e.g. 1.2.3)' value: ${{ jobs.semver.outputs.version }} version_tag: description: 'Semantic version tag (e.g. v1.2.3)' value: ${{ jobs.semver.outputs.version_tag }}
# Least privilege for what this workflow actually does. Without it the# job inherits whatever the calling repository's default happens to be,# which on older repositories is write-all.permissions: contents: write
jobs: semver: name: Semantic Version runs-on: ${{ inputs.runner }} timeout-minutes: 10
outputs: version: ${{ steps.semver.outputs.version }} version_tag: ${{ steps.semver.outputs.version_tag }}
steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0
- name: Fetch all tags run: git fetch --tags --force
- name: Calculate semantic version id: semver env: TAG_PREFIX: ${{ inputs.tag_prefix }} MAJOR_PATTERN: ${{ inputs.major_pattern }} MINOR_PATTERN: ${{ inputs.minor_pattern }} run: | LAST_TAG=$(git tag -l "${TAG_PREFIX}*" --sort=-v:refname | head -n1) if [ -z "$LAST_TAG" ]; then LAST_TAG="${TAG_PREFIX}0.0.0" echo "No previous tag found, starting from $LAST_TAG" else echo "Last tag: $LAST_TAG" fi
LAST_VERSION="${LAST_TAG#"${TAG_PREFIX}"}" IFS='.' read -r MAJOR MINOR PATCH <<< "$LAST_VERSION"
if git rev-parse "$LAST_TAG" >/dev/null 2>&1; then COMMITS=$(git log "${LAST_TAG}..HEAD" --pretty=format:'%s%n%b') else COMMITS=$(git log --pretty=format:'%s%n%b') fi
if [ -z "$COMMITS" ]; then echo "No new commits since $LAST_TAG" VERSION="${MAJOR}.${MINOR}.${PATCH}" elif echo "$COMMITS" | grep -qE "$MAJOR_PATTERN"; then MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 VERSION="${MAJOR}.${MINOR}.${PATCH}" elif echo "$COMMITS" | grep -qE "$MINOR_PATTERN"; then MINOR=$((MINOR + 1)); PATCH=0 VERSION="${MAJOR}.${MINOR}.${PATCH}" else PATCH=$((PATCH + 1)) VERSION="${MAJOR}.${MINOR}.${PATCH}" fi
echo "Calculated version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "version_tag=${TAG_PREFIX}${VERSION}" >> "$GITHUB_OUTPUT"
- name: Export version run: | echo "VERSION=${{ steps.semver.outputs.version_tag }}" >> $GITHUB_ENV echo "### Version: ${{ steps.semver.outputs.version_tag }}" >> $GITHUB_STEP_SUMMARY
- name: Update CHANGELOG.md if: inputs.update_changelog env: TAG_PREFIX: ${{ inputs.tag_prefix }} run: | set -e TODAY="$(date +%Y-%m-%d)" VERSION_LINE="## ${VERSION} - ${TODAY}"
PREV_TAG=$(git tag -l "${TAG_PREFIX}*" --sort=-v:refname | head -n1) if [ -n "$PREV_TAG" ] && git rev-parse "$PREV_TAG" >/dev/null 2>&1; then RANGE="${PREV_TAG}..HEAD" else RANGE="$(git rev-list --max-parents=0 HEAD)^..HEAD" fi
COMMITS="$(git log --pretty=format:'- %s (%h)' ${RANGE})" echo "Preparing CHANGELOG for ${VERSION}"
if [ -f CHANGELOG.md ]; then tail -n +1 CHANGELOG.md > /tmp/CHANGELOG_OLD.md else touch /tmp/CHANGELOG_OLD.md fi
{ echo "${VERSION_LINE}" echo "" if [ -n "${COMMITS}" ]; then echo "${COMMITS}" else echo "- No changes recorded." fi echo "" cat /tmp/CHANGELOG_OLD.md } > CHANGELOG.md
- name: Commit and push CHANGELOG if: inputs.update_changelog env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add CHANGELOG.md if ! git diff --cached --quiet; then git commit -m "chore(release): ${VERSION} - update CHANGELOG [skip ci]" git push else echo "No CHANGELOG changes to commit." fiShared (cross-cutting)·Reusable workflow·on: workflow_call
Shared Semver
Shared - Semantic Versioning
.github/workflows/shared-semver.yml