@@ -11,61 +11,71 @@ set -euo pipefail
1111REPO_ROOT=" $( git rev-parse --show-toplevel) "
1212VERSION_FILE=" $REPO_ROOT /VERSION.txt"
1313
14- # Read current version
15- CURRENT =" $( cat " $VERSION_FILE " | tr -d ' [:space:] ' ) "
14+ LAST_TAG= " $( git describe --tags --abbrev=0 2> /dev/null || echo " " ) "
15+ LAST_STABLE_TAG =" $( git describe --tags --abbrev=0 --exclude ' *-rc.* ' 2> /dev/null || echo " " ) "
1616
17- # Strip any existing prerelease suffix to get the base semver
18- BASE=" $( echo " $CURRENT " | sed ' s/-.*$//' ) "
19- MAJOR=" $( echo " $BASE " | cut -d. -f1) "
20- MINOR=" $( echo " $BASE " | cut -d. -f2) "
21- PATCH=" $( echo " $BASE " | cut -d. -f3) "
17+ # Sets HAS_BREAKING, HAS_FEAT, HAS_FIX from commits in the given range.
18+ scan_commits () {
19+ local range=" $1 "
20+ HAS_BREAKING=0; HAS_FEAT=0; HAS_FIX=0
21+ while IFS= read -r line; do
22+ if echo " $line " | grep -qE ' ^(feat|fix|perf|refactor|build|ci|revert)(\(.+\))?!:' ; then
23+ HAS_BREAKING=1
24+ elif echo " $line " | grep -qE ' ^feat(\(.+\))?:' ; then
25+ HAS_FEAT=1
26+ elif echo " $line " | grep -qE ' ^(fix|perf|refactor|revert)(\(.+\))?:' ; then
27+ HAS_FIX=1
28+ fi
29+ done < <( git log " $range " --format=" %s" 2> /dev/null)
30+ # Also scan commit bodies for the BREAKING CHANGE footer (conventional commits spec)
31+ if git log " $range " --format=" %b" 2> /dev/null | grep -qE ' ^BREAKING CHANGE:' ; then
32+ HAS_BREAKING=1
33+ fi
34+ }
2235
23- # Extract current rc counter (0 if not an rc version)
24- if echo " $CURRENT " | grep -qE ' \-rc\.([0-9]+)$ ' ; then
25- ALPHA_N= " $( echo " $CURRENT " | sed ' s/.*-rc\.// ' ) "
26- else
27- ALPHA_N=0
36+ # 1. Any new releasable commits since the last tag ( rc or stable)?
37+ # If not, there is nothing to release.
38+ scan_commits " ${LAST_TAG : + ${LAST_TAG} ..} HEAD "
39+ if [ $HAS_BREAKING -eq 0 ] && [ $HAS_FEAT -eq 0 ] && [ $HAS_FIX -eq 0 ] ; then
40+ exit 2
2841fi
2942
30- # Find the last tag on this branch to scope commit search
31- LAST_TAG=" $( git describe --tags --abbrev=0 2> /dev/null || echo " " ) "
43+ # 2. Determine the correct next stable version from ALL commits since the last
44+ # stable release. This ensures a breaking change added after an rc tag
45+ # (e.g. v0.3.0-rc.1) produces 1.0.0-rc.1, not 0.3.0-rc.2.
46+ scan_commits " ${LAST_STABLE_TAG: +${LAST_STABLE_TAG} ..} HEAD"
3247
33- if [ -n " $LAST_TAG " ]; then
34- COMMIT_RANGE=" $LAST_TAG ..HEAD"
48+ # Prefer the stable tag as the base — it stays correct even when VERSION.txt
49+ # on develop lags behind main after a stable release.
50+ if [ -n " $LAST_STABLE_TAG " ]; then
51+ BASE=" $( echo " $LAST_STABLE_TAG " | sed ' s/^v//' ) "
3552else
36- COMMIT_RANGE= " HEAD "
53+ BASE= " $( tr -d ' [:space:] ' < " $VERSION_FILE " | sed ' s/-.*$// ' ) "
3754fi
55+ MAJOR=" $( echo " $BASE " | cut -d. -f1) "
56+ MINOR=" $( echo " $BASE " | cut -d. -f2) "
57+ PATCH=" $( echo " $BASE " | cut -d. -f3) "
3858
39- # Analyse commits since the last tag
40- HAS_BREAKING=0
41- HAS_FEAT=0
42- HAS_FIX=0
43-
44- while IFS= read -r line; do
45- if echo " $line " | grep -qE ' ^(feat|fix|perf|refactor|build|ci|revert)(\(.+\))?!:' ; then
46- HAS_BREAKING=1
47- elif echo " $line " | grep -qE ' BREAKING CHANGE:' ; then
48- HAS_BREAKING=1
49- elif echo " $line " | grep -qE ' ^feat(\(.+\))?:' ; then
50- HAS_FEAT=1
51- elif echo " $line " | grep -qE ' ^(fix|perf|refactor|revert)(\(.+\))?:' ; then
52- HAS_FIX=1
53- fi
54- done < <( git log " $COMMIT_RANGE " --format=" %s" 2> /dev/null)
55-
56- # Determine bump type and compute next version
5759if [ $HAS_BREAKING -eq 1 ]; then
58- MAJOR=$(( MAJOR + 1 )) ; MINOR=0; PATCH=0; ALPHA_N=0
60+ MAJOR=$(( MAJOR + 1 )) ; MINOR=0; PATCH=0
5961elif [ $HAS_FEAT -eq 1 ]; then
60- MINOR=$(( MINOR + 1 )) ; PATCH=0; ALPHA_N=0
61- elif [ $HAS_FIX -eq 1 ]; then
62- PATCH=$(( PATCH + 1 )) ; ALPHA_N=0
62+ MINOR=$(( MINOR + 1 )) ; PATCH=0
6363else
64- # No releasable commits
65- exit 2
64+ PATCH=$(( PATCH + 1 ))
6665fi
66+ NEXT_VERSION=" ${MAJOR} .${MINOR} .${PATCH} "
6767
68- ALPHA_N=$(( ALPHA_N + 1 ))
69- NEW_VERSION=" ${MAJOR} .${MINOR} .${PATCH} -rc.${ALPHA_N} "
68+ # 3. If we are already in an RC series for this exact version, increment the
69+ # counter. Otherwise start a fresh series at rc.1.
70+ RC_BASE=" "
71+ RC_N=0
72+ if echo " $LAST_TAG " | grep -qE ' ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then
73+ RC_BASE=" $( echo " $LAST_TAG " | sed ' s/^v//' | sed ' s/-rc\.[0-9]*$//' ) "
74+ RC_N=" $( echo " $LAST_TAG " | sed ' s/.*-rc\.//' ) "
75+ fi
7076
71- echo " $NEW_VERSION "
77+ if [ " $RC_BASE " = " $NEXT_VERSION " ]; then
78+ echo " ${NEXT_VERSION} -rc.$(( RC_N + 1 )) "
79+ else
80+ echo " ${NEXT_VERSION} -rc.1"
81+ fi
0 commit comments