Skip to content

Commit 201efe4

Browse files
committed
feat: Improve changelog generation
1 parent abc8bed commit 201efe4

2 files changed

Lines changed: 81 additions & 20 deletions

File tree

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/bin/bash
22
# Script to generate release notes filtered by path
33
# Usage: generate-release-notes.sh <current-tag> <path-filter>
4-
# Example: generate-release-notes.sh js-sdk-v0.4.6 js/
4+
# Example: generate-release-notes.sh py-sdk-v0.7.0 py/
55

6-
set -e
6+
set -euo pipefail
77

88
if [ $# -lt 2 ]; then
99
echo "ERROR: Required arguments not provided"
@@ -18,37 +18,95 @@ PATH_FILTER=$2
1818
SDK_PREFIX=$(echo "$CURRENT_TAG" | sed -E 's/^([^-]+-[^-]+)-.*/\1/')
1919

2020
# Find the previous tag for this SDK
21-
PREVIOUS_TAG=$(git tag --list "${SDK_PREFIX}-v*" --sort=-v:refname | grep -v "^${CURRENT_TAG}$" | head -1)
21+
PREVIOUS_TAG=$(git tag --list "${SDK_PREFIX}-v*" --sort=-v:refname | grep -v "^${CURRENT_TAG}$" | head -1 || true)
2222

2323
if [ -z "$PREVIOUS_TAG" ]; then
2424
PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD)
2525
fi
2626

27+
# Detect the GitHub repository for PR links
28+
REPO_URL=$(git remote get-url origin 2>/dev/null | sed -E 's|git@github.com:|https://github.com/|; s|\.git$||')
29+
2730
# Generate the changelog
28-
CHANGELOG=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --oneline --no-merges -- ${PATH_FILTER})
31+
CHANGELOG=$(git log "${PREVIOUS_TAG}..${CURRENT_TAG}" --oneline --no-merges -- "${PATH_FILTER}")
2932

