hass.tibber_prices/scripts/release/generate-notes
Julian Pawlowski bb176135f6 fix(ci): ensure release notes end with newline before heredoc delimiter
cliff.toml has trim=true which strips git-cliff's trailing newline.
When written to GITHUB_OUTPUT via heredoc, the closing delimiter was
appended to the last content line instead of its own line, causing
"Matching delimiter not found" error.

Added printf '\n' in the workflow and echo "" in generate-notes to
guarantee a newline before the heredoc closing delimiter.

Impact: Release workflow no longer fails when generating release notes.
2026-04-07 15:16:03 +00:00

676 lines
24 KiB
Bash
Executable file

#!/bin/bash
# script/generate-notes: Generate release notes from conventional commits
#
# Parses conventional commits between git tags and generates formatted release
# notes. Supports multiple backends: GitHub Copilot CLI (AI), git-cliff
# (template), or manual grep/awk parsing. Can auto-update existing GitHub releases.
#
# Usage:
# ./scripts/release/generate-notes [FROM_TAG] [TO_TAG]
#
# Examples:
# ./scripts/release/generate-notes # Latest tag to HEAD
# ./scripts/release/generate-notes v1.0.0 v1.1.0
# ./scripts/release/generate-notes v1.0.0 HEAD
set -e
cd "$(dirname "$0")/../.."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Detect if running in CI (suppress colored output to stdout)
if [[ -n $CI || -n $GITHUB_ACTIONS ]]; then
# In CI, send info messages to stderr to keep release notes clean
log_info() {
echo -e "$@" >&2
}
else
# Local execution, show colored output
log_info() {
echo -e "$@"
}
fi
# Configuration
BACKEND="${RELEASE_NOTES_BACKEND:-auto}"
USE_AI="${USE_AI:-true}"
GITHUB_REPO="${GITHUB_REPOSITORY:-jpawlowski/hass.tibber_prices}"
# Parse arguments
FROM_TAG="${1:-$(git describe --tags --abbrev=0 2>/dev/null || echo "")}"
TO_TAG="${2:-HEAD}"
if [[ -z $FROM_TAG ]]; then
echo -e "${RED}Error: No tags found in repository${NC}" >&2
echo "Usage: $0 [FROM_TAG] [TO_TAG]" >&2
exit 1
fi
log_info "${BLUE}==> Generating release notes: ${FROM_TAG}..${TO_TAG}${NC}"
log_info ""
# Detect available backends
detect_backend() {
if [[ $BACKEND != auto ]]; then
echo "$BACKEND"
return
fi
# Skip AI in CI/CD or if disabled
if [[ $USE_AI == false || -n $CI || -n $GITHUB_ACTIONS ]]; then
if command -v git-cliff >/dev/null 2>&1; then
echo "git-cliff"
return
fi
echo "manual"
return
fi
# Check for GitHub Copilot CLI (AI-powered, best quality)
if command -v copilot >/dev/null 2>&1; then
echo "copilot"
return
fi
# Check for git-cliff (fast and reliable)
if command -v git-cliff >/dev/null 2>&1; then
echo "git-cliff"
return
fi
# Fallback to manual parsing
echo "manual"
}
BACKEND=$(detect_backend)
log_info "${GREEN}Using backend: ${BACKEND}${NC}"
log_info ""
# Backend: GitHub Copilot CLI (AI-powered)
generate_with_copilot() {
log_info "${BLUE}==> Generating with GitHub Copilot CLI (AI-powered)${NC}"
log_info "${YELLOW}Note: This will use one premium request from your monthly quota${NC}"
log_info ""
# Get commit log for the range with file statistics
# This helps the AI understand which commits touched which files
COMMITS=$(git log --pretty=format:"%h | %s%n%b%n" --stat --compact-summary "${FROM_TAG}..${TO_TAG}")
# Get code diff for user-facing files to give the AI real context about what changed.
# Limited to ~8KB to keep the prompt manageable.
DIFF_CONTEXT=$(git diff --unified=2 --diff-filter=AM \
-- "custom_components/tibber_prices/sensor/" \
-- "custom_components/tibber_prices/binary_sensor/" \
-- "custom_components/tibber_prices/config_flow_handlers/" \
-- "custom_components/tibber_prices/services/" \
-- "custom_components/tibber_prices/translations/" \
-- "custom_components/tibber_prices/number/" \
-- "custom_components/tibber_prices/switch/" \
"${FROM_TAG}..${TO_TAG}" 2>/dev/null | head -c 8000 || true)
if [[ -z $COMMITS ]]; then
log_info "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}"
exit 0
fi
# Create prompt for Copilot
PROMPT="You are writing a GitHub release announcement for a Home Assistant integration called **Tibber Prices**.
## YOUR AUDIENCE
Home Assistant users — people who automate their home and build dashboards. They are NOT developers.
They do not understand technical terms like: coordinator, entity, async, refactor, TypedDict, module,
class, iterator, cache, pool, BaseCalculator, interval_pool, or any Python/software engineering terminology.
## WHAT THIS INTEGRATION DOES
It fetches electricity prices from Tibber and provides sensors to Home Assistant, so users can:
- See the current electricity price on their dashboard
- Automate appliances to run during cheapest hours (dishwasher, washing machine, heat pump, EV charging)
- Get alerts when prices are unusually high or low
- Find the best time window in the day for flexible loads
## YOUR GOAL
Translate technical changes into real user benefits. Write as a product manager telling
users what they can now DO, what was BROKEN and is now FIXED, or why the integration
is now more RELIABLE.
## WRITING RULES — FOLLOW STRICTLY
**Rule 1 — No jargon, ever.**
NEVER use: coordinator, entity, async, class, module, package, cache invalidation, refactor, pool,
iterator, TypedDict, BaseCalculator, interval_pool, config entry, data class, state machine.
Always rephrase as a user experience.
**Rule 2 — Second person, present tense.**
Write: \"You can now see...\", \"Your automations will...\", \"The setup wizard now...\"
**Rule 3 — Bug fixes: describe the SYMPTOM, not the technical cause.**
❌ \"Fix timezone-aware datetime comparison at resolution boundary\"
✅ \"Fixed: Price data was missing or showed wrong values when switching to/from daylight saving time\"
❌ \"Restore missing while-loop increment preventing indefinite hang during period calculation\"
✅ \"Fixed: The integration could occasionally freeze when calculating the cheapest time windows\"
**Rule 4 — New features: lead with what the user can DO.**
❌ \"Add volatility calculator with BaseCalculator integration\"
✅ \"New sensors show how stable today's prices are — useful for deciding whether to run flexible appliances now or wait\"
**Rule 5 — Refactoring without user impact: omit completely.**
Only mention internal changes if they improve actual reliability or stability that users
would notice. If so, group them under \"⚡ More Reliable\" as a brief single line.
NEVER write a refactoring bullet per commit.
**Rule 6 — Combine related commits into one story.**
Do NOT list every commit as a separate bullet. Group related commits into a single
paragraph or bullet that tells the complete story. Use multiple commit links for the group.
**Rule 7 — Use Impact: sections.**
Commit bodies may contain an \"Impact:\" line — this is the user-facing description.
Prioritize it over the commit subject line.
**Rule 8 — Imagine explaining to your neighbor.**
Before writing each bullet, ask: \"Would my neighbor who uses Home Assistant to automate their
home understand this?\" If not, rephrase.
## OUTPUT FORMAT
Start with: # [Short, exciting title — max 6 words, NO version number]
Then sections using ### (H3 headings):
- ### 🎉 What's New
(New sensors, new config options, new automations possible — use this for feat commits with real user benefit)
- ### 🐛 Fixed
(Bugs users experienced — describe what was wrong, what is better now)
- ### ⚡ More Reliable
(Performance improvements OR refactoring that improves stability — only when users would notice)
- ### 📦 Updated Dependencies
(Dependency bumps — keep brief, one line per group)
Skip any section that has no content.
After the last section, ALWAYS end with this exact footer, no modifications:
---
If this release saved you some money on your electricity bill, a coffee would be much appreciated! ☕
[![Buy Me A Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=☕&slug=jpawlowski&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff)](https://www.buymeacoffee.com/jpawlowski)
## TITLE SELECTION
- Find the most user-impactful change: new sensors > bug fixes affecting data quality > reliability improvements > UI improvements > translations
- Ignore dev/CI/docs commits for the title
- Make it sound exciting: \"Smarter Cheap-Period Detection\", \"Reliable DST Price Data\", \"New Price Stability Sensors\"
- Example: if DST price data was wrong and now fixed → title = \"Reliable Prices Around Clock Changes\"
## WHAT TO INCLUDE vs SKIP
✅ Include: Changes to what sensors show, bugs fixing wrong data, config UI improvements,
automation reliability, dependency updates
❌ Skip: Script/CI/workflow changes, version bumps, pure code reorganization,
documentation commits, test changes
## FILE PATH GUIDE (for assessing importance)
- sensor/ = Price sensors users see → HIGH
- binary_sensor/ = On/off sensors for automations → HIGH
- config_flow_handlers/ or config_flow/ = Setup wizard users interact with → HIGH
- services/ = Actions users trigger → HIGH
- translations/ = Labels users see in the UI → MEDIUM
- number/ or switch/ = Controls users interact with → HIGH
- coordinator/ = Data fetching/processing (rarely user-visible unless fixing data bugs) → LOW-MEDIUM
- scripts/, .github/, docs/ = Developer-only → SKIP
---
**Commits to analyze (${FROM_TAG}${TO_TAG}):**
${COMMITS}
---
**Code diff for user-facing files (use this to understand WHAT actually changed — but always translate to user language):**
${DIFF_CONTEXT}
---
Output ONLY the release notes. Start directly with the # title.
End after the Buy Me A Coffee button. No meta-commentary, no explanations."
# Save prompt to temp file for copilot
TEMP_PROMPT=$(mktemp)
echo "$PROMPT" > "$TEMP_PROMPT"
# Use Claude Sonnet for better user-focused, plain-language release notes.
# Haiku tends to echo technical commit language; Sonnet better translates to user benefits.
# Can override with: COPILOT_MODEL=claude-haiku-4.5 ./scripts/release/generate-notes
COPILOT_MODEL="${COPILOT_MODEL:-claude-sonnet-4-6}"
# Call copilot CLI (it will handle authentication interactively)
copilot --model "$COPILOT_MODEL" < "$TEMP_PROMPT" || {
echo ""
log_info "${YELLOW}Warning: GitHub Copilot CLI failed or was not authenticated${NC}"
log_info "${YELLOW}Falling back to git-cliff${NC}"
rm -f "$TEMP_PROMPT"
if command -v git-cliff >/dev/null 2>&1; then
generate_with_gitcliff
else
generate_with_manual
fi
return
}
rm -f "$TEMP_PROMPT"
}
# Backend: git-cliff (template-based)
generate_with_gitcliff() {
log_info "${BLUE}==> Generating with git-cliff${NC}"
# Analyze commits to generate smart title
FROM_TAG_SHORT="${FROM_TAG}"
TO_TAG_SHORT="${TO_TAG}"
COMMITS_LOG=$(git log --pretty=format:"%s" "${FROM_TAG}..${TO_TAG}" 2>/dev/null || echo "")
# Count user-facing changes (exclude dev/ci/docs)
FEAT_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^feat\((sensors|binary_sensor|config_flow|services|coordinator|api)\):" || true)
FIX_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^fix\((sensors|binary_sensor|config_flow|services|coordinator|api|translations)\):" || true)
PERF_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^perf\(" || true)
# Generate intelligent title based on changes
if [ $PERF_COUNT -gt 0 ]; then
TITLE="# Performance & Reliability Improvements"
elif [ $FEAT_COUNT -gt 2 ]; then
TITLE="# New Features & Enhancements"
elif [ $FEAT_COUNT -gt 0 ] && [ $FIX_COUNT -gt 0 ]; then
TITLE="# New Features & Bug Fixes"
elif [ $FEAT_COUNT -gt 0 ]; then
TITLE="# New Features"
elif [ $FIX_COUNT -gt 2 ]; then
TITLE="# Bug Fixes & Improvements"
elif [ $FIX_COUNT -gt 0 ]; then
TITLE="# Bug Fixes"
else
TITLE="# Release Updates"
fi
echo "$TITLE"
echo ""
# Create temporary cliff.toml if not exists
if [ ! -f "cliff.toml" ]; then
cat > /tmp/cliff.toml <<'EOF'
[changelog]
header = ""
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}**: {% endif %}{{ commit.message | upper_first }}\
{% if commit.breaking %} [**BREAKING**]{% endif %}
{% endfor %}
{% endfor %}
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = false
split_commits = false
commit_parsers = [
{ message = "^feat", group = "🎉 New Features" },
{ message = "^fix", group = "🐛 Bug Fixes" },
{ message = "^doc", group = "📚 Documentation" },
{ message = "^perf", group = "⚡ Performance" },
{ message = "^refactor", group = "🔧 Maintenance & Refactoring" },
{ message = "^style", group = "🎨 Styling" },
{ message = "^test", group = "🧪 Testing" },
{ message = "^chore", group = "🔧 Maintenance & Refactoring" },
{ message = "^ci", group = "🔄 CI/CD" },
{ message = "^build", group = "📦 Build" },
]
EOF
CLIFF_CONFIG="/tmp/cliff.toml"
else
CLIFF_CONFIG="cliff.toml"
fi
git-cliff --config "$CLIFF_CONFIG" "${FROM_TAG}..${TO_TAG}"
echo "" # Ensure output ends with newline (cliff.toml trim=true removes trailing newline)
if [ "$CLIFF_CONFIG" = "/tmp/cliff.toml" ]; then
rm -f /tmp/cliff.toml
fi
}
# Backend: Manual parsing (fallback)
generate_with_manual() {
log_info "${BLUE}==> Generating with manual parsing${NC}"
echo ""
# Check if we have commits
if ! git log --oneline "${FROM_TAG}..${TO_TAG}" >/dev/null 2>&1; then
log_info "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}"
exit 0
fi
# Analyze commits to generate smart title
COMMITS_LOG=$(git log --pretty=format:"%s" "${FROM_TAG}..${TO_TAG}" 2>/dev/null || echo "")
# Count user-facing changes (exclude dev/ci/docs)
FEAT_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^feat\((sensors|binary_sensor|config_flow|services|coordinator|api)\):" || true)
FIX_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^fix\((sensors|binary_sensor|config_flow|services|coordinator|api|translations)\):" || true)
PERF_COUNT=$(echo "$COMMITS_LOG" | grep -cE "^perf\(" || true)
# Generate intelligent title based on changes
if [ $PERF_COUNT -gt 0 ]; then
echo "# Performance & Reliability Improvements"
elif [ $FEAT_COUNT -gt 2 ]; then
echo "# New Features & Enhancements"
elif [ $FEAT_COUNT -gt 0 ] && [ $FIX_COUNT -gt 0 ]; then
echo "# New Features & Bug Fixes"
elif [ $FEAT_COUNT -gt 0 ]; then
echo "# New Features"
elif [ $FIX_COUNT -gt 2 ]; then
echo "# Bug Fixes & Improvements"
elif [ $FIX_COUNT -gt 0 ]; then
echo "# Bug Fixes"
else
echo "# Release Updates"
fi
echo ""
# Create temporary files for each category
TMPDIR=$(mktemp -d)
FEAT_FILE="${TMPDIR}/feat"
FIX_FILE="${TMPDIR}/fix"
DOCS_FILE="${TMPDIR}/docs"
REFACTOR_FILE="${TMPDIR}/refactor"
TEST_FILE="${TMPDIR}/test"
OTHER_FILE="${TMPDIR}/other"
touch "$FEAT_FILE" "$FIX_FILE" "$DOCS_FILE" "$REFACTOR_FILE" "$TEST_FILE" "$OTHER_FILE"
# Process commits
git log --pretty=format:"%h %s" "${FROM_TAG}..${TO_TAG}" | while read -r hash subject; do
# Simple type extraction (before colon)
TYPE=""
if echo "$subject" | grep -q '^feat'; then
TYPE="feat"
elif echo "$subject" | grep -q '^fix'; then
TYPE="fix"
elif echo "$subject" | grep -q '^docs'; then
TYPE="docs"
elif echo "$subject" | grep -q '^refactor\|^chore'; then
TYPE="refactor"
elif echo "$subject" | grep -q '^test'; then
TYPE="test"
else
TYPE="other"
fi
# Create markdown line
LINE="- ${subject} ([${hash}](https://github.com/jpawlowski/hass.tibber_prices/commit/${hash}))"
# Append to appropriate file
case "$TYPE" in
feat)
echo "$LINE" >> "$FEAT_FILE"
;;
fix)
echo "$LINE" >> "$FIX_FILE"
;;
docs)
echo "$LINE" >> "$DOCS_FILE"
;;
refactor)
echo "$LINE" >> "$REFACTOR_FILE"
;;
test)
echo "$LINE" >> "$TEST_FILE"
;;
*)
echo "$LINE" >> "$OTHER_FILE"
;;
esac
done
# Output grouped by category
if [ -s "$FEAT_FILE" ]; then
echo "### 🎉 New Features"
echo ""
cat "$FEAT_FILE"
echo ""
fi
if [ -s "$FIX_FILE" ]; then
echo "### 🐛 Bug Fixes"
echo ""
cat "$FIX_FILE"
echo ""
fi
if [ -s "$DOCS_FILE" ]; then
echo "### 📚 Documentation"
echo ""
cat "$DOCS_FILE"
echo ""
fi
if [ -s "$REFACTOR_FILE" ]; then
echo "### 🔧 Maintenance & Refactoring"
echo ""
cat "$REFACTOR_FILE"
echo ""
fi
if [ -s "$TEST_FILE" ]; then
echo "### 🧪 Testing"
echo ""
cat "$TEST_FILE"
echo ""
fi
if [ -s "$OTHER_FILE" ]; then
echo "### 📝 Other Changes"
echo ""
cat "$OTHER_FILE"
echo ""
fi
# Cleanup
rm -rf "$TMPDIR"
}
# ============================================================================
# Check if auto-update is possible (before generation)
# ============================================================================
AUTO_UPDATE_AVAILABLE=false
CURRENT_TITLE=""
CURRENT_URL=""
if [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ] && command -v gh >/dev/null 2>&1 && [ "$TO_TAG" != "HEAD" ]; then
# Check if TO_TAG is a valid tag and release exists
if git rev-parse "$TO_TAG" >/dev/null 2>&1 && gh release view "$TO_TAG" >/dev/null 2>&1; then
AUTO_UPDATE_AVAILABLE=true
CURRENT_TITLE=$(gh release view "$TO_TAG" --json name --jq '.name' 2>/dev/null || echo "$TO_TAG")
CURRENT_URL=$(gh release view "$TO_TAG" --json url --jq '.url' 2>/dev/null || echo "")
fi
fi
# ============================================================================
# Generate release notes (only once)
# ============================================================================
# Validate backend availability
case "$BACKEND" in
copilot)
if ! command -v copilot >/dev/null 2>&1; then
echo -e "${RED}Error: GitHub Copilot CLI not found${NC}" >&2
echo "Install: npm install -g @github/copilot" >&2
echo "See: https://github.com/github/copilot-cli" >&2
exit 1
fi
;;
git-cliff)
if ! command -v git-cliff >/dev/null 2>&1; then
echo -e "${RED}Error: git-cliff not found${NC}" >&2
echo "Install: https://git-cliff.org/docs/installation" >&2
exit 1
fi
;;
esac
# Generate to temp file if auto-update is possible, else stdout
if [ "$AUTO_UPDATE_AVAILABLE" = "true" ]; then
TEMP_NOTES=$(mktemp)
case "$BACKEND" in
copilot)
generate_with_copilot > "$TEMP_NOTES"
;;
git-cliff)
generate_with_gitcliff > "$TEMP_NOTES"
;;
manual)
generate_with_manual > "$TEMP_NOTES"
;;
esac
else
# No auto-update, just output to stdout
case "$BACKEND" in
copilot)
generate_with_copilot
;;
git-cliff)
generate_with_gitcliff
;;
manual)
generate_with_manual
;;
esac
echo "" >&2
log_info "${GREEN}==> Release notes generated successfully!${NC}"
exit 0
fi
# ============================================================================
# Auto-update existing GitHub release
# ============================================================================
# Extract title and body from generated notes
# Title is the first line starting with '# ' (H1)
# Body is everything after the first blank line following the title
EXTRACTED_TITLE=""
NOTES_BODY=""
# Find the first line that starts with '# ' (skip any metadata before it)
TITLE_LINE_NUM=$(grep -n '^# ' "$TEMP_NOTES" | head -n 1 | cut -d: -f1)
if [ -n "$TITLE_LINE_NUM" ]; then
# Extract title (remove '# ' prefix from that specific line)
FIRST_TITLE_LINE=$(sed -n "${TITLE_LINE_NUM}p" "$TEMP_NOTES")
EXTRACTED_TITLE=$(echo "$FIRST_TITLE_LINE" | sed 's/^# //')
# Body starts from the line after the title
BODY_START_LINE=$((TITLE_LINE_NUM + 1))
NOTES_BODY=$(tail -n +$BODY_START_LINE "$TEMP_NOTES" | sed '1{/^$/d;}')
else
# No H1 title found, use entire content as body
NOTES_BODY=$(cat "$TEMP_NOTES")
fi
# Generate final title for GitHub release (without version tag - HACS adds it automatically)
if [ -n "$EXTRACTED_TITLE" ]; then
# Use the extracted title from release notes (no version prefix)
RELEASE_TITLE="$EXTRACTED_TITLE"
else
# Fallback: Keep current title if it looks meaningful
# (more than just the tag itself)
RELEASE_TITLE="$CURRENT_TITLE"
if [ "$RELEASE_TITLE" = "$TO_TAG" ]; then
# Current title is just the tag, generate from commit analysis
# This is the git-cliff style fallback (simple but functional)
FEAT_COUNT=$(echo "$NOTES_BODY" | grep -c "^### 🎉 New Features" || true)
FIX_COUNT=$(echo "$NOTES_BODY" | grep -c "^### 🐛 Bug Fixes" || true)
DOCS_COUNT=$(echo "$NOTES_BODY" | grep -c "^### 📚 Documentation" || true)
if [ "$FEAT_COUNT" -gt 0 ] && [ "$FIX_COUNT" -gt 0 ]; then
RELEASE_TITLE="New Features & Bug Fixes"
elif [ "$FEAT_COUNT" -gt 0 ]; then
RELEASE_TITLE="New Features"
elif [ "$FIX_COUNT" -gt 0 ]; then
RELEASE_TITLE="Bug Fixes"
elif [ "$DOCS_COUNT" -gt 0 ]; then
RELEASE_TITLE="Documentation Updates"
else
RELEASE_TITLE="Release Updates"
fi
fi
fi
# Save body (without H1 title) to temp file for GitHub
TEMP_BODY=$(mktemp)
echo "$NOTES_BODY" > "$TEMP_BODY"
# Show the generated notes (with title for preview)
if [ -n "$EXTRACTED_TITLE" ]; then
echo "# $EXTRACTED_TITLE"
echo ""
fi
cat "$TEMP_BODY"
echo "" >&2
log_info "${GREEN}==> Release notes generated successfully!${NC}"
echo "" >&2
log_info "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
log_info "A GitHub release exists for ${CYAN}$TO_TAG${NC}"
log_info "Current title: ${CYAN}$CURRENT_TITLE${NC}"
log_info "New title: ${CYAN}$RELEASE_TITLE${NC}"
if [ -n "$CURRENT_URL" ]; then
log_info "URL: ${CYAN}$CURRENT_URL${NC}"
fi
log_info "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
echo "" >&2
printf "${YELLOW}Do you want to update the release notes on GitHub? [y/N]:${NC} " >&2
read -r UPDATE_RELEASE
if [ "$UPDATE_RELEASE" = "y" ] || [ "$UPDATE_RELEASE" = "Y" ]; then
log_info "Updating release $TO_TAG on GitHub..."
log_info "Title: ${CYAN}$RELEASE_TITLE${NC}"
# Update release with both title and body
if gh release edit "$TO_TAG" --title "$RELEASE_TITLE" --notes-file "$TEMP_BODY" 2>&1 >&2; then
echo "" >&2
log_info "${GREEN}✓ Release notes updated successfully!${NC}"
if [ -n "$CURRENT_URL" ]; then
log_info "View at: ${CYAN}$CURRENT_URL${NC}"
fi
else
echo "" >&2
log_info "${RED}✗ Failed to update release${NC}"
log_info "You can manually update with:"
echo -e " ${CYAN}gh release edit $TO_TAG --notes-file -${NC} < notes.md" >&2
fi
else
log_info "Skipped release update"
log_info "You can update manually later with:"
echo -e " ${CYAN}./scripts/release/generate-notes $FROM_TAG $TO_TAG | gh release edit $TO_TAG --notes-file -${NC}" >&2
fi
rm -f "$TEMP_NOTES" "$TEMP_BODY"
exit 0
fi
# If no auto-update, just show success message
echo ""
log_info "${GREEN}==> Release notes generated successfully!${NC}"