From e08fd60070242382173da68fd240e1b6f0225a19 Mon Sep 17 00:00:00 2001 From: Julian Pawlowski Date: Sun, 9 Nov 2025 14:25:15 +0000 Subject: [PATCH] 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. --- .github/release.yml | 35 ++++ .github/workflows/auto-tag.yml | 73 +++++++ .github/workflows/release.yml | 67 +++++++ cliff.toml | 54 +++++ scripts/generate-release-notes | 354 +++++++++++++++++++++++++++++++++ scripts/prepare-release | 146 ++++++++++++++ scripts/setup | 17 ++ 7 files changed, 746 insertions(+) create mode 100644 .github/release.yml create mode 100644 .github/workflows/auto-tag.yml create mode 100644 .github/workflows/release.yml create mode 100644 cliff.toml create mode 100755 scripts/generate-release-notes create mode 100755 scripts/prepare-release diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..fa0badb --- /dev/null +++ b/.github/release.yml @@ -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: + - "*" diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml new file mode 100644 index 0000000..e85ee85 --- /dev/null +++ b/.github/workflows/auto-tag.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..800f9af --- /dev/null +++ b/.github/workflows/release.yml @@ -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<> $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 diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..8f14e11 --- /dev/null +++ b/cliff.toml @@ -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 diff --git a/scripts/generate-release-notes b/scripts/generate-release-notes new file mode 100755 index 0000000..aeaf9eb --- /dev/null +++ b/scripts/generate-release-notes @@ -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) + - ๏ฟฝ Dependencies (chore(deps)) + - ๏ฟฝ๐Ÿ”ง 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}" diff --git a/scripts/prepare-release b/scripts/prepare-release new file mode 100755 index 0000000..72d9cdf --- /dev/null +++ b/scripts/prepare-release @@ -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}" diff --git a/scripts/setup b/scripts/setup index d942def..4fa6476 100755 --- a/scripts/setup +++ b/scripts/setup @@ -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!"