中心的想法

open.awareride.com 是一个为散落在多个仓库里的内容而存在的渲染面。你正在读的博客写在一个单独的 Posts 仓库里。Packscope 的文档写在 packscope 仓库里。中心仓库 – awareride/awareride.github.io – 只负责构建与渲染。

问题是:如何把内容从 N 个外部仓库搬进中心的 src/content/,既不让中心变成合并冲突的重灾区, 又绝不让外部推送未经评审就落到 main

契约

每个外部仓库镜像中心的语言布局,并让一个 GitHub Action 把它的 posts/docs/ 拷进去:

外部仓库 中心
posts/ src/content/posts/
docs/ src/content/docs/${PRODUCT}/

${PRODUCT} 这一段是有意思的部分。外部仓库的文档按语言扁平存放 – docs/en/getting-started.md, 而不是 docs/packscope/en/getting-started.md。产品维度在同步时由工作流里的 PRODUCT 环境变量注入。 这让相对 Markdown 链接在 GitHub 上浏览仓库时仍然有效:它们相对 docs/ 解析,而不是 docs/packscope/

同步开 PR,绝不直推 main

同步工作流不推 main。它克隆中心、用 rsync 拷贝内容、提交到一个专属分支,并开一个 pull request:

rsync -a posts/ "$CLONE_DIR/src/content/posts/"
git checkout -b "sync-posts-${SRC}-${RUN_ID}"
git commit -m "posts: sync from ${SOURCE_REPO}"
git push -u origin "$BRANCH"
gh pr create --repo awareride/awareride.github.io --base main --head "$BRANCH" \
  --title "posts: sync from ${SOURCE_REPO}"

rsync -a posts/ dest/(注意末尾斜杠)的意思是“posts/ 的内容“,所以无论目标目录是否已存在, 它都不会造出 posts/posts/ – 这正是我早先用基于 cp -R 的 action 时踩到的坑。人工评审 PR 并合并。 没有任何东西在无人值守时落地。

鉴权用一个存为 DOCS_CENTRAL_HUB_TOKEN 的细粒度 PAT,放在外部仓库里,对中心有 Contents 与 Pull-requests 写权限。

是技能,不是复制粘贴

与其让每个外部仓库各自重造,整套东西被打包成一个技能:awareride-content-sync。外部仓库把它拷进 .agents/skills/awareride-content-sync/,一次性拿到工作流模板、校验脚本和文档。

校验器在同步前运行并作为门禁 – 纯 Node 标准库,零依赖:

node .agents/skills/awareride-content-sync/scripts/validate.mjs

它强制 frontmatter 合规,以及 slug 契约:一个文件的 slug 是它相对语言目录的路径,且在各语言 间必须逐字节一致。en/getting-started.mdzh/getting-started.md – 绝不是 zh/Getting-Started.md – 否则逐页回退会坏。

退役内容

rsync 拷贝只增改、不删除,这样一个项目的内容不会被另一个项目的页面误删。这也意味着在本地删掉 一个文件,并不会把它从中心删掉。

可选的答案是仓库根上的 sync-delete.list – 每行一条路径,末尾带斜杠表示整个目录。拷贝之后, apply-delete-list.mjs 精确移除这些路径,删除与新增一起出现在同一个可评审的 PR 里:

posts/en/old-post.md
docs/en/legacy/

在两个地方都能用的链接

源 Markdown 保留对 GitHub 友好的相对链接 – ./getting-started.md../zh/architecture.md。 在中心上,Astro 的 Markdown 处理器不重写正文链接,所以一个裸的 ./getting-started.md 会渲染成 href="./getting-started.md" 然后 404。一个小的 Sätteri mdast 插件在构建时修复了它:它扫描一次 src/content/**/*.md,建一张“文件路径 -> 站点 URL”的映射,并把相对 .md 链接改写成中心路由 – ./getting-started.md 变成 /packscope/docs/getting-started。无需改源文件,链接在 GitHub 上和站点上 都能用。

加起来是什么

一个外部仓库用扁平、GitHub 可读的布局写 Markdown。推到 main 时,工作流校验它、把它拷进中心 的一个分支、并开一个 PR。中心评审、合并,一次手动部署把它上线。内容留在它被维护的地方;中心只负责 渲染。


这是“构建 open.awareride.com”系列的一部分。上一篇: 不用 Starlight,做带逐页回退的两种语言。 从头开始读:用 Astro 重建 open.awareride.com