打开网易新闻 查看精彩图片

技术人的草稿箱,是个黑洞。写的时候爽,发的时候懒,最后攒到不敢看。有个日本开发者数了数自己的仓库:21篇技术文章,全部躺在 docs/blog-drafts/ 里吃灰,时间跨度从3月到4月,最长的已经躺了快一个月。

他的个人项目叫「自分株式会社」,用 Flutter Web + Supabase 搭的。功能迭代很快,每次加新东西都想写篇博客,但「打开编辑器→复制粘贴→调格式→选平台→点发布」这套流程,把热情磨成了拖延。

于是他干了件事:写了个 GitHub Actions 工作流,1条命令把21篇全部丢进 Qiita 和 dev.to。从第一个 commit 到最后一篇发布,只花了1天。

问题不是写不出来,是发不出去

问题不是写不出来,是发不出去

技术博客的死亡循环是这样的:晚上11点写完代码,兴奋地想记录,打开编辑器写了两行,发现要调格式、选标签、配封面,兴致没了,保存草稿,明天再说。明天复明天,docs/blog-drafts/ 里多了个 2026-03-28-note-comments.md。

这位开发者的21篇草稿,文件名整整齐齐按日期排开,像一排未完成的待办事项。2026-03-31-app-feedback.md、2026-04-01-workflow-automation.md……全是「未公开」状态。最讽刺的是,其中一篇的标题就叫 workflow-automation,讲工作流自动化,结果自己还没自动化。

他算过一笔账:每篇手动发布,Qiita 和 dev.to 各发一次,算上格式调整、标签填写、图片上传,平均15分钟。21篇就是5个多小时,分在21个不同的时间点,心理成本远超实际耗时。人不是机器,切换上下文的开销比想象中狠。

Flutter Web + Supabase 的组合有个特点:功能迭代快,素材源源不断。用户反馈系统上线了,写;评论功能重构了,写;新的部署流程跑通了,写。写是写不完的,但发是可以断的。断着断着,仓库成了墓地。

解法:把发布变成一条命令

解法:把发布变成一条命令

他的方案分两层:GitHub Actions 做触发和编排,Supabase Edge Function 做平台对接。核心目标只有一个:让「发布」这件事,从一系列决策变成一次回车。

工作流文件叫 blog-publish.yml,支持三个参数:draft_path 指定要发的文件,platforms 选 Qiita/dev.to 或同时发,dry_run 用来测试。命令长这样:

gh workflow run blog-publish.yml \

--field draft_path="docs/blog-drafts/2026-04-12-topic.md" \

--field platforms="qiita,devto"

执行后,Actions 做六件事:读文件、抽 frontmatter、写 Supabase 数据库、调 Edge Function 发文章、改 published 标记、记日志。六步串成一条流水线,中间任何环节出错,日志里能直接看到。

frontmatter 的处理是个脏活。他的草稿混着 Zenn 格式(topics: ["Flutter", "Supabase"])和 Qiita 格式(tags: Flutter,Supabase,AI),还有带方括号的数组形式。Shell 脚本里用 grep + sed + tr 组合拳,把各种变体统一成逗号分隔的纯文本。如果解析失败,fallback 是硬编码的 "Flutter,Supabase,buildinpublic"——至少能发出去,不会卡死。

Edge Function 叫 schedule-hub,部署在 Supabase 上。它暴露一个 HTTP 端点,接收标题、正文、标签、目标平台,然后并行调用 Qiita 和 dev.to 的 API。代码里用了个技巧:把 "blog.auto_publish" 写进 publicActions 数组,这样 GitHub Actions 用 SERVICE_ROLE_KEY 调用时,可以跳过 JWT 认证,直接执行。

正文处理靠 stripFrontmatter() 函数,用正则掐头去尾,只留 Markdown 本体。Qiita 和 dev.to 的 API 对 frontmatter 的容忍度不同,统一清洗后,两边都能接。

