Plate 主版本发布迁移同步:从 Changesets 版本化到 GitHub Releases 的自动化发布工作流重构
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本篇文章基于 Plate 仓库的发布迁移同步计划(docs/plans/2026-04-27-major-release-migration-sync.md),系统讲解 Plate 如何将"生成式 release 文档同步"硬切换为"GitHub Releases 驱动的发布架构":由 Changesets 发布包、工作流创建单一全局 GitHub Release、/docs/releases从 Version Packages PR 主体生成发布索引。读完本文,你将掌握一条完整可复用的 monorepo 发布流水线设计:确定性的发布说明生成、AI 改写与结构化校验、全局 Release 创建、发布文档页数据源切换,以及对应的测试与验证手段。
一、迁移目标:为什么要把发布文档同步"硬切换"掉
Plate 仓库此前的发布文档体系存在一个明显问题:content/releases/index.mdx中存储着由自动化生成的大块 changelog 内容,发布 PR 的正文被直接复制进页面,而不是由自动化程序整理。这种做法的维护成本高、内容易漂移,且仓库级别的 tag 对比(例如v53.0.1...v53.0.2)对于 Changesets 只发包不创建仓库级 tag 的发布方式来说通常是空的,无法作为文档的正确目标。
迁移计划确立了三个核心目标:
- 发布自动化只负责发布:以 Changesets 的版本输出和每个包
CHANGELOG.md中对应已发布版本的小节作为唯一事实来源(source of truth); - GitHub Releases 成为发布说明的内容管理系统(CMS):工作流在发布后创建一个全局
vX.Y.ZRelease,/docs/releases页面改为从发布数据生成渲染,而不是在 MDX 中存放大块生成的 changelog; - 明确保留与裁剪边界:保留 Plate 的
prepare-release-changesets步骤、自动发布复选框流程、发布后的 registry/template 同步任务;裁掉生成式 release body 同步、仓库 compare 链接、临时的global-release辅助脚本,以及参考对象 Better Auth 的 beta/LTS/snapshot 分支支持和产品域pr-analyzer。
从范围看,这次迁移只触及发布自动化本身,不涉及编辑器内核与业务功能。
二、旧工作流的事实调查(Findings)
在执行硬切换之前,计划先完成了对现状的调查,这些结论都能在仓库源码中得到印证:
- 发布入口:.github/workflows/release.yml 使用
changesets/action@v1创建标题为[Release] Version packages的版本 PR; - 前置脚本:tooling/scripts/prepare-release-changesets.mjs 在
changesets/action之前运行,它为运行时依赖方(runtime dependents)自动补充 changeset,但它发生在版本化之前,无法看到生成后的 changelog 与已版本化的包文件,这正是需要自定义version命令的原因; - 正确插入点:自定义 Changesets
version命令——先执行pnpm changeset version,再同步 release changelog 小节。changesets/action@v1支持version输入项来指定更新包版本与 changelog 的命令; - Linked 分组:.changeset/config.json 中 Plate 使用 linked 分组(
platejs、@platejs/*、@udecode/react*、@udecode/cn、@udecode/utils),与 Better Auth 的固定分组不同——已发布且被链接的包会对齐版本,但未变更的包并不必然在每次发布中都发布; - 对比样本:Base UI 把发布文档放在
docs/src/app/(docs)/react/overview/releases,采用"概览时间线 + 每个发布一页 + 元数据文件"的结构,但它的发布文档没有一个完整的同步脚本,且是单一产品的时间线形态;Plate 是 monorepo,一次发布横跨数十个包,单一时间线形态并不适用,最终采用"带折叠/展开的展开式信息流(expanded feed)"; - MDX 约束:Contentlayer 不接受 HTML 注释标记,生成的标记必须使用
{/* ... */}JSX 注释; - 数据保留策略:发布文档按包 tag 日期做保留,Version-PR 输出在合并前还没有 tag,因此新生成的条目使用当前同步日期;
content/releases/index.mdx曾是规范化的保留发布存储,同步脚本会在下一次运行时解析生成的<ReleaseIndex />数据,并删除残留的生成v*.mdx页面; - AI 路径来源:Better Auth 的 Claude action 使用
claude_code_oauth_token,Plate 保留这条路径,但始终以确定性的原始 notes 作为回退与校验的事实来源。
三、新架构蓝图:硬切换实施计划
3.1 工作流基线(Workflow Baseline)
新工作流以 Better Auth 的.github/workflows/release.yml为起点改造,保留:
push到main触发;workflow_dispatch仅用于确定性脚本就绪后的发布说明预览;concurrency并发控制、固定版本的 action、job 级权限、GitHub App token 支持、persist-credentials: false;changesets/action设置createGithubReleases: false(关闭其自建 Release 的行为);- 在
changesets/action之前保留node tooling/scripts/prepare-release-changesets.mjs; - 自动发布复选框检测与 Version Packages PR 合并路径,使用 App token 或
API_TOKEN_GITHUB而非默认的GITHUB_TOKEN(保证合并 release PR 后能再次触发发布工作流); - 发布后的
sync-release-artifacts任务(registry 与模板同步)。
裁剪掉 Better Auth 的:next分支触发与守卫、release/**分支与维护 dist-tag、main→next 同步 PR、snapshot 输入与整个 snapshot job、博客文章注入、包/产品域分类器。
3.2 发布命令(Release Commands)
根级脚本(见 package.json 的scripts字段)最终落地为:
ci:version:pnpm changeset version && pnpm install --no-frozen-lockfile,用于版本 PR 生成阶段,执行版本化并同步锁文件;ci:release:node tooling/scripts/release-packages.mjs,走 Plate 当前的发布路径,仍会在changeset publish前执行构建;- 从 Changesets
version命令中移除pnpm release:releases,因为/docs/releases不再存储生成的发布正文。
3.3 确定性发布说明(Deterministic Notes)
核心脚本是 tooling/scripts/release-notes.mjs,其设计要点:
- 输入:
changesets/action输出的PUBLISHED_PACKAGES; - 全局版本:从已发布包中取最高的语义化版本(
getGlobalReleaseVersion对版本做 semver 正则过滤后降序取第一个); - 工作区包映射:从
packages/与packages/udecode/下各目录的package.json构建包名→目录映射(getWorkspacePackages); - changelog 提取:对每个已发布包找到对应
CHANGELOG.md,用extractReleaseChanges精确定位该版本小节,并原样保留### Major Changes、### Minor Changes、### Patch Changes三组标题,空小节会被过滤,小节按 major→minor→patch 排序; - 输出形态:按包分组输出 markdown,每个包一个小节,包内按变更类型分节;贡献者从 changelog 正文的
by @user模式中收集,汇总为## Contributors小节; - 链接策略:不输出仓库 compare 链接;每条
Full changelog链接采用"首选包 tag"(存在platejs@版本时优先,否则取第一个匹配版本号的包 tag);每个包小节在验证通过后由add-package-changelogs子命令追加指向该包CHANGELOG.md的链接,并钉在GITHUB_SHA上。
计划明确不复制 Better Auth 的pr-analyzer.ts(将 conventional commit scope、PR label、变更文件映射到产品域的分类器),因为 Changesets 的包 changelog 已经提供了可靠的天然分组,Plate 只需要一个小型的"包映射 + changelog 解析器"。
3.4 Claude AI 改写(Claude Polish)
保留 Better Auth 的 Claude 改写形态,提示词模板位于 .github/prompts/release-notes-rewrite.md:
- 先由确定性脚本生成原始 release notes;
- 用
sed将模板中的__RAW_CHANGELOG_PATH__占位符替换为真实路径,构建 prompt; - 通过
anthropics/claude-code-action/base-action运行 Claude,凭据使用claude_code_oauth_token,限制工具为Read、Write、Bash(gh pr diff*)、Bash(gh pr view*)与--max-turns 100; - 校验:运行
release-notes.mjs validate,只在校验通过时使用 AI 输出,否则回退到原始 notes。
适配 Plate 的提示词核心约束(可在 .github/prompts/release-notes-rewrite.md 中看到完整原文):
- Plate 是一个面向 React 的开源富文本编辑器框架,改写要为使用者描述"改变了什么"而非内部实现;
- 保留包标题(
## \package-name``)及其顺序; - 保留
### Major/Minor/Patch Changes标题及其顺序; - 保留 PR 链接、作者链接、包名、
Full changelog链接; - 保留迁移说明,尤其是
### Major Changes下的破坏性变更; - 不增删条目、不发明包摘要、不使用 em dash、不添加
CHANGELOG链接(链接由工作流在校验后注入)。
校验规则(validateAiReleaseNotes)逐项断言:包标题列表完全一致、变更类型标题列表完全一致、包 changelog 链接与 full changelog 链接一致、PR 链接与 commit 链接一致、条目数(-开头的 bullet 数)不减少、迁移说明关键词(Migration)出现次数不减少、Contributors小节未被删除、贡献者句柄未被遗漏。任何一项失败都会删除.final文件并回退原始 notes。
3.5 全局 GitHub Release
发布成功(steps.changesets.outputs.published == 'true')后,工作流依次执行:
- 从
release-notes.mjs的输出中读取VERSION; - 若
refs/tags/v${VERSION}不存在,则在GITHUB_SHA上创建该 tag; - 若 Release 已存在则
gh release edit更新,否则gh release create创建;正文优先使用通过校验的 AI notes(${RAW_PATH}.final+.final.validated同时存在),否则使用原始 notes(未通过校验的 AI 输出会被显式警告并忽略); - 版本号含
-(预发布)或发布通道为beta时附加--prerelease; - Release 创建失败视为真实失败——因为文档页现在依赖 GitHub Releases 数据。
3.6 文档页(/docs/releases)
计划最初设想/docs/releases在运行时通过 API 拉取 GitHub Releases(next: { revalidate: 3600 }),但随后被第 25 步"superseded":停止运行时拉取,改为从 Version Packages PR 主体生成发布索引。最终落地为 apps/www/src/generated/release-index.json 这一生成产物,由 tooling/scripts/sync-version-package-releases.mjs 生成。
该脚本的工作方式:
- 通过
gh pr view --json ...读取指定 Version Packages PR,或gh pr list --search "[Release] Version packages"拉取最近 N 个已合并 PR; - 解析 PR 主体中每个
## packageName@version小节的### Major/Minor/Patch Changes内容,把同一全局版本的多个包归并为一个 release 条目(groupPackageChangesByVersion); - 把 Changesets 原始条目行(
- #4954 by @user – summary形态)重写为更紧凑的- summary (#4954)形态,并顺带收集贡献者; - 合并已有索引与解析结果(按 tag 去重、版本降序),按
--from参数过滤早期版本; - 通过
gh release list查询已存在的 GitHub Release URL,为Full changelog与CHANGELOG链接选择合适目标; - 将结果写入
release-index.json,实现幂等(重复运行不产生多余变更)。
页面侧,apps/www/src/app/(app)/docs/releases/page.tsx/docs/releases/page.tsx) 是静态页面(export const dynamic = 'force-static'),直接导入release-index.json,用 apps/www/src/lib/releases.ts 的工具函数把发布按大版本号分组:最近两个大版本作为"当前发布"展开渲染,更早的大版本进入 "Older releases" 卡片区并链接到各自的大版本页,v48 及更早则链接到迁移归档;页面渲染逻辑(apps/www/src/app/(app)/docs/releases/release-page-content.tsx/docs/releases/release-page-content.tsx))提供 "Package changes" 与 "Plate UI" 两个开关按钮和一个 RSS 订阅入口。
四、当前仓库中的最终实现:工作流逐步拆解
对照 .github/workflows/release.yml,硬切换后的完整发布流程如下:
- 触发与守卫:
push到main/next(仓库当前仍保留next分支支持 beta 通道,通过Guard release channel步骤区分latest与beta,main上若存在.changeset/pre.json则直接报错退出); - 自动发布检测:
actions/github-script遍历与该 commit 关联的 PR,检查 PR 主体是否勾选自动发布(isAutoReleaseChecked)、是否包含 changeset 文件(hasChangesetFile),输出enabled与source_pr; - 准备 changesets:
node tooling/scripts/prepare-release-changesets.mjs为运行时依赖方补自动 changeset; - 版本化或发布:
changesets/action@v1设置version: pnpm ci:version、publish: pnpm ci:release、createGithubReleases: false,同时注入NPM_CONFIG_TAG、PLATE_DISABLE_PUBLISH、PLATE_RELEASE_CHANNEL等环境变量; - 版本 PR 分支(未发布):检出 Version Packages PR,运行
node tooling/scripts/sync-version-package-releases.mjs --pr "$RELEASE_PR" --from v49生成release-index.json,若文件有变更则以[Release] Sync release docs提交并推回 PR 分支; - 发布分支(已发布):先推包 tag(
published-package-tags.mjs),再生成原始 notes、构建 AI prompt、运行 Claude 改写、校验,最后创建/更新全局vX.Y.ZRelease; - 自动合并:若自动发布被勾选,用 App token 合并 Version Packages PR(squash + 删分支),使合并事件再次触发
main上的发布流程; - 发布后同步:
sync-release-artifactsjob 在main上运行——重新同步发布文档(--latest 300 --from v49)、构建并推送 registry(build:registry+build:tw)、等待 npm 传播(await-npm-publish.mjs)、更新模板(templates:update --local)并在失败时创建修复 PR。
五、测试与验证矩阵
计划为每个关键行为都配套了聚焦测试,仓库中可找到 tooling/scripts/release-notes.test.mjs、tooling/scripts/release-workflow.test.mjs、tooling/scripts/sync-version-package-releases.test.mjs 等测试文件:
- 全局版本解析:
getGlobalReleaseVersion从混有不同版本号的PUBLISHED_PACKAGES中选出最高版本; - changelog 精确提取:
extractReleaseChanges只取目标版本小节,跨版本解析 bug 正是被测试捕获后修复的; - AI 输出校验:删除包标题、删除 PR 链接、删除条目、丢失迁移说明都会导致
validateAiReleaseNotes返回失败; - 工作流约束:
release-workflow.test.mjs直接读取.github/workflows/release.yml做正则断言——包含createGithubReleases: false、version: pnpm ci:version、publish: pnpm ci:release、gh release (create|edit)、sync-release-artifacts;同时断言不包含sync-main-to-next、sync-release-docs、global-release、pr-analyzer、snapshot:、release/**; - 脚本契约:断言
package.json中ci:version/ci:release的精确内容,并确认release:releases已从脚本中移除; - 路由重定向:
/docs/migration与/cn/docs/migration重定向到/docs/releases(在 apps/www/next.config.ts 中配置)。
验证命令(计划原文):
node --test:运行聚焦的发布工作流/文档测试;pnpm lint:fix:Biome 修复并检查(注意 Biome 要求脚本中的正则字面量必须是模块级常量,否则 lint 失败);pnpm --filter www typecheck:文档站点类型检查;- Browser Use:对
/docs/releases做桌面端与移动端截图验证(本地验证时使用http://localhost:3001/docs/releases,注意改版后需重启 dev server 以清除旧的已删除路由/chunk)。
六、执行过程中的关键教训(Errors 复盘)
计划文档最后记录了三类真实踩坑,对同类迁移很有参考价值:
- Lint 约束:首次运行
pnpm lint:fix失败,因为 Biome 要求脚本中的正则字面量为模块级常量,需要把正则移到文件顶部常量区(release-notes.mjs与sync-version-package-releases.mjs中都能看到这种组织方式); - Contentlayer 与 MDX 注释:
pnpm --filter www typecheck曾因 MDX 中的 HTML 注释而失败,生成的标记必须改用{/* ... */}JSX 注释(这一经验也被沉淀为专门的技术文档); - 构建冲突:误启动
pnpm --filter www build会先跑 CI 专属的 registry 构建,干扰 Next.js 构建,需要恢复生成的apps/www/public/r/*产物并改用定向 typecheck 路径。
七、总结
Plate 的这次发布迁移把"大块 changelog 塞进 MDX"的旧模式,重构为一条以 Changesets 为版本事实来源、以 GitHub Releases 为发布说明出口、以/docs/releases为展示层的完整闭环:确定性脚本保证可复现与可测试,Claude 改写提升文案质量但始终受结构化校验约束,全局 Release 失败即发布失败以保护文档数据依赖。对于任何需要治理 monorepo 发布文档的团队,这条"确定性生成 + AI 润色 + 强校验回退 + 生成态文档页"的流水线设计都是可以直接借鉴的样板。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考