feat(release): add semantic versioning workflow automation

Added intelligent version suggestion system based on Conventional Commits
analysis to support proper semantic versioning.

New scripts:
- check-if-released: Verify if commit exists in any version tag
  - Helps decide if legacy migration code is needed
  - Shows guidance for breaking changes vs simple migrations

- suggest-version: Analyze commits and suggest next version
  - Counts breaking changes, features, and bug fixes
  - Applies pre-1.0 rules: breaking→MINOR, feat→MINOR, fix→PATCH
  - Applies post-1.0 rules: breaking→MAJOR, feat→MINOR, fix→PATCH
  - Checks manifest.json and suggests alternatives (MAJOR/MINOR/PATCH)
  - Provides preview and release commands

Updated scripts:
- prepare-release: Now calls suggest-version when no argument provided
  - Shows suggested version before prompting
  - Maintains manual override capability

Impact: Developers get intelligent version suggestions based on actual
commit content, reducing versioning mistakes and following semver correctly.
This commit is contained in:
Julian Pawlowski 2025-11-09 15:32:44 +00:00
parent 7e57facf50
commit ddc718aabd
3 changed files with 300 additions and 1 deletions

90
scripts/check-if-released Executable file
View file

@ -0,0 +1,90 @@
#!/bin/bash
# Check if a commit or code change has been released (is contained in any version tag)
#
# Usage:
# ./scripts/check-if-released <commit-hash>
# ./scripts/check-if-released <commit-hash> --details
#
# Examples:
# ./scripts/check-if-released f4568be
# ./scripts/check-if-released HEAD~3 --details
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}/.."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Check if commit hash provided
if [ -z "$1" ]; then
echo "Usage: $0 <commit-hash> [--details]"
echo ""
echo "Examples:"
echo " $0 f4568be"
echo " $0 HEAD~3 --details"
exit 1
fi
COMMIT="$1"
DETAILS="${2:-}"
# Validate commit exists
if ! git rev-parse --verify "$COMMIT" >/dev/null 2>&1; then
echo -e "${RED}Error: Commit '$COMMIT' not found${NC}"
exit 1
fi
# Get full commit hash
FULL_HASH=$(git rev-parse "$COMMIT")
SHORT_HASH=$(git rev-parse --short "$COMMIT")
# Get commit info
COMMIT_SUBJECT=$(git log -1 --format="%s" "$COMMIT")
COMMIT_DATE=$(git log -1 --format="%ci" "$COMMIT")
echo "Checking commit: $SHORT_HASH"
echo "Subject: $COMMIT_SUBJECT"
echo "Date: $COMMIT_DATE"
echo ""
# Check if commit is in any version tag (v*.*.*)
TAGS=$(git tag --contains "$COMMIT" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' || true)
if [ -z "$TAGS" ]; then
echo -e "${GREEN}✓ NOT RELEASED${NC}"
echo "This commit is not part of any version tag."
echo ""
echo -e "${YELLOW}→ No legacy migration needed for code introduced in this commit${NC}"
exit 0
else
echo -e "${RED}✗ ALREADY RELEASED${NC}"
echo "This commit is included in the following version tags:"
echo "$TAGS" | sed 's/^/ - /'
echo ""
echo -e "${YELLOW}⚠ Breaking Change Decision:${NC}"
echo " 1. If migration is SIMPLE (e.g., .lower(), key rename) → Add it"
echo " 2. If migration is COMPLEX → Document in release notes instead"
echo " 3. Home Assistant style: Prefer breaking changes over code complexity"
echo ""
if [ "$DETAILS" = "--details" ]; then
echo ""
echo "First release containing this commit:"
FIRST_TAG=$(echo "$TAGS" | head -1)
echo " Tag: $FIRST_TAG"
git log -1 --format=" Date: %ci" "$FIRST_TAG"
echo ""
echo "Latest release:"
LATEST_TAG=$(git tag -l 'v*.*.*' --sort=-version:refname | head -1)
echo " Tag: $LATEST_TAG"
git log -1 --format=" Date: %ci" "$LATEST_TAG"
fi
exit 1
fi

View file

@ -10,7 +10,8 @@
# 5. Shows you what will be pushed (you decide when to push)
#
# Usage:
# ./scripts/prepare-release VERSION
# ./scripts/prepare-release [VERSION]
# ./scripts/prepare-release --suggest
# ./scripts/prepare-release 0.3.0
# ./scripts/prepare-release 1.0.0
@ -21,10 +22,24 @@ RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No Color
cd "$(dirname "$0")/.."
# Check if --suggest or no argument
if [[ "${1:-}" == "--suggest" ]] || [[ -z "${1:-}" ]]; then
./scripts/suggest-version
if [[ -z "${1:-}" ]]; then
echo ""
echo -e "${YELLOW}Provide version number as argument:${NC}"
echo " ./scripts/prepare-release X.Y.Z"
exit 0
fi
exit 0
fi
# Check if we have uncommitted changes
if ! git diff-index --quiet HEAD --; then
echo -e "${RED}❌ Error: You have uncommitted changes.${NC}"

194
scripts/suggest-version Executable file
View file

@ -0,0 +1,194 @@
#!/bin/bash
# Analyze commits since last release and suggest next version number
#
# Usage:
# ./scripts/suggest-version [--from TAG]
#
# Examples:
# ./scripts/suggest-version
# ./scripts/suggest-version --from v0.2.0
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${SCRIPT_DIR}/.."
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
# Parse arguments
FROM_TAG="${2:-}"
# Get current version from manifest.json
MANIFEST="custom_components/tibber_prices/manifest.json"
if [[ ! -f "$MANIFEST" ]]; then
echo -e "${RED}Error: Manifest file not found: $MANIFEST${NC}"
exit 1
fi
# Require jq for JSON parsing
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
MANIFEST_VERSION=$(jq -r '.version' "$MANIFEST")
MANIFEST_TAG="v${MANIFEST_VERSION}"
# Get latest version tag
if [ -z "$FROM_TAG" ]; then
FROM_TAG=$(git tag -l 'v*.*.*' --sort=-version:refname | head -1)
if [ -z "$FROM_TAG" ]; then
echo -e "${RED}Error: No version tags found${NC}"
exit 1
fi
fi
# Check if manifest version already has a tag
if git rev-parse "$MANIFEST_TAG" >/dev/null 2>&1; then
# Manifest version is already tagged - analyze from that tag
FROM_TAG="$MANIFEST_TAG"
echo -e "${YELLOW}Note: manifest.json version ${MANIFEST_VERSION} already tagged as ${MANIFEST_TAG}${NC}"
echo ""
fi
echo -e "${BOLD}Analyzing commits since $FROM_TAG${NC}"
echo ""
# Parse current version (from the tag we're analyzing from)
CURRENT_VERSION="${FROM_TAG#v}"
MAJOR=$(echo "$CURRENT_VERSION" | cut -d. -f1)
MINOR=$(echo "$CURRENT_VERSION" | cut -d. -f2)
PATCH=$(echo "$CURRENT_VERSION" | cut -d. -f3)
echo "Current released version: v${MAJOR}.${MINOR}.${PATCH}"
if [[ "$MANIFEST_VERSION" != "$CURRENT_VERSION" ]]; then
echo -e "${YELLOW}Manifest.json version: ${MANIFEST_VERSION} (not yet tagged)${NC}"
fi
echo ""
# Analyze commits (exclude version bump commits)
COMMITS=$(git log "$FROM_TAG"..HEAD --format="%s" --no-merges | grep -v "^chore(release):" || true)
if [ -z "$COMMITS" ]; then
echo -e "${YELLOW}No new commits since last release${NC}"
# Check if manifest.json needs to be tagged
if [[ "$MANIFEST_VERSION" != "$CURRENT_VERSION" ]]; then
echo ""
echo -e "${BLUE}Manifest.json has version ${MANIFEST_VERSION} but no tag exists yet.${NC}"
echo "Create tag with:"
echo " git tag -a v${MANIFEST_VERSION} -m \"Release ${MANIFEST_VERSION}\""
echo " git push origin v${MANIFEST_VERSION}"
fi
exit 0
fi
# Count commit types
BREAKING_COUNT=$(echo "$COMMITS" | grep -c "^[^:]*!:" || true)
FEAT_COUNT=$(echo "$COMMITS" | grep -cE "^feat(\(.+\))?:" || true)
FIX_COUNT=$(echo "$COMMITS" | grep -cE "^fix(\(.+\))?:" || true)
REFACTOR_COUNT=$(echo "$COMMITS" | grep -cE "^refactor(\(.+\))?:" || true)
DOCS_COUNT=$(echo "$COMMITS" | grep -cE "^docs(\(.+\))?:" || true)
OTHER_COUNT=$(echo "$COMMITS" | grep -vcE "^(feat|fix|refactor|docs)(\(.+\))?:" || true)
# Check for breaking changes in commit messages or Impact sections
BREAKING_IN_BODY=$(git log "$FROM_TAG"..HEAD --format="%b" --no-merges | grep -ci "BREAKING CHANGE:" || true)
TOTAL_BREAKING=$((BREAKING_COUNT + BREAKING_IN_BODY))
echo -e "${BOLD}Commit Analysis:${NC}"
echo ""
if [ $TOTAL_BREAKING -gt 0 ]; then
echo -e " ${RED}⚠ Breaking changes:${NC} $TOTAL_BREAKING"
fi
echo -e " ${GREEN}✨ New features:${NC} $FEAT_COUNT"
echo -e " ${BLUE}🐛 Bug fixes:${NC} $FIX_COUNT"
if [ $REFACTOR_COUNT -gt 0 ]; then
echo -e " ${YELLOW}🔧 Refactorings:${NC} $REFACTOR_COUNT"
fi
if [ $DOCS_COUNT -gt 0 ]; then
echo -e " 📚 Documentation: $DOCS_COUNT"
fi
if [ $OTHER_COUNT -gt 0 ]; then
echo -e " 📦 Other: $OTHER_COUNT"
fi
echo ""
# Determine version bump
SUGGESTED_MAJOR=$MAJOR
SUGGESTED_MINOR=$MINOR
SUGGESTED_PATCH=$PATCH
if [ $TOTAL_BREAKING -gt 0 ]; then
# Before v1.0.0: Breaking changes bump minor
# After v1.0.0: Breaking changes bump major
if [ $MAJOR -eq 0 ]; then
SUGGESTED_MINOR=$((MINOR + 1))
SUGGESTED_PATCH=0
BUMP_TYPE="MINOR (breaking changes in 0.x)"
BUMP_REASON="Breaking changes detected (before v1.0.0 → bump minor)"
else
SUGGESTED_MAJOR=$((MAJOR + 1))
SUGGESTED_MINOR=0
SUGGESTED_PATCH=0
BUMP_TYPE="MAJOR (breaking)"
BUMP_REASON="Breaking changes detected"
fi
elif [ $FEAT_COUNT -gt 0 ]; then
SUGGESTED_MINOR=$((MINOR + 1))
SUGGESTED_PATCH=0
BUMP_TYPE="MINOR (features)"
BUMP_REASON="New features added"
elif [ $FIX_COUNT -gt 0 ]; then
SUGGESTED_PATCH=$((PATCH + 1))
BUMP_TYPE="PATCH (fixes)"
BUMP_REASON="Bug fixes only"
else
SUGGESTED_PATCH=$((PATCH + 1))
BUMP_TYPE="PATCH (other)"
BUMP_REASON="Documentation/refactoring changes"
fi
SUGGESTED_VERSION="v${SUGGESTED_MAJOR}.${SUGGESTED_MINOR}.${SUGGESTED_PATCH}"
echo -e "${BOLD}${GREEN}Suggested Version: $SUGGESTED_VERSION${NC}"
echo -e " Bump type: ${BUMP_TYPE}"
echo -e " Reason: ${BUMP_REASON}"
echo ""
# Show alternative versions
echo -e "${BOLD}Alternative Versions:${NC}"
echo -e " ${YELLOW}MAJOR:${NC} v$((MAJOR + 1)).0.0 (if you want to release v1.0.0 or have breaking changes)"
echo -e " ${GREEN}MINOR:${NC} v${MAJOR}.$((MINOR + 1)).0 (if adding features)"
echo -e " ${BLUE}PATCH:${NC} v${MAJOR}.${MINOR}.$((PATCH + 1)) (if only fixes/docs)"
echo ""
# Show preview command
echo -e "${BOLD}Preview Release Notes:${NC}"
echo " ./scripts/generate-release-notes $FROM_TAG HEAD"
echo ""
echo -e "${BOLD}Create Release:${NC}"
echo " ./scripts/prepare-release ${SUGGESTED_MAJOR}.${SUGGESTED_MINOR}.${SUGGESTED_PATCH}"
echo ""
# Show warning if breaking changes detected
if [ $TOTAL_BREAKING -gt 0 ]; then
echo -e "${RED}${BOLD}⚠ WARNING: Breaking changes detected!${NC}"
echo -e "${RED}Make sure to document migration steps in release notes.${NC}"
echo ""
fi
# Show note about pre-1.0 versioning
if [ $MAJOR -eq 0 ]; then
echo -e "${YELLOW}Note: Pre-1.0 versioning (0.x.y)${NC}"
echo " - Breaking changes bump MINOR (0.x.0)"
echo " - Features bump MINOR (0.x.0)"
echo " - Fixes bump PATCH (0.0.x)"
echo " - After v1.0.0: Breaking changes bump MAJOR (x.0.0)"
fi