Skip to content

SemVer Release

SemVer Release #3

name: SemVer Release
# Weekly stable releases + manual trigger + hotfix branches
# Normal commits to master only create dev releases (via publish-dev.yml)
on:
schedule:
# Every Tuesday at 10:00 UTC
- cron: '0 10 * * 2'
workflow_dispatch:
inputs:
force:
description: 'Force release even without changes'
required: false
default: 'false'
type: boolean
env:
PYTHON_VERSION: "3.13"
jobs:
# Check for changes since last release (skip if no changes)
check-changes:
name: Check for releasable changes
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Check for feat/fix commits since last tag
id: check
run: |
# Get latest non-dev tag
LATEST_TAG=$(git describe --tags --abbrev=0 --match 'v[0-9]*' --exclude '*dev*' 2>/dev/null || echo "")
if [ -z "$LATEST_TAG" ]; then
echo "No previous release found, proceeding"
echo "has_changes=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "Latest stable tag: $LATEST_TAG"
# Check for feat/fix commits since last tag
CHANGES=$(git log $LATEST_TAG..HEAD --oneline --grep="^feat" --grep="^fix" --grep="^perf" | head -5)
if [ -n "$CHANGES" ] || [ "${{ inputs.force }}" = "true" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Changes found:"
echo "$CHANGES"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No releasable changes since $LATEST_TAG"
fi
# Validate Docker build before committing to a release
validate-docker:
name: Validate Docker Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build addon image (no push)
uses: docker/build-push-action@v6
with:
context: .
file: ./homeassistant-addon/Dockerfile
push: false
platforms: linux/amd64
# Semantic versioning and release creation
semantic-release:
needs: [check-changes, validate-docker]
if: needs.check-changes.outputs.has_changes == 'true'
name: Semantic Release
runs-on: ubuntu-latest
outputs:
released: ${{ steps.semantic.outputs.released }}
version: ${{ steps.semantic.outputs.version }}
permissions:
contents: write # Required to push commits and tags
id-token: write # Required for trusted publishing
pull-requests: write # Required to create pull requests
issues: write # Required for GitHub releases
steps:
# Generate GitHub App token with bypass permissions for tag creation
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
# Fallback to RELEASE_TOKEN (PAT) or GITHUB_TOKEN if app credentials not configured
continue-on-error: true
- uses: actions/checkout@v6
with:
fetch-depth: 0
# Priority: GitHub App token > RELEASE_TOKEN (PAT) > GITHUB_TOKEN
token: ${{ steps.app-token.outputs.token || secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
- name: Python Semantic Release
id: semantic
uses: python-semantic-release/python-semantic-release@v10.5.3
with:
# Use GitHub App token with bypass permissions
github_token: ${{ steps.app-token.outputs.token || secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
verbosity: "2"
# Don't create GitHub release here - we'll create a draft below
vcs_release: "false"
- name: Create draft GitHub release
if: steps.semantic.outputs.released == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.semantic.outputs.version }}"
TAG="v${VERSION}"
# Extract changelog for this version
awk "/^## v${VERSION}/,/^## v[0-9]/" CHANGELOG.md | head -n -1 > release_notes.md
if [ ! -s release_notes.md ]; then
echo "Release v${VERSION}" > release_notes.md
fi
# Create draft release (build-binary.yml will add binaries and publish)
gh release create "$TAG" \
--title "$TAG" \
--notes-file release_notes.md \
--draft
echo "✓ Created draft release $TAG (waiting for binaries)"
- name: Copy changelog to addon directory
if: steps.semantic.outputs.released == 'true'
run: |
set -e
cp CHANGELOG.md homeassistant-addon/CHANGELOG.md
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add homeassistant-addon/CHANGELOG.md
git diff --staged --quiet || git commit -m "chore(addon): sync changelog for Home Assistant add-on [skip ci]"
git push
- name: Update stable git tag
if: steps.semantic.outputs.released == 'true'
run: |
# Move the 'stable' tag to point to the new release
VERSION="${{ steps.semantic.outputs.version }}"
echo "Updating 'stable' tag to point to v$VERSION"
# Delete existing stable tag (local and remote)
git tag -d stable 2>/dev/null || true
git push origin :refs/tags/stable 2>/dev/null || true
# Create new stable tag pointing to the release commit
git tag stable "v$VERSION"
git push origin stable
echo "✓ 'stable' tag now points to v$VERSION"
# Build binaries and attach to release
build-and-release:
needs: semantic-release
if: needs.semantic-release.outputs.released == 'true'
uses: ./.github/workflows/_build-and-release.yml
with:
release_tag: v${{ needs.semantic-release.outputs.version }}
is_prerelease: false
permissions:
contents: write
# Build and push addon Docker images
build-addon:
needs: semantic-release
if: needs.semantic-release.outputs.released == 'true'
uses: ./.github/workflows/addon-publish.yml
with:
version: ${{ needs.semantic-release.outputs.version }}
permissions:
contents: read
packages: write