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
55 changes: 54 additions & 1 deletion scripts/preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,60 @@ elif [ -f yarn.lock ] && command -v yarn >/dev/null 2>&1; then
fi
fi

# 3) Check PR size locally (against BASE)
# 3) CHANGELOG validation (for non-docs branches)
# Branch prefixes that are exempt from CHANGELOG updates (configuration)
CHANGELOG_EXEMPT_PREFIXES="^(docs|chore|ci|test)/"
# Minimum lines in [Unreleased] to consider it non-empty
# This counts substantive content lines (not headings, blanks, or HTML comments)
# Minimum 3 ensures at least some documentation (e.g., 1-2 bullet points)
MIN_CHANGELOG_LINES=3

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

CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
# Use POSIX-compliant case statement instead of bash-specific [[ =~ ]] for portability
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
echo "Tip: Every feature/fix/refactor branch must update CHANGELOG.md" >&2
echo "Exempt branches: docs/*, chore/*, ci/*, test/*" >&2
exit 1
fi

# Check if there's actual content after [Unreleased] (robust to last/only section)
# Find line number of [Unreleased], then extract content up to next heading or EOF
# Use grep -nE for robustness with Keep a Changelog format (supports links)
UNRELEASED_START=$(grep -nE '^## \[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)
if [ -n "$UNRELEASED_END" ]; then
# Extract content between [Unreleased] and next heading (using helper function)
UNRELEASED_CONTENT=$(sed -n "$((UNRELEASED_START + 1)),$((UNRELEASED_START + UNRELEASED_END - 1))p" CHANGELOG.md | filter_changelog_content | wc -l)
else
# [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
fi
fi
fi

# 4) Check PR size locally (against BASE)
if ! git rev-parse -q --verify "origin/$BASE" >/dev/null 2>&1; then
echo "Warning: Cannot verify base branch origin/$BASE - skipping PR size check." >&2
echo "Tip: Run 'git fetch origin $BASE' to enable PR size checking." >&2
Expand Down