Skip to content

Commit 6940596

Browse files
author
Brian
committed
feat: Implement semantic version bumping based on conventional commits and generate detailed release changelogs.
1 parent 12de92f commit 6940596

2 files changed

Lines changed: 231 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "api-ape",
3-
"version": "2.2.4",
3+
"version": "2.3.0",
44
"description": "Remote Procedure Events (RPE) - A lightweight WebSocket framework for building real-time APIs. Call server functions from the browser like local methods with automatic reconnection, HTTP streaming fallback, and extended JSON encoding.",
55
"main": "index.js",
66
"browser": "./client/index.js",

scripts/publish.sh

Lines changed: 230 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,114 @@ echo "📦 Local version: $LOCAL_VERSION"
1111
NPM_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null || echo "0.0.0")
1212
echo "🌐 npm version: $NPM_VERSION"
1313

14-
# Check if versions match - if so, bump the patch version
15-
if [[ "$LOCAL_VERSION" == "$NPM_VERSION" ]]; then
16-
echo "⚠️ Local version matches npm. Incrementing patch version..."
14+
# Function to compare semver versions
15+
# Echoes: 0 if equal, 1 if first > second, 2 if first < second
16+
compare_versions() {
17+
local v1=$1 v2=$2
18+
IFS='.' read -r v1_major v1_minor v1_patch <<< "$v1"
19+
IFS='.' read -r v2_major v2_minor v2_patch <<< "$v2"
1720

18-
# Parse version parts
21+
if [[ $v1_major -gt $v2_major ]]; then echo 1; return; fi
22+
if [[ $v1_major -lt $v2_major ]]; then echo 2; return; fi
23+
if [[ $v1_minor -gt $v2_minor ]]; then echo 1; return; fi
24+
if [[ $v1_minor -lt $v2_minor ]]; then echo 2; return; fi
25+
if [[ $v1_patch -gt $v2_patch ]]; then echo 1; return; fi
26+
if [[ $v1_patch -lt $v2_patch ]]; then echo 2; return; fi
27+
echo 0
28+
}
29+
30+
# Check version relationship
31+
VERSION_CMP=$(compare_versions "$LOCAL_VERSION" "$NPM_VERSION")
32+
33+
if [[ $VERSION_CMP -eq 1 ]]; then
34+
# Local version is higher than npm - use it as-is (manually set)
35+
echo "✅ Local version ($LOCAL_VERSION) is higher than npm ($NPM_VERSION). Using manually set version."
36+
elif [[ $VERSION_CMP -eq 0 ]]; then
37+
# Versions match - need to bump based on commits
38+
echo "⚠️ Local version matches npm. Analyzing commits for semantic version bump..."
39+
40+
# Get the previous tag to analyze commits
41+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
42+
43+
if [[ -n "$PREV_TAG" ]]; then
44+
COMMIT_RANGE="$PREV_TAG..HEAD"
45+
else
46+
COMMIT_RANGE="HEAD"
47+
fi
48+
49+
# Analyze commits to determine bump type
50+
HAS_BREAKING=false
51+
HAS_FEAT=false
52+
HAS_FIX=false
53+
54+
while IFS= read -r line; do
55+
[[ -z "$line" ]] && continue
56+
57+
# Check for breaking changes (BREAKING CHANGE in message or ! after type)
58+
if [[ "$line" =~ BREAKING[[:space:]]CHANGE ]] || [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(feat|fix|refactor|chore)![:\(] ]]; then
59+
HAS_BREAKING=true
60+
fi
61+
62+
# Check for features
63+
if [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(feat|feature)[:\(] ]]; then
64+
HAS_FEAT=true
65+
fi
66+
67+
# Check for fixes
68+
if [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(fix|bugfix)[:\(] ]]; then
69+
HAS_FIX=true
70+
fi
71+
done < <(git log $COMMIT_RANGE --oneline)
72+
73+
# Parse current version
1974
IFS='.' read -r MAJOR MINOR PATCH <<< "$LOCAL_VERSION"
75+
76+
# Determine new version based on commit types
77+
if [[ "$HAS_BREAKING" == true ]]; then
78+
NEW_MAJOR=$((MAJOR + 1))
79+
NEW_VERSION="$NEW_MAJOR.0.0"
80+
BUMP_TYPE="major (breaking change)"
81+
elif [[ "$HAS_FEAT" == true ]]; then
82+
NEW_MINOR=$((MINOR + 1))
83+
NEW_VERSION="$MAJOR.$NEW_MINOR.0"
84+
BUMP_TYPE="minor (new feature)"
85+
elif [[ "$HAS_FIX" == true ]]; then
86+
NEW_PATCH=$((PATCH + 1))
87+
NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
88+
BUMP_TYPE="patch (bug fix)"
89+
else
90+
# Default to patch if no recognized commit types
91+
NEW_PATCH=$((PATCH + 1))
92+
NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
93+
BUMP_TYPE="patch (default)"
94+
fi
95+
96+
echo "📝 Bumping version ($BUMP_TYPE): $LOCAL_VERSION$NEW_VERSION"
97+
98+
# Update package.json with new version
99+
node -e "
100+
const fs = require('fs');
101+
const pkg = require('./package.json');
102+
pkg.version = '$NEW_VERSION';
103+
fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n');
104+
"
105+
106+
# Amend the last commit with the version bump
107+
git add package.json
108+
git commit --amend --no-edit
109+
git push --force-with-lease
110+
111+
LOCAL_VERSION="$NEW_VERSION"
112+
echo "✅ Version bumped and commit amended"
113+
else
114+
# Local version is lower than npm - this shouldn't happen, bump to npm + patch
115+
echo "⚠️ Local version ($LOCAL_VERSION) is lower than npm ($NPM_VERSION). Bumping from npm version..."
116+
117+
IFS='.' read -r MAJOR MINOR PATCH <<< "$NPM_VERSION"
20118
NEW_PATCH=$((PATCH + 1))
21119
NEW_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
22120