3033
if [ -z "$CHANGELOG" ]; then
3134
echo "## Changelog"
3235
echo ""
3336
echo "No changes found in ${PATH_FILTER} since ${PREVIOUS_TAG}"
3437
else
35-
echo "## Changelog"
36-
echo ""
38+
# Format a commit message as a markdown list item with PR link
39+
# Args: $1=type prefix, $2=commit message (without hash)
40+
format_line() {
41+
local type="$1"
42+
local msg="$2"
3743

38-
# Format each commit as a markdown list item with PR link
39-
while IFS= read -r line; do
40-
# Extract commit hash and message
41-
COMMIT_HASH=$(echo "$line" | awk '{print $1}')
42-
COMMIT_MSG=$(echo "$line" | cut -d' ' -f2-)
43-
44-
# Extract PR number if present (match the last occurrence)
45-
if [[ $COMMIT_MSG =~ \(#([0-9]+)\)[[:space:]]*$ ]]; then
46-
PR_NUM="${BASH_REMATCH[1]}"
47-
# Remove PR number from message (only the last occurrence)
48-
CLEAN_MSG=$(echo "$COMMIT_MSG" | sed -E 's/[[:space:]]*\(#[0-9]+\)[[:space:]]*$//')
49-
echo "* ${CLEAN_MSG} (#${PR_NUM})"
44+
# Strip the conventional commit prefix (e.g. "feat: ", "fix(scope): ")
45+
local display
46+
display=$(echo "$msg" | sed -E 's/^[a-zA-Z]+(\([^)]*\))?:[[:space:]]*//')
47+
48+
# Capitalize the first letter
49+
display="$(echo "${display:0:1}" | tr '[:lower:]' '[:upper:]')${display:1}"
50+
51+
# Label perf commits explicitly
52+
if [ "$type" = "perf" ]; then
53+
display="(perf) ${display}"
54+
fi
55+
56+
# Format PR link if present
57+
if [[ $display =~ \(#([0-9]+)\)[[:space:]]*$ ]]; then
58+
local pr_num="${BASH_REMATCH[1]}"
59+
local clean
60+
clean=$(echo "$display" | sed -E 's/[[:space:]]*\(#[0-9]+\)[[:space:]]*$//')
61+
echo "* ${clean} ([#${pr_num}](${REPO_URL}/pull/${pr_num}))"
5062
else
51-
echo "* ${COMMIT_MSG}"
63+
echo "* ${display}"
64+
fi
65+
}
66+
67+
# Print a changelog section if it has content
68+
print_section() {
69+
local title="$1"
70+
local content="$2"
71+
if [ -n "$content" ]; then
72+
echo "### ${title}"
73+
echo ""
74+
printf "%s" "$content"
75+
echo ""
5276
fi
77+
}
78+
79+
# Bucket commits by conventional commit type
80+
FEATURES=""
81+
FIXES=""
82+
CHORES=""
83+
OTHER=""
84+
85+
while IFS= read -r line; do
86+
# Extract message (skip short hash) and type prefix
87+
msg="${line#* }"
88+
type=$(echo "$msg" | sed -E 's/^([a-zA-Z]+)(\([^)]*\))?:.*/\1/' | tr '[:upper:]' '[:lower:]')
89+
90+
FORMATTED=$(format_line "$type" "$msg")
91+
case "$type" in
92+
feat|perf) FEATURES="${FEATURES}${FORMATTED}"$'\n' ;;
93+
fix) FIXES="${FIXES}${FORMATTED}"$'\n' ;;
94+
chore|ci|build|docs|style|refactor|test) CHORES="${CHORES}${FORMATTED}"$'\n' ;;
95+
*) OTHER="${OTHER}${FORMATTED}"$'\n' ;;
96+
esac
5397
done <<< "$CHANGELOG"
98+
99+
echo "## Changelog"
100+
echo ""
101+
102+
print_section "Features" "$FEATURES"
103+
print_section "Bug Fixes" "$FIXES"
104+
print_section "Maintenance" "$CHORES"
105+
print_section "Other Changes" "$OTHER"
106+
107+
# Extract version from tag (e.g. py-sdk-v0.7.0 -> 0.7.0)
108+
VERSION=$(echo "$CURRENT_TAG" | sed -E 's/^[^-]+-[^-]+-v//')
109+
echo "**Package**: https://pypi.org/project/braintrust/${VERSION}/"
110+
echo ""
111+
echo "**Full Changelog**: ${REPO_URL}/compare/${PREVIOUS_TAG}...${CURRENT_TAG}"
54112
fi

.github/workflows/publish-py-sdk.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,25 @@ jobs:
7373
- name: Generate release notes
7474
id: release_notes
7575
run: |
76+
VERSION="${RELEASE_TAG#py-sdk-v}"
7677
RELEASE_NOTES=$(.github/scripts/generate-release-notes.sh "${{ env.RELEASE_TAG }}" "py/")
7778
echo "notes<<EOF" >> $GITHUB_OUTPUT
7879
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
7980
echo "EOF" >> $GITHUB_OUTPUT
81+
echo "release_name=Python SDK v${VERSION}" >> $GITHUB_OUTPUT
8082
8183
- name: Create GitHub Release
8284
uses: actions/github-script@v7
8385
env:
8486
RELEASE_NOTES: ${{ steps.release_notes.outputs.notes }}
87+
RELEASE_NAME: ${{ steps.release_notes.outputs.release_name }}
8588
with:
8689
script: |
8790
await github.rest.repos.createRelease({
8891
owner: context.repo.owner,
8992
repo: context.repo.repo,
9093
tag_name: process.env.RELEASE_TAG,
91-
name: process.env.RELEASE_TAG,
94+
name: process.env.RELEASE_NAME,
9295
body: process.env.RELEASE_NOTES,
9396
draft: false,
9497
prerelease: false

0 commit comments

Comments
 (0)