image.png

你有没有过这样的经历:想写篇博客,结果被「打开电脑 → 装环境 → hexo new → 找图 → 调格式 → 部署」这一套流程劝退?

我最近把博客彻底「重构成了 Issue 驱动」:在 CNB 仓库里新建一个 Issue,文章就自动生成并部署上线;关闭 Issue,文章自动下线。 全程不需要本地环境,手机上都能发文。

这篇文章就讲讲这套「建 Issue 即发布」的自动化流水线是怎么搭出来的,以及它带给我的真实体验。


一、核心思路:把 Issue 当成「文章编辑器」

传统 Hexo 写文章的链路是这样的:

1
本地写 Markdown → hexo new → hexo generate → hexo deploy → 上线

而我现在的链路变成了:

1
在 CNB 新建 Issue → CI 自动转成文章 → push 回仓库 → 触发部署 → 上线

关键点在于中间那段 CI 脚本tools/issue-to-post.js)。它做的事其实很直白:

  1. 读取 Issue 的标题和正文;
  2. 把正文转成一篇 Hexo 文章;
  3. 正文里粘贴的图片自动下载到文章同名资源文件夹,并改写成本地引用;
  4. 用有写权限的令牌把文章 push 回仓库;
  5. 仓库的 push 事件触发 hexo generate + EdgeOne Pages 部署。

整个流程图:

1
2
3
4
5
6
7
8
9
10
┌─────────────┐   issue.open   ┌────────────────┐   push   ┌──────────────────┐
│ 新建 Issue │ ────────────▶ │ issue-to-post │ ───────▶ │ hexo generate + │
│ (写文章) │ │ 生成 .md 文章 │ │ EdgeOne 部署上线 │
└─────────────┘ └────────────────┘ └──────────────────┘
│ close ▲
▼ │
┌─────────────┐ issue.close ┌────────────────┐ push │
│ 关闭 Issue │ ────────────▶ │ 删除对应文章 │ ────────────────┘
│ (撤下文章) │ │ 重新构建部署 │
└─────────────┘ └────────────────┘

二、一个 Issue 长什么样?

Issue 的标题就是文章标题,正文就是正文,支持 Markdown,也支持 CNB 自带的 HTML <img> 图片语法。

标签和分类我在正文顶部的 front-matter 里指定:

1
2
3
4
5
6
7
8
---
tags: [教程, 效率]
categories: [技术]
---

这里开始写正文……

**加粗**、# 标题、列表都正常支持。

而且脚本很聪明:

  • 改标题也不怕:文章和 Issue 通过 issue_iid 关联,你改了 Issue 标题,脚本会先按 iid 删掉旧文章再重新生成,不会留下孤儿文件;
  • 关闭即下线:关掉 Issue,对应文章和资源文件夹一并删除,站点重新构建后自然消失;
  • 重新打开即复活:reopen 又会重新生成。

三、图片处理:粘贴即下载

写博客最烦的就是插图。在这套方案里,你在 Issue 里直接粘贴图片,CNB 会生成 /-/imgs/... 的相对地址。

脚本会自动:

  1. 把相对地址补全成真实的 https://cnb.cool/<owner>/<repo>/-/imgs/... 下载地址;
  2. 下载到 source/_posts/<slug>/ 同名资源文件夹;
  3. 把引用改写成相对路径。

更妙的是它做了防御性处理:如果下载回来的是 HTML(说明地址缺了仓库 slug 前缀),脚本不会把网页存成图片,而是保留原链接并告警,避免渲染出一坨 HTML 文本。


四、权限控制:别人的 Issue 不会乱发文章

仓库属于组织时,CI 默认令牌在 issue.* 事件下是只读的,而且 CNB 不暴露「执行动作的人」,只暴露 Issue 作者。

所以脚本用一份允许名单来做权限校验:

  • 环境变量 CNB_ISSUE_ALLOWED_OWNERS(可由密钥仓库注入,不入库);
  • 或仓库内配置文件 tools/issue-owners.json
1
{ "owners": ["devsss", "alice", "bob"] }

只有名单内用户创建的 Issue 才会被转成文章,其他人(比如读者提的 Issue)会被自动跳过。这样既开放了「用 Issue 写作」的能力,又不会让任何人都能往你博客塞内容。


五、CI 配置:四件事搞定

核心就是 .cnb.yml 里的几条流水线:

事件 动作
issue.open 生成文章并 push
issue.update 重新生成文章并 push
issue.reopen 重新生成文章并 push
issue.close 删除文章并 push
push hexo generate + 部署 EdgeOne Pages

部署用的是 EdgeOne Pages,一行命令搞定:

1
edgeone pages deploy ./public -n cnb-www-devsss -t $EO_SECRET

小坑提醒:issue.* 事件下的默认 CNB_TOKEN 是只读的,不能用来 push,必须导入一个「有写权限」的 GIT_TOKEN(从密钥仓库 cnb.yml 注入),否则文章推不回仓库。


六、真实体验:为什么我回不去了

用了两个月,最大的感受是写作门槛被砍到了地板

  • 零本地环境:手机打开 CNB,新建 Issue,写,提交,完事;
  • 所见即所得:Issue 里就能预览排版,不用本地起 server;
  • 撤销超简单:后悔了?关掉 Issue 文章就下线,比删文件还干净;
  • 图片零操心:粘贴即本地化,不怕图床跑路;
  • 版本可追溯:每篇文章都对应一个 Issue,讨论、修改历史一清二楚。

当然也有前提:你得有个 CNB 仓库 + 一份 EdgeOne 部署令牌。但这两样现在都是免费的,半小时就能配完。


七、小结

把 Issue 当成文章编辑器,本质是用 CI/CD 把「写作」和「发布」彻底解耦。你只管写,剩下的交给流水线。

如果你也想搭一套,照着这个仓库的 README 走一遍就行——克隆、改 _config.yml、配好密钥,然后新建你的第一个 Issue,开始写吧

这篇文章本身,就是我用这套流程发出去的。不信你去看仓库的 source/_posts/,它一定对应着一个 Issue 😉


相关链接