Supabase 文档站构建管线解析:Turborepo、pnpm 生命周期钩子与 codegen 协同工作流
本文以 apps/docs(Supabase 文档站)的构建管线为主体,结合仓库中的 turbo.jsonc、package.json 脚本与实际 codegen 源码,完整拆解“依赖包构建 → 示例/参考文档代码生成 → Next.js 构建 → sitemap 与 CDN 上传”的全链路流程。读完本文,你能理解文档站每次 pnpm build 背后各步骤的触发顺序、Turbo 缓存策略的关键配置,以及如何为文档应用安全地新增构建步骤与环境变量。
1. 管线总览:Turborepo 与 pnpm 两层编排
apps/docs 的构建由两层机制协同完成:
- Turborepo(根 turbo.jsonc 与 apps/docs/turbo.jsonc):负责按依赖顺序编排工作区任务,声明
inputs/outputs/env实现缓存; - pnpm 生命周期钩子(apps/docs/package.json):在
next build前后通过prebuild/postbuild脚本插入 codegen 与资产上传步骤。
整体数据流为:
依赖包 build(^build:common、ui、config、icons …)
↓
codegen:examples(复制 ../../examples → apps/docs/examples)
codegen:references(→ features/docs/generated/**)
build:markdown(guides + reference 的 .md 导出)
↓
docs#build(pnpm prebuild 链 → next build → pnpm postbuild 链)
↓
build:sitemap → upload-static-assets.sh(R2 CDN,仅生产风格部署)
从源码结构看,Turbo 层只声明到 build:markdown,而 build:federated-content、build:gz-archive 等步骤则由 pnpm 的 prebuild 在 next build 前串行执行——两层机制的职责边界清晰:跨包依赖与缓存归 Turbo,包内前后置步骤归 npm 生命周期。
2. 从仓库根目录触发的命令
根 package.json 中与文档站直接相关的脚本(以当前仓库实际内容为准):
"build": "turbo run build",
"build:docs": "turbo run build --filter=docs",
"dev:docs": "turbo run dev --filter=docs --parallel",
"test:docs": "turbo run test --filter=docs"
- 全量构建:
pnpm build→turbo run build,按依赖序构建所有包与应用。 - 仅构建文档站:
pnpm build:docs→turbo run build --filter=docs,Turbo 会先解析docs的^build依赖闭包,再执行文档站自身任务。 - 本地开发:
pnpm dev:docs(或在apps/docs下pnpm dev)。
运行环境前提(见根 package.json 的 engines/packageManager):pnpm 11.13、Node >= 22.13,且 preinstall 通过 only-allow pnpm 强制使用 pnpm。
3. Turbo 层做了什么
3.1 根 turbo.jsonc:^build 保证依赖先构建
根 turbo.jsonc 定义:
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**/*", "!.next/dev/**/*"],
}
dependsOn: ["^build"] 意味着 docs 的所有工作区依赖(common、ui、config、icons、shared-data、ai-commands 等,见 apps/docs/package.json 中 workspace:* 依赖列表)会在 docs 之前完成构建。全局缓存保留期由 cacheMaxAge: "14d" 控制。
3.2 apps/docs/turbo.jsonc:扩展并收紧 docs 任务
apps/docs/turbo.jsonc 通过 "extends": ["//"] 继承根配置,并为文档站细化任务:
"codegen:examples": {
"inputs": ["../../examples/**"],
"outputs": ["examples/**"],
},
"codegen:references": {
"inputs": ["spec/**"],
"outputs": ["features/docs/generated/**"],
},
"build:federated-content": {
"cache": false,
"env": [
"DOCS_GITHUB_APP_ID",
"DOCS_GITHUB_APP_INSTALLATION_ID",
"DOCS_GITHUB_APP_PRIVATE_KEY",
],
},
"build:markdown": {
"dependsOn": ["build:federated-content"],
"outputs": ["public/markdown/**", "public/markdown/manifest.json"],
},
"build": {
"dependsOn": ["^build", "codegen:examples", "codegen:references", "build:markdown"],
"env": [ /* 约 50 个变量,见下文 */ ],
"inputs": ["$TURBO_DEFAULT$"],
"outputs": [".next/**", "!.next/cache/**"],
}
几个值得注意的设计决策(源码注释原文佐证):
codegen:examples/codegen:references显式声明 inputs/outputs:这样 Turbo 才能对其缓存——例如examples/目录或spec/目录无变化时,直接恢复产物apps/docs/examples/**、features/docs/generated/**。build:federated-content关闭缓存:任务注释明确说明“输入是远端(GitHub)内容,缓存恢复可能复活陈旧内容”,故"cache": false。该脚本为 apps/docs/scripts/federated-content/fetch-federated-content.ts,依赖 GitHub App 三元组环境变量。build:markdown声明 outputs:注释解释“若声明了 outputs,缓存命中时 Turbo 可恢复这些产物;否则缓存命中会跳过脚本,导致next build找不到生成的 markdown”。这是典型的 Turbo 缓存陷阱:有副作用/产物的任务必须声明outputs。build任务的 env 清单:列出约 50 个影响产物的环境变量(NEXT_PUBLIC_SUPABASE_URL、NEXT_PUBLIC_SITE_URL、NEXT_PUBLIC_IS_PLATFORM、VERCEL_ENV、DOCS_GITHUB_APP_*、OPENAI_API_KEY、SUPABASE_SECRET_KEY等)。任何影响构建产物但未列入env的变量都会导致 Turbo 缓存出陈旧产物——新增环境变量时必须同步加入此清单(官方文档 build-pipeline.md 将其列为改动守则之一)。
Turbo 为 docs 编排的完整顺序即:依赖包 → 示例/参考 codegen → markdown 导出 → Next 构建。
4. pnpm 生命周期层:prebuild / build / postbuild
apps/docs/package.json 中实际的生命周期脚本(当前仓库版本,注意比早期文档多出一步 build:federated-content):
"prebuild": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples && pnpm build:federated-content && pnpm run build:markdown && pnpm run build:gz-archive",
"build": "next build",
"postbuild": "pnpm run build:sitemap && ./../../scripts/upload-static-assets.sh"
执行语义:turbo run build --filter=docs 最终执行 docs#build 时,pnpm 自动先跑 prebuild,再跑 next build,最后跑 postbuild:
- prebuild 五(六)步:GraphQL codegen → 参考文档 codegen → 复制 examples → 拉取 federated 内容 → 生成 guides + reference 的 markdown 导出 → 打包 tar.gz;
- build:
next build(ANALYZE=true时可换成build:analyze做包体积分析); - postbuild:生成 sitemap,随后调用仓库根目录的 scripts/upload-static-assets.sh(仅生产风格部署上传 R2)。
5. 逐个 codegen 脚本详解
| 脚本 | 实际命令 | 产物 / 作用 |
|---|---|---|
codegen:graphql |
tsx --conditions=react-server ./scripts/graphqlSchema.ts && graphql-codegen --config codegen.ts |
拉取/固化 GraphQL schema 并用 graphql-codegen 生成类型 |
codegen:examples |
shx cp -r ../../examples ./examples |
把 monorepo 根 examples/ 复制进 apps/docs/examples,供 MDX 中 $CodeSample 指令解析示例代码 |
codegen:references |
legacy + new 两段式 |
见下文 |
build:federated-content |
tsx … ./scripts/federated-content/fetch-federated-content.ts |
通过 GitHub App 拉取远端内容并产出 JSON 工件,供 markdown 生成器读取 |
build:markdown |
build:guides-markdown && build:reference-markdown |
生成 public/markdown/guides/**.md 与 public/markdown/reference/**.md |
build:gz-archive |
tsx ./internals/generate-gz-archive.ts |
打包 public/markdown/ 为 public/docs.tar.gz |
build:sitemap |
tsx ./internals/generate-sitemap.ts |
生成站点 sitemap(postbuild 阶段) |
5.1 参考文档 codegen 的双轨结构
codegen:references 实为两条管线串行:
"codegen:references:legacy": "tsx features/docs/Reference.generated.script.ts",
"codegen:references:new": "pnpm run codegen:references:new:ensure && tsx scripts/build-reference-content.ts"
- legacy 轨:apps/docs/features/docs/Reference.generated.script.ts 处理 Management API——将 OpenAPI v1+v2 合并为
api.latest.*JSON;规格下载/合并由 apps/docs/spec/Makefile(Redocly)完成。相关背景见 management-api-reference.md。 - new 轨:
ensure步骤先校验三份 TSDoc JSON(spec/reference/javascript/v2/supabase.json、spec/reference/server/v1/server.json、spec/reference/middleware/v1/middleware.json),缺失时调用make download.*目标下载;随后 apps/docs/scripts/build-reference-content.ts 从spec/reference/下的 TSDoc JSON 构建参考内容,输出落在features/docs/generated/**(这正是 Turbo 声明的codegen:referencesoutputs)。此外还有precodegen:references:new钩子会顺带生成 Dart 参考(codegen:references:dart)。
5.2 markdown 导出管线
- guides:apps/docs/internals/generate-guides-markdown.ts 遍历
content/guides/**/*.mdx,基于mdast/micromark(GFM + MDX 扩展)、gray-matterfrontmatter 解析,将大量自定义 MDX 组件(markdown-schema/下的Admonition、StepHike、TabPanel、PromptPanel、RegionsList等约 30 个映射)逐一降级为纯 Markdown,并借助 internal-links.ts 重写内部链接为带 base path 的绝对路径,产物为public/markdown/guides/**.md。 - reference:apps/docs/internals/generate-reference-markdown.ts 对
features/docs/generated/**的参考内容执行同类导出,产物为public/markdown/reference/**.md。 - 归档:apps/docs/internals/generate-gz-archive.ts 用
tar将public/markdown/全部条目排序后压缩为public/docs.tar.gz(源码注释强调排序条目 + portable 头以保证确定性输出),随站点静态资源在/docs/docs.tar.gz提供服务。
这一套“运行时页面”与“纯 Markdown 导出”并行输出的结构,即文档中提到的 LLM/Agent 消费面——Agent 直接读取 markdown 与 tar.gz 而非爬取 HTML,详见 llm-agent-surface.md 与 app-map.md。
6. postbuild:sitemap 与 R2 CDN 上传
postbuild 的第二步是仓库根目录共享的 scripts/upload-static-assets.sh,要点(以脚本源码为准):
- 触发条件:仅当
FORCE_ASSET_CDN=1或VERCEL_ENV=production时执行;FORCE_ASSET_CDN=-1(如 Studio 自托管场景)显式跳过。本地与 preview 部署均不上传。 - 桶选择:
NEXT_PUBLIC_ENVIRONMENT=staging时上传frontend-assets-staging,否则frontend-assets-prod(Cloudflare R2,通过ASSET_CDN_S3_ENDPOINT自定义 endpoint 走 S3 协议)。 - 路径设计:
s3://<bucket>/<SITE_NAME>/<VERCEL_GIT_COMMIT_SHA 前 12 位>/_next/static,按环境 + 应用 + 提交哈希隔离,旧版本资产留存一段时间以避免切换瞬间的“抖动”。 - 缓存策略:
--cache-control "public,max-age=604800,immutable"(7 天不可变缓存),同时同步.next/static与public/(public 上传是因为部分文件会被 CSS 相对路径引用,需走 CDN URL)。 - 目的(脚本头部注释):绕开 Vercel 出口流量费、规避 Cloudflare 代理的 Orange-to-Orange 超时问题、避免双重 TLS 终结带来的额外延迟。
7. 本地开发模式
在 apps/docs 下(或根目录 pnpm dev:docs):
pnpm dev # http://localhost:3001/docs
相关脚本(apps/docs/package.json):
"dev": "run-p --race dev:next dev:watch:troubleshooting",
"dev:next": "next dev --port 3001",
"dev:watch:troubleshooting": "node ./scripts/troubleshooting/watch.mjs",
"predev": "pnpm run codegen:graphql && pnpm run codegen:references && pnpm run codegen:examples",
"dev:secrets:pull": "AWS_PROFILE=supa-dev node ../../scripts/getSecrets.js -n local/docs"
predev先跑 GraphQL / reference codegen 与 examples 复制(不含 markdown 导出与归档,加快启动)。- 并发 watcher:
dev:watch:troubleshooting通过 apps/docs/scripts/troubleshooting/watch.mjs 同步 troubleshooting 内容(数据源为远程 schema,见supabase/migrations中troubleshooting_entries相关迁移)。 - 社区贡献者:在
.env中设置NEXT_PUBLIC_IS_PLATFORM=false。 - 内部环境:
pnpm run dev:secrets:pull从 AWS Secrets Manager 拉取(依赖 scripts/getSecrets.js 与 AWS profile)。 - 按需渲染:dev 模式下应用仅在被请求时构建路由,不做预渲染;preview 与 production 环境在构建期静态生成路由以保证访问速度。
8. 生产部署 vs CI
- 生产构建:文档站由 Vercel 部署,apps/docs/vercel.json 仅一行——
"buildCommand": "pnpm build"——即执行上文 docs 应用的完整 prebuild/build/postbuild 脚本链。 - GitHub Actions:主要运行
test:docs(Turbo 编排的 vitest,DOCS_SMOKE_URL环境变量可让 smoke 测试指向 preview 或 localhost 而非生产,见 turbo 中test任务的 env 声明)、lint、内容同步与 smoke 检查,并不在 CI 侧重复一套完整生产构建图。工作流面细节见 ci-and-lint.md。
9. 改动守则:为什么这套结构对变更敏感
官方参考文档 build-pipeline.md 给出的三条守则,均可在仓库中找到对应落点:
- 新增构建步骤:优先检查
prebuild/postbuild是否已有可复用的钩子(“复用管线,不要分叉管线”,见 adding-features.md);跨包产物应注册为 Turbo 任务并声明inputs/outputs。 - 新增环境变量:必须加入 apps/docs/turbo.jsonc 的
env列表,否则 Turbo 哈希不包含该变量,会命中陈旧缓存——这是该管线最常见的坑。 - 触碰 markdown 导出:先阅读 app-map.md 中“两条管线”一节——运行时页面与 markdown 导出共享数据源,但不共享代码路径,修改 MDX 组件时两条导出路径都需回归(
public/markdown/manifest.json与 tar.gz 内容都要验证)。
此外,若行为与预期不符,官方建议直接以 apps/docs/turbo.jsonc 与 apps/docs/package.json 的当前内容为准核对——本文所列脚本链(含 build:federated-content 这一步)即按当前仓库实际版本整理,与旧版参考文档中的简化描述可能存在差异。
10. 小结
Supabase 文档站的构建管线是一个“Turborepo 管依赖与缓存、pnpm 生命周期管前后置步骤”的教科书式 monorepo 案例:^build 保证工作区依赖先行,codegen:examples/codegen:references/build:markdown 以显式 inputs/outputs 接入 Turbo 缓存,prebuild 串起 GraphQL 类型、双轨参考文档生成、federated 内容拉取与 Markdown/tar.gz 导出,next build 完成页面构建,postbuild 收尾 sitemap 并按 VERCEL_ENV=production 条件将静态资产推上 Cloudflare R2。理解这条链路后,无论是新增文档生成步骤、接入新环境变量,还是排查“缓存命中但产物缺失”这类问题,都能定位到 apps/docs/turbo.jsonc、apps/docs/package.json 及 apps/docs/internals/ 下的对应脚本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00