Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 76 additions & 18 deletions .github/scripts/generate-release-notes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Usage: generate-release-notes.sh <current-tag> <path-filter>
# Example: generate-release-notes.sh js-sdk-v0.4.6 js/

set -e
set -euo pipefail

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

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

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

# Detect the GitHub repository for PR links
REPO_URL=$(git remote get-url origin 2>/dev/null | sed -E 's|[email protected]:|https://github.com/|; s|\.git$||')

# Generate the changelog
CHANGELOG=$(git log ${PREVIOUS_TAG}..${CURRENT_TAG} --oneline --no-merges -- ${PATH_FILTER})
CHANGELOG=$(git log "${PREVIOUS_TAG}..${CURRENT_TAG}" --oneline --no-merges -- "${PATH_FILTER}")

if [ -z "$CHANGELOG" ]; then
echo "## Changelog"
echo ""
echo "No changes found in ${PATH_FILTER} since ${PREVIOUS_TAG}"
else
echo "## Changelog"
echo ""
# Format a commit message as a markdown list item with PR link
# Args: $1=type prefix, $2=commit message (without hash)
format_line() {
local type="$1"
local msg="$2"

# Format each commit as a markdown list item with PR link
while IFS= read -r line; do
# Extract commit hash and message
COMMIT_HASH=$(echo "$line" | awk '{print $1}')
COMMIT_MSG=$(echo "$line" | cut -d' ' -f2-)

# Extract PR number if present (match the last occurrence)
if [[ $COMMIT_MSG =~ \(#([0-9]+)\)[[:space:]]*$ ]]; then
PR_NUM="${BASH_REMATCH[1]}"
# Remove PR number from message (only the last occurrence)
CLEAN_MSG=$(echo "$COMMIT_MSG" | sed -E 's/[[:space:]]*\(#[0-9]+\)[[:space:]]*$//')
echo "* ${CLEAN_MSG} (#${PR_NUM})"
# Strip the conventional commit prefix (e.g. "feat: ", "fix(scope): ")
local display
display=$(echo "$msg" | sed -E 's/^[a-zA-Z]+(\([^)]*\))?:[[:space:]]*//')

# Capitalize the first letter
display="$(echo "${display:0:1}" | tr '[:lower:]' '[:upper:]')${display:1}"

# Label perf commits explicitly
if [ "$type" = "perf" ]; then
display="(perf) ${display}"
fi

# Format PR link if present
if [[ $display =~ \(#([0-9]+)\)[[:space:]]*$ ]]; then
local pr_num="${BASH_REMATCH[1]}"
local clean
clean=$(echo "$display" | sed -E 's/[[:space:]]*\(#[0-9]+\)[[:space:]]*$//')
echo "* ${clean} ([#${pr_num}](${REPO_URL}/pull/${pr_num}))"
else
echo "* ${COMMIT_MSG}"
echo "* ${display}"
fi
}

# Print a changelog section if it has content
print_section() {
local title="$1"
local content="$2"
if [ -n "$content" ]; then
echo "### ${title}"
echo ""
printf "%s" "$content"
echo ""
fi
}

# Bucket commits by conventional commit type
FEATURES=""
FIXES=""
CHORES=""
OTHER=""

while IFS= read -r line; do
# Extract message (skip short hash) and type prefix
msg="${line#* }"
type=$(echo "$msg" | sed -E 's/^([a-zA-Z]+)(\([^)]*\))?:.*/\1/' | tr '[:upper:]' '[:lower:]')

FORMATTED=$(format_line "$type" "$msg")
case "$type" in
feat|perf) FEATURES="${FEATURES}${FORMATTED}"$'\n' ;;
fix) FIXES="${FIXES}${FORMATTED}"$'\n' ;;
chore|ci|build|docs|style|refactor|test) CHORES="${CHORES}${FORMATTED}"$'\n' ;;
*) OTHER="${OTHER}${FORMATTED}"$'\n' ;;
esac
done <<< "$CHANGELOG"

echo "## Changelog"
echo ""

print_section "Features" "$FEATURES"
print_section "Bug Fixes" "$FIXES"
print_section "Maintenance" "$CHORES"
print_section "Other Changes" "$OTHER"

# Extract version from tag (e.g. js-sdk-v0.7.0 -> 0.7.0)
VERSION=$(echo "$CURRENT_TAG" | sed -E 's/^[^-]+-[^-]+-v//')
echo "**Package**: https://www.npmjs.com/package/braintrust/v/${VERSION}"
echo ""
echo "**Full Changelog**: ${REPO_URL}/compare/${PREVIOUS_TAG}...${CURRENT_TAG}"
fi
5 changes: 4 additions & 1 deletion .github/workflows/publish-js-sdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,23 +127,26 @@ jobs:
if: github.event_name == 'push'
id: release_notes
run: |
VERSION="${RELEASE_TAG#js-sdk-v}"
RELEASE_NOTES=$(.github/scripts/generate-release-notes.sh "${{ env.RELEASE_TAG }}" "js/")
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "release_name=JavaScript SDK v${VERSION}" >> $GITHUB_OUTPUT

- name: Create GitHub Release
if: github.event_name == 'push'
uses: actions/github-script@v8
env:
RELEASE_NOTES: ${{ steps.release_notes.outputs.notes }}
RELEASE_NAME: ${{ steps.release_notes.outputs.release_name }}
with:
script: |
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: process.env.RELEASE_TAG,
name: process.env.RELEASE_TAG,
name: process.env.RELEASE_NAME,
body: process.env.RELEASE_NOTES,
draft: false,
prerelease: false
Expand Down
Loading