Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flare-stack-blog-toolkit

给 du2333/flare-stack-blog (TanStack Start + TipTap + Better Auth 的 Cloudflare Workers 博客系统)用的非官方运维工具链。

它解决的问题:往这套系统批量发文章,官方没有导入接口。 唯一能程序化发文的通道是 Admin API + API Key,而脚本/Agent 拿不到浏览器 session cookie —— 所以第一件事是给自己造一把 API Key(见下文)。

顺带还做了:Markdown 自动转 TipTap JSON、发布后上线巡检、一键换站点图标与头像。

所有脚本零依赖(Node 18+ 原生 fetch / FormData / Blob),站点地址全靠环境变量传入, 代码里不写死任何域名。


一、它能做什么

脚本 干什么
publish.mjs 批量发布 content/*.md:建草稿 → 设分类/slug → 建/挂标签 → 发布
md2tiptap.mjs Markdown → TipTap(ProseMirror) JSON 转换器(发布的前提)
verify.mjs 上线巡检:总数、状态分布、分类计数、逐条 /post/{slug} 状态码、atom.xml 条目数
make-index.mjs 把本地稿子与线上已发布文章求交集,生成可浏览的全站清单 HTML
upload-assets.mjs 把 favicon 六件套 + 头像上传到站点 R2 资源区
set-site-assets.mjs 改站点配置里的图标/头像指向,并让公共缓存失效
make-site-art.py 用代码画站点图标与头像(SVG → Chrome 高倍渲染 → Pillow 降采样)

二、前置:造一把 Admin API Key

用 @better-auth/api-key 插件。几个必须知道的点:

  • Key 明文格式 = prefix + 随机串(本项目 defaultPrefix="fsb_",随机部分 64 字符)
  • 数据库里不存明文,存的是 SHA-256(明文) → base64url
  • start 字段存明文前 6 位(fsb_Ez),后台列表展示用
  • enableSessionForAPIKeys=true,所以 Key 可以直接当 session 用

生成并插库:

KEY="fsb_$(node -e "console.log(require('crypto').randomBytes(48).toString('base64url').slice(0,64))")"
HASH=$(node -e "console.log(require('crypto').createHash('sha256').update(process.argv[1]).digest('base64url'))" "$KEY")
START=${KEY:0:6}
NOW=$(node -e "console.log(Date.now())")
INSERT INTO apikey
(id, config_id, name, start, reference_id, prefix, key, enabled, rate_limit_enabled, created_at, updated_at)
VALUES
('auto-' || hex(randomblob(8)), 'default', 'automation', '<START>', '<USER_ID>',
 'fsb_', '<HASH>', 1, 0, <NOW>, <NOW>);

reference_id 必须是该站点的 owner user id(查 user 表)。执行要带 --remote:

cd <worker 源码目录>
CI=1 wrangler d1 execute DB --remote --file=<sql 文件>

wrangler d1 execute 偶尔报 fetch failed / No internet connection,直接重试,第二次通常就过。

把明文 key 写进工作目录的 .fsb-apikey.txt(只放一行),它已经在 .gitignore 里。

验证:

curl -s -H "x-api-key: $KEY" https://blog.example.com/api/admin/posts | head -c 200
# 期望 200 + {"items":[...],"total":N};无 key 应为 401

三、快速开始

git clone https://github.com/FBX-Dev/flare-stack-blog-toolkit.git
cd flare-stack-blog-toolkit

export FSB_BASE=https://blog.example.com
echo 'fsb_你的key' > .fsb-apikey.txt

mkdir -p content
cp examples/example.md content/01-example.md

# 先空跑,核对分类/标签/块数是否正确
node scripts/publish.mjs --dry

# 真发(增量发布务必带 --only,否则整个 content 目录会被再发一遍)
node scripts/publish.mjs --only=01

# 巡检
node scripts/verify.mjs

# 生成全站清单
node scripts/make-index.mjs

四、内容文件格式

每篇一个 .md,顶部 --- 包住元数据,空行,然后正文:

---
title: 标题
summary: 摘要(列表页显示)
category: 技术
tags: 标签A,标签B
slug: my-post-slug
---

## 第一个小标题

正文……

slug 强烈建议手写。 中文标题自动生成的 slug 可能为空或重复, 而前台 URL 就是 /post/{slug},为空就访问不到。

支持的 Markdown 语法:##/###/#### 标题、段落、- 无序列表、1. 有序列表、 > 引用、--- 分割线;行内 **粗**、*斜*、`码`、[文字](链接)。

一级标题 # 会被自动降为二级 —— 这个 schema 的 heading 只到 2–4 级。

五、这套 API 的踩坑备忘

用之前先看这几条,能省几小时:

  1. 发布是四步,顺序不能错 POST /api/admin/posts → PATCH /api/admin/posts/{id}(设 categoryId + slug) → PUT /api/admin/posts/{id}/tags → POST /api/admin/posts/{id}/publish

  2. GET /api/admin/posts 单次最多返回 50 条,limit 传 200 也只给 50。 文章超过 50 篇时必须用 offset 翻页,否则会出现 「总数看着对、逐条检查却只覆盖前 50 篇」,而且不报任何错。 (?page=2 无效,接口只认 offset。total 字段仍然返回真实总数,容易造成假象。) 本仓库的 verify.mjs / make-index.mjs 已内置翻页。

  3. 列表项不含 categoryId(只有 id/title/summary/slug/status/时间)。 要按分类汇总,用 GET /api/admin/categories 的 postCount。

  4. 前台 URL 是 /post/{slug},不是 /posts/{slug} —— 后者是 API 路由,直接访问 404。

  5. contentJson 是 TipTap JSON,没有 markdown 转换接口,得自己转。 安全可用的节点:doc / paragraph / heading(2-4) / bulletList / orderedList / listItem / blockquote / codeBlock / horizontalRule / text(marks:bold italic code link)。

  6. 改站点配置必须整段提交:PATCH /api/admin/config 传 section:"site" + expectedRevision + 完整的 config.site,省略字段会回落默认值。 revision 版本号在响应体的 revisions.site,不在 config.site 里面。 revision 过期返回 409,要重新 GET 再改,别盲目重试。 改完记得 POST /api/admin/cache/invalidate,否则首页还吐旧的 <link rel="icon">。

  7. 上传资源走 POST /api/admin/config/assets(multipart,字段 file + assetPath)。 assetPath 有白名单前缀:favicon/、social/、themes/fuwari/。 落盘 key 是 asset/${assetPath},公开地址 = /images/asset/${assetPath},同路径重传即覆盖。

六、环境变量

所有脚本共用 scripts/_env.mjs 读取配置:

变量 默认 说明
FSB_BASE 必填 站点根 URL,结尾斜杠自动去掉
FSB_KEY_FILE ./.fsb-apikey.txt API Key 明文文件
FSB_CONTENT_DIR ./content 文章目录
FSB_REPORT_OUT ./publish-report.json 发布报告
FSB_INDEX_OUT ./index.html 清单 HTML
FSB_SITE_TITLE FSB_BASE 的 hostname 清单页站点名
FSB_CAT_ORDER 空 清单页分类顺序(逗号分隔)
FSB_INDEX_NOTE 空 清单页顶部说明,支持 {n}
FSB_ASSET_DIR ./assets 图稿目录
FSB_ASSET_URL_BASE /images/asset 资源 URL 前缀

FSB_BASE 故意不给默认值 —— 默认值一旦指向某个真实站点, 别人 clone 下来直接跑就会打到别人家的服务器上。

七、换站点图标与头像

# 1) 生成图稿(需要 Chrome;Python 需要 Pillow)
ART_CALLSIGN=YOURCALL ART_OUT=./assets python scripts/make-site-art.py

# 2) 上传到 R2
node scripts/upload-assets.mjs --dry     # 先看要传什么
node scripts/upload-assets.mjs

# 3) 改配置指向 + 失效缓存
node scripts/set-site-assets.mjs --dry
node scripts/set-site-assets.mjs

make-site-art.py 的思路值得说一句:SVG 是唯一几何源。 Pillow 不能栅格化 SVG,Chrome 能但小尺寸直出有锯齿,所以走 「Chrome 2048px 渲染一次 → Pillow LANCZOS 降采样出全套尺寸」, 这样 16px 的 .ico 和 512px 的 PWA 图标与 SVG 完全同源,不会出现 「Chrome 里和 Safari 里长得不一样」。

两个容易翻车的细节:

  • 圆角 vs 满幅要分开:favicon 系列用圆角 + 透明角(标签页里好看); apple-touch-icon 和 PWA manifest 图标用满幅不透明 —— iOS/Android 会自己套形状蒙版, 透明角会被填成黑边。
  • 生成后一定看一眼输出目录里的 _usage_light.png / _usage_dark.png 预览条, 确认 16px 下还认得出图形,再上传。

八、已知限制

  • 没有官方导入接口,本工具链是逆向 Admin API 得到的,上游改版可能失效。
  • 发布 20 篇实测约 6 分钟(每篇 4 次 API 调用 + 标签创建 + 400ms 节流), 篇数多时请放后台跑,别放在会超时的前台任务里。
  • 只覆盖「发文 + 站点资源」这两块,不做主题开发、不做数据库迁移。
  • 脚本里对响应结构的解析做了几种兼容写法(json.items || json.data), 但仍然假定是 Better Auth 系的返回形状。

九、License

MIT

本项目与 flare-stack-blog 上游无隶属关系,属于第三方工具。

About

给 flare-stack-blog 用的非官方运维工具链:批量发文(Admin API + 自造 API Key)、Markdown 转 TipTap JSON、上线巡检、一键换站点图标与头像 | Unofficial ops toolkit for flare-stack-blog

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages