* ci(release): gate PRs on an authored release note Second slice of the release-notes pipeline (#1256 landed the format and generator): make the .changes/ habit survive contact with reality. - "Release note gate" job in ci.yml, PR-only: validates every .changes/*.md, then requires an added note (or the no-release-note label) when the PR touches runtime code under apps/ or libs/. Tests, e2e projects, the website, mock servers, shared testing helpers, snapshots and docs are auto-exempt. - Policy lives in tools/release/check-release-note-gate.mjs as a pure function fed PR files+labels as JSON — unit-tested (10 cases) instead of encoded in workflow bash. The failure message lists the triggering files and names the exact fix. - Labels are fetched live rather than from the stale event payload, so applying the label and re-running the check works without a new push. - The job is dependency-free Node: no pnpm install, runs in seconds. - release-notes and release-cut skills added under .claude/skills/ and mirrored to .codex/skills/; CLAUDE.md/AGENTS.md sections updated to point at the gate and the skills. The no-release-note label itself was created in the repository. Tests: 47 passing in release-tools (10 new gate cases); gate-step shell verified with shellcheck at the CI severity; ci.yml YAML-parse checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(agents): make the release skills discoverable by Claude Code too `.codex/skills/**` was un-ignored so Codex picks up repository skills in any clone, but `.claude` was ignored wholesale — and Claude Code only discovers skills under `.claude/skills/`. The release-notes and release-cut skills therefore existed only on whichever machine authored them. Mirror both skills into `.claude/skills/` and opt them in by name rather than un-ignoring the directory: contributors keep personal skills there (i18n-fill, website, …) which must stay local and out of `git status`. CLAUDE.md/AGENTS.md updated so the "skills live under .codex/skills/" claim does not go stale, including the requirement to keep mirrored copies in sync. The CI gate and the CLAUDE.md/AGENTS.md section remain the load-bearing enforcement; skills only carry the detail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ci): only a note this PR authored satisfies the release-note gate Review follow-ups on #1257 (Codex P2 ×2, Greptile P1). - Drop `renamed` from the accepted statuses. The PR files API compares base…head, so a note created and then renamed inside the same PR still reports as `added`; a `renamed` entry means the file already existed on the base branch. Accepting it let a runtime-code PR pass by moving another PR's unconsumed note, which documents nothing and gives the generator no adding commit to resolve a PR link from. - Require a direct child of `.changes/`. `loadNotes()` reads only the immediate directory, so `.changes/sub/note.md` satisfied the old prefix check while never being validated or rendered into any release surface. Tests: renamed and nested notes now assert a failing gate (12 gate cases). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2.9 KiB
name, description
| name | description |
|---|---|
| release-cut | Cut an IPTVnator release — bump the version, generate release notes from .changes/, scaffold the website post, tag, and verify the draft. Use when asked to release, cut a version, prepare release notes, or publish a new version. |
Release Cut
The pipeline turns accumulated .changes/*.md notes into all three release
surfaces. Order matters: the tag build extracts the CHANGELOG section into
the GitHub release body and fails if it is missing, so the changelog step
is not optional.
Sequence
-
Pick the version — deliberate choice, edit
versionin the rootpackage.json. Bare semver only: any suffix flips electron-updater into prerelease mode and leaks into installer version fields. -
Review the notes — read every file in
.changes/. Fix wording (user language, not reviewer language), then:pnpm run release:notes:validate -
Generate the changelog section (idempotent per version — rerunning replaces the section, so regenerate freely until it reads well):
pnpm run release:notes:changelog -
Scaffold the website post:
pnpm run release:notes:blogOutput is
apps/website/src/content/blog/v0-XX-release-notes.mdxwithdraft: true. The narrative intro, headlines, anddescriptionare editorial — fill everyTODOby hand. One post per minor version: for a patch release, edit the existing post (the scaffold refuses to overwrite without--force). -
Screenshots — only from the release capture script against the mock servers (
tools/release/), never from a real playlist or account: real streams, logos, and TMDB artwork are copyrighted, and credentials must never reach a published image. Output goes toapps/website/public/blog/v0-XX/screenshots/. -
Consume the notes (the only destructive step):
node tools/release/build-release-notes.mjs --consume -
Commit, tag, push:
git add CHANGELOG.md .changes apps/website package.json git commit -m "chore(release): v0.XX.0" git tag v0.XX.0 && git push && git push --tags -
Verify the draft release once
build-and-make.yamlfinishes: authored notes on top, GitHub's generated commit list below, all platform assets present (.dmg/.zip+latest-mac.yml,.exe/.msi+latest.yml,.deb/.rpm/.AppImage/.snap/.flatpak+latest-linux*.yml, blockmaps). Publish manually; flip the blog post todraft: false.
Failure modes
- create-release fails with "CHANGELOG.md has no section for X" — step 3 was skipped. Run it, commit, delete and re-push the tag.
- Snap store publication is a separate manual flow after the public
release exists (
publish-snap.yaml). - Post-release checklist candidates: i18n drift (
pnpm run i18n:check), update the websitev0-XXblog assets, announce in Telegram.