Skip to content

Commit dd60335

Browse files
authored
[feat] Add --auto flag to release script (#15)
feat(release): add automatic version detection flag
1 parent 4cb5c94 commit dd60335

1 file changed

Lines changed: 164 additions & 10 deletions

File tree

scripts/release.sh

Lines changed: 164 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
set -eu
33

44
usage() {
5-
echo "Usage: $0 <version>"
5+
echo "Usage: $0 <version|--auto>"
66
echo ""
77
echo "Create and push a release tag to trigger the GitHub Actions release workflow."
88
echo ""
99
echo "Examples:"
10-
echo " $0 0.1.0"
11-
echo " $0 v0.1.0"
10+
echo " $0 1.1.0"
11+
echo " $0 v1.1.0"
12+
echo " $0 --auto"
1213
exit 1
1314
}
1415

@@ -17,16 +18,169 @@ die() {
1718
exit 1
1819
}
1920

20-
# Require a version argument
21+
# Detect next version by analyzing commits since the last tag.
22+
# Classifies commits by prefix and determines the appropriate semver bump.
23+
detect_next_version() {
24+
# Find latest semver tag
25+
latest_tag=$(git tag -l 'v*' | sort -V | tail -1)
26+
if [ -z "$latest_tag" ]; then
27+
latest_tag="v0.0.0"
28+
echo "No existing tags found. Starting from v0.0.0."
29+
commit_range="HEAD"
30+
else
31+
commit_range="${latest_tag}..HEAD"
32+
fi
33+
34+
# Get commits since last tag
35+
commits=$(git log "$commit_range" --oneline 2>/dev/null || true)
36+
if [ -z "$commits" ]; then
37+
echo "No commits since $latest_tag. Nothing to release."
38+
exit 0
39+
fi
40+
41+
commit_count=$(echo "$commits" | wc -l | tr -d ' ')
42+
43+
# Classify commits by prefix and determine bump type
44+
bump="patch"
45+
breaking_commits=""
46+
feat_commits=""
47+
other_commits=""
48+
49+
# Use a temp file to handle BREAKING CHANGE body detection without subshell issues
50+
body_breaking_file=$(mktemp)
51+
trap 'rm -f "$body_breaking_file"' EXIT
52+
53+
# Check commit bodies for BREAKING CHANGE
54+
git log "$commit_range" --format="%H" 2>/dev/null | while IFS= read -r hash; do
55+
body=$(git log -1 --format="%b" "$hash")
56+
case "$body" in
57+
*"BREAKING CHANGE"*)
58+
subject=$(git log -1 --format="%s" "$hash")
59+
echo "$subject" >> "$body_breaking_file"
60+
;;
61+
esac
62+
done
63+
64+
# Process each commit
65+
OLD_IFS="$IFS"
66+
IFS='
67+
'
68+
for line in $commits; do
69+
subject=$(echo "$line" | sed 's/^[a-f0-9]* //')
70+
case "$subject" in
71+
\[breaking\]*|\[BREAKING\]*)
72+
bump="major"
73+
breaking_commits="${breaking_commits} - ${subject}
74+
"
75+
;;
76+
\[feat\]*|\[Feat\]*)
77+
if [ "$bump" != "major" ]; then
78+
bump="minor"
79+
fi
80+
feat_commits="${feat_commits} - ${subject}
81+
"
82+
;;
83+
*)
84+
other_commits="${other_commits} - ${subject}
85+
"
86+
;;
87+
esac
88+
done
89+
IFS="$OLD_IFS"
90+
91+
# Incorporate BREAKING CHANGE from commit bodies
92+
if [ -s "$body_breaking_file" ]; then
93+
bump="major"
94+
OLD_IFS="$IFS"
95+
IFS='
96+
'
97+
for body_subject in $(cat "$body_breaking_file"); do
98+
# Avoid duplicating commits already listed as [breaking]
99+
case "$breaking_commits" in
100+
*"$body_subject"*) ;;
101+
*) breaking_commits="${breaking_commits} - ${body_subject} (BREAKING CHANGE in body)
102+
" ;;
103+
esac
104+
done
105+
IFS="$OLD_IFS"
106+
fi
107+
108+
# Parse current version
109+
major=$(echo "$latest_tag" | sed 's/^v//' | cut -d. -f1)
110+
minor=$(echo "$latest_tag" | sed 's/^v//' | cut -d. -f2)
111+
patch=$(echo "$latest_tag" | sed 's/^v//' | cut -d. -f3)
112+
113+
# Compute next version
114+
case "$bump" in
115+
major)
116+
major=$((major + 1))
117+
minor=0
118+
patch=0
119+
;;
120+
minor)
121+
minor=$((minor + 1))
122+
patch=0
123+
;;
124+
patch)
125+
patch=$((patch + 1))
126+
;;
127+
esac
128+
129+
next_version="v${major}.${minor}.${patch}"
130+
131+
# Print summary
132+
echo ""
133+
echo "Current version: $latest_tag"
134+
echo "Commits since $latest_tag: $commit_count"
135+
echo ""
136+
137+
if [ -n "$breaking_commits" ]; then
138+
echo " Breaking changes (major):"
139+
printf "%s" "$breaking_commits"
140+
echo ""
141+
fi
142+
143+
if [ -n "$feat_commits" ]; then
144+
echo " Features (minor):"
145+
printf "%s" "$feat_commits"
146+
echo ""
147+
fi
148+
149+
if [ -n "$other_commits" ]; then
150+
echo " Other:"
151+
printf "%s" "$other_commits"
152+
echo ""
153+
fi
154+
155+
echo "Next version: $next_version"
156+
echo ""
157+
158+
# Prompt for confirmation
159+
printf "Continue with release? [y/N] "
160+
read -r confirm
161+
case "$confirm" in
162+
[yY]|[yY][eE][sS]) ;;
163+
*) echo "Aborted."; exit 0 ;;
164+
esac
165+
166+
# Set version for the rest of the script
167+
version="$next_version"
168+
}
169+
170+
# Require exactly one argument
21171
[ $# -eq 1 ] || usage
22172

23-
version="$1"
173+
if [ "$1" = "--auto" ]; then
174+
detect_next_version
175+
else
176+
version="$1"
24177

25-
# Normalize: ensure v prefix
26-
case "$version" in
27-
v*) ;;
28-
*) version="v${version}" ;;
29-
esac
178+
# Normalize: ensure v prefix
179+
case "$version" in
180+
v*) ;;
181+
*) version="v${version}" ;;
182+
esac
183+
fi
30184

31185
# Validate semver format (vX.Y.Z)
32186
echo "$version" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$' \

0 commit comments

Comments
 (0)