23-
echo "📝 Bumping version: $LOCAL_VERSION$NEW_VERSION"
121+
echo "📝 Bumping version: $NPM_VERSION$NEW_VERSION"
24122

25123
# Update package.json with new version
26124
node -e "
@@ -37,8 +135,6 @@ if [[ "$LOCAL_VERSION" == "$NPM_VERSION" ]]; then
37135

38136
LOCAL_VERSION="$NEW_VERSION"
39137
echo "✅ Version bumped and commit amended"
40-
else
41-
echo "✅ Local version ($LOCAL_VERSION) differs from npm ($NPM_VERSION). Proceeding..."
42138
fi
43139

44140
TAG="v$LOCAL_VERSION"
@@ -65,6 +161,127 @@ if [[ "$TAG_EXISTS" == false ]]; then
65161
git push origin "$TAG"
66162
fi
67163

164+
# Generate changelog from commits since last release
165+
echo "📋 Generating changelog..."
166+
167+
# Get the previous tag (most recent tag before the current one)
168+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
169+
170+
if [[ -n "$PREV_TAG" ]]; then
171+
COMMIT_RANGE="$PREV_TAG..HEAD"
172+
echo " Commits from $PREV_TAG to HEAD"
173+
else
174+
COMMIT_RANGE="HEAD"
175+
echo " All commits (no previous tag found)"
176+
fi
177+
178+
# Get commits and group by type
179+
declare -a FEAT_COMMITS
180+
declare -a FIX_COMMITS
181+
declare -a REFACTOR_COMMITS
182+
declare -a DOCS_COMMITS
183+
declare -a STYLE_COMMITS
184+
declare -a TEST_COMMITS
185+
declare -a CHORE_COMMITS
186+
declare -a OTHER_COMMITS
187+
188+
while IFS= read -r line; do
189+
[[ -z "$line" ]] && continue
190+
191+
# Extract the type and message
192+
if [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(feat|feature)[:\(] ]]; then
193+
FEAT_COMMITS+=("${line#* }")
194+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(fix|bugfix)[:\(] ]]; then
195+
FIX_COMMITS+=("${line#* }")
196+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(refactor)[:\(] ]]; then
197+
REFACTOR_COMMITS+=("${line#* }")
198+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(docs|doc)[:\(] ]]; then
199+
DOCS_COMMITS+=("${line#* }")
200+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(style)[:\(] ]]; then
201+
STYLE_COMMITS+=("${line#* }")
202+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(test|tests)[:\(] ]]; then
203+
TEST_COMMITS+=("${line#* }")
204+
elif [[ "$line" =~ ^[a-f0-9]+[[:space:]]+(chore|build|ci)[:\(] ]]; then
205+
CHORE_COMMITS+=("${line#* }")
206+
else
207+
OTHER_COMMITS+=("${line#* }")
208+
fi
209+
done < <(git log $COMMIT_RANGE --oneline)
210+
211+
# Build changelog
212+
CHANGELOG=""
213+
214+
if [[ ${#FEAT_COMMITS[@]} -gt 0 ]]; then
215+
CHANGELOG+="## Features\n"
216+
for commit in "${FEAT_COMMITS[@]}"; do
217+
CHANGELOG+="- $commit\n"
218+
done
219+
CHANGELOG+="\n"
220+
fi
221+
222+
if [[ ${#FIX_COMMITS[@]} -gt 0 ]]; then
223+
CHANGELOG+="## Bug Fixes\n"
224+
for commit in "${FIX_COMMITS[@]}"; do
225+
CHANGELOG+="- $commit\n"
226+
done
227+
CHANGELOG+="\n"
228+
fi
229+
230+
if [[ ${#REFACTOR_COMMITS[@]} -gt 0 ]]; then
231+
CHANGELOG+="## Refactoring\n"
232+
for commit in "${REFACTOR_COMMITS[@]}"; do
233+
CHANGELOG+="- $commit\n"
234+
done
235+
CHANGELOG+="\n"
236+
fi
237+
238+
if [[ ${#DOCS_COMMITS[@]} -gt 0 ]]; then
239+
CHANGELOG+="## Documentation\n"
240+
for commit in "${DOCS_COMMITS[@]}"; do
241+
CHANGELOG+="- $commit\n"
242+
done
243+
CHANGELOG+="\n"
244+
fi
245+
246+
if [[ ${#STYLE_COMMITS[@]} -gt 0 ]]; then
247+
CHANGELOG+="## Styling\n"
248+
for commit in "${STYLE_COMMITS[@]}"; do
249+
CHANGELOG+="- $commit\n"
250+
done
251+
CHANGELOG+="\n"
252+
fi
253+
254+
if [[ ${#TEST_COMMITS[@]} -gt 0 ]]; then
255+
CHANGELOG+="## Tests\n"
256+
for commit in "${TEST_COMMITS[@]}"; do
257+
CHANGELOG+="- $commit\n"
258+
done
259+
CHANGELOG+="\n"
260+
fi
261+
262+
if [[ ${#CHORE_COMMITS[@]} -gt 0 ]]; then
263+
CHANGELOG+="## Chores\n"
264+
for commit in "${CHORE_COMMITS[@]}"; do
265+
CHANGELOG+="- $commit\n"
266+
done
267+
CHANGELOG+="\n"
268+
fi
269+
270+
if [[ ${#OTHER_COMMITS[@]} -gt 0 ]]; then
271+
CHANGELOG+="## Other Changes\n"
272+
for commit in "${OTHER_COMMITS[@]}"; do
273+
CHANGELOG+="- $commit\n"
274+
done
275+
CHANGELOG+="\n"
276+
fi
277+
278+
# Fallback if no commits found
279+
if [[ -z "$CHANGELOG" ]]; then
280+
CHANGELOG="No notable changes in this release."
281+
fi
282+
283+
echo " Changelog generated!"
284+
68285
# Create GitHub release (triggers the publish workflow)
69286
echo "🚀 Creating GitHub release..."
70287

@@ -73,9 +290,9 @@ REPO_NAME="api-ape"
73290

74291
# Try gh CLI first, fall back to curl
75292
if command -v gh &> /dev/null; then
76-
gh release create "$TAG" \
293+
echo -e "$CHANGELOG" | gh release create "$TAG" \
77294
--title "$TAG" \
78-
--generate-notes
295+
--notes-file -
79296
else
80297
# Use GitHub API with curl
81298
if [[ -z "$GITHUB_TOKEN" ]]; then
@@ -85,6 +302,9 @@ else
85302
exit 1
86303
fi
87304

305+
# Escape the changelog for JSON
306+
CHANGELOG_ESCAPED=$(echo -e "$CHANGELOG" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
307+
88308
# Create release via GitHub API
89309
RESPONSE=$(curl -s -X POST \
90310
-H "Authorization: token $GITHUB_TOKEN" \
@@ -93,7 +313,7 @@ else
93313
-d "{
94314
\"tag_name\": \"$TAG\",
95315
\"name\": \"$TAG\",
96-
\"generate_release_notes\": true
316+
\"body\": $CHANGELOG_ESCAPED
97317
}")
98318

99319
# Check if release was created

0 commit comments

Comments
 (0)