Skip to content

chore: release v4.7.0 (#139) #8

chore: release v4.7.0 (#139)

chore: release v4.7.0 (#139) #8

Workflow file for this run

name: Release
# Publishes a GitHub Release when a v* tag is pushed (HACS reads GitHub
# releases). Every release is published as a PRE-RELEASE (and off "latest") so a
# new version is never served to the default / HACS channel before it has been
# tested — promote it to a full release later (step 4). For final-version tags
# (no -alpha/-beta/-rc suffix) it then rolls main forward to the next minor so
# unreleased work no longer carries the just-released version.
#
# Release flow:
# 1. bin/release.sh <version> -> opens the chore/release PR (bumps manifest)
# 2. merge the PR
# 3. git tag v<version> && git push <remote> v<version> -> triggers this workflow
# 4. test the pre-release, then promote it to the latest full release:
# gh release edit v<version> --prerelease=false --latest=true
#
# The next-minor bump is pushed directly to main with the default GITHUB_TOKEN;
# where main is protected that push is rejected — run bin/bump-dev.sh to land the
# same bump via a PR instead.
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
name: Publish GitHub Release
outputs:
version: ${{ steps.version.outputs.version }}
prerelease: ${{ steps.version.outputs.prerelease }}
steps:
- uses: actions/checkout@v5
- name: Extract and validate version from tag
id: version
env:
REF: ${{ github.ref }}
run: |
VERSION="${REF#refs/tags/v}"
# Reject off-spec tags (the same semver contract bin/release.sh
# enforces), so a hand-pushed bad tag can't slip through.
bin/bump-version.sh --validate "$VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Whether the tag itself is a semver pre-release (-alpha/-beta/-rc).
# The GitHub Release is always published as a pre-release regardless
# (see below); this output only gates the main-forward bump.
if [[ "$VERSION" == *-* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi
- name: Validate manifest version matches tag
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
MANIFEST_VER=$(python3 -c "import json; print(json.load(open('custom_components/cover_time_based/manifest.json'))['version'])")
if [ "$VERSION" != "$MANIFEST_VER" ]; then
echo "::error::Tag v$VERSION does not match manifest.json version $MANIFEST_VER"
exit 1
fi
- name: Build release notes from CHANGELOG
id: notes
env:
VERSION: ${{ steps.version.outputs.version }}
PRERELEASE: ${{ steps.version.outputs.prerelease }}
run: |
# Use the hand-written, user-facing CHANGELOG section as the release
# notes. changelog-section.sh exits 0 (found), 1 (no section for this
# version), or >=2 (its own error, e.g. CHANGELOG.md missing):
# - found -> use the section as the release body
# - exit 1, pre-release -> auto-generated notes (-alpha/-beta/-rc may
# legitimately have no entry yet)
# - exit 1, final tag -> fail: a shipped release must have notes
# (matches the repo's block-on-drift posture)
# - exit >=2 -> a real error; fail regardless of tag
rc=0
bin/changelog-section.sh "$VERSION" > release-notes.md || rc=$?
if [ "$rc" -eq 0 ]; then
echo "found=true" >> "$GITHUB_OUTPUT"
elif [ "$rc" -eq 1 ] && [ "$PRERELEASE" = "true" ]; then
echo "No CHANGELOG.md section for pre-release $VERSION; using auto-generated notes."
echo "found=false" >> "$GITHUB_OUTPUT"
elif [ "$rc" -eq 1 ]; then
echo "::error::No CHANGELOG.md section for final release $VERSION — add the entry before tagging."
exit 1
else
echo "::error::changelog-section.sh failed (exit $rc) for $VERSION — see its message above."
exit "$rc"
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v3
with:
# The CHANGELOG section when present, otherwise GitHub's auto-generated
# notes (see the previous step).
body_path: ${{ steps.notes.outputs.found == 'true' && 'release-notes.md' || '' }}
generate_release_notes: ${{ steps.notes.outputs.found != 'true' }}
# Every release is created as a pre-release and off "latest", so a new
# version is never pushed straight to users on the default / HACS
# channel. Test it, then promote to the full latest release with:
# gh release edit v<version> --prerelease=false --latest=true
prerelease: true
make_latest: false
bump:
needs: publish
# Only final-version tags (no pre-release suffix) roll main forward; an
# -alpha/-beta/-rc tag leaves the version alone.
if: needs.publish.outputs.prerelease == 'false'
runs-on: ubuntu-latest
name: Bump main to next minor
steps:
- uses: actions/checkout@v5
with:
ref: main
- name: Compute next minor and bump version files
id: bump
env:
VERSION: ${{ needs.publish.outputs.version }}
run: |
NEXT=$(python3 -c "import os; mj, mn, _ = os.environ['VERSION'].split('-')[0].split('.'); print(f'{mj}.{int(mn)+1}.0')")
echo "next=$NEXT" >> "$GITHUB_OUTPUT"
# Same verified bump bin/release.sh uses, so the two can't drift.
bin/bump-version.sh "$NEXT"
- name: Commit and push the bump directly to main
env:
NEXT: ${{ steps.bump.outputs.next }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
# No-op guard: if the manifest is already at $NEXT (e.g. a re-run, or
# main was hand-bumped), there's nothing to commit — exit cleanly
# rather than letting `git commit` fail the job under `set -e`.
if git diff --cached --quiet; then
echo "manifest already at $NEXT — nothing to bump; skipping commit/push."
exit 0
fi
git commit -m "chore: bump version to $NEXT for development"
# main is unprotected, so the default GITHUB_TOKEN can push directly.
git push origin HEAD:main