Files
iptvnator/.codex/skills/release-cut/SKILL.md
T
4grayandClaude Opus 5 b4ec68c1fa feat(release): manifest-driven screenshot capture with fail-closed mock-data guards (#1261)
Third slice of the release-notes pipeline (#1256 format+generator, #1257 CI
gate): release screenshots become reproducible and provably mock-only.

The v0.20 capture script was single-use (hard-coded slugs, paths, hero) and
fail-open: a lost IPTVNATOR_E2E_DATA_DIR silently fell back to the user's
real ~/.iptvnator database, `...process.env` leaked ambient TMDB keys and
proxies, nothing gated network access, and no frame content was ever
validated. Each hole leaks real playlists, credentials, or copyrighted
artwork into published screenshots without a single signal.

New pipeline:

- tools/release/screenshots.manifest.json — declarative shots (slug, title,
  named setup steps, themes). Adding a feature shot = one manifest entry.
- capture-release-screenshots.ts — orchestrator; output goes to
  apps/website/public/blog/<release>/screenshots/<slug>-<theme>.png, release
  slug derived from package.json (or --release), --only/--theme filters.
- capture-app-driver.ts / capture-navigation.ts — launch, seeding, theme,
  and the named-action vocabulary; actions are order-independent (every
  portal action starts from the dashboard).
- screenshot-guards.mjs — the fail-closed policy, pure and unit-tested:
  G1 the real database is snapshotted (sha256+mtime) before launch and must
     be byte-identical after; the isolated DB must actually exist
  G2 the app receives an allowlisted environment, never ...process.env
  G3 deny-by-default network gate; known app-level calls (GitHub update
     check) are answered by local stubs; any other blocked request fails
     the run — a silently-blocked TMDB call would leave a frame that looks
     broken rather than unsafe
  G4 every frame is scanned before capture: external img/background URLs,
     credential-shaped text, MAC addresses, non-localhost m3u8 references
  G5 TMDB enrichment asserted disabled via the renderer's IndexedDB
  Any violation deletes every frame captured in the run and exits non-zero.

The guards paid for themselves on the first live run: G3 caught the mock
server redirecting stream endpoints to a public demo HLS
(test-streams.mux.dev) — meaning earlier hand-run captures could embed
third-party video frames. The M3U shot now deliberately captures the groups
layout without starting playback.

`.changes` validation now cross-checks `screenshot:` slugs against the
manifest, so a note cannot reference an image the capture run never
produces.

Verified end-to-end: 10/10 shots (5 slugs × dark/light) captured against
dist build + xtream-mock-server, frames visually inspected (fictional
titles/artwork only), guard-violation paths exercised live. 67 unit tests
in release-tools, lint green, script files within the repo size limit.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 00:19:02 +02:00

3.3 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

  1. Pick the version — deliberate choice, edit version in the root package.json. Bare semver only: any suffix flips electron-updater into prerelease mode and leaks into installer version fields.

  2. Review the notes — read every file in .changes/. Fix wording (user language, not reviewer language), then:

    pnpm run release:notes:validate
    
  3. Generate the changelog section (idempotent per version — rerunning replaces the section, so regenerate freely until it reads well):

    pnpm run release:notes:changelog
    
  4. Scaffold the website post:

    pnpm run release:notes:blog
    

    Output is apps/website/src/content/blog/v0-XX-release-notes.mdx with draft: true. The narrative intro, headlines, and description are editorial — fill every TODO by hand. One post per minor version: for a patch release, edit the existing post (the scaffold refuses to overwrite without --force).

  5. Screenshots — only from the fail-closed capture script against the mock servers, never from a real playlist or account: real streams, logos, and TMDB artwork are copyrighted, and credentials must never reach a published image.

    pnpm nx run electron-backend:build-e2e   # once
    pnpm run release:screenshots             # all manifest shots, dark+light
    

    Output goes to apps/website/public/blog/v0-XX/screenshots/. New feature to showcase = new entry in tools/release/screenshots.manifest.json (slug must match the note's screenshot: field). The run aborts and deletes its frames on any guard violation (real-DB touch, external request, credential-shaped text in frame, TMDB active).

  6. Consume the notes (the only destructive step):

    node tools/release/build-release-notes.mjs --consume
    
  7. 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
    
  8. Verify the draft release once build-and-make.yaml finishes: 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 to draft: 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 website v0-XX blog assets, announce in Telegram.