关于 blume-migrate
blume-migrate 是将现有文档仓库迁移到 Blume 的 Agent 技能。它根据源框架选择对应映射指南,转换内容、导航和配置,并记录无法迁移或被舍弃的部分。
主要功能
- 01识别多种文档源框架
- 02按框架参考指南映射内容
- 03转换配置、导航与页面结构
- 04处理链接、资源路径和重定向
- 05报告舍弃或近似转换的内容
适用场景
- 01将 Mintlify 或 Docusaurus 文档迁移至 Blume
- 02把既有导航结构转换为 Blume 页面组织
- 03迁移中修正前置元数据与内部链接
- 04评估源站无法直接映射的功能
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ haydenbleasel/ blume --skill blume-migrate安装后在 Claude Code 中输入/blume-migrate确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/references/docsify.mddocus.mddocusaurus.mdfern.mdfumadocs.mdgitbook.mdgithub-wiki.mdjekyll.mdmdbook.mdmintlify.mdmkdocs.mdmonorepo.mdnextra.mdreadme.mdredocly.mdstarlight.mdvitepress.mdvuepress.mdscripts/docsify-codemod.mjsdocus-codemod.mjsfern-codemod.mjsgithub-wiki-codemod.mjsinclude-excerpts.mjsjekyll-codemod.mjsmintlify-codemod.mjsmkdocs-codemod.mjsoperation-routes.mjspin-heading-ids.mjsreadme-codemod.mjs详细介绍
迁移到 Blume
Blume 是一个基于 Astro/Vite、以 Markdown 为先的文档框架。将 Markdown/MDX 文件放入文件夹,即可获得导航、搜索、主题、Open Graph 图片和组件库,无需应用脚手架代码——框架就是模板。无需克隆起始模板;项目只需管理自己的内容和一个 blume.config.ts。
你的任务是将源文档仓库转换为符合 Blume 惯例的项目,而不是逐字逐项地照搬。阅读此文件,检测源框架,打开对应的 references/<framework>.md 查看具体映射,然后按以下流程迭代。报告所有删除或近似处理的内容。
在整个技能(包括 references/ 文件)中,<skill> 表示包含此 SKILL.md 的目录的绝对路径——请根据你读取此文件的位置解析(例如 node_modules/blume/skills/blume-migrate 或 .claude/skills/blume-migrate)。这是一个需要替换的占位符,切勿将其当作字面路径。
迁移理念
- 目标是符合 Blume 惯例,而非机械移植。 优先使用文件系统派生导航,而不是穷举式的显式
navigation.sidebar。优先使用:::指令,而不是 JSX 提示框。优先采用 Blume 默认值,而不是在配置中重复声明。 - 每个字段都有默认值;
{}是有效配置。 仅映射源中_明确声明_的内容。如果源使用框架默认值,就不要写入配置。 - 删除 Blume 没有对应功能的外观元素——并说明原因。 Navbar CTA、自定义主题、有条件的重定向以及无法映射的图标,应向用户报告,而不是悄悄丢弃或伪造。
- 转换,而非保留。 Blume 的页面 frontmatter 架构是严格的——未知键会导致构建错误。源中专有的 frontmatter 键必须映射为 Blume 键或移除(并报告),绝不能抱着“或许会通过校验”的想法将其留下。
迁移工作流
- 检测源框架并阅读其参考文件:
docs.json/mint.json→ Mintlify(references/mintlify.md)——导航最深入,且由配置声明。docusaurus.config.*→ Docusaurus(references/docusaurus.md)。meta.json+fumadocs-*依赖项(内容位于content/docs/下)→ Fumadocs(references/fumadocs.md)。_meta.{js,ts,json}+nextra依赖项 → Nextra(references/nextra.md)。- 调用
starlight({…})的astro.config.*→ Starlight(references/starlight.md)。 .vitepress/config.{js,ts,mjs,mts}(通常位于docs/.vitepress/)或vitepress依赖项 → VitePress(references/vitepress.md)。.vuepress/是 VuePress(见下一条)。.vuepress/config.{js,ts,mjs}(v1 还支持config.yml/config.toml;通常位于docs/.vuepress/)、vuepress.config.*或vuepress依赖项 → VuePress(references/vuepress.md)。它在 Markdown 层面基于references/vitepress.md,两者共用该层,因此两个文件都要读。docus依赖项(通常位于docs/package.json中,旁边有extends: ['docus']的nuxt.config.ts),或适用于 1.x 的@nuxt-themes/docus→ Docus(references/docus.md)。页面使用 MDC,即 Nuxt Content 的组件语法:::note…::不是 Blume 指令,Blume 会将其原样显示为文本(BLUME_MDC_SYNTAX会在对应行发出警告)。mkdocs.yml/mkdocs.yaml(也包括 Zensical 的zensical.toml和 ProperDocs 的properdocs.yml)→ MkDocs / Material for MkDocs(references/mkdocs.md):导航在配置中声明,使用 Python-Markdown 扩展语法(!!!提示框、===选项卡、--8<--片段)以及 Python 依赖项,因此通常没有package.json。先运行node <skill>/scripts/mkdocs-codemod.mjs --write <content-dir>,然后在首次构建后运行pin-heading-ids.mjs。fern/docs.yml(与fern/fern.config.json同目录)→ Fern(references/fern.md)。没有docs.yml的fern/文件夹只用于生成 SDK:文档位于别处。.gitbook.yaml/.gitbook.yml、gitbook-docs.yaml,或与.gitbook/文件夹同目录的SUMMARY.md→ GitBook(references/gitbook.md)。book.json+SUMMARY.md表示旧版 GitBook CLI 或 HonKit(使用同一参考文件中的专属章节);book.toml+src/SUMMARY.md表示 mdBook(references/mdbook.md)。book.toml(位于书籍根目录,代码仓库中通常在docs/、book/或guide/;章节位于其[book] src指定的目录中,并与SUMMARY.md同目录)→ mdBook(references/mdbook.md)。所有 URL 都以.html结尾,SUMMARY.md是完整导航,{{#include}}会从整个仓库提取带锚点的代码:先构建旧版书籍,因为它提供 URL 列表、旧 ID,以及唯一记录了 include 内容的资料。sidebars.yaml或@redocly/realm系列依赖项(通常与redocly.yaml同目录,页面中使用 Markdoc{% … %}标签)→ Redocly(references/redocly.md)。GitBook 页面也会使用{% … %}标签,因此先检查 GitBook 文件。若redocly.yaml只包含 lint/bundle 键(extends、rules、decorators、apis),则它是 Redocly CLI 配置,而非文档站点:保留它,并根据其他信号检测框架。docs/、reference/、recipes/或custom_pages/中的_order.yaml文件(ReadMe 的双向 Git 同步),或在页面旁运行readmeio/rdme的 CI 步骤,且页面带有category/parentDoc(或category.uri/parent.uri)前置元数据(由rdme上传)→ ReadMe(references/readme.md)。ReadMe 将设置、重定向、术语表和变量保存在控制面板中,而非仓库里;变更日志也只存在于main分支。- 设置了
window.$docsify的index.html(通常位于docs/,与_sidebar.md同目录),或package.json中有docsify-cli→ Docsify(references/docsify.md)。先运行node <skill>/scripts/docsify-codemod.mjs --write --snapshot .docsify-old --group-folders <docs-dir>,然后在首次构建后运行pin-heading-ids.mjs --old .docsify-old --map docsify-routes.json。其默认 URL 位于#之后(/#/page),任何服务端重定向都无法匹配;参考文件会添加客户端脚本。 _config.yml(根目录或docs/)→ Jekyll(references/jekyll.md);若_config.yml或Gemfile中有just-the-docs,则为 Just the Docs。先用项目本地的 bundle 构建旧站点:导航来自前置元数据(parent/nav_order),并根据旧构建的侧边栏重新生成,URL 以/或.html结尾。旁边有_toc.yml则表示 Jupyter Book,而非 Jekyll。先从 Jekyll 源目录运行node <skill>/scripts/jekyll-codemod.mjs --old <old build> --write .,然后在首次构建后运行pin-heading-ids.mjs --old <old build>。<repo>.wiki.git的克隆仓库(根目录有Home.md,通常还有_Sidebar.md和_Footer.md,链接使用[[Page Name]],且没有配置文件)→ GitHub Wiki(references/github-wiki.md)。此情况无法通过信号自动检测:源类型已知。其旧 URL 仍位于 github.com,无法进行重定向,因此参考文件会为旧 wiki 编写路由表和链接存根,而非redirects。先运行node <skill>/scripts/github-wiki-codemod.mjs routes --repo <owner>/<repo> --write,对wiki-routes.json分类,然后运行convert --repo <owner>/<repo> --write。- 其他情况 → 直接应用本文件中的思路;没有特定框架的参考文件,因此需要手动盘点。
- 还要注意宿主仓库,与源框架无关: pnpm/Turbo 工作区、非
docs/内容布局或 Vercel 部署都需要集成步骤(content.root范围限定、minimumReleaseAge、锁文件、vercel.json、Astro/Vite 补丁)——这些都在references/monorepo.md中。目标不是一个简单的单包文档文件夹时,务必阅读该文件。
- 在更改任何内容之前盘点仓库(正文只有
<meta http-equiv="refresh">的页面是重定向,不是页面:为其旧 URL 添加重定向,并在报告中说明):配置文件、内容树、导航定义、片段/局部文件/include、静态资源、OpenAPI/AsyncAPI 规范和 GraphQL 架构、重定向、i18n 区域设置、自定义组件和图标用法。记录哪些内容是显式声明的,哪些依赖默认值。旧站点意外发布的页面(.github/模板、点文件夹中的文件、规划笔记)不属于内容:不要纳入,不要添加重定向,并在报告中列出。 - 编写
blume.config.ts,使用blume中的defineConfig。只映射已声明的字段(见参考文件的映射表);其他所有内容都依赖默认值。最精简的配置为defineConfig({ title: "…" })。 - 重组内容。 将
blume.config.ts放在运行旧文档构建命令的package.json所在目录中;该目录可能是packages/docs这样的工作区包,而不是仓库根目录。选择content.root(默认为docs)——检查.md/.mdx文件实际所在位置,不要假定有docs/文件夹。 许多仓库将内容直接放在应用目录下(apps/docs/api/、.../getting-started/),没有docs/子文件夹;遇到这种情况时,将content.root设为该目录,并将content.include限定在实际内容文件夹,而不是让content.root: "."扫描所有内容(见references/monorepo.md§1)。使用数字前缀排序(01-intro.mdx),使用(group)/文件夹分组但不增加 URL 段,仅在文件系统排序不足以表达意图时添加meta.ts(defineMeta)。如果源项目已通过侧边文件声明每个文件夹的导航——例如 Fumadocs 的meta.json、Nextra 的_meta.*——就属于这种情况:将每个文件转换为meta.ts,保留其标题/图标/顺序/折叠设置,而不是丢弃它并寄希望于文件名能重现原有意图。文件系统推断只适用于没有声明文件夹级导航的文件夹,绝不是丢弃已有声明的理由。 如果某个分组只存在于源导航配置中,而其页面在磁盘上是平铺的,则将它转换为(group)/文件夹:这会添加侧边栏分组并保留所有 URL,无需重定向(将页面移入更深层文件夹会改变 URL,并破坏相对资源路径)。只有当源导航确实无法通过文件来表达时,才使用显式的navigation.sidebar。按选项卡将页面重组为文件夹会改变 URL——在过程中记录每个旧路径 → 新路径;第 5 步会将它们转换为redirects。 - 改写页面。 将前置元数据映射到 Blume 的严格架构;将提示框 JSX 转换为
:::指令——指令(以及数学、mermaid、软件包安装代码围栏)仅适用于 MDX,因此需要它们的.md页面必须改名为.mdx;重命名组件;将片段/局部文件转换为<include>语句或直接内联(Blume 不支持基于导入的 include:import Snippet from "…"加<Snippet />必须改为其中一种方式;如果源项目包含文件的一部分(mdBookANCHOR、VitePress#region、MkDocs 代码片段行范围)或内容根目录之外的文件,请将<skill>/scripts/include-excerpts.mjs复制到项目中,并在每次dev、build和validate前运行,而不是粘贴项目编译的代码:它会一次性报告所有缺失的锚点或区域,而且恢复源文件中的标记比使用容易悄然过时的行范围更好);修复资源路径;将内部链接改写为新路由(包括 OpenAPI/GraphQL 操作链接——参见 API 参考章节,其 slug 与大多数源项目不同——以及 OpenAPI 描述中的链接;Blume 会渲染并验证这些链接,但重定向from会掩盖过时链接;将写死旧站点自身域名的链接改为根相对路径);为第 4 步中每个移动过的路由添加redirects条目(采用哈希路由的源项目,例如 Docsify,还需要参考文件中的客户端重定向,因为redirects只匹配路径);将所有图标名称转换为 Lucide(Blume 只支持 Lucide,不支持 FontAwesome/Tabler)。删除正文中重复的 H1(title会渲染 H1;正文从##开始),并修复指向其锚点(#<h1-slug>)的链接,因为该锚点已不存在:删除片段。blume validate --strict还会报告旧站点从未检查过的片段;逐一修复或删除,并在报告中说明。没有前置元数据title的页面会使用开头的# H1作为标题,并且只显示一次,因此可以保留;不过将其移入title仍是惯用写法。如果源项目有手动维护的变更日志,且仓库在 GitHub 上开源,请提议用githubReleases()数据源替换它(见下方“变更日志”),而不是移植已有条目。对于 Mintlify,先运行随附的 codemod——node <skill>/scripts/mintlify-codemod.mjs --write <content-dir>会以确定性方式重新映射图标并删除/重命名不受支持的前置元数据键,同时报告其他需要手动处理的内容(未知图标、OpenAPI 占位标记;见references/mintlify.md)。对于 Docus,先运行随附的 codemod:node <skill>/scripts/docus-codemod.mjs --write content会像 remark-mdc 一样配对 MDC 块,转换 Nuxt UI 文本组件、代码围栏标签、图标、前置元数据和.navigation.yml文件,让 MDX 文本安全,并列出所有REVIEW项(自定义组件和落地页组件保持原样;请根据线上站点检查它们,不要只依赖本地构建;见references/docus.md)。对于 VitePress,先运行随附的 codemod:node <skill>/scripts/vitepress-codemod.mjs --write <srcDir>会转换容器(按照 markdown-it 的配对方式配对)、代码组、提示框、围栏标签、<<<导入和 include、.html链接、徽章和 H1 标题,并列出所有待处理的REVIEW项(见references/vitepress.md)。转换任何源项目组件之前,先检查其构建输出:主题从未注册的组件在源站点中渲染为空内容(``),因此转换后会显示读者从未见过的内容;请在报告中说明。对于 mdBook,首先转换所有{{#…}}辅助项:带锚点、行范围以及位于根目录外的代码会成为生成的摘录;单行文件会成为variables;然后去掉隐藏的#行和rust,…围栏属性,并转换 admonish 块(见references/mdbook.md)。对于 Jekyll,转换所有 Kramdown IAL({: .note })和 Liquid 标签:在.md中,它们会作为纯文本发布(Liquid 标签会发出BLUME_TEMPLATE_TAG警告,IAL 会发出BLUME_MD_ATTRIBUTE_LIST警告);在.mdx中则会导致构建失败(blume check会将每个 IAL 的行标记为BLUME_MDX_ATTRIBUTE_LIST,将 Liquid 标签的行标记为BLUME_MDX_SYNTAX;如果包含的局部文件中有 Liquid 标签,则标记该标签在局部文件中的行)。先运行随附的 codemod:它会转换提示框 IAL、页内目录、H1、前置元数据、Markdown include、Liquid 链接和变量;将 Just the Docs 侧边栏重建为文件夹、slug和meta.ts;并将重定向和变量写入jekyll-migration.json(见references/jekyll.md)。对于 GitHub Wiki,运行随附的 codemod(先routes,再convert):它会生成路由表(每个页面名称对应一个平铺路由),并转换代码之外的所有[[…]]链接,否则 Blume 会将它们显示为纯文本(BLUME_WIKILINK_UNSUPPORTED只会对指向页面名称或含有|的链接发出警告;Markdown 页面将链接文本写在前面:[[text|Page]])。对于 Redocly,转换所有 Markdoc{% … %}标签:Blume 不会解析它们,因此未转换的标签在.md中会作为纯文本发布(BLUME_TEMPLATE_TAG会在对应行发出警告),并导致.mdx构建失败。对于 VuePress,先将所有README.md重命名为index,然后运行 VitePress codemod(VuePress 内容的限制见references/vuepress.md),再搜索带连字符的主题标签(<code-group>、<code-block>、<router-link>):它们会作为未知 HTML 元素通过.mdx,且不会产生诊断。对于 MkDocs,在转换前为每个页面决定使用.md还是.mdx:MDX 不接受 HTML 注释、<https://…>自动链接、未闭合的空元素标签,以及 Python-Markdown 可接受的{…}属性列表(blume check会在对应行标记:MDX 无法解析的内容为BLUME_MDX_SYNTAX,后两项分别为BLUME_MDX_UNCLOSED_ELEMENT和BLUME_MDX_ATTRIBUTE_LIST);代码片段路径相对于片段的base_path(默认值为运行 MkDocs 的位置)解析,而不是相对于页面(见references/mkdocs.md)。对于 Fern,URL 来自docs.yml,而非文件路径:移动任何内容之前,先运行node <skill>/scripts/fern-codemod.mjs --sitemap old-urls.txt(先试运行,再加上--write);首次构建后运行其endpoints步骤,将operation-routes.mjs的输出与 Fern 的旧 API URL 合并,并写入fern-redirects.json;使用fern export将 Fern Definition 导出为 OpenAPI(见references/fern.md)。对于 GitBook,先将每个页面放在其旧 URL 对应的位置(GitBook 的 URL 取决于SUMMARY.md,而非文件树),然后转换所有{% … %}标签:在.md中,未转换的标签会作为纯文本发布(BLUME_TEMPLATE_TAG会发出警告);在.mdx中则会导致构建失败。对于 ReadMe,先运行随附的 codemod:node <skill>/scripts/readme-codemod.mjs --write --site <hub origin> --glossary <saved hub page> .会将每个页面保留在原有的平铺 URL 上,转换提示框、doc:/ref:链接(blume validate会将任何遗留链接标记为BLUME_UNSUPPORTED_LINK_SCHEME)、ReadMe 组件和前置元数据,写入meta.ts、覆盖项和重定向,并列出需要人工处理的内容;首次构建后,其--routes步骤会添加端点重定向和逐标签排序(见references/readme.md)。粘贴的控件中的 JSX 事件处理程序(onClick={…})可以顺利构建,但在静态页面上不会运行:BLUME_MDX_EVENT_HANDLER会逐一标记它们,因此应将控件移入 island(islands/),或将其删除并在报告中说明。 - 采用
package.json。 将dev/build/start改为blume dev/blume build/blume preview,移除旧框架依赖项,并添加blume。只有配置文件而无清单的源项目(例如仅有docs.json的 Mintlify)需要新建一个清单。**在 pnpm 工作区中:如果pnpm-workspace.yaml/.npmrc设置了minimumReleaseAge,只将blume添加到minimumReleaseAgeExclude(不要关闭安全限制),以便安装刚发布的版本。Yarn 的npmMinimalAgeGate也会以同样方式拒绝新发布的blume:将其添加到npmPreapprovedPackages。务必在同一项更改中使用仓库自身的包管理器重新生成锁文件(从工作区根目录运行pnpm install、yarn install、npm install或bun install;检查packageManager和锁文件名称),并与package.json一同提交——CI 和托管平台会基于冻结锁文件进行安装,因此过期的锁文件会在构建启动前导致失败。在内容根目录创建第一个新文件(如meta.ts或局部文件)后,运行git check-ignore -v <content root>/meta.ts:如果仓库的.gitignore规则涵盖内容根目录,所有新文件都会在git add时被排除,而旧页面仍处于跟踪状态,因此需要修正规则,或说明每个新文件都需要使用git add -f。Blume 需要 Node 22.19 或更高版本:在同一项更改中更新.nvmrc/.node-version、engines.node和所有 CInode-version。pnpm 11 及更高版本只有在pnpm-workspace.yaml批准后才会运行依赖项的构建脚本:添加allowBuilds: { esbuild: true }(以及blume init写入的"@scarf/scarf": false),除非工作区已列出esbuild;并保留其余条目。如果文档包有自己的.gitignore,就在其中添加.blume/、dist/和.env.local;否则添加到根目录的.gitignore(blume init的做法;vercel()构建会自行添加.vercel/),并在将资源移入public/之前删除任何忽略public/的行。blume build会清空<project>/dist:**如果宿主仓库已将dist/用于其他用途(Python wheel、库构建),请将 Blume 项目放在单独文件夹中(docs/)。如果仓库使用 Prettier,请将其*.md覆盖规则(.prettierrc、.editorconfig)扩展到*.mdx,并确保{/* */}注释位于单行内。重命名或移动页面后,在整个仓库中搜索其旧路径(发布配置、CI、bot 和忽略文件)。还要检查 Blume 项目目录中的任何tsconfig.json:Blume 构建会读取该文件,因此如果某个extends仅在根工作区安装后才能解析,或include指向旧框架的文件夹,blume build就会失败。先安装工作区并修复路径;如果该文件仅供旧框架使用,则将其删除(Nuxt 的tsconfig.json如果extends或references./.nuxt/*,会触发BLUME_TSCONFIG_EXTENDS;postinstall: nuxt prepare脚本也应随 Nuxt 依赖项一同移除)。**如果仓库使用(或用户希望使用)Ultracite 进行格式化:**其 oxfmt 格式化程序会破坏你刚写的:::指令,除非你加入随附的assets/[email protected],并在patchedDependencies中注册它——参见references/monorepo.md§6。另见references/monorepo.md§2–3。 - 接入宿主仓库并部署(较复杂的仓库)。 宿主平台保存在控制面板中的设置(Netlify 的发布目录、构建命令、
NODE_VERSION)无法提交到仓库:请在总结中列出。在.gitignore中只替换文档自身的条目,并保留其他包会用到的条目。对于使用 Vercel 的 monorepo,提供考虑根目录的安装/构建方案和apps/docs/vercel.json:静态构建时使用outputDirectory: "dist";使用deployment: vercel()时不要设置outputDirectory,因为其构建会写入.vercel/output(将.vercel/output/**添加到 Turborepo 输出中)。告知用户无法提交到仓库的设置(Vercel Root Directory、Node 22,以及AI_GATEWAY_API_KEY等服务器密钥)。如果工作区固定了 Vite 版本,且blume build在 Astro/Vite 内部崩溃,则应用 pnpm-patch 解决方案。所有可直接复制粘贴的内容都在references/monorepo.md§4–5。 - 验证。 运行
blume build(前置元数据架构、重复路由、配置——默认情况下,任何错误诊断都会导致构建失败;绝不要传入--no-strict,该选项会强行构建,并悄然丢弃前置元数据无效或无法解析的.mdx页面)以及blume validate --strict(内部链接、标题锚点、资源、重定向目标——链接检查器位于validate中,而非build),修复诊断,然后运行blume dev进行视觉检查。最后撰写总结,说明迁移、丢弃和近似处理的内容,以及所做的每项仓库特定修改(pnpm-workspace、vercel.json、配置 glob),并说明原因,以及留给用户手动完成的步骤(Astro 补丁、Vercel 控制面板设置)。
Blume 的思维模型
对大多数来源而言,尤其是 Mintlify,最大的转变在于:导航由文件系统生成,而不是在配置中声明。
导航即文件树
- 文件夹变为分组,文件变为页面。 页面的侧边栏标签为其 frontmatter
title;分组的标签为人性化处理后的文件夹名称。 - 排序按优先级从高到低解析: 显式的
navigation.sidebar(替换整个树)→ 文件夹的meta.tspages数组 → 页面的 frontmattersidebar.order→ 文件系统(index优先,然后按数字文件名前缀排序,例如01-,最后按字母顺序)。 meta.ts用于细化单个文件夹(defineMeta({ title, icon, order, collapsed, pages, display, directory }))。pages数组按 slug 列出子项(去掉数字前缀、括号和扩展名;如果某个条目没有对应项,会发出BLUME_META_UNKNOWN_PAGE警告并提示可能的 slug,因此请填写正确的 slug);未列出的子项会按其自身的sidebar.order排序,然后按文件系统顺序排列(index仍排在最前)——因此不完整的pages列表也安全,但如果源项目声明了完整顺序,就应列出所有子项。directory会沿树向下继承:对于不应显示卡片列表的嵌套文件夹,设置directory: "none"。在flat分组中,pages无法将散落页面与子分组交错排列,在任何模式下也无法在侧边栏顶层交错排列:如果源项目存在交错结构(mdBook、MkDocsnavigation.sections),请使用display: "group",这样顺序会保留在分组内部,并说明顶层有哪些内容发生了移动。- 侧边栏渲染模式:全局默认值,支持按文件夹覆盖。
blume.config.ts中的navigation.sidebar.display可以是"flat"(默认)、"group"(可折叠)或"page"(进入子面板),并一次性设置所有分组的模式。文件夹可以覆盖自身分组的模式:在其meta.ts中设置display,或者——当文件夹有index页面时,可在该索引页面的 frontmatter 中使用sidebar.display简写。优先级:索引页面 frontmatter →meta.ts→ 全局配置 →flat;覆盖只应用于对应分组(嵌套子分组按自身的优先级链解析)。因此,源项目每个类别的折叠/进入子面板模式会迁移到各自文件夹——只有当整个侧边栏都要改变时,才使用全局模式。在显式的navigation.sidebar下,配置项自身的display字段是唯一的分组级控制方式(此时会忽略 frontmatter/metadisplay,并发出BLUME_SIDEBAR_DISPLAY_IGNORED警告)。 - 显式的
navigation.sidebar会完全取代文件系统生成导航。 仅当文件无法表达所需导航结构时才使用它。其条目可以是页面路由字符串、分组({ label, items })或链接({ label, href })。 - 配置声明的嵌套结构在磁盘上没有对应结构——应将其实体化,否则会被静默扁平化。 当源项目(Mintlify
groups、Nextra_meta、Docusaurus 侧边栏等)声明了嵌套分组时,其页面通常平铺在同一个文件夹中,分组关系仅存在于配置里。基于文件系统生成的导航会看到平铺的文件夹,并丢弃内部嵌套分组。要保留嵌套结构,首先使用(group)/文件夹:将页面移入<folder>/(<group>)/,并使用meta.ts设置标签、顺序和collapsed。带括号的文件夹会添加侧边栏分组,但不会增加 URL 路径段,因此无需更改路由或设置重定向;其他文件夹中的页面也可以通过保留旧路由的slug加入该分组。若分组的落地页是同级文件,也可以保留所有 URL:将落地页移至(group)/index.md,并设置slug: <its old route>,这样带 slug 的索引页就会成为分组链接。将页面移入更深一层的文件夹会破坏相对链接和资源路径(./img.png、../other.md),因此需要重新设置它们的路径。(Docsify 是个例外:无论路径如何,其旧#/URL 都会由脚本重新映射,因此它的迁移方案会将页面移入实际文件夹。)只有在上述方式无法表达所需结构时,才将页面移入真实子文件夹(URL 会改变:添加redirects),或者在显式的navigation.sidebar中声明分组,这样无需移动文件即可嵌套现有路由。清点资源时,应递归遍历配置中的pages/导航数组,并记录配置嵌套深度超过磁盘目录深度的位置;这个差距正是会丢失的部分。
标签页和选择器
navigation.tabs({ label, path, icon? })会渲染在页眉顶部的分区,并按路由限定侧边栏范围——标签页path下的文件夹会成为该分区,因此除了标签页本身无需额外配置;内容应按每个标签页一个文件夹来组织。源项目的顶层标签页(Mintlifynavigation.tabs、顶层产品/分区切换器)映射为这些页眉标签页——请保留为标签页;不要将其扁平化到单个全局navigation.sidebar中。 Blume 会根据URL 前缀选择当前标签页(为路由添加前缀的最长标签页path),因此标签页中的每个页面都必须位于该标签页的单个path下;源项目中混有任意路由的标签页无法直接迁移——要么将其页面移到同一个前缀下(路由变更 → 添加redirects),要么接受最接近的结构,并在报告中说明选择(详情见references/mintlify.md)。筛选会双向生效:对于位于标签页path下方的路由,侧边栏只显示该标签页的文件夹(当前路由位于标签页下方时,该标签页也会高亮);对于根路由或未归属标签页的路由(或path为/的标签页),标签页文件夹会隐藏,侧边栏只显示不属于任何标签页的独立页面(完整树结构会作为后备,因此侧边栏不会空白)。对迁移的影响:添加标签页后,首页侧边栏会自动隐藏分区内容——这是预期行为,并非页面丢失;不要手动为此添加排除项。navigation.selectors({ kind, label, items: [{ label, path, icon?, description?, tag? }] },kind=dropdown/product/version/language)通过根据当前路由确定选项的页眉下拉菜单,对整个站点进行划分(产品、版本)。navigation.actions([{ label, href }])会在页眉中、图标按钮左侧放置普通链接;navigation.cta({ label, href },单数)是页眉唯一的实心行动号召按钮。源项目的页眉栏应配置在此处——“Log in”/“Status”链接放入actions,“Sign up”/“Get started”按钮放入cta。http(s)类型的href会在新标签页中打开;内部路由会在构建时根据页面进行验证,因此由同一主机上的其他应用提供服务的路由必须写成绝对 URL。两者在手机上(sm以下)都会隐藏,但没有导航切换按钮的页面上的cta除外;文档页面中必须在手机上保留的链接应放入featured。navigation.featured({ label, href, icon? })会将链接固定在侧边栏顶部,位于所有分区上方——适用于博客、更新日志或支持页面等需要始终能一键访问的页面。这是不受标签页范围限制的例外:与生成的树状结构不同,精选链接会在所有路由和断点下显示。href可指向任意位置——外部 URL 会在新标签页中打开,内部路由(/contact)会在构建时根据页面进行验证。与其他位置一样,icon可以是 Lucide 名称(或图片路径/URL/内联 SVG)。源项目中始终可见的页眉/实用链接(Mintlify 锚点、博客/联系链接)应配置在此处——参见references/mintlify.md。
路由和路径
- 路由是相对于
content.root的内容路径,其中会去除数字前缀(01-intro.mdx→/intro;版本号或以日期YYYY-MM-DD、D-M-YYYY/M-D-YYYY或YYYY-MM开头的名称会保留完整名称:12-05-2022.mdx→/12-05-2022;旧站点中其他以数字开头但保留完整名称的名称,应通过slug固定),且**(group)/文件夹不会增加路径段**。index文件映射到其文件夹的路由。Frontmatter 中的slug会覆盖生成的路由。README.md是路径为/README的普通页面,不是其文件夹的首页:请将其重命名为index.md(Docsify、MkDocs、GitBook 和 VuePress 源项目都使用 README 作为首页)。
blume.config.ts 结构
defineConfig({...})——所有字段均为可选,并且都有默认值:
- 站点:
title、description、logo(SVG 字符串,或{ image: string | { light, dark, alt }, text, href })、banner(字符串,或{ content, link: { href, text }, dismissible, id }——没有颜色/类型)。Logo 会显示在页眉中title旁边,因此,字标 Logo 会重复显示品牌名(“Acme Acme”)——设置text: ""可只显示标志。**优先使用字符串形式,而不是{ light, dark }:**如果本地有 Logo SVG,且它是单色的(纯黑或纯白),请将其中的fill/stroke改为currentColor,并使用logo: "/logo.svg"——这样它会继承主题的文本颜色,自动适配浅色/深色模式,无需准备单独的浅色/深色文件。 theme:accent(两种模式共用的颜色字符串,或按模式设置的{ light, dark })、action(颜色)、mode(light/dark/system)、radius、fonts({ body, display, mono }——每项可以是精选 Google 字体的 slug、适用于任意 Google/Fontsource/Bunny/Fontshare 字体系列的{ name, provider?, weights? }对象,或用于本地字体文件的{ name, variants: [{ src, weight?, style? }] })、background和backgroundImage(每项可以是字符串,或按模式设置的{ light, dark })。旧版的accentDark/backgroundDark/backgroundImageDark字段已合并到这些按模式设置的对象中——单个字符串仍会应用于两种模式,因此只有在两种模式不同的情况下才使用{ light, dark }。每种颜色都必须是 CSS 颜色或强调色预设(来源中的纯"3b82f6"需要加上#),否则会显示BLUME_THEME_COLOR_INVALID警告,浏览器也会忽略该值。没有theme.strict,也没有theme.css配置字段——自定义 CSS 应放在项目根目录的theme.css文件中(会自动读取),来源中的“严格外观”标志也应删除。content:root(默认值为"docs",相对于运行blume的项目目录)、include/exclude(相对于content.root的 glob 数组;默认值为["**/*.{md,mdx}"]/["**/_*", "**/.*"]——设置exclude会在这些默认值基础上追加,因此局部文件和点目录不会发布;若要有意发布以下划线_开头的文件或点文件,请用!列出默认规则:exclude: ["!**/_*"])、sources(从blume/sources导入的适配器数组:filesystem({ root, include, exclude })、obsidian({ vault })、githubReleases({ owner, repo })、notion({ database })、sanity({ projectId, dataset, query })、contentful({ space, contentType })、payload({ url, collection })、strapi({ url, contentType })、mdxRemote({ github })、custom(source);所有接受选项对象的工厂还接受prefix和pollInterval,而custom(source)接受一个自行设置prefix的ContentSource实例。1.x 的{ type: "…" }对象已移除——将type改为工厂调用,并将其他字段作为选项传入;但{ type: "custom", source }除外,它应改为custom(source),并将实例作为唯一参数。root/include/exclude是单个filesystem()的简写,不能与sources并列使用——请将它们移到filesystem()条目中。OpenAPI/AsyncAPI/GraphQL 不属于这些适配器;它们是顶层reference列表中的适配器)、pages(自定义.astro目录)、defaultType。如果文档直接位于项目目录下(没有docs/子目录),请将root设置为该目录,并将include限定到实际的内容文件夹,而不是扫描所有内容——参见references/monorepo.md§1。basePath(顶层):整个站点的挂载路径(例如"/docs"),会添加到每条路由前面,但在侧边栏中不可见(没有包装分组)。对于所有文档都通过某个前缀提供服务的来源,这是正确的配置项(Docusaurus 的routeBasePath、Fumadocs 的/docsbaseUrl)——它与会添加导航分组的每个来源的prefix,以及用于主机子目录的deployment.base不同。navigation:tabs、selectors、actions和cta(页眉链接和一个填充样式按钮)、featured(固定在每条路由侧边栏上方的链接)、sidebar({ display, items }——display是上述全局渲染模式;items是显式树结构)、repo(true/false,或者在文档仓库为私有且必须保持github未设置时,用于页脚 GitHub 链接的绝对 GitHub URL)。除非别无选择,否则避免显式设置navigation.sidebar——尽量使用根据文件系统生成的侧边栏。只有当文件树大致符合预期的侧边栏布局时,它才有效,因此应先调整文件夹结构;只有当文件无法表达所需结构时,才使用sidebar.items(参见上文“配置声明的嵌套”)。search——从blume/search导入的适配器:orama()(默认值,因此使用它时可完全省略search)、flexsearch()、pagefind()、algolia({ appId, apiKey, indexName })、oramaCloud({ endpoint, apiKey, indexId? })、typesense({ host, collection, apiKey })、mixedbread({ storeId }),或者用false禁用。可直接传入适配器(search: algolia({…})),也可以在同时设置精选链接或索引选项时使用search: { provider, popular, indexing }。Blume 1.x 的search.provider字符串和search.algolia/oramaCloud/typesense/mixedbread凭据块已移除——如果来源配置(或旧版blume.config.ts)中有provider: "algolia", algolia: { appId, indexName, searchApiKey },请改写为search: algolia({ appId, indexName, apiKey: searchApiKey })(现在所有接受搜索专用密钥的适配器都使用apiKey;管理密钥应保留在环境变量中)。ai(助手、“在聊天中打开”——ai.assistant.provider接受从blume/ai导入的适配器描述符:gateway({ model })(默认值为openai/gpt-5.5)、openai({ model })(或者,适用于任何兼容 OpenAI 的端点,使用openai({ baseUrl, name, model, apiKeyEnv }))、anthropic({ model })、gemini({ model })、grok({ model })、openrouter({ model, reasoning })、llmgateway({ model })或inkeep({ model })。每个适配器都接受model、apiKeyEnv、headers和原样透传的providerOptions(headers的值会原样写入生成的路由源码,因此只能用于非机密的静态标头——Bearer 令牌或其他任何凭据都应放在apiKeyEnv指定的环境变量中,绝不可放在headers中)。除inkeep()外,每个适配器也接受reasoning:Inkeep 使用自己的回答流程,因此inkeep({ reasoning })会因包含无法识别的键而导致验证失败——请从来源中删除此处的reasoning设置,并在报告中说明。ai.assistant上没有扁平的provider/model/apiKeyEnv/baseUrl/headers/reasoning字段——以这种方式配置 AI 助手的来源(包括 Blume < 2.0)都应映射为一次适配器调用。enabled、instructions、retrieval、suggestions、cors和endpoint属于ai.assistant本身;没有ai.ask——Blume 2.0.0 及更早版本使用此名称,因此旧版blume.config.ts应将整个配置块移到ai.assistant)、agents(llms.txt、JSON API、MCP 服务、已发布的技能、发现清单、robots 内容信号)、reference(从blume/reference导入的适配器列表——openapi({ spec | sources, route, … })、asyncapi({ … })、graphql({ spec, endpoint, … })——不要使用 1.x 的openapi/asyncapi/graphql配置块)、redirects、seo、markdown、analytics(从blume/analytics导入的适配器列表——每个提供方对应一个工厂(posthog({ key, host })、googleAnalytics({ id })、plausible({ domain, host })、mixpanel({ token, region })、segment({ key })等;文档页面列出了全部适配器)、vercel(),以及用于没有专用适配器的服务的script({ src | content, strategy, attributes })——不要使用以提供方为键的对象)、deployment、i18n、toc、lastModified、github({ owner, repo, branch?, dir?, host?, api? }——如果来源的编辑 URL 位于github.com以外的 GitHub Enterprise 域名上,请设置host)。analytics——来自blume/analytics的适配器,包括用于来源自定义 head 脚本的script()。如果来源的 head 脚本中包含已知分析代码片段(gtag 加载器与gtag('config')配对、GTM、Plausible 等),应使用对应提供方的工厂,而不是script()。适配器只会在生产构建中加载,并会在配置了consent时等待用户同意,因此,页面运行所需的脚本(支持组件、小部件、表单嵌入)应放到PageFooter布局插槽组件中(defineComponents)。deployment是从blume/deploy导入的主机适配器——vercel()、netlify()、cloudflare()或node()——也可以是用于任意主机静态构建的普通{ site, base }(默认是静态构建)。指定主机适配器会切换到该主机上的服务器输出(若要在该主机上保持静态构建,请传入output: "static");可命名的选项为site、base和output,其他选项则会原样转发给底层的@astrojs/*适配器。没有deployment.adapter字符串,也没有deployment.output字段:需要服务器端渲染的来源(助手、MCP 服务、Mixedbread 搜索、API Playground 代理)应导入import { vercel } from "blume/deploy",并使用deployment: vercel();静态来源不需要配置——除非其文档通过子路径提供服务,此时应使用deployment: { base: "/docs" }(关于何时添加site,请参见下文)。请注意,cloudflare和vercel也从blume/analytics导出——如果配置同时使用两者,请为其中一个设置别名(import { cloudflare as cloudflareDeploy } from "blume/deploy")。- **除非目标主机是 Blume 能自动检测的主机,否则请将来源的站点 URL 保留为
deployment.site。**Blume 只会从 Vercel 和 Netlify 的平台构建环境中填入site(使用稳定的生产域名),并会在blume dev期间使用开发服务器的localhostURL;在这两个主机上,请不要设置site,让自动检测来确定已部署的 URL。其他所有情况——Cloudflare Pages(它只提供每次部署对应的CF_PAGES_URL)、GitHub Pages、S3 或其他静态主机、自定义 CDN、Cloudflare Workers、node()服务器——都无法自动检测;如果未设置site,站点地图、OG 图片、RSS 订阅、AI 目录和绝对规范 URL 都会被静默省略。因此,如果来源配置中有url/site字段,而目标不是上述两个主机之一,请保留该值:静态构建使用deployment: { site: "https://…" },主机适配器则使用其site选项(node({ site }))。如果无法判断站点将部署到何处,请保留该值,并在报告中说明。 - **网站图标通过文件名约定配置,不使用配置项。**将
icon.{svg,png,ico}或favicon.{svg,png,ico}(以及apple-icon.png或apple-touch-icon.png)放在项目根目录或public/中——Blume 会自动检测。没有favicon配置字段。来源中以{ light, dark }给出的网站图标应映射为一对文件:将浅色文件复制为约定名称(例如public/icon.png),并将深色文件作为其-dark同级文件——使用相同目录和扩展名,并在扩展名前添加-dark(public/icon-dark.png)。如果两个文件格式不同,请转换其中一个,使其扩展名一致;只有与已解析图标完全匹配的同级文件才会被读取。 该架构从blume/schema导出;完整字段参考位于已安装的blume包的docs/configuration/目录中(如何找到该目录,请参阅下文的“完整文档”)。
图标只能使用 Lucide
Blume 会在所有接受图标的位置解析 不带前缀的 kebab-case Lucide 名称,包括 frontmatter icon、sidebar.icon、meta.ts icon、navigation.tabs/selectors 图标,以及 Card/Step/Icon/等组件的 props。它不支持 FontAwesome 或 Tabler,也没有 iconType prop。名称必须采用 kebab-case(例如 book-open,而非 BookOpen)——PascalCase 的 React 组件名称(在 Fumadocs/lucide-react 源码中很常见)无法解析,因而不会渲染。从使用其他图标集(Mintlify 和 Fern 使用 FontAwesome)的源迁移时,请将每个名称映射到最接近的 Lucide 图标;如果找不到对应图标,请移除该图标并报告。写入图标名称前,请在 lucide.dev/icons 确认其存在。
页面 frontmatter(严格模式——未知键会导致构建失败)
---
title: Install # renders as the page H1 — remove any duplicate H1 in the body
description: Install Blume and scaffold your first project.
type: doc # doc (default); blog and changelog drive feeds, other values only mean something under content.types
sidebar:
label: Install # overrides title in the sidebar
order: 2
icon: download
badge: New
hidden: false
seo:
title: …
description: …
image: /og/install.png
canonical: https://…
noindex: false
search:
exclude: false
tags: [api]
slug: install # override the generated route
draft: false
lastModified: 2026-06-20 # pin the "last updated" date
---
另外也有效的有:date/authors(博客/更新日志信息流)、changelog(更新日志元数据)、deprecated,以及顶层简写 hidden(对应 sidebar.hidden)和 noindex(对应 seo.noindex)。
编写功能(在 .mdx 中无需导入)
- 丰富功能仅适用于 MDX。 指令、
package-install、mermaid 和数学公式都接入了 MDX 处理器;在普通.md文件中,:::note会保持为字面文本——构建仍会成功,只会在对应行显示BLUME_MD_DIRECTIVE警告。迁移时,将任何使用(或应使用)这些功能的.md文件重命名为.mdx。 MDX 对文本的要求比 Markdown 更严格:任何后面紧跟非空格字符、但并未开启标签的<(<7、<50 ms、<= 5、List<T>),以及单独出现的{或}都会导致构建失败,因此在重命名的页面中要将它们转义(使用<或\<,以及\{)。使用lastModified: "git"时,迁移所编辑的每个页面都会显示迁移提交的日期;如果网站显示日期,请在报告中说明这一点。仅重命名或移动的页面会保留原日期(Blume 会跟踪重命名)。这对 Docusaurus/Starlight 源文件影响最大,因为它们的.md内容中到处都是:::提示框。普通 Markdown(标题、表格、带标题/高亮的围栏代码)在.md中没有问题。 - 使用指令编写提示框:
:::note、:::tip、:::warning、:::danger、:::info、:::success,可选标题放在方括号中,并会渲染其中的行内 Markdown::::warning[Heads up]。别名:caution→warning、error→danger、important→note、warn→warning。需要自定义图标或色调的提示框应使用<Callout type icon color title>(icon使用 Lucide 图标,color使用任意 CSS 颜色);指令无法设置这些属性。**markdown-it 源(VitePress、VuePress、Docusaurus v2)使用::: tip Title:冒号后有空格时,它永远不会被识别为指令,即使在.mdx中也是如此,而会渲染为字面文本(.mdx中只有在提示框名称处才会显示BLUME_DIRECTIVE_SPACED_NAME警告);在已打开的:::容器内,它会关闭该容器并丢弃其文本(BLUME_DIRECTIVE_CLOSING_TEXT);名称后的标题也会被丢弃(:::tip Title,BLUME_DIRECTIVE_OPENING_TEXT)。**请规范为:::tip[Title]。MDC 的双冒号块(Docus,以及任何 Nuxt Content 网站中的::note…::)对 Blume 来说是叶级指令,会作为文本输出(BLUME_MDC_SYNTAX);MDC 的[text]{.class}跨度会导致 MDX 构建失败(BLUME_MDX_ATTRIBUTE_LIST会指出对应行)。GitHub 提示(> [!NOTE],GitHub 的五种提示,以及作为 danger 处理的[!CAUTION])在.mdx中会渲染为提示框;标记后的文本会作为标题。在.md中,它们会保留为显示标记的引用块(BLUME_MD_GITHUB_ALERT),因此请将这些页面重命名为.mdx。 - 无需导入的 MDX 组件:
Callout、Card/CardGroup、Columns/Column、Steps/Step、Tabs/Tab、Accordion/AccordionItem、Expandable、FileTree、Tree/Tree.Folder/Tree.File、CodeGroup、Frame、Panel、Tooltip、Tile、Badge、Icon、TypeTable/AutoTypeTable、Color、YouTube、Visibility、GithubInfo、Component、CodeBlock、Diff、Prompt、Math、Changelog,以及手写的端点字段ParamField/ResponseField/RequestExample/ResponseExample(遵循 Mintlify 的语义:<ParamField path|query|header|body="name">根据字段位置命名字段;参见docs/references/api-pages.mdx)。(不包含在内——请转换掉:<Warning>/<Note>/<Info>/<Tip>/<Check>/<Error>→ 对应的:::指令,以及RequestField。像 Fern 和 Mintlify 那样,不带code属性、用作围栏包裹器的<CodeBlock>会按原样渲染围栏,但会忽略自身的title:请移除包裹器,并将标题写入围栏元数据中。用于显示子内容、但未提供子内容且使用了其他工具属性的组件(<Badge type="tip" text="beta" />)会渲染为空,并显示BLUME_UNKNOWN_PROP警告:请将内容放在标签之间。目标写法请参阅参考文件。) - 围栏代码增强功能:
```package-install→ 包管理器选项卡;```mermaid→ 渲染的图表;代码块标题(```ts server.ts)、行号(lineNumbers)和高亮({1,4-5}、// [!code ++])。 - 容易导致迁移问题的围栏和标题: Shiki 不识别的围栏语言会以纯文本形式渲染,并显示
BLUME_UNKNOWN_CODE_LANGUAGE警告(大小写无关:JSON和json的高亮效果相同):请修正语言标识符,或用text表示纯文本。用逗号将语言和属性连在一起的写法(```rust,ignore)也属于此类。若用空格分隔,rustdoc 和 mdBook 的关键字(ignore、no_run、should_panic、edition2021、editable等)会显示BLUME_CODE_FENCE_OPTION警告,且不会出现在标题中;但任何其他第二个单词都会成为代码块标题:请只保留语言标识符。其他工具的围栏选项(hl_lines="2 3"、linenums="1"、showLineNumbers、Mintlify 的lines、wordWrap、filename="…")不起作用,不会出现在标题中,并会显示BLUME_CODE_FENCE_OPTION警告;请使用 Blume 的写法({2,3}、lineNumbers、wrap、title="…")。带选项但没有语言的围栏必须补上语言(```text title="x" lineNumbers),否则第一个选项会被当作语言。语言标识符之后的每个单词都会成为标题;方括号包裹的标题(```ts [file.ts],来自 VitePress 和 MDC)会去掉方括号;紧随其后的范围([file.ts]{2})会高亮对应行。Expandable、AccordionItem和提示框标题会渲染行内 Markdown(代码、强调、链接);Step标题是纯字符串,会按原样显示 Markdown,且没有锚点,因此请将带锚点的标题作为<Step>内的第一行。原始 HTML<img src>和<a href>永远不会经过优化:指向页面旁文件的相对路径会与页面一起发布(与[spec](./spec.pdf)和<Card img="./cover.png">一样);根路径会像 Markdown 链接一样添加deployment.base;blume validate会检查这两种情况。但指向页面的相对<a href>会根据页面 URL 解析,而非根据页面所在文件夹解析。请将图片转换为 Markdown,以便进行优化;页面链接则转换为 Markdown 链接或根路由。 - 数学公式:
$$…$$在.mdx中无需任何配置即可渲染(不存在markdown.math字段)——可单独成行作为块级公式,也可在句子中作为行内公式。单个$始终会保留为字面文本,因此请将每个行内$…$原地转换为$$…$$,放在句子中(不要另起一行),并检查每一对符号确实是数学公式,而非货币金额或 shell 变量。
OpenAPI
reference: [openapi({ sources: [{ spec, label?, route? }] })] — 从 blume/reference 导入的 openapi() 适配器(spec 是单源简写)——会为每个操作生成一个真实页面,并免费提供路由、侧边栏、搜索和 OG 图片。引用不会自动获得页眉标签——请添加一个指向适配器 route 的 navigation.tabs 条目(引用路由是有效的标签目标),否则无法从页眉访问 API 引用。切勿手动迁移生成的 API 引用页面(源文件中的每个端点占位页面):删除它们,并将 openapi() 指向规范。若要保留某个源的 Scalar 嵌入内容,请在 reference 中用 scalar({ spec, theme?, …scalarOptions })(同样从 blume/reference 导入)代替 openapi():它会将 OpenAPI 或 AsyncAPI 文档渲染为每个源一个嵌入页面,将所有未明确命名的键原样传递给 Scalar,并且不接受原生显示选项(codeSamples、expandSchemas、playground)。openapi()/asyncapi() 没有 renderer 选项——使用该选项会导致验证失败。Blume 1.x 的顶层 openapi/asyncapi/graphql 块已移除——如果源配置(或旧版 blume.config.ts)中包含 openapi: { enabled: true, ... },请将其改写为 reference 中的条目,并删除 enabled;带有 renderer: "scalar" 的 1.x 块应改为单独的 scalar({ … }) 条目,并保留该块的 route、sources 和 noindex,同时将其 theme 以及 scalar: { … } 对象中的键直接传给 scalar()(scalar({ spec, theme: "purple", localization }))。
- 默认将规范纳入仓库。 远程
spec:URL 会让每次构建都依赖在构建时获取规范——这会在 CI、离线环境或代理后造成单点故障,且获取失败会跳过整个引用。建议将规范提交到仓库(openapi/<name>.json),并让spec指向本地路径;如果源站将其放在public/openapi.json,请移动它(如果读者需要下载,则放到public/specs/openapi.json):该 URL 是 Blume 发布其自身文档 API 描述的位置(BLUME_PUBLIC_OPENAPI_JSON)。如果保留 URL,请明确说明,并考虑添加一个prebuild步骤,在刷新本地副本时提供回退方案。 - 操作路由有自己的 slug 规则——
<route>/<slugified-tag>/<slugified-operationId>(例如,标签Models、IDlistModels→/api-reference/models/list-models)。驼峰式或帕斯卡式标签或操作 ID 会在词界处分割成 kebab-case,然后再生成 slug(InboxesThreads→inboxes-threads,getHTTPResponse→get-http-response),因此不要只将其转为小写;如果操作没有operationId,则使用其方法和路径(GET /pets/{id}→get-pets-id)。slug 只需在其标签内保持唯一:同一标签中的第二个list会加上其方法(list-post)。这通常与源站的端点链接不一致(Mintlify 等平台采用不同的 kebab-case 规则),因此请重写所有指向操作的入站链接。blume validate会像解析其他路由一样解析操作页面,因此能发现遗漏的链接。 - 规范在其自身路径上提供服务时(Redocly 会在
/apis/x/openapi提供apis/x/openapi.yaml):设置源站的route,这样在侧边栏允许的情况下,概览页可以保留旧 URL(源站的引用说明了文件夹路由何时更合适);操作、标签和分区 URL 仍需要重定向。 - 操作 URL 重定向来自旧站点地图,每个操作对应一个精确条目,应通过生成而非手动输入来创建:先构建一次,然后运行
node <skill>/scripts/operation-routes.mjs dist,它会打印每个引用中的端点(GET /pets/{id})及其对应的 Blume 路由,这些信息取自构建结果(--spec <route>=<file.json>可添加 operationId);通过规范将每个旧 URL 与其端点匹配。在添加重定向前,先重写入站链接:blume validate会接受任何重定向的from作为有效链接目标,因此重定向存在后,它就不会再标记旧格式的链接。 - 标签名称和顺序来自规范。 标签的
x-displayName用作其分组名称;标签按规范的tags列表排列,操作则按规范中写出的路径和方法排列。系统不会读取x-tagGroups:如果源站按其排序,请为每个标签添加<route>/<tag-slug>/meta.ts→defineMeta({ order }),并将其位置设为展平后的x-tagGroups中的位置。标签文件夹的meta.ts会与生成的设置合并,因此只需设置有差异的内容(title、order或用于调整操作顺序的pages列表)。在有范围限制的content.include下,添加一个能匹配标签文件夹的 glob,否则系统永远不会读取该文件(BLUME_META_OUTSIDE_INCLUDE)。 - 附加在生成的端点页面上的源站说明文字(ReadMe 和 Mintlify 支持此功能)应放在对应操作的
description中,并通过 overlay 设置:Markdown 和:::指令会在操作页面上渲染,但 JSX 不会(<和{会被转义)。 - 以非 OpenAPI 格式定义的 API(例如 Fern Definition)需要先导出为 OpenAPI;源站的引用说明了具体方法。
- Try it 请求会直接从读者的浏览器发出。 对每个 API 服务器检查 CORS(
curl -si -X OPTIONS -H 'Origin: https://<docs-host>' -H 'Access-Control-Request-Method: GET' <server>/<path>);将没有匹配的access-control-allow-origin的服务器列为后续处理项(在服务器输出型主机上,playground: { proxy: true }可解决此问题),或者对这些服务器关闭 playground。 - 保留手写的概念性页面。 源站通常会在同一标签页中,将手写的“Introduction/Authentication”页面与端点组放在一起。放置在
openapi()适配器route下的常规内容页面会合并到引用标签页的侧边栏中——因此请保留这些页面(身份验证、错误、速率限制),只删除逐个端点的占位页面。
GraphQL
reference: [graphql({ spec, endpoint? })] 是 GraphQL 对应的配置:Blume 会将架构(SDL 文本或 introspection JSON 结果,本地路径或 URL)转换为每个根字段一个页面(按 Queries/Mutations/Subscriptions 分组),以及每个命名类型一个页面(Objects、Input Objects、Enums、Interfaces、Unions、自定义 Scalars)。上述所有 OpenAPI 规则同样适用:绝不要手动迁移源项目生成的 GraphQL 参考页面(将其删除,并将 spec 指向架构),将远程架构存储在本地,将手写的概念页面放在 route 下(默认为 /graphql),添加 navigation.tabs 条目,并重写指向这些页面的链接——路由格式为 <route>/<slugified-group>/<slugified-name>(例如 /graphql/queries/pets、/graphql/objects/pet),blume validate 会像解析其他页面一样解析它们。与 openapi() 适配器的区别:
- 将
endpoint设为在线 GraphQL URL。 架构不像 OpenAPI 文档,不会指定服务器——endpoint是 Try It 交互式操作区和代码示例的目标(不设置时,它们会显示占位 URL;而playground: { proxy: true }会在构建时发出警告,因为代理没有可允许的来源)。使用多个架构时,配置为sources: [{ spec, endpoint?, label?, route? }];单个源的endpoint会覆盖适配器级别的设置。 - 不支持 Scalar 嵌入——GraphQL 架构没有对应的
scalar();graphql()也不接受expandSchemas。参考内容始终由 Blume 渲染(Scalar 嵌入只读取 OpenAPI 和 AsyncAPI 文档)。源项目的 GraphQL 操场/浏览器嵌入(GraphiQL、Apollo Explorer)没有直接对应功能,内置的 Try It 面板除外;如果该嵌入提供了面板不具备的功能,请报告。 - 只接受 SDL 和 introspection JSON。 如果源项目通过编程方式构建架构(代码中的
GraphQLSchema实例),必须将其打印为 SDL(使用graphql中的printSchema)并提交;请报告此转换。
更新日志
如果源项目有手动维护的更新日志(changelog.mdx、按日期组织的条目文件夹、Mintlify <Update> 区块、ReadMe 的 changelogs/ 文件夹),且其发行说明存放在该仓库的 GitHub releases 中,请提议用 githubReleases() 内容源替代——发行说明会自动成为更新日志,无需维护文件。这只是提议,并非自动重写:有些团队会维护经过整理的更新日志,内容与 GitHub releases 并非一一对应,因此在删除页面前,请先确认发行说明是唯一可信来源。
将其与文件系统内容源一同添加到 content.sources 下(两者均从 blume/sources 导入;设置 sources 后,内容根目录移入 filesystem()):
import { filesystem, githubReleases } from "blume/sources";
content: {
sources: [
filesystem({ include: ["docs/**/*.mdx"], root: "." }),
githubReleases({
owner: "haydenbleasel",
repo: "ultracite",
prefix: "changelog",
}),
],
},
- 每个发行版本都会在
/<prefix>/下生成一个type: changelog页面(prefix: "changelog"→/changelog/…);省略prefix即挂载到根目录。 - 可选字段:
limit(限制生成的发行版本数量,按时间从新到旧排列,默认值为 100)、prereleases(包含预发行版)、drafts(包含草稿——需要具有仓库写入权限的令牌)、pollInterval(开发环境轮询间隔,单位为秒;省略则在当前会话期间冻结)。 - 私有仓库会从
GITHUB_TOKEN读取令牌;令牌绝不会直接写入配置。该适配器会声明此令牌,因此未设置时,blume dev/build会发出警告;公开仓库无需令牌也能正常运行。 - 接入内容源后,删除旧的更新日志页面(并添加从旧路由到新
/<prefix>/…slug 的redirects)。如果原来有标题栏/侧边栏链接,请使用navigation.featured固定该链接。若要在列表上方保留旧更新日志的简介,请编写changelog/index.mdx(mode: center),包含简介和<Changelog />;后者会渲染生成的发行版本列表。
重定向是静态的
在 blume.config.ts 中使用 redirects: [{ from, to, status? }] 数组,可在调整路由结构时映射旧 URL——Blume 会自行处理这些重定向,因此任何会移动页面的重组(每个标签页一个文件夹、物化嵌套分组、重命名 slug、将页面提升为索引页)只需在这里添加一条记录即可修复,无需配置托管服务。**重组是这类问题的主要来源:**第 4 步中移动的每个页面(每个标签页一个文件夹、重命名 slug、将页面提升为索引页)都需要添加一条记录,否则旧 URL 会返回 404。status 默认为 301(永久重定向——浏览器会无限期缓存);这适用于真正的页面迁移,但绝不要对可能撤销的重定向使用 301/308。模式会按原样传递::name 表示一个路径段,:name* 或末尾的 * 表示其余路径({ from: "/beta/:slug*", to: "/v2/:slug*" })。模式不能同时匹配某个页面;如果模式覆盖了保留的页面,构建就会失败(BLUME_REDIRECT_MATCHES_PAGE),因此请缩小匹配范围。模式无法表达的规则(正则参数、标头或 Cookie 条件)应移至托管服务级配置(_redirects、vercel.json);并报告这些规则。
如果重定向的 from 与某个页面的路由相同(包括生成的参考概览页),就会发出警告 BLUME_REDIRECT_MATCHES_PAGE,且重定向优先生效:该页面永远不会发布。请删除重定向或移动页面。页面的 Markdown 镜像(/x.md、/x.mdx、首页的 /index.md)也同样如此:Agent 将无法访问它,因此请删除重定向。也绝不要将页面自身的文件夹 URL 设为重定向来源(如 /x/,常见于源站提供带尾斜杠 URL 的情况):它同样会覆盖页面。对于 /x/index.html 的重定向(源站提供 .html URL),它无害但没有必要:静态托管服务会通过 x/index.html 在该路径提供页面,因此静态构建不会为它写入重定向页面,只有托管服务文件(_redirects、vercel.json)会保留这条规则。redirects 只匹配路径:页面 URL 位于 # 之后时,不会到达重定向规则(参见 references/docsify.md)。如果旧站点托管在你无法控制的平台(例如 GitHub wiki),就无法将重定向指向其 URL:不要承诺提供重定向(参见 references/github-wiki.md,了解可行的处理方式)。尾斜杠无需单独设置重定向:blume dev 和 blume preview 会对页面和重定向来源一视同仁,将 /a/ 以 301 重定向到 /a(保留查询参数),与 Vercel 的行为一致;其他静态托管服务会通过 a/index.html 提供 /a/,而服务端构建(vercel()、node())则会让重定向的 from 匹配 /a/。静态托管服务的重定向规则是否匹配带斜杠的 from 取决于具体服务,因此旧链接使用了尾斜杠时,请在预览部署中检查一个链接。在 Netlify 上,请设置 deployment: netlify({ output: "static" }):只有该构建的 _redirects 规则会强制生效(301!),否则 Netlify 会以 200 状态提供 Astro 在每个 from 路径写入的 meta-refresh 页面,而不是真正的 301(直接使用 netlify() 会切换为服务端输出)。
验证与报告
- 运行
blume build— 它会验证 frontmatter 架构、重复路由和配置,并默认在出现任何错误诊断时失败(不要传入--no-strict:该选项仍会构建,并静默丢弃 front matter 无效或无法解析的.mdx页面)。然后运行blume validate --strict— 链接、标题锚点、资源和重定向目标在这里验证,而不是在build中验证(添加--external还会检查出站 HTTP 链接)。OpenAPI 操作页面是真实路由,validate也会检查它们,因此指向这些页面的失效链接也会被发现。持续迭代,直到两项检查都通过。将build(或blume check)打印的所有关于页面中存在其他工具语法的警告(BLUME_TEMPLATE_TAG、BLUME_MDC_SYNTAX、BLUME_WIKILINK_UNSUPPORTED、BLUME_MD_*、BLUME_CODE_FENCE_OPTION、BLUME_MDX_*等)视为遗漏的转换:每条警告都会指出文件和行号,以及 Blume 中对应的写法,而validate --strict会因这些问题失败。validate会按照页面渲染后的内容读取链接:被更早出现的同标签链接定义覆盖的链接定义不会被检查(它是无效文本;请删除),注释中的链接也不会被检查。validate --strict还会因 OpenAPI 规范警告(BLUME_OPENAPI_*)而失败,例如未声明的路径参数、localhost服务器,或 3.1 规范中的nullable。这些问题无法通过编辑内容解决:请在源文件的overlays中使用 OpenAPI Overlay 修复,以免改动生成的规范或第三方规范,并将问题报告给源文件维护者处理;对于 Blume 没有生成器的codeSamplesID(BLUME_OPENAPI_UNKNOWN_CODE_SAMPLE),则应从配置中删除。如果添加了重定向,请在构建后运行blume audit --only redirects:对于to指向不存在目标的重定向,validate会发出警告(位置为blume.config.ts中对应的行),但如果to是另一个重定向的from,则会接受。因此,只有audit会在构建后逐条跟踪重定向,并报告循环和链式重定向。若要检查所有旧 URL,可向blume dev或blume preview发起请求(curl -sL;两者都会应用所有重定向,包括模式重定向,并返回对应的状态码),也可以根据dist/和重定向规则检查(未设置主机适配器时使用dist/blume-redirects.json,否则使用主机的_redirects或vercel.json)。**旧标题锚点:**每种源站生成标题 ID 的方式都与 Blume 不同。首次构建后,运行node <skill>/scripts/pin-heading-ids.mjs --old <old site URL or its built-HTML dir>(页面迁移时使用--map dist/blume-redirects.json,未设置主机适配器时构建该文件,使用--write应用更改,使用--only-linked仅固定有链接指向的 ID),然后重建,直到报告 0;在服务器构建中,它会自行读取dist/client(ReadMe 和 GitHub wiki 除外:它们的标题 ID 已与 Blume 匹配,ReadMe hub 会对抓取请求限流,而且脚本无法读取 wiki 页面)。任何剩余的BLUME_BROKEN_ANCHOR在旧站上原本就已失效,因此请将明显的链接改为正确目标,删除其余链接的片段,并报告这些问题。 - 运行
blume dev,并从视觉上检查网站(如果blume dev或preview --port N中指定的 N 已被占用,它们会改用下一个空闲端口,因此请求 URL 前请先读取它们打印的Local http://…行)——检查导航结构、选项卡、主题和渲染后的组件。 - 撰写迁移摘要,涵盖:迁移了哪些内容(配置、N 个页面、导航、API 参考)、舍弃了哪些内容(导航栏 CTA、自定义主题、条件重定向、无法映射的图标、不受支持的组件),以及建议的后续操作(使用
blume eject完全掌控,使用blume add将组件引入项目并进行自定义)。
完整文档
映射详情位于 references/ 中:每种源框架对应一个文件(mintlify.md、docusaurus.md、fumadocs.md、nextra.md、starlight.md、vitepress.md、vuepress.md、mkdocs.md、mdbook.md、docus.md、fern.md、gitbook.md、readme.md、redocly.md、docsify.md、jekyll.md、github-wiki.md),此外还有用于宿主仓库集成的 monorepo.md(内容布局检测、pnpm minimumReleaseAge、重新生成冻结锁文件、Vercel monorepo 配方,以及 Astro/Vite 补丁)。Mintlify 图标和 frontmatter 处理流程由 scripts/mintlify-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改)。Docsify 内容处理流程由 scripts/docsify-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改,使用 --snapshot <dir> 记录旧标题 ID,供 pin-heading-ids.mjs 使用,使用 --group-folders 为每个侧边栏分组创建一个文件夹及路由表)。Fern 的文件放置、frontmatter、组件和重定向处理流程由 scripts/fern-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改,首次构建后运行 endpoints)。ReadMe 内容处理流程由 scripts/readme-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改,构建后使用 --routes <file> 处理端点重定向)。MkDocs 内容处理流程由 scripts/mkdocs-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改)。Jekyll 内容和导航处理流程由 scripts/jekyll-codemod.mjs 自动执行(零依赖、确定性;仅运行一次,写入 jekyll-migration.json,并拒绝处理已有该文件的文件夹;使用 --write 应用更改,使用 --old <build> 处理 URL 和侧边栏,使用 --callouts 处理 _config.yml 未指定任何提示框的站点)。GitHub wiki 处理流程由 scripts/github-wiki-codemod.mjs 自动执行(零依赖、确定性、幂等;routes 写入路由表,convert --write 应用转换,check 根据构建产物验证路由表和旧 URL,stubs 为旧 wiki 写入链接存根)。Docus 内容处理流程由 scripts/docus-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改)。VitePress 内容处理流程由 scripts/vitepress-codemod.mjs 自动执行(零依赖、确定性、幂等;使用 --write 应用更改,使用 --redirects <old build> 生成 .html 重定向列表)。重定向所需的操作路由来自 scripts/operation-routes.mjs(零依赖、只读;读取已构建的 dist/);部分文件和根目录外的引用由 scripts/include-excerpts.mjs 生成(零依赖、确定性;复制到项目中后,它会在每次构建前运行);旧标题 ID 由 scripts/pin-heading-ids.mjs 固定(零依赖;默认报告,使用 --write 应用更改)。权威的 Blume 文档随已安装的软件包一同打包,位于其 docs/ 目录中——不一定在仓库根目录:在工作区 monorepo 中(尤其是 pnpm),该软件包位于依赖它的工作区的 node_modules 中(例如 apps/docs/node_modules/blume/docs);从依赖该软件包的目录运行 node -e "console.log(require.resolve('blume/package.json'))",即可打印确切位置。(在 Blume 仓库检出中,文档源文件位于 apps/docs/content/docs。)以下页面最为相关:
configuration/index.mdx—blume.config.ts中的所有字段。content/navigation.mdx— 侧边栏、标签和选择器模型。content/meta.mdx—meta.ts和显示模式。content/syntax.mdx— 指令、代码功能和数学公式。content/components.mdx— 组件库和 API。content/frontmatter.mdx— 严格的页面架构。