IPTVnator Release Cut 发布流程实战:从变更笔记到草稿 Release 的完整契约
IPTVnator Release Cut 发布流程实战从变更笔记到草稿 Release 的完整契约【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator导读本文基于 IPTVnator 仓库中的release-cut技能文档.codex/skills/release-cut/SKILL.md与其背后的完整发布管线契约docs/architecture/release-pipeline.md系统讲解该项目的版本发布Release Cut全流程如何在打 tag 前完成预检、如何把.changes/中的变更笔记扇出到 CHANGELOG、博客、Telegram/Reddit 公告与高亮卡片等多个发布素材如何安全推送 master 与精确 tag以及如何用只读验证器把关草稿 Release 与 27 个发布资产。读完本文你将掌握一套可直接照搬的、以单份笔记、多端分发、显式排序、手工发布为核心的发布执行方案以及每一条约束背后的源码级理由。说明文中出现的v0.24.0、v0.25.1等版本号均为技能文档中的示例值当前仓库根 package.json 的版本为0.23.0。实际执行时以当时package.json中的版本为准。一、理解发布管线的两个阶段IPTVnator 的发布流程被刻意拆成两个阶段职责完全不同日常 PR 阶段每一个对用户可见的变更在开发上下文还新鲜的时候向.changes/目录写入一个area-slug.md笔记文件由 CI 的 Release note gate 强制约束。发布阶段tools/release/build-release-notes.mjs把这些笔记一次性扇出到所有发布素材然后删除它们版本号则不做任何推导通过手工 bump 根 package.json 中的version字段来刻意选定。这套设计的目标很朴素与其在发布日靠 commit 标题反推三个月的工作不如在每次 PR 合入时顺手把面向用户的描述写好。.changes/目录中现存的大量真实笔记如.changes/epg-programme-guide.md、.changes/portal-mark-movie-watched.md、.changes/playback-native-container-routing.md等就是这种工作流长期运行的直接证据。笔记的具体格式与字段约束见 .changes/README.md核心要点是 frontmatter 中typebreaking/feature/fix/perf/internal与area小写 slug与 conventional-commit 的 scope 一致必填issues、screenshot、highlight可选正文面向用户书写且上限 400 字符。二、Preflight发布前的预检release-cut技能文档要求从干净且最新的master分支开始工作并把目标远端显式命名随后确认根package.json中是裸 semver如0.24.0不带v前缀精确的vversiontag 在本地与远端都不存在CI 全绿所有变更笔记校验通过。预检命令pnpm run release:notes:validate pnpm run i18n:checkrelease:notes:validate实际执行node tools/release/build-release-notes.mjs --validate见根 package.json其内部通过loadNotes()解析.changes/下每个笔记并额外校验screenshot:字段引用的 slug 必须真实存在于 tools/release/screenshots.manifest.json 的清单中否则报错退出见 tools/release/build-release-notes.mjs 的校验逻辑。i18n:check执行node tools/i18n/check-drift.mjs确保多语言文案没有漂移。技能文档还强调tag 工作流会用node tools/release/extract-changelog-section.mjs --public ${VERSION}把 CHANGELOG 中对应版本的公开段落写入 GitHub Release body。因此打 tag 之前完整版 CHANGELOG包括内部备注必须已经提交——发布体内容以仓库内已提交的 CHANGELOG 为唯一事实来源。三、Generate从笔记到全渠道发布素材技能文档给出了七步生成流程其中第 5、6 步必须先于第 7 步执行原因见下一节。1. 设置版本号修改根 package.json 的version字段为裸 semver。build-release-notes.mjs的resolveVersion()默认就是从package.json读取版本并把版本正则限制为/^\d\.\d\.\d$/——这保证了单一事实来源见 tools/release/build-release-notes.mjs。如需在 bump 之前预览某个版本可用--version 0.24.0覆盖。2. 生成 CHANGELOG 段落pnpm run release:notes:changelog该命令执行build-release-notes.mjs --format changelog把当前.changes/中的全部笔记渲染为 CHANGELOG 的一个版本段落并插入到CHANGELOG.md中!-- next-release --标记之下。重复运行同一版本会替换旧段落而非重复追加upsertChangelogSection的行为。3. 生成博客脚手架pnpm run release:notes:blog对应--format blog会向apps/website/src/content/blog/vX-Y-release-notes.mdx写入发布博文脚手架如v0-24。注意两条规则Minor 发布脚手架是全新文件需要人工补齐所有 editorial 字段叙事引言、影响描述等脚手架中以TODO标记Patch 发布网站每个 minor 版本只发布一篇博文patch 版本必须编辑已有的vX-Y博文禁止重新脚手架或强制覆盖。实现上writeBlogScaffold()在目标文件已存在且未传--force时直接抛错拒绝覆盖见 tools/release/build-release-notes.mjs。脚手架的具体排版由renderBlogScaffoldtools/release/release-notes-blog.mjs决定叙事引言 →ReleaseMeta→ What changed 表格 → 高亮##段落 → Breaking changes → 按主题分组的特性段落 →## Performance→## Everything else把剩余修复折叠进Spoiler→ 更新前提醒 →## Thanks→ 下载链接卡片。主题映射来自BLOG_THEMES未映射的 area 落入 Other changes 而不是报错。4. 只在 mock 服务器上采集截图pnpm nx run electron-backend:build-e2e # 先构建 e2e 目标 pnpm run release:screenshotsrelease:screenshots执行 tools/release/capture-release-screenshots.ts只允许对 mock 服务器截图Xtream mock server、Stalker mock server 等绝不允许对真实播放列表或账号截图——流、台标与元数据受版权保护凭据也绝不能进入公开图片。截图发布到apps/website/public/blog/vX-Y/screenshots/。该采集是 fail-closed 的它会证明真实~/.iptvnator/databases目录包括 SQLite WAL 侧文件未被触碰、以白名单环境启动应用、记录并拦截所有非 localhost 流量、逐帧扫描外部资源与凭据形状文本并断言 TMDB 增强功能保持关闭详见 .changes/README.md 的 Screenshots 一节与 tools/release/screenshot-guards.mjs。5. 渲染公告草稿输出保存到仓库外pnpm --silent run release:notes:telegram pnpm --silent run release:notes:reddit两个命令都向stdout输出可直接粘贴的公告文本因此必须加--silent——否则 pnpm 的生命周期横幅形如 iptvnator0.23.0 release:notes:telegram …会混入同一 stdout重定向保存的帖子开头就会多出两行构建噪音。平台长度约束由渲染器保证Telegram纯文本 ≤ 4096 字符超出部分折叠为 N more 计数器RedditMarkdown ≤ 40,000 字符仓库累计笔记渲染已达约 37,000建议标题受 300 字符上限约束超出从分组列表尾部breaking → feature → fix → perf 排序影响最小的先丢弃裁剪。breaking 变更永远不会被折叠若仅 breaking 变更就超出 Telegram 上限渲染直接以可操作的错误失败而不是偷偷丢弃一条。内部发布全部笔记为type: internal时两个格式都在 stderr 打印说明、stdout 留空并以 0 退出。发布公告本身是手工动作发生在发布完成之后。6. 生成高亮卡片pnpm run release:cards:generate对应 tools/release/generate-highlight-cards.mjs布局层在 tools/release/highlight-cards.mjs用 sharp 渲染 1200×630Open Graph 尺寸的卡片每个highlight:笔记一张卡外加一张同时写成hero.png与博客 frontmatter 引用的hero.jpg的发布主视觉卡。输出落在dist/release-highlight-cards/vversion/仓库外、不入版本控制同名重跑会先清理上一次生成的文件避免改名或删除的高亮留下陈旧的待发布图片。生成后需要人工审查若hero.jpg需要作为博文主图则手工复制进博文资源目录。7. 消费笔记--consumenode tools/release/build-release-notes.mjs --consume第 5、6 步必须在--consume之前完成highlight:元数据只存在于将被删除的笔记文件中。--consume是破坏性边界——build-release-notes.mjs会遍历.changes/下每个笔记并调用rmSync(note.sourcePath)逐一删除见 tools/release/build-release-notes.mjs 的 consume 分支它也是整条管线中唯一会删除文件的模式。随后只暂存 release 拥有的文件包括精确的网站博文与资源以及git add -A -- .changes提交并打精确 taggit commit -m chore(release): v0.24.0 git tag v0.24.0四、为什么顺序是强约束highlight:的一生发布管线中最关键的一条排序约束来自highlight:字段的生命周期highlight:是笔记 frontmatter 中的可选字段用于命名本版本两三个头号变更上限60 字符且在type: internal上被拒绝。在普通发布素材中highlight:驱动三种行为Telegram 以它打头并把其余折叠成 N moreReddit 为每个高亮开辟## Highlights小节博客脚手架在开头 What changed 表格中为每个高亮占一行并生成置于其他内容之前的专属##段落。高亮卡片同样只从笔记的highlight:字段读取。CHANGELOG 虽然保留了每个条目的正文但highlight:只存在于笔记文件中消费之后不可恢复。因此所有读取highlight:的素材——两个公告与卡片——必须在--consume之前渲染而卡片还要拼接截图又必须在release:screenshots之后。这就是技能文档强调Steps 5 and 6 must precede--consume的源码级原因。另一个细节highlight:的 60 字符是创作指导而非渲染保证因为字符数不等于渲染宽度。卡片布局层tools/release/highlight-cards.mjs采用刻意反向的宽度估算模型——枚举窄字符、其余一律按宽字符处理使得估算只会偏高而不会偏低34 个W在 font-size 52 下实测约 1948px而可用宽度仅约 1072px字符上限的行照样溢出画布。这一保证由 tools/release/highlight-cards.test.mjs 通过 sharp 实际渲染每个样本并断言估算值不低于实测墨迹宽度来守护。五、推送与外部效应master 与 tag 严格隔离技能文档对推送有非常明确的两条命令要求先推远端master再以第二条独立命令只推精确的vversiontag绝不用宽泛的git push --tags。以远端upstream、版本v0.25.1为例git push upstream master git push upstream v0.25.1这样做的原因在于外部效应master与v*推送都可能触发 Docker 镜像发布而tag 构建会创建一个草稿 GitHub Release对应 .github/workflows/build-and-make.yaml 中的create-releasejob。分开推送可以精确控制每一步触发的副作用宽泛的--tags则会把不该上线的 tag 一并推出去。tag 工作流在发布体上的具体行为见 .github/workflows/build-and-make.yamltag 事件下先用node tools/release/extract-changelog-section.mjs --public ${VERSION}提取本地 CHANGELOG 的公开段落作为作者正文再把 GitHub 自动生成的 commit 列表追加其后组成FULL_BODY写入草稿。也就是说发布体永远非空——作者正文缺失无法用简单的空字符串检测来发现。六、草稿验证只读把关release:verify:drafttag 推送后运行pnpm run release:verify:draft执行 tools/release/verify-draft-release.mjs。它严格只读绝不发布、编辑或删除任何内容。验证管线分三步找到 rungh run list只反映当下已索引的 run其--limit只是返回条数上限、并不会等待刚推的 tag 往往还没被索引。验证器以 10 次尝试、每次间隔 6 秒的方式轮询超时才判定tag 从未推送。等待完成进行中的 run 通过gh run watch --exit-status流式跟进已完成但结论非成功的 run 立即失败。缺失的gh二进制或被中断的 watch 会被如实报告spawnSync将两者呈现为status: null而不是误报为构建失败。检查草稿校验草稿状态、作者正文以及下面完整的资产集合。作者正文的检查方式值得注意它把发布体与本地 CHANGELOG.md 中对应版本的段落做包含比较而不是和空字符串比较——因为 tag 工作流总是会追加 GitHub 生成的 notesFULL_BODY空字符串测试永远不可能失败。内部发布公开段落合法为空被如实报告为无需作者正文而不是发出警告。已经发布的 Release 依然会得到资产审计报告事后审计有用但绝不会返回成功退出码——对一个前置发布门禁来说发布后报通过等于声称边界已经被跨越。七、27 资产契约多平台矩阵完整性检查requiredAssetRules()见 tools/release/verify-draft-release.mjs对着一个真实的完整矩阵构建验证过定义了发布必须携带的 27 个资产。当构建矩阵增减目标时必须在同一 PR 中同步更新该函数。完整清单如下平台资产macOS-mac-{x64,arm64}.{dmg,zip} 各一个.blockmap8 个Windows-windows-x64-setup.exe.blockmap2 个DEB-linux-{amd64,arm64,armv7l}.deb3 个AppImage-linux-{x86_64,arm64,armv7l}.AppImage3 个Snap-linux-{amd64,armhf}.snap2 个RPM-linux-x86_64.rpm1 个Flatpak-linux-x86_64.flatpak1 个Pacman-linux-x64.pacman或-linux-x86_64.pkg.tar.*1 个更新器元数据latest.yml、latest-mac.yml、latest-linux.yml、latest-linux-arm.yml、latest-linux-arm64.yml5 个源码合规linux-frame-copy-runtime-sources.tar.xz1 个实现细节上规则用纯字符串比较而非由版本拼接的正则——requiredAssetRules()是被导出的对插值后的版本值做正确转义将是一个常设陷阱。Pacman 规则之所以接受两种形状是因为 Electron Builder 历史上输出过两种 pacman 工件形态。任何没有被规则认领的资产以NOTE:报告且不导致失败新构建目标应当浮出水面供人注意而不是在规则更新前卡死整个发布。八、验证与发布之后的手工动作草稿验证通过后发布动作是手工的人工审查 author 正文与生成的 commits手工发布 GitHub Release——发布动作会自动验证 Snap 资产并将其上传到edge已安装 Snap 的冒烟测试以及 candidate/stable 晋升仍然是手动的参见 tools/packaging/validate-snap-release-boundary.mjs在工件验证期间保持博文为草稿随后用后续 commit 发布博文并验证网站部署。技能的配套验证命令pnpm run release:notes:validate # 每个笔记都能解析并通过 schema pnpm nx run release-tools:test # 工具链自身的单元测试 pnpm nx run release-tools:lint九、失败安全如何优雅回滚技能文档给出了两个典型失败场景的处理方式CHANGELOG 段落缺失tag 构建中的extract-changelog-section.mjs找不到对应版本段落时直接让发布失败——一个忘记执行release:notes:changelog就打的 tag 不可能静默地只发布 PR 标题级笔记见 tools/release/extract-changelog-section.mjs其在 section 缺失或为空时返回非零退出码并给出可操作的重试提示。恢复步骤重新生成 → 提交 → 在确认其精确目标后先删除本地与远端坏 tag→ 重新打 tag。除此之外还有一条硬性纪律在源码归档与 Snap 契约通过之前绝不发布草稿。--consume后.changes/变空本身也是一个信号——空目录意味着这次发布没有用户可见变更而内部发布与空目录被刻意区分为两种结局空目录会失败因为它几乎总是意味着某一步在--consume之后才运行这正是整条管线要防止的唯一排序错误详见 docs/architecture/release-pipeline.md。十、把发布流程串起来完整命令序列综合技能文档、.changes/README.md与发布管线契约一次完整发布的标准序列是# 预检 pnpm run release:notes:validate pnpm run i18n:check # 1. bump 根 package.json 的 version # 2. 生成 CHANGELOG 段落 pnpm run release:notes:changelog # 3. 生成博客脚手架patch 版本则编辑既有 vX-Y 博文 pnpm run release:notes:blog # 4. 只对 mock 服务器采集截图 pnpm nx run electron-backend:build-e2e pnpm run release:screenshots # 5. 渲染公告草稿到仓库外--silent 防横幅污染 stdout pnpm --silent run release:notes:telegram pnpm --silent run release:notes:reddit # 6. 生成高亮卡片并人工审查 pnpm run release:cards:generate # 7. 消费笔记破坏性边界必须最后 node tools/release/build-release-notes.mjs --consume # 暂存 release 拥有的文件并提交打 tag git add -A -- .changes git commit -m chore(release): v0.24.0 git tag v0.24.0 # 先推 master再单独推精确 tag git push upstream master git push upstream v0.24.0 # 只读验证草稿与 27 资产 pnpm run release:verify:draft核心心智模型可以浓缩为一句话一切读取highlight:的步骤都在--consume之前一切外部副作用Docker、草稿 Release都由精确推送精确触发而发布本身永远是人的决定。这套约束既写进了技能文档也固化在 docs/architecture/release-pipeline.md 的契约、tools/release/build-release-notes.mjs 的实现与 .github/workflows/build-and-make.yaml 的工作流中——三份证据指向同一条边界这正是它值得信赖的原因。【免费下载链接】iptvnator:tv: Cross-platform IPTV player application with multiple features, such as support of m3u and m3u8 playlists, favorites, TV guide, TV archive/catchup and more.项目地址: https://gitcode.com/GitHub_Trending/ip/iptvnator创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考