把 GitHub Action 抽成跨项目复用
背景
有些 GitHub Actions 一开始只是为某个项目临时写的。比如构建镜像时发一条飞书通知,开始构建发一次,结束时根据结果再发一次。
这种 action 如果只服务一个项目,放在当前仓库的 .github/actions 里没有问题。但一旦第二个、第三个项目也想复用,同一份脚本就会开始被复制。复制几次以后,问题很快就出来了:
- 一个项目改了通知卡片,另一个项目忘了同步;
- 参数名字和行为慢慢变得不一致;
- 修一个小 bug,要在多个仓库里重复改;
- 新项目接入时,还要去旧项目里翻实现细节。
所以我单独建了一个 forge-actions 仓库,用来放跨项目共用的 GitHub Actions。
这个仓库的目的很明确:把已经稳定下来的 CI 小工具沉淀成共享 action。业务项目只负责调用,不再关心 action 内部怎么拼消息、怎么签名、怎么测试。
第一个抽出来的 action
这次先迁移的是 feishu-notify。
它做的事情很简单:在 GitHub Actions 运行过程中,给飞书自定义机器人发送一张通知卡片。常见场景是:
- workflow 开始时提醒一次;
- workflow 成功时发成功通知;
- workflow 失败或取消时发异常通知;
- 卡片里带上仓库、分支、提交、触发人、运行链接等信息。
迁移后,action 放在共享仓库的这个位置:
.github/actions/feishu-notify/项目里包含三类文件:
action.yml:定义 action 的输入参数和运行方式;send-card.js:真正发送飞书卡片的 Node.js 脚本;send-card.test.js:用 Node 内置测试框架覆盖消息构造逻辑。
这里没有引入额外 npm 依赖。对一个通知 action 来说,这样更省心:少一层安装、少一份 lockfile,也少一点 CI 环境里的不确定性。
其他项目如何使用
其他仓库不需要复制 send-card.js,直接在 workflow 里引用共享仓库即可。
最小示例如下:
- name: Send Feishu notification uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@main with: webhook: ${{ secrets.FEISHU_WEBHOOK_URL }} secret: ${{ secrets.FEISHU_BOT_SECRET }} status: success title: Build success summary: Image has been built and published. run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ref-name: ${{ github.ref_name }} sha: ${{ github.sha }} actor: ${{ github.actor }} repository: ${{ github.repository }} workflow: ${{ github.workflow }} run-number: ${{ github.run_number }} event-name: ${{ github.event_name }}如果需要额外展示一些业务字段,可以用 extra-fields 传一个 JSON 对象:
- name: Send Feishu notification uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@main with: webhook: ${{ secrets.FEISHU_WEBHOOK_URL }} secret: ${{ secrets.FEISHU_BOT_SECRET }} status: success title: Publish success summary: Artifact has been published. run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ref-name: ${{ github.ref_name }} sha: ${{ github.sha }} actor: ${{ github.actor }} repository: ${{ github.repository }} workflow: ${{ github.workflow }} run-number: ${{ github.run_number }} event-name: ${{ github.event_name }} extra-fields: '{"Environment":"production","Artifact":"docker image"}'webhook 和 secret 都应该放在 GitHub Secrets 里,不要写进 workflow 文件。secret 是可选的,如果飞书机器人没有开启签名校验,可以不传。
一个完整一点的用法
我更习惯在 workflow 里发两类通知:开始通知和结束通知。
开始通知直接写在构建任务前面:
- name: Notify build started uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@main with: webhook: ${{ secrets.FEISHU_WEBHOOK_URL }} secret: ${{ secrets.FEISHU_BOT_SECRET }} status: started title: Build started summary: GitHub Actions workflow has started. run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ref-name: ${{ github.ref_name }} sha: ${{ github.sha }} actor: ${{ github.actor }} repository: ${{ github.repository }} workflow: ${{ github.workflow }} run-number: ${{ github.run_number }} event-name: ${{ github.event_name }}结束通知通常要放在 if: always() 的步骤里,否则前面的构建失败后,通知步骤可能不会执行:
- name: Notify build finished if: always() uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@main with: webhook: ${{ secrets.FEISHU_WEBHOOK_URL }} secret: ${{ secrets.FEISHU_BOT_SECRET }} status: ${{ job.status }} title: Build finished summary: GitHub Actions workflow has finished. run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ref-name: ${{ github.ref_name }} sha: ${{ github.sha }} actor: ${{ github.actor }} repository: ${{ github.repository }} workflow: ${{ github.workflow }} run-number: ${{ github.run_number }} event-name: ${{ github.event_name }}feishu-notify 支持的状态是:
startedsuccessfailurecancelled
job.status 在结束步骤里正好能覆盖常见结果,适合拿来做最终通知。
关于版本引用
上面的示例用的是 @main:
uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@main这适合刚迁移、还没有正式发版本的时候用。等 action 稳定以后,更推荐改成 tag 或 commit SHA:
uses: sephymartin-lab/forge-actions/.github/actions/feishu-notify@v1这样业务项目不会因为共享仓库的最新提交而突然改变行为。共享 action 仓库继续演进,业务项目按自己的节奏升级版本。
本地验证
共享 action 最怕的是“看起来只是改了一点通知文案,结果所有项目都受影响”。所以迁移时我保留了测试文件,并用 Node.js 20 直接跑测试:
rtk mise exec node@20 -- node --test .github/actions/feishu-notify/send-card.test.js除了测试,还要顺手检查几件事:
action.yml能被 YAML 解析;send-card.js语法没有问题;- 旧项目里不再引用本地 action;
- 新项目 README 里有输入参数、Secrets 说明和调用示例。
这些检查不复杂,但能避免共享工具变成新的不确定因素。
小结
这类 action 抽出来以后,收益主要不在“少写几行 YAML”,而是在边界变清楚了。
业务项目只声明自己什么时候通知、传什么上下文;共享仓库负责通知格式、签名逻辑、字段展示和测试。后面如果要接入更多项目,也不用再复制脚本,只要引用同一个 action,再把 webhook 和 secret 配好就行。
对团队内部的 CI 工具来说,这种小仓库很实用。它不需要做得很大,但应该把接口、示例和验证方式写清楚。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












