mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-30 13:23:41 +00:00
Major restructuring of the scripts/ directory with consistent output
formatting, improved organization, and stricter error handling.
Breaking Changes:
- Updated development environment to Home Assistant 2025.7+
- Removed Python 3.12 compatibility (HA 2025.7+ requires Python 3.13)
- Updated all HA core requirements from 2025.7 requirement files
- Added new dependencies: python-multipart, uv (for faster package management)
- Updated GitHub Actions workflows to use Python 3.13
Changes:
- Created centralized output library (scripts/.lib/output.sh)
- Unified color codes and Unicode symbols
- Consistent formatting functions (log_header, log_success, log_error, etc.)
- Support for embedded formatting codes (${BOLD}, ${GREEN}, etc.)
- Reorganized into logical subdirectories:
- scripts/setup/ - Setup and maintenance scripts
- bootstrap: Install/update dependencies (used in CI/CD)
- setup: Full DevContainer setup (pyright, copilot, HACS)
- reset: Reset config/ directory to fresh state (NEW)
- sync-hacs: Sync HACS integrations
- scripts/release/ - Release management scripts
- prepare: Version bump and tagging
- suggest-version: Semantic version suggestion
- generate-notes: Release notes generation
- check-if-released: Check release status
- hassfest: Local integration validation
- Updated all scripts with:
- set -euo pipefail for stricter error handling
- Consistent SCRIPT_DIR pattern for reliable sourcing
- Professional output with colors and emojis
- Unified styling across all 17 scripts
- Removed redundant scripts:
- scripts/update (was just wrapper around bootstrap)
- scripts/json_schemas/ (moved to schemas/json/)
- Enhanced clean script:
- Improved artifact cleanup
- Better handling of accidental package installations
- Hints for reset and deep clean options
- New reset script features:
- Standard mode: Keep configuration.yaml
- Full mode (--full): Reset configuration.yaml from git
- Automatic re-setup after reset
- Updated documentation:
- AGENTS.md: Updated script references and workflow guidance
- docs/development/: Updated all references to new script structure
Impact: Development environment now requires Python 3.13 and Home Assistant
2025.7+. Developers get consistent, professional script output with better
error handling and logical organization. Single source of truth for styling
makes future updates trivial.
654 lines
23 KiB
Bash
Executable file
654 lines
23 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'
|
|
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 "$@" >&2
|
|
}
|
|
else
|
|
# Local execution, show colored output
|
|
log_info() {
|
|
echo "$@"
|
|
}
|
|
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 "${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}\")
|
|
|
|
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 tasked with generating professional release notes in GitHub-flavored Markdown from conventional commits.
|
|
|
|
**CRITICAL Format Requirements:**
|
|
|
|
1. **START with a single-line title using '# ' (H1 heading):**
|
|
- Analyze ALL commits and create a SHORT, descriptive title (max 5-7 words)
|
|
|
|
**HOW TO CHOOSE THE TITLE:**
|
|
Step 1: Scan commit scopes and identify USER-FACING changes:
|
|
- feat(sensors): ✅ User feature (they see new sensors)
|
|
- feat(config_flow): ✅ User feature (they use config UI)
|
|
- feat(services): ✅ User feature (they call services)
|
|
- fix(translations): ✅ User fix (they see corrected text)
|
|
- feat(release): ❌ Developer tool (users don't see this)
|
|
- feat(ci): ❌ Infrastructure (users don't care)
|
|
- docs: ❌ Documentation (nice but not a feature)
|
|
|
|
Step 2: Among user-facing changes, prioritize by impact:
|
|
- New sensor types > Config improvements > Bug fixes > Translations
|
|
- Multiple related sensors = name the feature domain (e.g., \"Price Volatility Analysis\")
|
|
- Major config flow changes = \"Enhanced Configuration Experience\"
|
|
|
|
Step 3: Ignore developer/internal changes for title selection:
|
|
- Even if there are 10 release/ci/docs commits, they don't appear in title
|
|
- Title must reflect what USERS experience when they upgrade
|
|
|
|
**EXAMPLE DECISION PROCESS:**
|
|
Given these commits:
|
|
- feat(sensors): Add 4 volatility sensors with classification
|
|
- feat(release): Add automated release notes generation
|
|
- fix(translations): Fix hassfest validation errors
|
|
- docs: Restructure documentation
|
|
|
|
WRONG title: \"Release Automation & Documentation\" (developer-focused!)
|
|
RIGHT title: \"Price Volatility Analysis Features\" (user sees new sensors!)
|
|
|
|
**GOOD TITLES (user-focused):**
|
|
- \"# Advanced Price Analysis Features\" (new sensors = user feature)
|
|
- \"# Enhanced Configuration Experience\" (config flow = user-facing)
|
|
- \"# Improved Reliability & Performance\" (stability = user benefit)
|
|
|
|
**BAD TITLES (avoid these):**
|
|
- \"# Release Automation\" (developers don't read release notes!)
|
|
- \"# Developer Experience\" (not user-facing!)
|
|
- \"# New Features & Bug Fixes\" (boring, says nothing)
|
|
|
|
Keep it exciting but professional. Do NOT include version number.
|
|
|
|
2. **After title, use '### ' (H3 headings) for sections:**
|
|
- ### 🎉 New Features (for feat commits)
|
|
- ### 🐛 Bug Fixes (for fix commits)
|
|
- ### 📚 Documentation (for docs commits)
|
|
- ### 📦 Dependencies (for chore(deps) commits)
|
|
- ### 🔧 Maintenance & Refactoring (for refactor/chore commits)
|
|
- ### 🧪 Testing (for test commits)
|
|
|
|
3. **Smart grouping**: If multiple commits address the same logical change:
|
|
- Combine them into one release note entry
|
|
- Link all related commits: ([hash1](url1), [hash2](url2))
|
|
- Use a descriptive summary that captures the overall change
|
|
|
|
4. Extract 'Impact:' sections from commit bodies as user-friendly descriptions
|
|
|
|
5. Format each item as: - **scope**: Description ([hash](https://github.com/${GITHUB_REPO}/commit/hash))
|
|
|
|
6. **Exclude from release notes (but still consider for context):**
|
|
- Manifest.json version bumps: chore(release): bump version
|
|
- Development environment: feat/fix/chore(devcontainer), feat/fix/chore(vscode), feat/fix/chore(scripts)
|
|
- CI/CD infrastructure: feat/fix/chore/ci(ci), feat/fix/chore(workflow), feat/fix/chore(actions)
|
|
- IMPORTANT: These should NEVER influence the title choice, even if they're the majority!
|
|
|
|
7. **Include in release notes (these CAN influence title):**
|
|
- Dependency updates: chore(deps) - these ARE relevant for users
|
|
- Any user-facing changes regardless of scope (sensors, config, services, binary_sensor, etc.)
|
|
- Bug fixes that users experience (translations, api, coordinator, etc.)
|
|
|
|
8. **Understanding the file paths (use this to assess importance):**
|
|
- custom_components/tibber_prices/sensor/ = User-facing sensors (HIGH priority for title)
|
|
- custom_components/tibber_prices/binary_sensor.py = User-facing binary sensors (HIGH priority)
|
|
- custom_components/tibber_prices/config_flow.py = User-facing configuration (HIGH priority)
|
|
- custom_components/tibber_prices/services.py = User-facing services (HIGH priority)
|
|
- custom_components/tibber_prices/translations/*.json = User-facing translations (MEDIUM priority)
|
|
- scripts/ = Developer tools (LOW priority, exclude from title)
|
|
- .github/workflows/ = CI/CD automation (LOW priority, exclude from title)
|
|
- docs/ = Documentation (LOW priority, exclude from title)
|
|
|
|
**Key insight:** Large changes (100+ lines) in custom_components/tibber_prices/ are usually important user features!
|
|
|
|
9. **Output structure example:**
|
|
# Title Here
|
|
|
|
### 🎉 New Features
|
|
|
|
- **scope**: description ([abc123](url))
|
|
|
|
### 🐛 Bug Fixes
|
|
...
|
|
|
|
---
|
|
|
|
**Now, here are the commits to analyze (between ${FROM_TAG} and ${TO_TAG}):**
|
|
|
|
${COMMITS}
|
|
|
|
---
|
|
|
|
**Generate the release notes now. Start with '# ' title, then use '### ' for sections.**
|
|
|
|
**IMPORTANT: Output ONLY the release notes in Markdown format. Do NOT include:**
|
|
- Explanations or rationale for your choices
|
|
- Meta-commentary about the process
|
|
- Analysis of why you chose the title
|
|
- Any text after the last release note section
|
|
|
|
End the output after the last release note item. Nothing more."
|
|
|
|
# Save prompt to temp file for copilot
|
|
TEMP_PROMPT=$(mktemp)
|
|
echo "$PROMPT" > "$TEMP_PROMPT"
|
|
|
|
# Use Claude Haiku 4.5 for faster, cheaper generation (same quality for structured tasks)
|
|
# Can override with: COPILOT_MODEL=claude-sonnet-4.5 ./scripts/release/generate-notes
|
|
COPILOT_MODEL="${COPILOT_MODEL:-claude-haiku-4.5}"
|
|
|
|
# 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 "${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 "${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 " ${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 " ${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}"
|