卡住的第5步:GitHub 的分支保护

卡住的第5步:GitHub 的分支保护

整个流程里,唯一需要人工介入的是第5步:把 published: false 改成 published: true,并推回主分支。不是不想自动,是 GitHub 的安全机制不让。

GitHub Actions 默认用的 GITHUB_TOKEN,权限被框死在当前工作流,没法绕过分支保护规则。他试过三种办法:直接 push 到 main,报 GH006 Protected branch;调 merges API,返回 HTTP 409;用 gh pr create,发现 Actions 环境根本不允许创建 PR。三条路全堵死。

现在的折中方案:Actions 创建一个带时间戳的分支,比如 blog-publish/20250413120000,代码改完推上去,然后停住。开发者本地执行 git merge --no-edit && git push origin main,手动合进去。21篇草稿,就是21次本地合并,比原来手动发文章还是快得多。

完全自动化的钥匙是有的:申请一个带 bypass 权限的 Personal Access Token,存成 BLOG_PAT 密钥,替换掉 GITHUB_TOKEN。这样 Actions 就能直接推 main 了。他还没开,可能是权限审批麻烦,也可能是觉得「本地点一下」已经够好。

一天清完21篇,他发现了什么

一天清完21篇,他发现了什么

批量发布的过程,像给仓库做了一次大扫除。有些草稿写的时候觉得很重要,过两周再看,发现技术方案已经迭代掉了,发出来反而误导人。有些当时觉得没写完,其实核心观点已经到位,硬憋着不发,纯粹是完美主义作祟。

他最后用的批量命令很简单:grep -rl "^published: false" 找出所有未发布文件,sort 排个序,然后循环调 workflow。实际执行时,platforms 参数设成 "qiita,devto",一次命令同步发两个平台。Qiita 是日本技术社区的主力,dev.to 覆盖英语读者,两边受众重叠度不高,值得双发。

有个细节他没写进博客,但代码里留了痕迹:dry_run 参数。默认 false,设成 true 时,整个流程跑完,最后一步不会真发,只是打印日志。21篇里,他先用 dry_run 跑了3篇,确认标签解析、图片链接、代码块格式都没问题,才全量放开。技术人的谨慎,体现在这种小地方。

Supabase 在这套架构里扮演的是「状态中心」。blog_posts 表存文章元数据,schedule_task_runs 表存执行日志,Edge Function 做无状态的 API 网关。这种设计让 GitHub Actions 保持轻量,逻辑复杂的部分下沉到 Supabase,两边通过 HTTP 交互,解耦得很干净。

他提到一个有趣的副作用:自动化之后,写草稿的心态变了。以前写的时候会想「这篇发出去要折腾好久」,现在知道「写完 push 仓库,1条命令就上线」,动笔的阻力小了很多。docs/blog-drafts/ 里的文件数,从21降到0之后,一周又新增了4篇——但这次,它们不会躺太久。

这套方案的成本几乎为零。GitHub Actions 的免费额度够个人项目用,Supabase 的免费 tier 也覆盖 Edge Function 的调用量。唯一的隐性成本是时间:写 workflow、调 shell 脚本、处理 frontmatter 的边界情况,前前后后花了两个晚上。但21篇草稿,按原来的手动流程,光是发布时间就要5小时,还不算心理内耗。这笔账,怎么算都是赚的。

技术博客的门槛,从来不在于写作能力,而在于发布 friction。他解决的不是「怎么写得更好」,而是「怎么让写好的东西见人」。这个思路,对任何有内容产出需求的技术人都适用:文档、教程、release note、changelog,本质都是同一类问题。

现在他的仓库里,docs/blog-drafts/ 是空的。下一个问题或许是:当发布变得太容易,质量把控怎么做?他没有给出答案,只是在代码里留了个 TODO:给 workflow 加自动拼写检查。如果你也在攒草稿,会先把发布自动化,还是先写完那篇「怎么保证输出质量」?