mirror of
https://github.com/better-auth/better-auth.git
synced 2026-08-21 06:56:28 -05:00
221 lines
6.1 KiB
Bash
Executable File
221 lines
6.1 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Release Preview Script
|
|
#
|
|
# Shows what will be included in the next release by comparing
|
|
# the current branch against the last stable release tag.
|
|
#
|
|
# Handles the cherry-pick history gap where stable tags may not
|
|
# be direct ancestors of the current branch (reverse cherry-pick model).
|
|
#
|
|
# Breaking changes are detected via the `breaking` label on merged PRs
|
|
# (using GitHub CLI), not just the `!` marker in commit messages.
|
|
#
|
|
# Usage:
|
|
# ./scripts/release-preview.sh # auto-detect last stable tag
|
|
# ./scripts/release-preview.sh v1.5.6 # specify base tag explicitly
|
|
|
|
set -euo pipefail
|
|
|
|
BRANCH="origin/main"
|
|
BASE_TAG="${1:-}"
|
|
|
|
# --- Detect base tag ---
|
|
|
|
if [ -z "$BASE_TAG" ]; then
|
|
BASE_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
|
if [ -z "$BASE_TAG" ]; then
|
|
echo "Error: no stable tag found (vX.Y.Z format)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "Base tag: $BASE_TAG"
|
|
echo "Branch: $BRANCH"
|
|
echo ""
|
|
|
|
# --- Check if tag is a direct ancestor ---
|
|
|
|
if git merge-base --is-ancestor "$BASE_TAG" "$BRANCH" 2>/dev/null; then
|
|
MODE="direct"
|
|
echo "Mode: direct (tag is ancestor of branch)"
|
|
echo ""
|
|
RANGE="$BASE_TAG..$BRANCH"
|
|
else
|
|
MODE="cherry-pick"
|
|
MERGE_BASE=$(git merge-base "$BASE_TAG" "$BRANCH")
|
|
echo "Mode: cherry-pick (tag is NOT ancestor of branch)"
|
|
echo " Common ancestor: $(git log -1 --oneline "$MERGE_BASE")"
|
|
echo ""
|
|
echo " Some commits may have been cherry-picked into $BASE_TAG."
|
|
echo " Filtering by PR number to exclude already-released changes."
|
|
echo ""
|
|
RANGE="$MERGE_BASE..$BRANCH"
|
|
fi
|
|
|
|
# --- Collect commits ---
|
|
|
|
# Extract PR numbers using the GitHub merge commit pattern: (#NNN)
|
|
# This avoids false positives from issue references like "closes #999"
|
|
extract_prs() {
|
|
grep -Eo '\(#[0-9]+\)' | grep -Eo '[0-9]+' | sort -u
|
|
}
|
|
|
|
if [ "$MODE" = "direct" ]; then
|
|
ALL_COMMITS=$(git log "$RANGE" --oneline)
|
|
else
|
|
TAG_PRS=$(git log "$BASE_TAG" --oneline | extract_prs)
|
|
BRANCH_PRS=$(git log "$RANGE" --oneline | extract_prs)
|
|
|
|
# comm requires sorted input; extract_prs already sorts
|
|
# Use printf to avoid empty string producing a blank line
|
|
NEW_PRS=$(comm -23 <(printf '%s\n' $BRANCH_PRS) <(printf '%s\n' $TAG_PRS))
|
|
|
|
ALL_COMMITS=$(git log "$RANGE" --oneline | while IFS= read -r line; do
|
|
pr=$(echo "$line" | grep -Eo '\(#[0-9]+\)' | grep -Eo '[0-9]+' | head -1)
|
|
if [ -z "$pr" ]; then
|
|
echo "$line"
|
|
elif printf '%s\n' $NEW_PRS | grep -q "^${pr}$"; then
|
|
echo "$line"
|
|
fi
|
|
done)
|
|
fi
|
|
|
|
# --- Detect breaking changes via PR labels ---
|
|
|
|
echo "Checking merged PRs for 'breaking' label..."
|
|
|
|
PR_NUMBERS=$(echo "$ALL_COMMITS" | grep -Eo '\(#[0-9]+\)' | grep -Eo '[0-9]+' | sort -u)
|
|
|
|
BREAKING_LINES=""
|
|
BREAKING_PR_NUMS=""
|
|
for pr_num in $PR_NUMBERS; do
|
|
result=$(gh pr view "$pr_num" --repo better-auth/better-auth --json labels,title \
|
|
--jq '(.labels | map(.name) | join(",")) + "\t" + .title' 2>/dev/null || true)
|
|
if echo "$result" | grep -q 'breaking'; then
|
|
title=$(echo "$result" | cut -f2)
|
|
BREAKING_LINES="${BREAKING_LINES}
|
|
#${pr_num} ${title}"
|
|
BREAKING_PR_NUMS="${BREAKING_PR_NUMS} ${pr_num}"
|
|
fi
|
|
done
|
|
|
|
# Also detect breaking changes from commit message ! marker (e.g. feat!:, fix!(scope):)
|
|
BANG_COMMITS=$(echo "$ALL_COMMITS" | grep -E '^[a-f0-9]+ \w+!(\(|:)' || true)
|
|
if [ -n "$BANG_COMMITS" ]; then
|
|
while IFS= read -r line; do
|
|
pr=$(echo "$line" | grep -Eo '\(#[0-9]+\)' | grep -Eo '[0-9]+' | head -1)
|
|
# Skip if already captured via PR label
|
|
if [ -n "$pr" ] && echo "$BREAKING_PR_NUMS" | grep -q " ${pr}\b"; then
|
|
continue
|
|
fi
|
|
BREAKING_LINES="${BREAKING_LINES}
|
|
${line}"
|
|
if [ -n "$pr" ]; then
|
|
BREAKING_PR_NUMS="${BREAKING_PR_NUMS} ${pr}"
|
|
fi
|
|
done <<< "$BANG_COMMITS"
|
|
fi
|
|
|
|
# Trim leading newline
|
|
BREAKING_LINES=$(echo "$BREAKING_LINES" | sed '/^$/d')
|
|
|
|
echo ""
|
|
|
|
# --- Categorize ---
|
|
|
|
# Exclude commits that belong to breaking PRs from feat/fix
|
|
NON_BREAKING_COMMITS="$ALL_COMMITS"
|
|
for bp in $BREAKING_PR_NUMS; do
|
|
NON_BREAKING_COMMITS=$(echo "$NON_BREAKING_COMMITS" | grep -vE "#${bp}([^0-9]|$)" || true)
|
|
done
|
|
# Also exclude ! marker commits without PR numbers
|
|
NON_BREAKING_COMMITS=$(echo "$NON_BREAKING_COMMITS" | grep -vE '^[a-f0-9]+ \w+!(\(|:)' || true)
|
|
|
|
FEATS=$(echo "$NON_BREAKING_COMMITS" | grep -E '^[a-f0-9]+ feat' || true)
|
|
FIXES=$(echo "$NON_BREAKING_COMMITS" | grep -E '^[a-f0-9]+ fix' || true)
|
|
CHORES=$(echo "$ALL_COMMITS" | grep -E '^[a-f0-9]+ chore' || true)
|
|
DOCS=$(echo "$ALL_COMMITS" | grep -E '^[a-f0-9]+ docs' || true)
|
|
CI=$(echo "$ALL_COMMITS" | grep -E '^[a-f0-9]+ ci' || true)
|
|
OTHER=$(echo "$ALL_COMMITS" | sed '/^$/d' | grep -vE '^[a-f0-9]+ (feat|fix|chore|docs|ci)' || true)
|
|
|
|
count() {
|
|
local input="$1"
|
|
if [ -z "$input" ]; then
|
|
echo 0
|
|
else
|
|
echo "$input" | sed '/^$/d' | grep -c . 2>/dev/null || echo 0
|
|
fi
|
|
}
|
|
|
|
TOTAL=$(count "$ALL_COMMITS")
|
|
FEAT_COUNT=$(count "$FEATS")
|
|
FIX_COUNT=$(count "$FIXES")
|
|
BREAKING_COUNT=$(count "$BREAKING_LINES")
|
|
|
|
# --- Summary ---
|
|
|
|
echo "========================================"
|
|
echo " Release Preview: $BASE_TAG -> v?.?.?"
|
|
echo "========================================"
|
|
echo ""
|
|
echo " Total new commits: $TOTAL"
|
|
echo " Features: $FEAT_COUNT"
|
|
echo " Bug fixes: $FIX_COUNT"
|
|
echo " Breaking changes: $BREAKING_COUNT"
|
|
echo ""
|
|
|
|
if [ "$BREAKING_COUNT" -gt 0 ]; then
|
|
echo " Suggested version: MINOR (breaking changes found)"
|
|
elif [ "$FEAT_COUNT" -gt 0 ]; then
|
|
echo " Suggested version: MINOR (new features)"
|
|
else
|
|
echo " Suggested version: PATCH (fixes only)"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# --- Detail ---
|
|
|
|
if [ "$BREAKING_COUNT" -gt 0 ]; then
|
|
echo "## Breaking Changes"
|
|
echo ""
|
|
echo "$BREAKING_LINES" | sed 's/^/ /'
|
|
echo ""
|
|
fi
|
|
|
|
if [ -n "$FEATS" ]; then
|
|
echo "## Features"
|
|
echo ""
|
|
echo "$FEATS" | sed 's/^/ /'
|
|
echo ""
|
|
fi
|
|
|
|
if [ -n "$FIXES" ]; then
|
|
echo "## Bug Fixes"
|
|
echo ""
|
|
echo "$FIXES" | sed 's/^/ /'
|
|
echo ""
|
|
fi
|
|
|
|
if [ -n "$DOCS" ]; then
|
|
echo "## Docs ($(count "$DOCS"))"
|
|
echo ""
|
|
echo " (use --verbose to list)"
|
|
echo ""
|
|
fi
|
|
|
|
if [ -n "$CHORES" ]; then
|
|
echo "## Chores ($(count "$CHORES"))"
|
|
echo ""
|
|
echo " (use --verbose to list)"
|
|
echo ""
|
|
fi
|
|
|
|
if [ -n "$OTHER" ]; then
|
|
echo "## Other"
|
|
echo ""
|
|
echo "$OTHER" | sed 's/^/ /'
|
|
echo ""
|
|
fi
|