
ClickHouse 文档工程实战从 Docusaurus 到 Mintlify 的迁移工具链与 slug 映射机制【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHousedocs/_migration/README.md 描述了一套一次性one-shot工具与数据文件用于把 ClickHouse 官方文档从 Docusaurus 站点框架整体迁移到 Mintlify 框架且明确声明“迁移完成后整个目录可删除线上站点运行时不依赖它”。本文以该 README 为骨架结合仓库中 9 个 Python 脚本与 2 个 CSV 数据文件的真实实现完整讲清这套工具链的输入输出、增量迁移判定、内容转换规则以及无法自动解析链接的兜底流程。读完后你能掌握如何用 slugfrontmatter 中的slug:字段作为跨框架的页面身份锚点建立 URL 映射如何用 SHA-256 哈希做增量迁移判定以及 MDX 内容转换中导入重写、组件替换、锚点修复等关键变换。一、目录定位文档仓库中的“施工区”这套工具链位于 docs/_migration/。当前仓库的docs/目录本身已经是 Mintlify 站点根目录有 docs.json、index.mdx并含ar/、zh/、ru/等 8 个翻译目录而_migration/目录保留了迁移过程的“施工记录”两个数据文件slug-map.csv约 2010 行逐页映射记录与slug-aliases.csv约 650 行人工复核过的别名表七类脚本映射生成generate-slug-map.py、内容迁移migrate.py2538 行是工具链核心、别名建议suggest-slug-aliases.py、别名应用apply-slug-aliases.py、映射校验verify_mapping.py、无 slug 页面匹配match_slugless.py、问题诊断find_dup_imports.py、find_orphans.py。README 最后还指向了一份迁移工作流说明.claude/skills/migrate-docusaurus-to-mintlify/SKILL.md但在当前检出中该 skill 目录并不存在从源码结构看它属于迁移执行者的本地环境约定而非本仓库交付物因此实际工作流需要从脚本自身的 docstring 中拼读每个脚本头部都有Usage:说明。一个关键设计决策可以从 migrate.py 顶部常量读出翻译目录TRANSLATION_DIRS {ar, es, fr, ja, ko, pt-BR, ru, zh}被注释明确标注为“由本地化机器人管理绝不被迁移器触碰”迁移器只处理默认语言英文源文件。这解释了为什么本仓库中 8 个翻译目录的内容与英文页保持平行结构却不在slug-map.csv的逐页映射范围内。二、核心数据文件slug-map.csv 与 slug-aliases.csvslug-map.csv逐页映射台账每行对应一个 Docusaurus 页面列为docusaurus_slug, docusaurus_file, mintlify_file, old_url, new_url, status, source_hash, migrated, migrated_hash, migrated_at, manually_checked。以仓库中真实的一行slug-map.csv 第 3 行为例/about-us/adopters,docs/about-us/adopters.md,resources/about/adopters.mdx,原站URL前缀/about-us/adopters,新站预览URL前缀/resources/about/adopters,matched,34a238caf7e241c3,true,34a238caf7e241c3,2026-06-22T14:29:1700:00,false注意几个工程细节status三种取值matched唯一匹配、ambiguous多个 Mintlify 页面共享同一 slugmintlify_file列用|拼接所有候选、unmatched未找到对应页。对当前仓库实际统计1952 行matched、2 行ambiguous、55 行unmatched其中 1884 行已标记migratedtrue——迁移接近收尾台账即完工凭证。source_hash/migrated_hash双哈希列前者是 Docusaurus 源文件内容的 SHA-256 前 16 位见 generate-slug-map.py 的hash_file实现按 64KB 分块哈希后者在迁移成功后被migrate.py回写为同一值。两列相等 migratedtrue即“源文件自上次迁移后未再变更”是增量跳过的判定条件。new_url由文件路径推导而非 sluggenerate-slug-map.py 的 docstring 解释Mintlify 中页面 URL 就是“相对 docs.json 的文件路径去掉扩展名”因此新 URL 从mintlify_file构造这也是为什么about-us/adopters.md迁移后落位到resources/about/adopters.mdx——两个框架的目录结构可以完全不同slug 才是连接双方的身份键。知识库里页面无 slugDocusaurus 的knowledgebase/页面没有slug:字段其 slug 由文件名隐式生成因此 generate-slug-map.py 的collect_knowledgebase单独按/knowledgebase/相对路径去扩展名规则收集匹配时L173-L176再退化为按 basename 配对。slug-aliases.csv无法自动解析的链接人工裁决表列为old_slug, count, suggested_target, confidence, alternates。当前仓库共 653 行confidence分布为no-match196、manual157、basename-unique150、basename-ambiguous57、redirect-direct36、basenameparent-unique33、contains-ambiguous13、contains-unique11。manual行代表人工复核确认的目标例如/tutorial.md→/core/get-started/quickstarts/tutorial是后续脚本中置信度最高的一档。三、第一阶段generate-slug-map.py 建立全局映射脚本用法generate-slug-map.pypython _migration/generate-slug-map.py python _migration/generate-slug-map.py --docusaurus ~/Desktop/clickhouse-docs注意前提它需要本地检出的 Docusaurus 上游仓库默认路径~/Desktop/clickhouse-docs本仓库只保留 Mintlify 侧。核心算法L130-L190遍历 Docusaurus 仓库仅从每个.md/.mdx文件的frontmatter 块内解析slug:L84-L91 特意限制在首个--- ... ---内避免正文中展示 frontmatter 示例的页面被误匹配遍历本 Mintlify 仓库按slug:建倒排索引对每个上游 slug 做唯一性判定恰好一个候选 →matched多个 →ambiguousmintlify_file用|拼接后续由POST_TRANSFORM_OVERRIDES与链接解析器消歧零个 →unmatched重跑不丢进度写文件前先读入旧 CSV保留每行已有的migrated/migrated_hash/migrated_at/manually_checked四个跟踪列L149-L161映射是“活”的可随上游更新反复再生成。另外两个参数值得说明--old-base默认为原 Docusaurus 文档站的/docs前缀--mintlify-base默认为一个私有 Mintlify 预览域名前缀——两者只用于填充old_url/new_url两列作为对照不参与文件写入。四、第二阶段migrate.py——内容转换引擎4.1 用法与增量判定python _migration/migrate.py path # 迁移单个文件或目录 python _migration/migrate.py --all # 迁移本仓库所有页面 python _migration/migrate.py path --dry-run python _migration/migrate.py --all --force # 即使已是最新也重新迁移增量逻辑在 migrate_file当 slug-map 行满足migratedtrue且migrated_hash source_hash时直接返回 “up-to-date”--force可覆盖。迁移成功后驱动端把migrated_hash、migrated_at回写进 CSVupdate_slug_map。内容来源也有讲究L2151-L2159 注释迁移器从 Docusaurus 源文件读取内容它保留了标题 ID、空行等写作细节本仓库文件只是目标路径只有查不到上游对应文件时才读本仓库自身内容——这对应手工编写的落地页。4.2 通用转换规则以下变换均来自 migrate.py 中可对照的正则与函数覆盖 Docusaurus → Mintlify 的框架差异源侧Docusaurus目标侧Mintlify实现位置frontmatter 的sidebar_position/sidebar_class_name/hide_table_of_contents及pagination_*前缀键直接删除sidebar_label改写为sidebarTitletransform_frontmatter正文顶部的重复 H1与 frontmattertitle重复删除首个 H1允许其前有空行/import/注释/自闭合 JSXdrop_redundant_h1useBaseUrl(x)hook 调用裸字符串xMintlify 的 URL 就是文件路径L286-L293标题锚点# Title \{#anchor\}Docusaurus 为躲 JSX 解析必须转义花括号# Title {#anchor}transform_heading_anchors裸iframe带 width/heightFrame包裹 移除宽高属性使其自适应transform_iframesTOCInline /整体删除Mintlify 自动生成每页目录L334-L341HTML 注释!-- ... --MDX 中非法MDX 注释{/* ... */}围栏代码块内保持原样MIGRATE:标记不转换transform_html_commentsimport 行紧贴 JSXMDX 会解析报 Unexpected ExpressionStatement强制插入空行ensure_blank_after_imports:::note等 admonition 语法Note/Tip/Info/Warning/Danger组件caution/danger/important均归一为Warning/Danger映射见 ADMON_TAGadmonitions 区段4.3 导入重写映射表驱动的最复杂部分transform_imports 按源模块前缀逐类处理import ... from react→ 删除Mintlify 只允许本地导入React hooks 以运行时全局暴露相对路径./data.json→ 删除导入记录“变量 → 解析后路径”映射供后续内联Mintlify 不能导入 JSON 模块site/static/images/...的图片变量导入 → 删除路径改指本仓库/images/...static/段由文件拷贝去掉URL 同步变化site/docs/.../*.md的兄弟片段导入 → 按basename 最长公共路径后缀匹配到/snippets/basename.mdx多候选时优先路径后缀更接近者平局时优先默认语言版本避免英文页导入译文片段见 _prefer_snippet_candidatetheme/.../Name→ 查找本仓库snippets/components/Name/Name.jsx或扁平snippets/components/Name.jsxbadge 类组件强制改写为命名导入{ NameBadge }因为 Mintlify 的 MDX 编译器对 badge 的默认导入会静默吞掉后续兄弟节点L1041-L1060 注释docusaurus/useBaseUrl、docusaurus/useBrokenLinks、clickhouse/click-ui/bundled等无 Mintlify 对应物 → 删除正文用法由其他变换负责查不到任何映射 → 保留原文并写入{{/* MIGRATE: unmapped import ... */}}标记留待人工处理。此外还有两个安全机制_is_self_import 检测“片段按 basename 解析后指向正在迁移的文件自身”的情况会导致 Mintlify MDX 加载器无限递归、挂死 dev server并丢弃该导入prune_unused_mdx_imports 删除正文中从未使用的.mdx片段导入。4.4 逐文件覆盖POST_TRANSFORM_OVERRIDES与保护清单标准转换无法覆盖的场景注册在 POST_TRANSFORM_OVERRIDES 字典中按上游 Docusaurus 路径为键在全部标准变换之后执行。仓库中登记的典型案例包括docs/sql-reference/index.md→ 整个替换为手写的CardGroup参考首页并保留原slug:保证重复迁移幂等Cloud 2026 变更日志 → 删除手写的 RSS admonitionMintlify 原生提供 RSS并把每个## 日期小节包裹为 MintlifyUpdate label...组件Java 客户端版本页 →ClientVersionDropdownVersion结构重写为按versions{[...]}顺序生成的一组View titlevX上游rds_maria.md未闭合的sql围栏 → 补上闭合符否则 Mintlify 会把整页剩余内容吞进代码块KapaLinkDocusaurus 全局注册的“Ask AI”组件→ 改写为调用window.Kapa.open({ mode: ai })的按钮。另一侧是绝不覆盖清单SKIP_FILES / SKIP_PATH_PREFIXES 列出手工编写、与上游刻意分叉的文件如clickstack/index.mdx的卡片网格落地页、concepts/core-concepts/academic-overview.mdx经重度后处理的 VLDB 论文页、products/kubernetes-operator/整目录因权威源在独立仓库等迁移器对其直接跳过防止--force重迁移摧毁人工成果。4.5 资产同步与扩展名归一--sync-assets把上游static/images/**拷贝到本仓库images/**默认只补缺失文件、不覆盖本地既有保护仓库特化覆盖--overwrite-drifted时按字节比对覆盖内容已漂移的文件但永不触碰本地独有的孤儿文件sync_image_assets输出统一为.mdx即使源文件是.md写入目标.mdx并删除旧扩展名文件L2259-L2271理由是 Mintlify 两种都渲染但统一扩展名让跨文件 import/链接更可预测。五、第三阶段unknown slug 标记的闭环处理迁移器解析正文链接时凡目标 slug 在映射表中查不到就在原链接后写入标记文本这个标记刻意保留 HTML 注释形态transform_html_comments显式跳过MIGRATE:注释以便后续脚本可全局检索。闭环由两个脚本完成建议——suggest-slug-aliases.py 全仓扫描收集所有 unknown-slug 标记并按出现次数计数然后按三级策略给出目标与置信度 ① 若参考仓库默认~/Desktop/clickhouse-main的redirects.json有该 slug →redirect-direct目标再经 slug-map 二次翻译则为redirect-mapped ② basename 在全部已知 Mintlify URL 中唯一 →basename-unique多个候选时用父目录收窄唯一则basenameparent-unique否则basename-ambiguous前 5 个备选写入alternates ③ 放宽到“URL 路径段包含该 basename” →contains-unique/contains-ambiguous全部落空 →no-match。 结果写入 slug-aliases.csv供人工逐行复核复核通过的手工行标记为manual。应用——apply-slug-aliases.py 按置信度分层接受别名默认只接受高置信档basename-unique/basenameparent-unique/redirect-direct/redirect-mapped与manual--include-ambiguous、--include-contains显式放行低置信档--dry-run只统计不落盘L48-L66。替换时精确匹配的别名直接整体替换丢弃源 fragment仅裸 slug 匹配的别名保留原#anchorsplit_frag逻辑L41-L45。这套“标记 → 建议 → 人工裁决 → 分层应用”的设计把 55 个unmatched页面和数百处断链的裁决成本压到了一张可评审的 CSV 上。六、校验与诊断四把“尺子”verify_mapping.py以 frontmatterslug为键分别扫描 Docusaurus 与 Mintlify 两棵树产出带颜色的映射报告mapped/unmapped/new_additions与进度条并提供交互式菜单导出 CSV。两个提升准确度的细节docs.json中redirects数组的 source 视为“已映射”load_redirectsget-started/quickstarts/下不进侧边栏导航的页面也计入已覆盖scan_unlisted_pages。结尾还有按语言jp→ja、ko、ru、zh统计的翻译迁移进度条。match_slugless.py专治“Mintlify 侧页面缺slug:frontmatter”的漏网鱼按“相对路径精确 → 最长尾段路径 → 唯一文件名”三级规则找 Docusaurus 源文件--apply时以上游字节覆盖、按上游大小写重命名并同步修正docs.json中的页面路径。find_dup_imports.py检测“页面已声明某标识符、而它导入的.mdx片段又声明了同名导入”的组合——Mintlify 会把被导入 MDX 片段的 import **提升hoist**进父 bundle造成Identifier X has already been declared症状是页面白屏。脚本自带围栏代码块识别避免把示例代码中的import行误判为真实导入。find_orphans.py找出磁盘上存在但docs.json导航中无条目的.mdx页面跳过_前缀片段、AGENTS.md等非页面文件以及core/get-started/quickstarts/这类有意挂在动态组件下的前缀。七、端到端工作流与适用前提把各脚本的 docstring 串起来完整流水线是本地检出 Docusaurus 上游仓库~/Desktop/clickhouse-docs │ ▼ generate-slug-map.py → 再生成 slug-map.csvmatched/ambiguous/unmatched │ ▼ migrate.py --all [--dry-run] → 逐页转换写入 .mdx回写 migrated/hash 列 │ --sync-assets 同步上游图片到 images/ ▼ suggest-slug-aliases.py → 扫描 unknown-slug 标记产出 slug-aliases.csv 草稿 │ ▼ 人工复核 slug-aliases.csv手工行标 manual │ ▼ apply-slug-aliases.py [--include-ambiguous] → 改写标记为真实链接 │ ▼ verify_mapping.py / find_dup_imports.py / find_orphans.py → 终验需要注意的适用前提其一migrate.py与generate-slug-map.py都要求本地存在 Docusaurus 上游仓库且上游路径可通过--docusaurus调整本仓库自身只承载 Mintlify 侧结果与台账。其二整个工具链幂等性靠哈希与覆盖清单保证——重复运行migrate.py对未变更源文件是空操作对POST_TRANSFORM_OVERRIDES与SKIP_FILES保护的文件则永远产出相同结果这也是 README 称其为“一次性工具”的原因当slug-map.csv中 55 个unmatched归零、unknown-slug 标记清零后该目录即可按 README 声明整体移除不影响线上站点。其三从当前仓库数据1884/2009 已迁移、别名表 653 行已复核看这套工具链记录的正是 ClickHouse 官方文档从 Docusaurus 切换到 Mintlify 这一真实迁移工程的全过程快照对任何要做“静态站点框架整体搬迁 存量 URL 全量保真”的团队其“slug 作为跨框架身份键 双哈希增量台账 低置信链接人工裁决”的方法论可以直接借鉴。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考