SemVer Release #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |