React Spectrum 周度 API Diff 自动化:基线快照构建、发布检测与差异追踪全解
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
本文基于 react-spectrum 仓库中的 scripts/weekly-api-diff/README.md 展开,完整讲解这套“周度 API Diff 自动化”的工作原理:如何用 GitHub Actions 与 macOS launchd 两条路径,每周自动对比main分支的 API 面与最近一次发布的基线,计算周环比增量,把快照提交到独立的 snapshots 仓库,并通过 LLM 摘要推送到 Slack。读完后你能掌握 monorepo 中“发布基线 vs 开发分支”类型级差异对比的完整工具链,以及一套可复刻的定时 API 变更播报方案。
一、整体定位与文件清单
这套自动化解决的问题是:react-spectrum 是一个包含数百个包的 monorepo,每周都有大量组件 props 与 hook 签名在变化;团队需要一份“相对最近一次发布版本,main 分支上还挂着哪些未发布的 API 变更”的清单,并在每周固定时间把变化推送给 Slack。
仓库中该功能的文件布局如下(完整清单来自 README):
| 文件 | 位置 | 作用 |
|---|---|---|
| GitHub Actions workflow | .github/workflows/weekly-api-diff.yml | 主自动化,在 GitHub 基础设施上每周一 9am PT 运行 |
| Prompt(唯一事实来源) | scripts/weekly-api-diff/prompt.md | 本地 launchd 回退方案中 Claude 的指令,修改后需同步到~/weekly-tsdiffer.md |
| Prompt(实际生效副本) | ~/weekly-tsdiffer.md | launchd 每次运行实际读取的文件 |
| launchd plist(参考副本) | scripts/weekly-api-diff/launchd.plist | 本地回退方案的参考配置,注释内含安装说明 |
| launchd plist(实际生效副本) | ~/Library/LaunchAgents/com.<username>.weekly-tsdiffer.plist | macOS 调度器实际读取的 plist |
| Secrets(本地) | ~/.secrets | 含SLACK_TSDIFF_CHROMATIC_BOT_TOKEN(chmod 600,严禁提交) |
| Snapshots 仓库(本地) | ~/dev/react-spectrum-api-snapshots | 存储每周 diff 文本(仅本地回退方案使用) |
| Snapshots 仓库(GitHub) | LFDanLu/react-spectrum-api-snapshots | 每周 diff 的公开存档 |
| 运行日志 | /tmp/weekly-tsdiffer.log | 每次本地运行的 stdout/stderr |
| 错误日志 | /tmp/weekly-tsdiffer-error.log | Claude prompt 中的步骤级错误记录 |
两条执行路径共用同一套底层脚本,区别只在于触发方式和汇总方式:
- GitHub Actions(主路径):cron 定时触发,用 GitHub Models 做摘要,推 Slack;
- macOS launchd(回退路径):周一 9 点由本机调度触发,直接调用
claude -p执行一份自然语言 prompt(prompt.md),逻辑与 Actions 完全对齐。
二、GitHub Actions 主流程逐步骤解析
Workflow 定义见 .github/workflows/weekly-api-diff.yml。README 中的 8 步流程与 workflow 中实际 steps 一一对应:
2.1 触发与检出
on: schedule: - cron: '0 17 * * 1' # Monday 9am PST / 10am PDT (GH Actions cron is UTC) workflow_dispatch: # manual trigger for testing注意 GH Actions 的 cron 表达式按 UTC 计算,0 17 * * 1即太平洋时间周一 9 点;另外支持workflow_dispatch手动触发,便于测试。checkout 步骤显式指定fetch-depth: 0,注释说明原因:build:api-published需要完整 git 历史来定位最后一次 Publish 提交。运行环境固定为ubuntu-latest+ Node 24 + yarn cache。
2.2 构建两侧 API 快照
- run: yarn build:api-branch # 构建当前 main 的 API 快照(注释称 CI 上约 2 分钟) - run: yarn build:api-published # 构建发布基线(使用最后一次 minor/major 发布提交)这两条脚本对应 package.json 中的定义:
"build:api-published": "node scripts/buildBranchAPI.js --githash=$(git rev-list -n 1 $(git tag -l 'react-aria-components@*' | grep -E '@[0-9]+\\.[0-9]+\\.0$' | sort -V | tail -1)) --output=base-api", "build:api-branch": "node scripts/buildBranchAPI.js", "compare:apis": "node scripts/compareAPIs.js"从命令可以读出“发布基线”的自动检测逻辑:列出所有react-aria-components@x.y.0形式的 tag(只保留 minor/major,用grep -E '@[0-9]+\\.[0-9]+\\.0$'过滤掉 patch 版本),按版本排序取最新一个,再通过git rev-list -n 1找到该 tag 指向的提交,把这个提交作为--githash传给快照构建脚本。这就是 README 中“auto-detects last Publish commit”的具体实现。
2.3 生成 diff 并计算周环比
- name: Generate diff run: yarn compare:apis --isCI > /tmp/diff-current.md || true--isCI控制输出为纯 Markdown(不加终端彩色),|| true表示即使比较脚本认为“无变更”也允许流程继续。随后 workflow 用actions/checkout@v4把 snapshots 仓库检出到snapshots/目录(使用SNAPSHOTS_REPO_TOKEN),并在 “Save diff and compute delta” 这一步完成:
- 发布检测:
CURRENT_PUBLISH取git log --grep='^Publish$' --oneline -1的第一个字段(即最近一个消息恰为Publish的提交短哈希),与 snapshots 仓库里的last-publish-hash.txt(PREV_PUBLISH)比较; - minor/major 与 patch 区分:若两者不同,再用
git tag --points-at "$CURRENT_PUBLISH"检查该 Publish 提交上是否有@react-spectrum/s2@x.y.0或react-aria-components@x.y.0形式的 tag——有则判定为 minor/major 发布(NEW_RELEASE=true,重置基线),无则判定为 patch 发布,只更新哈希、继续走周环比逻辑; - 周环比增量:对 snapshots 仓库
diffs/下按字典序最大的历史 diff(ls ... | sort -r | head -1,按文件名日期排序而非 mtime)执行diff "$PREV" /tmp/diff-current.md > /tmp/weekly-delta.txt,得到“本周相对上周”的变化; - 提交决策:仅当
/tmp/diff-current.md非空,且(NEW_RELEASE=true或 delta 非空)时才提交diffs/$TODAY.md;同时会把 delta 加工成 snapshots/deltas/$TODAY.md——用grep '^> '/grep '^< '把 diff-of-diffs 拆成两段带标题的纯文本(“本周新增的 API 变更”与“本周已随发布消失的 API 变更”),workflow 注释说明这是为了“可以直接喂给模型”。最后执行git diff --cached --quiet || (git commit -m "weekly api diff $TODAY" && git push),保证无变化时不产生空提交。
2.4 汇总与四种 Slack 消息
README 第 8 步说明,汇总阶段会基于以下状态发四种消息之一:
| 场景 | 消息内容 |
|---|---|
| diff 为空(相对发布版本无待发布变更,release 已消费全部改动) | “No API changes detected vs last release...” |
NEW_RELEASE=true(上次 diff 之后出现了新发布) | 提示新发布落地,链接指向新基线的完整 diff |
| 周环比 delta 为空(与上周 diff 相同) | “No new API changes since last diff (PREV_DATE): PREV_URL” |
| 正常(本周有增量变化) | 由 LLM(GitHub Models)对周环比 delta 生成的摘要 |
三、快照构建与 API 对比的源码实现
上面三条 yarn 脚本是整个自动化中最有技术含量的部分,值得结合源码看细节。
3.1 buildBranchAPI.js:在隔离的临时 workspace 中生成 api.json
scripts/buildBranchAPI.js 的职责(其文件头注释原文):通过.parcelrc中的apiCheckpipeline 运行文档构建器,为每个包生成“可见(对外暴露)类型定义”的 JSON。核心流程:
可选的历史提交检出:若传入
--githash(build:api-published场景),执行git archive <hash> | tar -x -C <tempdir>把该提交的源码树解包到临时目录,作为快照源;不传则直接以当前工作树为源;合成一个最小 workspace:在
tempy临时目录中生成新的package.json,workspaces覆盖packages/*/*等,devDependencies 只保留根 package.json 中@parcel前缀、parcel、patch-package、postcss、react等少量依赖(buildBranchAPI.js),使安装尽量快且只从 npm 拉外部依赖;重写每个子包 manifest:复制
packages/下的包(排除spectrum-css、example-theme、dev/等)后,删除main/module/devDependencies等字段,删除指向 workspace 内部包的dependencies(避免兄弟包因版本 pin 不匹配而去 npm 安装),并统一注入两个关键字段(buildBranchAPI.js):json.apiCheck = 'dist/api.json'; json.targets = { apiCheck: {} };这两个字段就是 Parcel 构建入口:每个包都会以
apiChecktarget 构建,产物落到dist/api.json。执行构建:
yarn parcel build packages/react-aria-components packages/@react-{spectrum,aria,stately}/* packages/@internationalized/{message,string,date,number} --target apiCheck,最后把packages/拷回dist/branch-api/(--output参数可改为base-api),删除临时目录。
其中真正生成类型 JSON 的是 parcel-transformer-docs 转换管线——仓库根 .parcelrc 中有一条规则:
"apiCheck:*.{js,ts,tsx,json}": ["parcel-transformer-docs"]即凡是被apiChecktarget 处理到的 TS/JS/JSON 文件都走 docs transformer,其核心逻辑与文档站类型渲染器(dev/docs中的types.js)一致,把 TypeScript 类型系统还原为带exports/links结构的 JSON。
3.2 buildPublishedAPI.js:从 npm 拉取“已发布世界”
scripts/buildPublishedAPI.js 与 branch 版本同构,但数据源完全不同——它构建的是“用户当前从 npm 上装到的 API 面”:
- 遍历本仓库所有包,对非 private 且存在于 npm(且至少有一个非
nightly版本,见 buildPublishedAPI.js 的npm view <name> versions检查)的包,注入pkg.dependencies[name] = 'latest'; - 在临时目录
yarn install后,把node_modules中这些已发布包的实际源码移到packages/下参与构建(buildPublishedAPI.js),同样注入apiCheck字段并删除dist/,再yarn constraints --fix建立内部链接,最终用同一套 parcel 命令构建,产物落到dist/base-api/。
也就是说,branch 快照 = 本仓库当前代码的类型面,base 快照 = npm 最新发布版本的类型面,两者用同一管线生成,保证结构可比。
3.3 compareAPIs.js:接口重建、依赖图与可读 diff
scripts/compareAPIs.js 的头部注释概括得很清楚:读取两侧构建出的api.json,重建接口、建立接口间依赖图、对重建结果做 diff,并利用依赖图说明“某个接口是因为它的依赖变了而连带变化”。关键机制:
- 配对策略:先以 published 侧为基准,按包名在 branch 侧找同名
dist/api.json;branch 侧存在但 published 侧没有、且package.json非private的,视为“即将发布的新包”,同样纳入对比(compareAPIs.js); - 接口重建(rebuildInterfaces):把 JSON 中每个 export 还原为按字母序排列的伪源码文本(props 逐条排序、含 optional/默认值),同时用
processType把类型树(union、intersection、application、function、object 等十余种节点)递归渲染为可读字符串;遇到link节点时记录dependantOnLinks依赖边(compareAPIs.js); - diff 与传播分析:对每对接口用
Diff.structuredPatch计算 hunk,followDependencies找出“该接口因哪些已变更的依赖而变化”(输出changed by:段落),invertDependencies+followInvertedDependencies反向找出“该接口变化会影响哪些下游接口”(输出it changed:段落); --isCI模式:diff 包裹进```diff代码块,受影响接口列表改用<details><summary>it changed</summary>...</details>折叠块(compareAPIs.js),避免长清单刷屏——周度自动化正是用这一模式把输出直接重定向为/tmp/diff-current.md;- 降噪:
normalizeDefault会把默认值的引号风格、逗号/冒号空格统一(注释说明是为了抹平“oxlint 格式化”引入的伪差异),避免格式差异污染周度 diff。
输出按包分组:每个有变化的包输出### <包路径>,其下每个变化的接口输出#### <包>:<导出名>加 diff 文本,这正是 snapshots 仓库diffs/里存档的文档格式。
另外,package.json 还定义了两个便捷脚本,方便本地手动复核同一套管线:
"check-apis": "yarn build:api-branch --githash=\"origin/main\" --output=\"base-api\" && yarn build:api-branch && yarn compare:apis", "check-published-apis": "yarn build:api-published && yarn build:api-branch && yarn compare:apis"四、本地 launchd 回退路径
当不想依赖 GH Actions(例如想在本机跑、或 Actions 资源不足)时,README 提供了 macOS launchd 回退方案,三步流程:
- launchd 每周一 9 点触发(笔记本睡眠后会补跑);
- 执行
claude -p "$(cat ~/weekly-tsdiffer.md)",带 bash/read 权限; - Claude 按照 prompt.md 中“与 GH Actions workflow 相同”的步骤顺序执行。
4.1 prompt.md 的九步工作流
prompt.md 是一份完整的可执行剧本,要求“按顺序执行所有步骤、不要提前停止、某步失败则记录到/tmp/weekly-tsdiffer-error.log并尽量继续”。配置段声明了仓库路径、snapshots 仓库路径、Slack 通道与环境变量名。九步要点:
date +%Y-%m-%d取TODAY;git checkout main && git pull origin main;yarn build:api-branch(prompt 注明耗时 10–30 分钟),产物在dist/branch-api/;- 若
dist/base-api/已有内容则跳过,否则yarn build:api-published(同样 10–30 分钟); yarn compare:apis --isCI | tee /tmp/diff-current.txt——特别强调只捕获 stdout(不加2>&1),避免 yarn 的 stderr 混入 diff 文件;- 发布检测与周环比:
git log --grep='^Publish$' --oneline -1 | awk '{print $1}'得CURRENT_PUBLISH,与 snapshots 仓库last-publish-hash.txt的PREV_PUBLISH比较;若不同则NEW_RELEASE=true直接进下一步,否则取diffs/中字典序最大的历史文件diff出WEEKLY_DELTA(首次运行则记为 “(first run, no previous diff to compare against)”); - 提交决策(与 workflow 的
[[ -s file ]]逻辑一致):diff 为空则跳过提交;NEW_RELEASE=true且 diff 非空、或WEEKLY_DELTA非空且 diff 非空则提交diffs/$TODAY.txt与更新后的last-publish-hash.txt,其余情况跳过; - 摘要规则:按四种 case 选择消息(与第二节 2.4 的四类一致),且对正常 case 定义了明确的分组与分类规则——
- delta 是“diff 的 diff”:整个组件区块带
+前缀只表示“该组件这周开始相对基线有变化”,不代表组件本身是新增;只有 diff 中出现+ ComponentName这种新导出行才能称为新组件; - 同一族组件(如 Checkbox、Radio、Switch)新增同一 prop(如
description)时应合并为一个 feature 描述; - 新包装组件(如
CheckboxField、RadioField)与内部组件的新 prop 应合并描述其共同启用的能力(如 “help text support”); - 现有组件新增 prop 必须显式点出,不能被“新增导出数量”淹没;
- prop 签名变化(如回调多一个参数)要标记为潜在 breaking change;
- Calendar 家族(Calendar、RangeCalendar、CalendarState、DateRangePicker)习惯一起变,应合并描述;
- delta 是“diff 的 diff”:整个组件区块带
- 用
curl调 Slackchat.postMessage(Bearer token 来自SLACK_TSDIFF_CHROMATIC_BOT_TOKEN),并校验响应包含"ok": true。
4.2 launchd plist 参考配置
scripts/weekly-api-diff/launchd.plist 中的关键配置:
<key>ProgramArguments</key> <array> <string>/bin/zsh</string> <string>-c</string> <string>source $HOME/.nvm/nvm.sh && source $HOME/.secrets && claude -p "$(cat $HOME/weekly-tsdiffer.md)" --allowedTools "Bash,Read" --dangerously-skip-permissions</string> </array> <key>StartCalendarInterval</key> <dict> <key>Weekday</key><integer>1</integer> <key>Hour</key><integer>9</integer> <key>Minute</key><integer>0</integer> </dict>即每周一 9:00(Weekday 1)以 zsh 执行:先 source nvm 与~/.secrets注入 Slack token,再调用claude -p并把--allowedTools限定为Bash,Read。plist 注释里还给出 load/unload/kickstart(手动试跑)与看日志的命令。
4.3 Prompt 更新流程与全新机器安装
更新 prompt 需要三步(README “Updating the Prompt” 一节):编辑 scripts/weekly-api-diff/prompt.md → 提交到仓库 → 同步到生效位置cp scripts/weekly-api-diff/prompt.md ~/weekly-tsdiffer.md。
全新 macOS 机器的完整安装步骤(README “Local Fallback Setup” 一节,原样保留以便直接执行):
# 1. Copy prompt to home dir cp scripts/weekly-api-diff/prompt.md ~/weekly-tsdiffer.md # 2. Install launchd plist (substitutes your macOS username into the Label) sed "s/<username>/$USER/g" scripts/weekly-api-diff/launchd.plist > ~/Library/LaunchAgents/com.$USER.weekly-tsdiffer.plist launchctl bootout gui/$(id -u)/com.$USER.weekly-tsdiffer 2>/dev/null || true launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.$USER.weekly-tsdiffer.plist # 3. Add Slack bot token to ~/.secrets (chmod 600) echo 'export SLACK_TSDIFF_CHROMATIC_BOT_TOKEN=xoxb-...' >> ~/.secrets chmod 600 ~/.secrets # 4. Clone snapshots repo git clone https://github.com/LFDanLu/react-spectrum-api-snapshots ~/dev/react-spectrum-api-snapshots # 5. Build the release baseline (one-time, ~20 min) cd ~/dev/react-spectrum yarn build:api-published注意两点适用前提:该回退方案依赖 macOS(launchd 为 macOS 专属调度器)与本机已安装claudeCLI;发布基线是一次性构建(约 20 分钟),之后每周只需构建 branch 侧快照。
五、GitHub Actions 所需 Secrets
README 列出的三个 Actions secrets:
| Secret | 说明 |
|---|---|
SLACK_TSDIFF_CHROMATIC_BOT_TOKEN | Slack bot token |
SLACK_CHANNEL_ID | 目标 Slack 频道 |
SNAPSHOTS_REPO_TOKEN | 对react-spectrum-api-snapshots仓库具有 Contents: read+write 权限的 GitHub PAT |
其中前两者与本地回退方案的~/.secrets(chmod 600、绝不入库)形成对照:同一份敏感信息在两条路径上分别以“Actions Secrets”和“本地 shell profile”的方式注入。
六、小结:这套设计做对了什么
从源码结构看,这套周度 API Diff 自动化的工程价值集中在四个设计决策上:
- 两侧快照用同一管线生成:branch 侧与 published 侧都走
apiChecktarget +parcel-transformer-docs,api.json结构完全一致,diff 才具备类型级语义; - 基线自动跟随 minor/major 发布:
build:api-published通过react-aria-components@x.y.0tag 自动定位基线提交,patch 发布则不重置基线,保证“周度 diff”始终相对最近的正式版本; - diff-of-diffs 的增量语义:对历史 diff 再做一次
diff,并把>/<两段加工成带标题的纯文本喂给模型,使 LLM 摘要只针对“本周新增/本周消失”的少量行,而不是每周重新总结全量; - 双路径冗余 + prompt 即文档:prompt.md 同时是本地 Claude 的执行剧本和人类可读的操作手册,九步流程与 Actions workflow 严格对齐,任何一侧的逻辑变更都可以直接对照源码脚本(buildBranchAPI.js、buildPublishedAPI.js、compareAPIs.js)验证。
如果要复用这套方案到其他 monorepo,最小移植集为:两个快照构建脚本(含apiChecktarget 约定)、compareAPIs.js的依赖图 diff、一个带日期文件名的 snapshots 仓库(diffs/+last-publish-hash.txt+deltas/),以及 cron/launchd 二选一的触发器——其余细节(tag 命名规则、发布检测方式)按各项目的发布流水线调整即可。
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考