✨ 重点

三项配套修复,闭合 v0.8.15 收到的 #175 后续反馈。无破坏性变更、无新增配置键、无强制迁移。既有项目构建与发布方式保持不变。

🐛 修复:开发服务器尊重生产 URL

v0.8.15 引入了从 url 路径自动推导 base 的能力,因此 GitHub Pages 项目站点 https://user.github.io/repo 会自动得到 base = /repo/,并产出带子路径前缀的资源 URL。然而开发服务器始终在根(/)提供输出目录——只要 url 携带子路径,localhost 上所有资源都会 404。

社区常用的临时方案是显式写 "base": "/":能让开发模式可用,却关闭了自动推导,从而在生产部署中又把搜索弄坏。两种状态各有一个环境是坏的。

修复方案:base 是自动推导得到时,开发模式强制 base = /。覆盖仅在以下条件全部满足时生效:

  • isDev: true(构建路径完全不受影响)
  • _baseAutoDerived 已设置(用户在配置里显式声明的 base 始终被尊重)
  • 环境变量 DOCMD_PROJECT_PREFIX 未设置(workspace 开发模式依赖它)

用户不再需要在配置里写任何 base 条目。

关键词搜索与语义搜索现在共享同一个输出目录:

  • 此前: search-index.json 位于输出根,语义文件位于 .docmd-search/
  • 此后: 二者都位于 .docmd-search/。默认语言:.docmd-search/search-index.json。其它语言:.docmd-search/<locale>/search-index.json

这让输出根保持干净,也统一了客户端请求的契约,是下一项修复的前提。

🐛 修复:移除运行期的 manifest.json HEAD 探测

搜索客户端会在每次页面加载时对 .docmd-search/manifest.json 发起 HEAD 请求,以兜住“语义依赖在构建过程中才装好”这种极少数情况。可是在任何纯关键词站点上,这个探测都返回 404——正是用户报告的错误。

修复方案: 语义模式只由搜索弹窗上的构建期 data-semantic 标记决定。该标记在 onConfigResolved 中依据 semanticUsable 计算得出,保证构建与客户端对当前模式判断一致。极少数的“中途安装”情况会在下一次重新构建时自然解决(开发模式会自动触发重建)。

🐛 修复:关键词索引始终作为回退方案生成

在移除运行期探测之后,另一个竞态浮现:当语义索引成功时,关键词 search-index.json 被完全跳过。如果弹窗上的 data-semantic 标记已过期(在渲染时被设为 false,因为依赖尚未安装),客户端会回退到关键词模式,但又因不存在关键词索引而返回 404。

修复方案: 关键词索引现在始终生成,即便语义构建已成功。该索引极小(多数项目仅几 KB),作为运行期的安全网。当语义已运行时,TUI 消息被抑制,日志保持干净。

🐛 修复:构建后补盖 data-semantic 标记

搜索弹窗上的 data-semantic="true" 属性是在渲染期依据 semanticUsable 计算的,它检查 docmd-search 是否可解析。在新装项目上,依赖在 onPostBuild 阶段才安装——此时页面早已在无该标记的状态下渲染完成。结果客户端即便语义索引已存在,仍停留在关键词模式。

修复方案: 语义索引构建成功后,每一页 HTML 都会被后处理,确保弹窗携带 data-semantic="true"。此举闭合竞态,无需重新引入运行期 HEAD 探测。后处理遍历会跳过 .docmd-search/node_modules/,在大站点上保持快速。

🧰 自动安装不再前置阻断 workspace 子项目

此前在 spawn 包管理器之前,会先在 cwd 查找 package.json 的前置检查已被移除。npm、pnpm、yarn、bun 都会自己向上回溯寻找项目根,因此前置检查在两种它声称要处理的情形下都是错的:

  1. workspace 子项目(例如 docs/docmd-search/,而 docs/package.json 在上一层)。
  2. 任何插件或模板需要安装、而项目根在多层目录之下。

修复方案: 一律先尝试 spawn。“未找到 package.json”的友好提示现在仅当 spawn 真正失败 任何祖先目录都没有 package.json 时才出现。模板与插件走同一条路径。

用一个全新 brutetest 端到端验证,配置含 @docmd/template-summerplugins.search.semantic: true:首次构建时两者都静默自动安装;summer 的 CSS/JS 落到 site/assets/template/,语义索引写入 site/.docmd-search/

🏗 通过规范 <base href> 实现可移植部署

--offline 且包含子路径(siteRootAbs !== '/')的构建现在会在文档 <head> 中插入唯一一个 <base href="/<repo>/"> 标签。<head> 中的资源 URL 采用简单相对路径(如 assets/foo.css),由 <base> 标签在运行时统一解析到正确位置,无论站点部署在何处。

为什么重要: 之前的方式把子路径硬编码进每条资源 URL。改名仓库的流程是——你改了仓库名,下一次构建仍然输出 /old-name/assets/...,直到更新 config.url 并重新构建。引入 <base> 之后,路径只根据部署位置计算一次并统一应用。把站点在仓库、主机、目录挂载之间迁移不再需要修改 config.url,只要 url 已经与当前部署位置匹配即可。

三模式边界 保证行为清晰:

模式 <base> 资源路径
build(子路径) 插入 简单相对 'assets/foo.css'
build(根部署) 无(使用文档默认) 简单相对 'assets/foo.css'
dev 深度感知 ./../
build --offline 无(file:// 没有 host) 深度感知 ./../

规范化器(packages/parser/src/utils/url-utils.ts 中的 normaliseBaseTag)会先剥离老模板或第三方插件可能输出的任何 <base> 标签,然后在 </title> 之后的规范位置注入唯一的一份。部署形态的决定点只有一个,模板无需关心。

📦 升级

upgrade
npx @docmd/core@0.8.16 build

无需任何配置改动。如果之前为 v0.8.15 写过 "base": "/" 的临时方案,现在可以删除——开发服务器会自动适配。