From 64e5bf0b7202eef296e8e9be37b16cc07713b735 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 25 Mar 2026 13:57:41 -0500 Subject: [PATCH] ci: add automated version bump and tagging from conventional commits Add version.yml workflow that runs on pushes to main, parses conventional commit messages since the last tag, determines the semver bump (feat=minor, fix=patch, breaking=major), updates Cargo.toml version, commits, and pushes a version tag. The tag push triggers the existing release.yml publish workflow. Skips chore(release) commits to prevent infinite loops. --- .github/workflows/version.yml | 99 +++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/version.yml diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..adb8140 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,99 @@ +name: Version + +on: + push: + branches: [main] + +permissions: + contents: write + +jobs: + version: + name: Bump version and tag + runs-on: ubuntu-latest + # Skip if the push was a version bump commit (prevent infinite loop) + if: "!startsWith(github.event.head_commit.message, 'chore(release):')" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get latest tag + id: latest_tag + run: | + tag=$(git tag -l 'v*' --sort=-v:refname | head -n1) + if [ -z "$tag" ]; then + tag="v0.0.0" + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "Latest tag: $tag" + + - name: Determine version bump from commits + id: bump + run: | + latest_tag="${{ steps.latest_tag.outputs.tag }}" + current="${latest_tag#v}" + IFS='.' read -r major minor patch <<< "$current" + + # Get commit messages since last tag + if git rev-parse "$latest_tag" >/dev/null 2>&1; then + commits=$(git log "$latest_tag"..HEAD --pretty=format:"%s") + else + commits=$(git log --pretty=format:"%s") + fi + + if [ -z "$commits" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + bump="none" + while IFS= read -r msg; do + if echo "$msg" | grep -qiE '^feat(\(.+\))?!:|^[a-z]+(\(.+\))?!:|BREAKING CHANGE'; then + bump="major" + break + elif echo "$msg" | grep -qiE '^feat(\(.+\))?:'; then + if [ "$bump" != "major" ]; then + bump="minor" + fi + elif echo "$msg" | grep -qiE '^fix(\(.+\))?:'; then + if [ "$bump" = "none" ]; then + bump="patch" + fi + fi + done <<< "$commits" + + if [ "$bump" = "none" ]; then + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + case "$bump" in + major) major=$((major + 1)); minor=0; patch=0 ;; + minor) minor=$((minor + 1)); patch=0 ;; + patch) patch=$((patch + 1)) ;; + esac + + new_version="${major}.${minor}.${patch}" + echo "version=$new_version" >> "$GITHUB_OUTPUT" + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "Bump: $bump -> v$new_version" + + - name: Update Cargo.toml version + if: steps.bump.outputs.skip != 'true' + run: | + new_version="${{ steps.bump.outputs.version }}" + sed -i "s/^version = \".*\"/version = \"$new_version\"/" Cargo.toml + echo "Updated Cargo.toml to version $new_version" + + - name: Commit and tag + if: steps.bump.outputs.skip != 'true' + run: | + new_version="${{ steps.bump.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Cargo.toml + git commit -m "chore(release): v${new_version}" + git tag "v${new_version}" + git push origin main --tags