Skip to content
Merged
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
40 changes: 28 additions & 12 deletions scripts/preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,29 @@ if [ -f docs/openapi.yaml ] && command -v npx >/dev/null 2>&1; then
fi

# 4) CHANGELOG validation (for non-docs branches)
# Branch prefixes that are exempt from CHANGELOG updates (configuration)
CHANGELOG_EXEMPT_PREFIXES="^(docs|chore|ci|test)/"
# Branch prefixes that are exempt from CHANGELOG updates
# Note: These must be kept in sync with the case statement below
CHANGELOG_EXEMPT_PREFIXES="docs chore ci test"
# Minimum lines in [Unreleased] to consider it non-empty
# Typically: 3 lines = one line each for Added, Changed, Fixed sections
MIN_CHANGELOG_LINES=3

# Helper function to filter CHANGELOG content (POSIX-compliant with whitespace tolerance)
filter_changelog_content() {
grep -Ev '(^##|^$|^[[:space:]]*<!--|^[[:space:]]*-->)'
}

CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
if [ -f CHANGELOG.md ] && [ "$CURRENT_BRANCH" != "main" ] && [[ ! "$CURRENT_BRANCH" =~ $CHANGELOG_EXEMPT_PREFIXES ]]; then
# Use POSIX-compliant case statement instead of bash-specific [[ =~ ]] for portability
# The case patterns below must match the prefixes in CHANGELOG_EXEMPT_PREFIXES
BRANCH_IS_EXEMPT=false
case "$CURRENT_BRANCH" in
docs/*|chore/*|ci/*|test/*)
BRANCH_IS_EXEMPT=true
;;
esac

if [ -f CHANGELOG.md ] && [ "$CURRENT_BRANCH" != "main" ] && [ "$BRANCH_IS_EXEMPT" = false ]; then
# Check if CHANGELOG has [Unreleased] section
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
echo "❌ CHANGELOG.md missing [Unreleased] section" >&2
Expand All @@ -191,18 +206,20 @@ if [ -f CHANGELOG.md ] && [ "$CURRENT_BRANCH" != "main" ] && [[ ! "$CURRENT_BRAN
UNRELEASED_START=$(grep -n '^## \[Unreleased\]' CHANGELOG.md | cut -d: -f1)
if [ -n "$UNRELEASED_START" ]; then
# Find next heading after [Unreleased], or use EOF if none found
UNRELEASED_END=$(tail -n +"$((UNRELEASED_START + 1))" CHANGELOG.md | grep -n '^## ' | head -1 | cut -d: -f1)
# Use grep -m 1 to stop after first match for better performance
UNRELEASED_END=$(tail -n +"$((UNRELEASED_START + 1))" CHANGELOG.md | grep -n -m 1 '^## ' | cut -d: -f1)
if [ -n "$UNRELEASED_END" ]; then
# Extract content between [Unreleased] and next heading
UNRELEASED_CONTENT=$(sed -n "$((UNRELEASED_START + 1)),$((UNRELEASED_START + UNRELEASED_END - 1))p" CHANGELOG.md | grep -v '^##' | grep -v '^$' | grep -v '^<!--' | grep -v '^-->' | wc -l)
# Extract content between [Unreleased] and next heading (using helper function)
# UNRELEASED_END is relative line number from tail, so add to UNRELEASED_START without -1
UNRELEASED_CONTENT=$(sed -n "$((UNRELEASED_START + 1)),$((UNRELEASED_START + UNRELEASED_END))p" CHANGELOG.md | filter_changelog_content | wc -l)
else
# [Unreleased] is the last section, extract all remaining content
UNRELEASED_CONTENT=$(tail -n +"$((UNRELEASED_START + 1))" CHANGELOG.md | grep -v '^##' | grep -v '^$' | grep -v '^<!--' | grep -v '^-->' | wc -l)
# [Unreleased] is the last section, extract all remaining content (using helper function)
UNRELEASED_CONTENT=$(tail -n +"$((UNRELEASED_START + 1))" CHANGELOG.md | filter_changelog_content | wc -l)
fi

if [ "$UNRELEASED_CONTENT" -lt "$MIN_CHANGELOG_LINES" ]; then
echo "⚠️ Warning: [Unreleased] section appears empty in CHANGELOG.md" >&2
echo "Did you forget to document your changes?" >&2
echo "⚠️ Warning: [Unreleased] section appears empty in CHANGELOG.md (found fewer than $MIN_CHANGELOG_LINES content lines)" >&2
echo "This is non-blocking, but please add a summary of your changes to the [Unreleased] section before merging." >&2
fi
fi
fi
Expand Down Expand Up @@ -292,8 +309,7 @@ else
echo "" >&2
echo "💡 Available options:" >&2
echo " 1. Split PR: Recommended approach" >&2
echo " 2. Override check: touch .preflight-allow-large-pr" >&2
echo " 3. Bypass hook: git push --no-verify (not recommended)" >&2
echo " 2. Override check: touch .preflight-allow-large-pr (for exceptional cases)" >&2
echo "" >&2
echo "Note: Lock files and license files are already excluded" >&2
echo " See .preflight-exclude for custom exclusion patterns" >&2
Expand Down