hass.tibber_prices/scripts/release/generate-notes
Julian Pawlowski 4d9b1545b0
Some checks are pending
Auto-Tag on Version Bump / Check and create version tag (push) Waiting to run
Deploy Docusaurus Documentation (Dual Sites) / Build and Deploy Documentation Sites (push) Waiting to run
Lint / Ruff (push) Waiting to run
Validate / Hassfest validation (push) Waiting to run
Validate / HACS validation (push) Waiting to run
feat(release): enhance AI prompt for generating user-focused release notes
2026-04-06 15:28:17 +00:00

675 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}"
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}"