chore: migrate java repo to GitHub Release–based publish flow with strict version gating #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Runs only when production code under src/main/ changes. Version must be > latest v* tag | |
| # (not compared to the base branch, so rebases onto an already-bumped main still pass when tag requires a new release). | |
| name: Check Version Bump | |
| on: | |
| pull_request: | |
| jobs: | |
| check-version-bump: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate version and changelog updates | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION_FILE="pom.xml" | |
| CHANGELOG_FILE="CHANGELOG.md" | |
| BASE_SHA="${{ github.event.pull_request.base.sha }}" | |
| HEAD_SHA="${{ github.event.pull_request.head.sha }}" | |
| mapfile -t CHANGED_FILES < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA") | |
| if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then | |
| echo "No changed files detected." | |
| exit 0 | |
| fi | |
| is_production_source_change() { | |
| local f="$1" | |
| [[ "$f" == src/main/* ]] | |
| } | |
| has_source_changes=false | |
| for file in "${CHANGED_FILES[@]}"; do | |
| if is_production_source_change "$file"; then | |
| has_source_changes=true | |
| break | |
| fi | |
| done | |
| if [ "$has_source_changes" = false ]; then | |
| echo "Skipping: no src/main/ production code changes." | |
| exit 0 | |
| fi | |
| changed_file() { | |
| local target="$1" | |
| for file in "${CHANGED_FILES[@]}"; do | |
| if [ "$file" = "$target" ]; then | |
| return 0 | |
| fi | |
| done | |
| return 1 | |
| } | |
| changed_file "$VERSION_FILE" || { echo "Version bump required in $VERSION_FILE."; exit 1; } | |
| changed_file "$CHANGELOG_FILE" || { echo "Matching changelog update required in $CHANGELOG_FILE."; exit 1; } | |
| extract_version() { | |
| python3 -c 'import sys,xml.etree.ElementTree as ET;r=ET.fromstring(sys.stdin.read());ns={"m":r.tag.split("}")[0].strip("{")} if r.tag.startswith("{") else None;n=(r.find("m:version",ns) if ns else r.find("version"));print((n.text or "").strip() if n is not None else "")' | |
| } | |
| head_version=$(extract_version < "$VERSION_FILE") | |
| # Changelog uses "## Date" sections with versions on "#### vX.Y.Z" lines; support that and legacy "## vX.Y.Z". | |
| CHANGELOG_HEAD=$( | |
| sed -nE 's/^####[[:space:]]+v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' "$CHANGELOG_FILE" | head -1 | |
| ) | |
| if [ -z "$CHANGELOG_HEAD" ]; then | |
| CHANGELOG_HEAD=$(sed -nE 's/^##[[:space:]]+v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' "$CHANGELOG_FILE" | head -1) | |
| fi | |
| [ -n "$CHANGELOG_HEAD" ] || { echo "::error::Could not find a version in $CHANGELOG_FILE (expected '#### vX.Y.Z' under a date section or legacy '## vX.Y.Z')."; exit 1; } | |
| [ "$CHANGELOG_HEAD" = "$head_version" ] || { echo "::error::$CHANGELOG_FILE top release version ($CHANGELOG_HEAD) does not match project version ($head_version)."; exit 1; } | |
| latest_tag=$(git tag --list 'v*' --sort=-version:refname | sed -n '1p') | |
| latest_version="${latest_tag#v}" | |
| [ -n "$latest_version" ] || latest_version="0.0.0" | |
| version_gt() { | |
| python3 -c 'import sys;v=lambda s:[int(x) if x.isdigit() else 0 for x in (s.strip().lstrip("v").split("-",1)[0].split("+",1)[0].split(".")+["0","0","0"])[:3]];print("true" if v(sys.argv[1])>v(sys.argv[2]) else "false")' "$1" "$2" | |
| } | |
| [ "$(version_gt "$head_version" "$latest_version")" = "true" ] || { echo "Version must be greater than latest tag version ($latest_version). Found $head_version."; exit 1; } |