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:
Julian Pawlowski 2025-11-09 14:25:15 +00:00
parent 8880df1bee
commit e08fd60070
7 changed files with 746 additions and 0 deletions

35
.github/release.yml vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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}"

View file

@ -6,6 +6,23 @@ set -e
cd "$(dirname "$0")/.." 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 scripts/bootstrap
echo "==> Project is now ready to go!" echo "==> Project is now ready to go!"