Flips an existing GitHub pre-release to a full release and makes it "Latest", rewriting its release notes to cover every change since the previous full (non-prerelease) release — not just since the immediately preceding tag, which is usually another pre-release. Also surfaces any warnings (breaking changes, required migrations, manual steps) at the top of the notes.
This only edits an existing release object. It does not create a new tag, does not re-run .github/workflows/release.yml (that workflow triggers on release: [published], which only fires once, at initial creation — editing prerelease/make_latest afterward is silent), and does not touch the already-uploaded red_energy.zip asset. If no code changed between the pre-release and now, the existing asset is still correct.
-
List current pre-releases and confirm which one to promote.
gh release list --repo <owner>/<repo> --json tagName,isPrerelease,publishedAt \
--jq '.[] | select(.isPrerelease) | "\(.tagName)\t\(.publishedAt)"'
Always ask the user to confirm the exact version, even if only one pre-release exists or one seems obviously implied — never guess. Do not proceed until they name it explicitly.
-
Confirm the target actually exists and is a pre-release.
gh release view <version> --repo <owner>/<repo> --json tagName,isPrerelease,isDraft
If isPrerelease is already false, stop and tell the user it's already a full release rather than proceeding.
-
Find the previous full release — not the previous tag. This must be done before promoting, since releases/latest excludes pre-releases and will otherwise just re-resolve to the version being promoted.
gh api repos/<owner>/<repo>/releases/latest --jq '.tag_name'
This is the correct baseline for the changelog — it may be several versions back if multiple pre-releases shipped in between (e.g. promoting 1.14.3 when 1.14.0 was the last full release should include everything from 1.14.1, 1.14.2, and 1.14.3, not just 1.14.3's own commits).
If releases/latest 404s (no full release exists yet), ask the user what baseline to use instead of guessing.
-
Generate the changelog against that explicit baseline. Do NOT use gh release create --generate-notes or call generate-notes without previous_tag_name — both default to the immediately preceding tag (usually the prior pre-release), which undercounts everything already shipped in earlier pre-releases.
gh api repos/<owner>/<repo>/releases/generate-notes \
-f tag_name=<version> \
-f target_commitish=main \
-f previous_tag_name=<previous-full-release> \
--jq '.body'
-
Scan the same range for anything warning-worthy, then check the commits/PRs actually in range:
git log --oneline <previous-full-release>..<version>
gh pr list --repo <owner>/<repo> --state merged --search "<previous-full-release>..<version>" 2>/dev/null
Look for:
- A manifest MAJOR version bump (
grep '"version"' custom_components/red_energy/manifest.json at each boundary) — HA custom integration major bumps often mean breaking config/entity changes
- Commit/PR titles or bodies containing "breaking", "migration", "manual step", "remove", "rename" in a way that affects existing users' entities/config (e.g. a sensor rename that changes
unique_id, a required re-auth, a config entry migration)
- Anything in
config_migration.py version-gate changes within the range
Only include a warning if there's a concrete reason — don't invent generic caution text. If nothing warrants a warning, omit the section entirely rather than padding it.
-
Compose the final release body: if warnings were found, prepend a ## ⚠️ Warnings section (bulleted, specific, linking the relevant PR) above the generated ## What's Changed notes. Otherwise use the generated notes unchanged.
-
Show the user the composed notes and the promotion plan (from version → to "Latest", baseline used) and get explicit confirmation before writing anything — this changes what HACS/watchers see as the recommended install target for everyone, which is harder to walk back cleanly than creating a new pre-release.
-
Apply the promotion:
gh release edit <version> --repo <owner>/<repo> --prerelease=false --latest --notes-file <file>
Use --notes-file (write the composed body to a temp file first) rather than --notes inline — release notes are multi-paragraph markdown and shell-quoting them inline is error-prone.
-
Verify:
gh release view <version> --repo <owner>/<repo> --json isPrerelease,tagName
gh api repos/<owner>/<repo>/releases/latest --jq '.tag_name'
Confirm isPrerelease is now false and releases/latest now resolves to <version>.
-
Report the release URL (https://github.com/<owner>/<repo>/releases/tag/<version>) and summarize what changed — GitHub URLs aren't sensitive, always link them.