mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
feat(release): add automated release notes generation system
Implemented multi-backend release notes generation: **Scripts:** - prepare-release: Bump manifest.json + create tag (foolproof workflow) - generate-release-notes: Parse conventional commits with 3 backends * GitHub Copilot CLI (AI-powered, smart grouping) * git-cliff (template-based, fast and reliable) * Manual grep/awk (fallback, always works) - setup: Auto-install git-cliff via cargo in DevContainer **GitHub Actions:** - auto-tag.yml: Automatically create tag on manifest.json version bump - release.yml: Generate release notes and create GitHub release on tag push - release.yml: Button config for GitHub UI release notes generator **Configuration:** - cliff.toml: Smart filtering rules * Excludes: manifest bumps, dev-env changes, CI/CD changes * Includes: Dependency updates (relevant for users) * Groups by conventional commit type with emoji **Workflow:** 1. Run `./scripts/prepare-release 0.3.0` 2. Push commit + tag: `git push origin main v0.3.0` 3. CI/CD automatically generates release notes and creates GitHub release Impact: Maintainers can prepare professional releases with one command. Release notes are automatically generated from conventional commits with intelligent filtering and categorization.
This commit is contained in:
parent
8880df1bee
commit
e08fd60070
7 changed files with 746 additions and 0 deletions
35
.github/release.yml
vendored
Normal file
35
.github/release.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Configuration for GitHub's automatic release notes generation
|
||||
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
|
||||
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
- skip-changelog
|
||||
authors:
|
||||
- dependabot
|
||||
categories:
|
||||
- title: 🎉 New Features
|
||||
labels:
|
||||
- enhancement
|
||||
- feature
|
||||
- feat
|
||||
- title: 🐛 Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- fix
|
||||
- title: 📚 Documentation
|
||||
labels:
|
||||
- documentation
|
||||
- docs
|
||||
- title: 🔧 Maintenance & Refactoring
|
||||
labels:
|
||||
- chore
|
||||
- refactor
|
||||
- dependencies
|
||||
- title: 🧪 Testing
|
||||
labels:
|
||||
- test
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
73
.github/workflows/auto-tag.yml
vendored
Normal file
73
.github/workflows/auto-tag.yml
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
name: Auto-Tag on Version Bump
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'custom_components/tibber_prices/manifest.json'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
check-and-tag:
|
||||
name: Check and create version tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Need full history for git describe
|
||||
|
||||
- name: Extract version from manifest.json
|
||||
id: manifest
|
||||
run: |
|
||||
VERSION=$(jq -r '.version' custom_components/tibber_prices/manifest.json)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Manifest version: $VERSION"
|
||||
|
||||
- name: Check if tag already exists
|
||||
id: tag_check
|
||||
run: |
|
||||
if git rev-parse "v${{ steps.manifest.outputs.version }}" >/dev/null 2>&1; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
echo "✓ Tag v${{ steps.manifest.outputs.version }} already exists"
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
echo "✗ Tag v${{ steps.manifest.outputs.version }} does not exist yet"
|
||||
fi
|
||||
|
||||
- name: Validate version format
|
||||
if: steps.tag_check.outputs.exists == 'false'
|
||||
run: |
|
||||
VERSION="${{ steps.manifest.outputs.version }}"
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "❌ Invalid version format: $VERSION"
|
||||
echo "Expected format: X.Y.Z (e.g., 1.0.0)"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Version format valid: $VERSION"
|
||||
|
||||
- name: Create and push tag
|
||||
if: steps.tag_check.outputs.exists == 'false'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
TAG="v${{ steps.manifest.outputs.version }}"
|
||||
git tag -a "$TAG" -m "chore(release): version ${{ steps.manifest.outputs.version }}"
|
||||
git push origin "$TAG"
|
||||
|
||||
echo "✅ Created and pushed tag: $TAG"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
if [ "${{ steps.tag_check.outputs.exists }}" = "true" ]; then
|
||||
echo "ℹ️ Tag v${{ steps.manifest.outputs.version }} already exists. Skipping tag creation." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ Successfully created tag v${{ steps.manifest.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Next step:** Release workflow will now generate release notes and create GitHub release." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
67
.github/workflows/release.yml
vendored
Normal file
67
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
name: Generate Release Notes
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*' # Triggers on version tags like v1.0.0, v2.1.3, etc.
|
||||
|
||||
permissions:
|
||||
contents: write # Needed to create/update releases
|
||||
|
||||
jobs:
|
||||
release-notes:
|
||||
name: Generate and publish release notes
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for git-cliff
|
||||
|
||||
- name: Get previous tag
|
||||
id: previoustag
|
||||
run: |
|
||||
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
echo "previous_tag=${PREVIOUS_TAG}" >> $GITHUB_OUTPUT
|
||||
echo "Previous tag: ${PREVIOUS_TAG}"
|
||||
|
||||
- name: Install git-cliff
|
||||
run: |
|
||||
wget https://github.com/orhun/git-cliff/releases/download/v2.4.0/git-cliff-2.4.0-x86_64-unknown-linux-gnu.tar.gz
|
||||
tar -xzf git-cliff-2.4.0-x86_64-unknown-linux-gnu.tar.gz
|
||||
sudo mv git-cliff-2.4.0/git-cliff /usr/local/bin/
|
||||
git-cliff --version
|
||||
|
||||
- name: Generate release notes
|
||||
id: release_notes
|
||||
run: |
|
||||
FROM_TAG="${{ steps.previoustag.outputs.previous_tag }}"
|
||||
TO_TAG="${GITHUB_REF#refs/tags/}"
|
||||
|
||||
echo "Generating release notes from ${FROM_TAG} to ${TO_TAG}"
|
||||
|
||||
# Use our script with git-cliff backend (AI disabled in CI)
|
||||
# git-cliff will handle filtering via cliff.toml
|
||||
USE_AI=false ./scripts/generate-release-notes "${FROM_TAG}" "${TO_TAG}" > release-notes.md
|
||||
|
||||
# Output for GitHub Actions
|
||||
{
|
||||
echo 'notes<<EOF'
|
||||
cat release-notes.md
|
||||
echo EOF
|
||||
} >> $GITHUB_OUTPUT - name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: ${{ steps.release_notes.outputs.notes }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
generate_release_notes: false # We provide our own
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "✅ Release notes generated and published!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tag:** ${GITHUB_REF#refs/tags/}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Previous tag:** ${{ steps.previoustag.outputs.previous_tag }}" >> $GITHUB_STEP_SUMMARY
|
||||
54
cliff.toml
Normal file
54
cliff.toml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Configuration for git-cliff
|
||||
# See: https://git-cliff.org/docs/configuration
|
||||
|
||||
[changelog]
|
||||
# Template for the changelog body
|
||||
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 %} \
|
||||
([{{ commit.id | truncate(length=7, end="") }}](https://github.com/jpawlowski/hass.tibber_prices/commit/{{ commit.id }}))
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
"""
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# Parse conventional commits
|
||||
conventional_commits = true
|
||||
# Include all commits (even non-conventional)
|
||||
filter_unconventional = false
|
||||
split_commits = false
|
||||
|
||||
# Commit parsers - map commit types to categories
|
||||
commit_parsers = [
|
||||
# Skip manifest.json version bumps (release housekeeping)
|
||||
{ message = "^chore\\(release\\): bump version", skip = true },
|
||||
# Skip development environment changes (not user-relevant)
|
||||
{ message = "^(feat|fix|chore|refactor)\\((devcontainer|vscode|scripts|dev-env|environment)\\):", skip = true },
|
||||
# Skip CI/CD infrastructure changes (not user-relevant)
|
||||
{ message = "^(feat|fix|chore|ci)\\((ci|workflow|actions|github-actions)\\):", skip = true },
|
||||
# Keep dependency updates - these ARE relevant for users
|
||||
{ message = "^chore\\(deps\\):", group = "📦 Dependencies" },
|
||||
# Regular commit types
|
||||
{ message = "^feat", group = "🎉 New Features" },
|
||||
{ message = "^fix", group = "🐛 Bug Fixes" },
|
||||
{ message = "^docs?", 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 = "^build", group = "📦 Build" },
|
||||
]
|
||||
|
||||
# Protect breaking changes
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/jpawlowski/hass.tibber_prices/issues/${2}))" },
|
||||
]
|
||||
|
||||
# Filter out commits
|
||||
filter_commits = false
|
||||
354
scripts/generate-release-notes
Executable file
354
scripts/generate-release-notes
Executable file
|
|
@ -0,0 +1,354 @@
|
|||
#!/bin/sh
|
||||
|
||||
# script/generate-release-notes: Generate release notes from conventional commits
|
||||
#
|
||||
# This script generates release notes by parsing conventional commits between git tags.
|
||||
# It supports multiple backends for generation:
|
||||
#
|
||||
# 1. GitHub Copilot (VS Code Copilot CLI) - Intelligent, context-aware
|
||||
# 2. git-cliff - Fast, template-based Rust tool
|
||||
# 3. Manual parsing - Simple grep/awk fallback
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/generate-release-notes [FROM_TAG] [TO_TAG]
|
||||
# ./scripts/generate-release-notes v1.0.0 v1.1.0
|
||||
# ./scripts/generate-release-notes v1.0.0 HEAD
|
||||
# ./scripts/generate-release-notes # Uses latest tag to HEAD
|
||||
#
|
||||
# Environment variables:
|
||||
# RELEASE_NOTES_BACKEND - Force specific backend: copilot, git-cliff, manual
|
||||
# USE_AI - Set to "false" to skip AI backends (for CI/CD)
|
||||
|
||||
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
|
||||
|
||||
# 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}"
|
||||
echo "Usage: $0 [FROM_TAG] [TO_TAG]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${BLUE}==> Generating release notes: ${FROM_TAG}..${TO_TAG}${NC}"
|
||||
echo ""
|
||||
|
||||
# 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)
|
||||
echo "${GREEN}Using backend: ${BACKEND}${NC}"
|
||||
echo ""
|
||||
|
||||
# Backend: GitHub Copilot CLI (AI-powered)
|
||||
generate_with_copilot() {
|
||||
echo "${BLUE}==> Generating with GitHub Copilot CLI (AI-powered)${NC}"
|
||||
echo "${YELLOW}Note: This will use one premium request from your monthly quota${NC}"
|
||||
echo ""
|
||||
|
||||
# Get commit log for the range
|
||||
COMMITS=$(git log --pretty=format:"%h | %s%n%b%n---" "${FROM_TAG}..${TO_TAG}")
|
||||
|
||||
if [ -z "$COMMITS" ]; then
|
||||
echo "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create prompt for Copilot
|
||||
PROMPT="Generate release notes in GitHub-flavored Markdown from these conventional commits.
|
||||
|
||||
**Commits between ${FROM_TAG} and ${TO_TAG}:**
|
||||
|
||||
${COMMITS}
|
||||
|
||||
**Format Requirements:**
|
||||
1. Parse conventional commit format: type(scope): description
|
||||
2. Group by type with emoji headings:
|
||||
- 🎉 New Features (feat)
|
||||
- 🐛 Bug Fixes (fix)
|
||||
- 📚 Documentation (docs)
|
||||
- <20> Dependencies (chore(deps))
|
||||
- <20>🔧 Maintenance & Refactoring (refactor, chore)
|
||||
- 🧪 Testing (test)
|
||||
3. **Smart grouping**: If multiple commits address the same logical change or feature:
|
||||
- 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. Keep it concise, focus on user-visible changes
|
||||
7. **Exclude from release notes**:
|
||||
- Manifest.json version bumps: chore(release): bump version
|
||||
- Development environment: feat/fix/chore(devcontainer), feat/fix/chore(vscode), feat/fix/chore(scripts), feat/fix/chore(dev-env)
|
||||
- CI/CD infrastructure: feat/fix/chore/ci(ci), feat/fix/chore/ci(workflow), feat/fix/chore/ci(actions)
|
||||
8. **Include in release notes**:
|
||||
- Dependency updates: chore(deps) - these ARE relevant for users
|
||||
- Any user-facing changes regardless of scope
|
||||
9. Output ONLY the markdown, no explanations
|
||||
|
||||
**Examples of smart grouping:**
|
||||
- Multiple commits fixing the same bug → One entry with all commits
|
||||
- Commits adding a feature incrementally → One entry describing the complete feature
|
||||
- Related refactoring commits → One entry summarizing the improvement
|
||||
|
||||
Generate the release notes now:"
|
||||
|
||||
# Save prompt to temp file for copilot
|
||||
TEMP_PROMPT=$(mktemp)
|
||||
echo "$PROMPT" > "$TEMP_PROMPT"
|
||||
|
||||
# Call copilot CLI (it will handle authentication interactively)
|
||||
copilot < "$TEMP_PROMPT" || {
|
||||
echo ""
|
||||
echo "${YELLOW}Warning: GitHub Copilot CLI failed or was not authenticated${NC}"
|
||||
echo "${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 (fast Rust tool)
|
||||
generate_with_gitcliff() {
|
||||
echo "${BLUE}==> Generating with git-cliff${NC}"
|
||||
|
||||
# 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() {
|
||||
echo "${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
|
||||
echo "${YELLOW}No commits found between ${FROM_TAG} and ${TO_TAG}${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
# Execute based on backend
|
||||
case "$BACKEND" in
|
||||
copilot)
|
||||
if ! command -v copilot >/dev/null 2>&1; then
|
||||
echo "${RED}Error: GitHub Copilot CLI not found${NC}"
|
||||
echo "Install: npm install -g @github/copilot"
|
||||
echo "See: https://github.com/github/copilot-cli"
|
||||
exit 1
|
||||
fi
|
||||
generate_with_copilot
|
||||
;;
|
||||
git-cliff)
|
||||
if ! command -v git-cliff >/dev/null 2>&1; then
|
||||
echo "${RED}Error: git-cliff not found${NC}"
|
||||
echo "Install: https://git-cliff.org/docs/installation"
|
||||
exit 1
|
||||
fi
|
||||
generate_with_gitcliff
|
||||
;;
|
||||
manual)
|
||||
generate_with_manual
|
||||
;;
|
||||
*)
|
||||
echo "${RED}Error: Unknown backend: ${BACKEND}${NC}"
|
||||
echo "Valid backends: copilot, git-cliff, manual"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "${GREEN}==> Release notes generated successfully!${NC}"
|
||||
146
scripts/prepare-release
Executable file
146
scripts/prepare-release
Executable file
|
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# script/prepare-release: Prepare a new release by bumping version and creating tag
|
||||
#
|
||||
# This script:
|
||||
# 1. Validates the version format (X.Y.Z)
|
||||
# 2. Updates custom_components/tibber_prices/manifest.json
|
||||
# 3. Commits the change with conventional commit format
|
||||
# 4. Creates an annotated git tag
|
||||
# 5. Shows you what will be pushed (you decide when to push)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/prepare-release VERSION
|
||||
# ./scripts/prepare-release 0.3.0
|
||||
# ./scripts/prepare-release 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Check if we have uncommitted changes
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo -e "${RED}❌ Error: You have uncommitted changes.${NC}"
|
||||
echo "Please commit or stash them first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse version argument
|
||||
VERSION="${1:-}"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo -e "${RED}❌ Error: No version specified.${NC}"
|
||||
echo ""
|
||||
echo "Usage: $0 VERSION"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 0.3.0 # Bump to version 0.3.0"
|
||||
echo " $0 1.0.0 # Bump to version 1.0.0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Strip 'v' prefix if present
|
||||
VERSION="${VERSION#v}"
|
||||
|
||||
# Validate version format (X.Y.Z)
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo -e "${RED}❌ Error: Invalid version format: $VERSION${NC}"
|
||||
echo "Expected format: X.Y.Z (e.g., 0.3.0, 1.0.0)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="v$VERSION"
|
||||
MANIFEST="custom_components/tibber_prices/manifest.json"
|
||||
|
||||
# Check if manifest.json exists
|
||||
if [[ ! -f "$MANIFEST" ]]; then
|
||||
echo -e "${RED}❌ Error: Manifest file not found: $MANIFEST${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tag already exists (locally or remotely)
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo -e "${RED}❌ Error: Tag $TAG already exists locally!${NC}"
|
||||
echo "To remove it: git tag -d $TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git ls-remote --tags origin | grep -q "refs/tags/$TAG"; then
|
||||
echo -e "${RED}❌ Error: Tag $TAG already exists on remote!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current version
|
||||
CURRENT_VERSION=$(jq -r '.version' "$MANIFEST")
|
||||
echo -e "${BLUE}Current version: ${CURRENT_VERSION}${NC}"
|
||||
echo -e "${BLUE}New version: ${VERSION}${NC}"
|
||||
echo ""
|
||||
|
||||
# Update manifest.json
|
||||
echo -e "${YELLOW}📝 Updating $MANIFEST...${NC}"
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo -e "${RED}❌ Error: jq is not installed${NC}"
|
||||
echo "Please install jq: apt-get install jq (or brew install jq)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create backup
|
||||
cp "$MANIFEST" "$MANIFEST.backup"
|
||||
|
||||
# Update version with jq
|
||||
if ! jq ".version = \"$VERSION\"" "$MANIFEST" > "$MANIFEST.tmp"; then
|
||||
echo -e "${RED}❌ Error: Failed to update manifest.json${NC}"
|
||||
mv "$MANIFEST.backup" "$MANIFEST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$MANIFEST.tmp" "$MANIFEST"
|
||||
rm "$MANIFEST.backup"
|
||||
|
||||
echo -e "${GREEN}✓ Updated manifest.json${NC}"
|
||||
|
||||
# Stage and commit
|
||||
echo -e "${YELLOW}📦 Creating commit...${NC}"
|
||||
git add "$MANIFEST"
|
||||
git commit -m "chore(release): bump version to $VERSION"
|
||||
echo -e "${GREEN}✓ Created commit${NC}"
|
||||
|
||||
# Create annotated tag
|
||||
echo -e "${YELLOW}🏷️ Creating tag $TAG...${NC}"
|
||||
git tag -a "$TAG" -m "chore(release): version $VERSION"
|
||||
echo -e "${GREEN}✓ Created tag $TAG${NC}"
|
||||
|
||||
# Show preview
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${GREEN}✅ Release $VERSION prepared successfully!${NC}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}Review the changes:${NC}"
|
||||
git log -1 --stat
|
||||
echo ""
|
||||
echo -e "${BLUE}Review the tag:${NC}"
|
||||
git show "$TAG" --no-patch
|
||||
echo ""
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${YELLOW}Next steps:${NC}"
|
||||
echo ""
|
||||
echo -e " ${GREEN}✓ To push and trigger release:${NC}"
|
||||
echo -e " git push origin main $TAG"
|
||||
echo ""
|
||||
echo -e " ${RED}✗ To abort and undo:${NC}"
|
||||
echo -e " git reset --hard HEAD~1 # Undo commit"
|
||||
echo -e " git tag -d $TAG # Delete tag"
|
||||
echo ""
|
||||
echo -e "${BLUE}What happens after push:${NC}"
|
||||
echo " 1. Both commit and tag are pushed to GitHub"
|
||||
echo " 2. CI/CD detects the new tag"
|
||||
echo " 3. Release notes are generated automatically"
|
||||
echo " 4. GitHub release is created"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
|
|
@ -6,6 +6,23 @@ set -e
|
|||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Install optional release note backend: GitHub Copilot CLI (AI-powered)
|
||||
if command -v npm >/dev/null 2>&1; then
|
||||
echo "==> Installing GitHub Copilot CLI for AI-powered release notes..."
|
||||
npm install -g @github/copilot 2>/dev/null || {
|
||||
echo " ⚠️ Warning: GitHub Copilot CLI installation failed (optional)"
|
||||
echo " ℹ️ You can install it manually: npm install -g @github/copilot"
|
||||
}
|
||||
fi
|
||||
|
||||
# Install optional release note backend: git-cliff (template-based)
|
||||
if command -v cargo >/dev/null 2>&1; then
|
||||
echo "==> Installing git-cliff for template-based release notes..."
|
||||
cargo install git-cliff || {
|
||||
echo " ⚠️ Warning: git-cliff installation failed (optional)"
|
||||
}
|
||||
fi
|
||||
|
||||
scripts/bootstrap
|
||||
|
||||
echo "==> Project is now ready to go!"
|
||||
|
|
|
|||
Loading…
Reference in a new issue