一次聚焦的硬化发布。
没有新功能——只是修复自 0.8.7 发布以来报告的粗糙之处。
此处的每项变更要么是用户发现的回归、CI 工作流错误,要么是构建时的可靠性问题。
🐛 Bug 修复
Docker:卷挂载权限修复
docmd init 和其他写入文件的命令在 /docs 是由容器内置 docmd 用户(uid 1001)以外的其他 uid 拥有的主机挂载卷时,会以"Permission denied"失败。
镜像现在捆绑了 su-exec,入口点会检测挂载目录所属的 uid:gid,并在运行任何命令前以该身份重新执行——这是官方 postgres 和 redis 镜像使用的相同模式。
# 以下在主机卷上无需 -u 标志即可工作
docker run -v $(pwd):/docs ghcr.io/docmd-io/docmd:latest init
docker run -v $(pwd):/docs -p 3000:3000 ghcr.io/docmd-io/docmd:latest
触发"Operation not permitted"所有权保留警告的 cp -a(归档)模式已移除。
-u $(id -u):$(id -g) 变通方法不再需要,已从所有 README 和 Docker 部署指南中移除。
Docker:init 在 CI 中不再阻塞
交互式的"是否要覆盖这些文件?"提示在非 TTY 环境(CI、不带 -it 的 docker run、管道输入)中会挂起。
CLI 现在会检测非交互模式并自动跳过该提示——默认保留现有文件(安全),或接受 -y/--yes 以选择覆盖。
新的 --force 标志在任何环境中都可用于显式覆盖。
# 在 CI / Docker / 管道输入中强制覆盖
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init --force
# 或使用 -y
docker run --rm -v $(pwd):/docs ghcr.io/docmd:latest init -y
docmd add 在 npx 上:Windows 上的误报"npm not found"
npx @docmd/core add <plugin> 在 Windows PowerShell 上打印"系统 PATH 上找不到包管理器 ‘npm’",即使 npm --version 在同一 shell 中正常工作。
根本原因:当通过 npx 启动 @docmd/core 时,派生的子进程在 Windows 上继承了一个被剥离的 PATH。
安装程序现在相对于 process.execPath(Node 自身的路径)解析 npm/pnpm/yarn/bun,无论 @docmd/core 是如何启动的,这都能正常工作。
错误消息现在还会检测特定情况(Node 可达 + 二进制缺失)并建议直接通过主机的包管理器安装插件。
@docmd/template-* 的模板注册表查找
将 theme.template 设置为官方模板(如 summer 或 @docmd/template-summer)会打印:
⬢ Plugin "template-summer" not found in official registry - manual installation required
根本原因:自动安装注册表查找从包名中去除了 @docmd/plugin- 来推导注册表键,但没有去除 @docmd/template-。
因此 @docmd/template-summer 解析为键 template-summer 而不是 summer,后者在注册表中不存在。
现在两个前缀都被正确去除。
自定义/第三方模板不受影响。
语义搜索的对等依赖处理
当设置了 plugins.search.semantic = true 且 ML 对等依赖(@huggingface/transformers、onnxruntime-node)不存在时,构建会静默回退到关键词搜索,并在 docmd-search 内部深处抛出令人困惑的错误:
⬢ Semantic indexing failed: Embedding failed: Failed to load model "Xenova/all-MiniLM-L6-v2": Cannot find package '@huggingface/transformers'
两处改进:
- 清晰的前置检查 —— 插件现在在自动安装后验证对等依赖是否可解析,并发出包含缺失包名的可操作错误,而不是让失败从
docmd-search内部浮出水面。 - 过时的模块缓存修复 —— 在构建过程中自动安装
docmd-search后,当前 Node 进程的模块缓存无法看到新包。
索引现在在子进程中运行,因此以全新的模块解析启动。
npm install自动安装还会传递--foreground-scripts,以便onnxruntime-node的 postinstall(本机二进制下载)实际在启用了 npmallow-scripts安全功能的 CI 环境中运行。
npm-publish:packages/templates/* 现已加入发布循环
npm-publish.yml 工作流在遍历 packages/*、packages/legacy/*、packages/plugins/*、packages/engines/*,但遗漏了 packages/templates/*。
像 @docmd/template-summer 和 @docmd/template-rain 这样的模板没有被发布。
现已加入。
docmd init 默认不再发起网络调用
之前 docmd init 每次运行都会从 raw.githubusercontent.com/docmd-io/docmd-skills 拉取 SKILL.md。
如果拉取成功,获取的内容会覆盖捆绑的默认值。
两处变更:
- 默认现在是仅本地的 —— 捆绑的
SKILL.md内容随包提供,在不发起任何网络调用的情况下写入。 - 通过
--with-skill标志或DOCMD_FETCH_REMOTE_SKILL=1环境变量选择启用 从 docmd-skills 仓库获取最新的 SKILL.md(CI 环境和离线工作站可以继续使用捆绑的默认值)。
docmd init --with-skill # 从 docmd-skills 获取最新的 SKILL.md
DOCMD_FETCH_REMOTE_SKILL=1 docmd init # 相同,通过环境变量
docker-publish:停止对 main 的每次推送触发
docker-publish.yml 工作流具有 push: branches: [main] 触发器,导致它在每次提交到 main 时运行,而不仅仅是发布事件。
已移除——现在仅在 v* 标签推送、发布的版本和手动调度时触发。
Dockerfile:在 CI 中使 pnpm install 可靠
生产阶段使用 corepack prepare pnpm@10.33.2 --activate(带显式版本),但构建器阶段的 corepack prepare --activate(不带版本)在 GitHub Actions CI 中失败,因为它要求当前工作目录中有 package.json。
两个阶段现在直接使用 npm install -g pnpm@10.33.2——更简单,没有 corepack 解析步骤,在 CI 和本地行为一致。
在干净构建上,pnpm 还在非 TTY Docker 环境中拒绝在没有交互式确认的情况下删除现有的 node_modules 目录。
install 命令现在传递 --config.confirmModulesPurge=false,这是 pnpm 针对这种情况的定向标志。
更广泛的 ENV CI=true 路由已被考虑并拒绝,因为它会泄漏到每个后续的 RUN。
UI 翻译键在已发布的包中作为字面文本呈现
builtWith、copyContext、copiedToClipboard 和其他模板翻译的字符串在使用已发布的 @docmd/ui 包构建的输出中作为字面键文本呈现。
monorepo 正常工作,因为 packages/ui/translations/ 与 dist/ 并存,但已发布包的 files 数组不包含 translations/ 目录,因此 pnpm pack 将其从 tarball 中剥离。
已添加 translations 到 files 数组;loadTranslations 现在还会在目录缺失时发出清晰的警告,以便未来的回归不会作为静默的字面键输出浮出水面。
@docmd/template-* 注册表键不匹配
将 theme.template 设置为 summer 或 @docmd/template-summer 会打印"Plugin ‘template-summer’ not found in official registry — manual installation required",即使该包已安装。
根本原因:自动安装注册表查找从包名中去除了 @docmd/plugin- 来推导注册表键,但没有去除 @docmd/template-。
因此 @docmd/template-summer 解析为键 template-summer 而不是 summer。
两个前缀现在都被正确去除。
自定义/第三方模板不受影响——它们完全绕过注册表查找。
--offline 模式:导航链接不导航 (#164)
点击 --offline 构建中导航侧边栏子链接时,仅切换父项的折叠状态,而不打开目标页面。
根本原因:data-spa-enabled 标志在离线构建中保持为 true,因此 SPA 单击处理程序绑定并使用 preventDefault() 消费单击事件。
但 file:// 没有用于 SPA 路径获取的 fetch,因此导航从未发生。
在两处修复:
data-spa-enabled现在在isOfflineMode为 true 时为false,因此 SPA 处理程序从不绑定,浏览器执行正常的整页导航。- 主 SPA 单击处理程序中的安全网检查在
file://协议下退出,因此即使标志错误,单击事件也不会被消费。
docmd build --offline # SPA 现在自动禁用
# 0.8.7 上的变通方法:通过本地服务器打开,或设置 layout.spa: false
--offline 模式:全面的 file:// 鲁棒性修复
在修复 #164 时,审计了客户端 JS 接触 localStorage、fetch、XMLHttpRequest 或依赖 SPA/网络的每个其他位置。
所有 localStorage 写入(侧边栏折叠、主题切换、语言偏好、Cookie 同意)现在都被 try/catch 包裹,以便 file:// 构建在禁用本地存储的基于 Chromium 的浏览器中不会抛出。
版本切换器的 HEAD fetch 检查在 file:// 下被跳过(浏览器无论如何都会阻止),直接进入直接导航。
无样式 i18n 加载器中的 XHR 用 onerror 处理包裹。
URL 构建器已为离线模式附加 /index.html(未更改)。
结果:通过 file:// 打开已构建的站点现在可以端到端工作,没有 JS 错误,没有断开的导航,也没有静默的功能丢失。
📚 文档更新
更新了以下文档以反映上述修复:
- 部署 → Docker —— 镜像详情表已更新以反映 root + su-exec 模型,"自定义工作目录和文件所有权"部分不再推荐
-u $(id -u):$(id -g)变通方法(不再需要)。 docker/DOCKER.md—— 权限问题部分已重写。- 所有六个 README(en、de、es、fr、ja、zh)—— 移除了
-u $(id -u):$(id -g)说明。
Docker 文档将在后续扩展以明确涵盖 init --force、-y 标志以及非 TTY 行为。
🔒 安全基础(Phase 0 — 内部)
此版本落地了 0.8.8 中其余安全工作所基于的共享安全原语。
这些更改是非破坏性的、纯增量——尚无公共 API 更改。
新的 safePath 归属和 UserPath 品牌
safePath(root, relativePath) —— DEVELOPMENT-BENCHMARK.md S1 所需的路径遍历防护——已从 @docmd/api 移至 @docmd/utils,以便在没有 API 包依赖的情况下随处可用。
@docmd/api 继续重新导出它以保持向后兼容。
与之配套的:一个新的 UserPath TypeScript 品牌类型。
通过 asUserPath() 强制转换的任何字符串都被标记为用户控制输入;下游代码在调用任何 fs.* 之前应通过 safePath() 解析它。
该品牌在运行时是无操作(零成本),是类型系统信号。
集中的转义辅助函数
四个辅助函数现在从 @docmd/utils 提供:
escHtml—— HTML 文本和内联插值attrEsc—— HTML 属性值的语义别名jsonInject——<script>注入的JSON.stringify包装scriptLiteral—— 内联 JS 字符串上下文的JSON.stringify包装
每个转义辅助函数都使用 XSS canary 负载进行单元测试(19 个断言)。
一条规范的路径意味着未来的贡献者不必重新发明 .replace(/&/g, '&') 模式。
两条新的内部 ESLint 规则
docmd/no-unsafe-fs-read—— 当路径参数未通过safePath()解析时,标记fs.readFile、fs.writeFile、fs.unlinkSync等。在 lint 时捕获潜在的 CWE-22 违规。目前warn严重性;Phase 1 修复表面后提升为error。docmd/require-verify-client—— 标记每个没有verifyClient回调的new WebSocketServer({...})。立即设置为error,以便两个现有的开发服务器 WebSocket 实例(开发服务器和工作区开发服务器)在 CI 中失败,直到它们获得 origin 检查。Phase 1 PR 1.D 添加了 verifyClient 回调。
初始 lint 通过揭示了 monorepo 中 141 个路径遍历警告和 2 个 verify-client 错误——这些成为 Phase 1 清理积压。
新的 security.html 配置键(默认翻转为 escape)
markdown 解析器现在遵循具有三个值的 security.html 配置键:
| 值 | 行为 | 用例 |
|---|---|---|
'escape'(0.8.8 中的默认值) |
markdown 中的原始 HTML 被 HTML 转义并显示为文本 | 默认——对用户生成的内容安全 |
'allow' |
原始 HTML 通过到渲染输出 | 故意使用原始 HTML(如 <details> 块)的文档站点 |
'strip' |
原始 HTML 从渲染输出中完全删除 | 锁定内容(公司 wiki、受监管的行业) |
行为变更:0.8.8 之前的默认值实际上是
'allow'。如果您的文档依赖原始 HTML(例如<details>、<summary>、嵌入的<iframe>),请将security: { html: 'allow' }添加到您的配置以保留旧行为。容器块(::: callout、::: tabs、::: steps、::: cards等)不受影响——它们由自定义规则解析,而不是原始 HTML。
端到端覆盖位于 scripts/brute-test-security.js(22 个断言跨越所有三个策略加上回退行为)。
路径遍历硬化(私人披露)
三个内部文件访问路径已针对路径遍历进行硬化。无公共 API 更改。请参阅安全公告了解受影响的版本。
修复位于 packages/core/src/commands/mcp.ts、packages/api/src/hooks.ts 和 packages/plugins/openapi/src/index.ts。
三者都通过 @docmd/utils 中集中的 safePath 辅助函数进行,因此任何未来的代码路径都由同一原语把关。
scripts/brute-test-security.js 场景 S7 到 S10 中的端到端覆盖(15 个断言,包括项目根外的故意 canary 文件和恶意本地路径插件装置)。
开发服务器硬化(私人披露)
开发服务器 WebSocket 处理程序和 init 命令已硬化。
开发服务器现在默认绑定到 127.0.0.1(使用 DOCMD_HOST=0.0.0.0 公开到 LAN),两个 WebSocketServer 实例在接收握手前对循环回址允许列表验证 Origin 头。
init 命令在每次运行时不再发起任何网络调用——捆绑的 SKILL.md 内容是本地写入的;用户通过 npx docmd-skills [dir] 安装完整的智能体技能集。
scripts/brute-test-security.js 场景 S11 和 S12 中的端到端覆盖(16 个断言,包括截获的 fetch 调用的快照,证明零网络属性)。
HTML 转义硬化(私人披露)
插值到元标签和插件 head/body 注入点的用户可控值现在通过 @docmd/utils 中集中的 attrEsc / sanitizeHeadInjection 辅助函数进行转义。
og/twitter 元标签、链接 href 方案和插件生成的 head HTML 都通过同一规范的转义路径。
容器块(::: callout、::: tabs 等)已由 Phase 0 默认的 html: 'escape' markdown-it 策略保护,不需要进一步更改。
scripts/brute-test-security.js 场景 S13 到 S15 中的端到端覆盖(13 个断言,包括一个项目内插件,其 generateMetaTags 返回 <script> 和 javascript: 负载)。
插件输入验证(私人披露)
analytics 插件现在在注入 Google Analytics 脚本之前对 measurementId(必须匹配 G-XXXXXXXX)和 trackingId(必须匹配 UA-XXXXXXX-Y)进行格式验证。
无效的 ID 会被跳过并发出控制台警告,而不是被原始插值。
PWA 插件现在根据 CSS 颜色正则(#hex、rgb()、hsl() 或 CSS 命名颜色)对 themeColor 进行格式验证,并在无效输入时回退到默认的 #1e293b。
两个验证器都使用 @docmd/utils 中的 scriptLiteral() / attrEsc() 进行纵深防御。
scripts/brute-test-security.js 场景 S16 和 S17 中的端到端覆盖(15 个断言:无效的 GA4 / UA / themeColor 负载、有效情况、默认回退路径)。
MCP 和捆绑的 SKILL.md 现在是 docmd-skills-aware
docmd-skills 作为独立的 npm 包发布(https://www.npmjs.com/package/docmd-skills)。由 docmd init 写入的捆绑 SKILL.md 和 MCP get_skill 后备现在都指向智能体的一行安装命令:
npx docmd-skills ~/.claude/skills # 或 ~/.cursor/skills、./.skills 等
完整的智能体技能集(docmd-skills、docmd-dev、docmd-writer)由这一个命令安装。捆绑的 SKILL.md 足以满足 MCP get_skill 消费;npx docmd-skills 是通向丰富的、多文件技能集的路径,用于交互式智能体使用。
🛡️ 安全硬化(Phase 1 — 13 个 CVE 修复)
Battle Test 0.8.6 审计在核心 + 插件中发现了 13 个 CVE 级问题。
全部在 0.8.8 中修复。
在这些修复之前,对 docmd <cmd> 退出代码把关的 CI 流水线在悄悄通过损坏的构建。
亮点
跨站 WebSocket 劫持(CSWSH)— N-S1:开发服务器 WebSocket 没有
verifyClient回调,允许任何源打开连接并触发threads:add-thread文件写入。
新的verifyClient工厂(packages/core/src/utils/ws-origin-guard.ts)默认允许回环以及任何显式的额外主机。
dev.ts和workspace.ts中的两个 WebSocket 调用站点现在以退出代码 1 拒绝未列入允许列表的源。MCP read_doc 路径遍历 — T-S1 / S-3:
packages/core/src/commands/mcp.ts中的path.resolve(cwd, route)接受绝对路径(如/etc/passwd)和../转义。
替换为safePath(cwd, asUserPath(route)),它在遍历时抛出。safePath原语位于@docmd/utils,是所有文件解析调用的单一事实来源。通过本地子文件夹进行插件 RCE — T-S8:
packages/api/src/hooks.ts中的require.resolve(name, { paths: resolvePaths })搜索父目录,从同级文件夹加载任意 npm 包。
本地路径插件现在通过safePath(projectRoot, asUserPath(name))解析,该函数限制在项目根目录内。插件 head XSS — T-S7:从
generateMetaTags返回<script>alert(1)</script>的插件将活动脚本注入到每个页面。
@docmd/utils中的新sanitizeHeadInjection()删除<script>/<style>并中和javascript:/vbscript:URI。
连接到generator.ts以用于injectHead/injectBody。OpenAPI / Analytics / PWA / Threads XSS — T-S3 / S-4 / S-5 / S-7:所有四个插件现在在插入 HTML 之前对不受信任的配置输入(GA4
measurementId、UAtrackingId、PWAthemeColor)进行格式验证,并通过@docmd/utils中的attrEsc()/scriptLiteral()进行转义。Init 向 GitHub 发起网络调用 — T-S5:已移除。
docmd-skills现在是一个独立的 npm 包——捆绑的SKILL.md是默认值;仅通过旧版--with-skill标志选择启用网络调用(为向后兼容而保留)。开发服务器默认绑定到
0.0.0.0— T-S6:默认翻转为127.0.0.1。DOCMD_HOST环境变量选择启用 LAN 绑定。
TUI 在绑定到非回环地址时发出警告。
@docmd/utils 中的新辅助函数
| 函数 | 用途 |
|---|---|
escHtml(s) |
转义 HTML 文本和 <script> 块 |
attrEsc(s) |
转义 HTML 属性值 |
jsonInject(s) |
安全注入到 JSON 上下文 |
scriptLiteral(s) |
安全注入到 JS 字符串字面量 |
safePath(root, userPath) |
路径遍历安全的文件解析 |
asUserPath(s) |
品牌化的 UserPath 类型 |
normalisePath(s) |
POSIX 路径规范化 |
sanitizeHeadInjection(html) |
删除 <script>、<style>,中和 javascript: / vbscript: |
测试覆盖
scripts/brute-test-security.js —— 13 个场景中的 88 个断言(S-1、S-3、S-4、S-5、S-6、S-7、S-9、S-10、S-12、S-13、S-15、S-16、S-17)涵盖每个已记录的 CVE 路径。所有 13 个问题都在 CLI 表面进行回归测试——CI 运行自动捕获任何回归。
📦 容器解析器(Phase 2 — 5 个 bug 修复)
0.8.6 审计发现了五个静默容器解析器 bug(F1–F5),它们产生错误输出但没有错误也没有退出代码变化。
解析器现在是确定性的、大括号感知的、缩进感知的。
修复内容
- F1 — 嵌套网格:
::: grids+ N×::: grid+ 每个卡片一个:::过去会将整个块转储为原始<p>::: grids<br>::: grid<br>...<br>:::</p>文本。规范化器添加显式的:::关闭,以便grids规则匹配。 - F2 — 自闭合标签损坏:
::: tag+ 孤立:::会损坏后续每个容器的深度计数器。自闭合名称(button、tag、embed)受到尊重;孤立:::与[normaliser] WARNING一起被删除。 - F3 — 关闭类型不匹配:
::: callout ... ::: card ... :::默默重新扎根解析器。callout 在 EOF 处自动关闭,并带有[normaliser] ERROR。 - F4 — 三重关闭擦除内容:裸露的
:::行作为<p>:::</p>段落泄漏到页面中。孤立项与警告一起被删除。 - F5 — 5 层嵌套 callout 折叠:嵌套 callout 仅呈现最外层。规范化器为内层插入隐式关闭,以便每个
callout规则递归匹配。
实现
新的 packages/parser/src/utils/container-normaliser.ts —— 旧版 robust-parser-shim/(146 行)的纯 TypeScript 移植。
线性扫描、缩进感知栈、无模块级可变状态。
在用户插件的 onBeforeParse 之前以及 markdown-it 的渲染之前,连接到 packages/parser/src/markdown-processor.ts 的 processContent 和 processContentAsync 中。
在 smartDedent 之前在 createDepthTrackingContainer 内重新运行,以便内部递归渲染看到平衡的输入。
确定性契约
规范化器是其输入的纯函数——没有 Date.now,没有 Math.random,没有模块级可变状态。
三层保护契约:
- 声明性 —— 文件头中的
DETERMINISM AUDIT块记录模块不使用的四个非确定性原语。 - 经验性 ——
packages/parser/test/container-normaliser.test.js具有 60 个断言,包括 100 路并发解析和真正的node:worker_threads跨工作线程解析。 - 防回归 ——
packages/core/src/engine/worker-parser.ts中的verifyDeterminismAtBoot()在工作线程初始化时解析已知输入,并断言输出与冻结快照匹配。任何未来的非确定性都会在启动时使工作线程崩溃。
🔧 CLI 契约(Phase 3 — 7 个 bug 修复)
五个已记录的 CLI 失败路径默默退出 0;两个 validate 路径返回错误结果。
所有七个都已修复。
修复内容
| 命令 | 失败情况 | 修复前退出 | 修复后退出 |
|---|---|---|---|
docmd build |
配置中存在未知插件 | 0 | 1 |
docmd migrate |
无 --docusaurus/etc. 标志 |
0 | 1 |
docmd migrate --help |
(它是一个帮助打印) | 0 | 0(未变) |
docmd remove <nonexistent> |
插件不在注册表中 | 0 | 1 |
docmd validate --json |
损坏的链接 | 0 | 1 |
docmd add <plugin> 在 TS/MJS/CJS 上 |
配置文件未触动 | 0 | 1 + 已修复 |
docmd remove <plugin> |
插件条目留在配置中 | 0 | 1 + 已修复 |
docmd build(工作区) |
原始堆栈跟踪,退出 0 | 0 | 1 + 干净的 TUI |
docmd init 示例 |
教授 F2 陷阱模式 | OK | 已清理 |
docmd validate(尾随斜杠) |
对 /page/ 的误报 |
1 | 0 |
实现
- 新的
packages/core/src/utils/exit.ts导出exitCodeFor(err)、exitWithError(err, opts?)、failWith(message, opts?)—— 退出代码契约的单一事实来源。 @docmd/api中的新getPluginLoadErrors()单独跟踪插件 LOAD 失败与运行时钩子错误。build.ts在loadPlugins()之后检查它并抛出,由现有的 catch 块转换为退出 1。- 新的
packages/plugins/installer/src/config-editor.ts—— 一个大括号平衡扫描器,统一处理所有五种支持的配置格式(docmd.config.{json,js,mjs,cjs,ts})。旧版基于正则的注入器仅匹配module.exports = {...},并对export default defineConfig({...})默默地无操作。resolveConfigPath()仅检查.json/.js,将在项目的.ts旁边默默地脚手架一个新文件。
测试覆盖
scripts/brute-test.js —— 三个 Phase 3 部分的 22 个新断言(测试 26:退出代码,测试 27:插件添加/删除,测试 29:验证 + 工作区 + 初始化)。全部在 tests/cli-contracts/ 下。
🤖 OKF 插件(0.8.8 — 新核心插件)
一个新的核心插件为 AI 智能体消费生成一个 Open Knowledge Format bundle。
bundle 位于 site/okf/,包含类型化清单(okf.yaml)、交互式图形查看器,以及每个页面一个 markdown 文件。
开箱即用
site/okf/
├── okf.yaml ← 类型化清单(bundle 摘要)
├── index.md ← 按类型分组的 Karpathy 风格目录
├── graph.html ← 交互式力导向查看器
├── graph.json ← 图形数据(节点 + 边)
├── graph.js ← 查看器运行时(原生,无 CDN 依赖)
├── graph.css ← 查看器样式(支持主题)
├── concepts/<slug>.md ← 每个页面一个 markdown 文件
└── _meta/
├── bundle.json ← okf.yaml 的 JSON 镜像
└── lint-report.txt ← 生成过程中产生的警告
默认行为(0.8.8)
- 默认启用 —— OKF 是一个核心插件,如
llms和seo。
构建自动加载它,即使用户尚未在其配置中添加plugins.okf,也会生成 bundle。
支持三种退出路径(false、enabled: false,或能力过滤器)。 - 默认单语言 —— bundle 仅包含默认语言的页面。
默认语言的文件位于 bundle 根目录(无en/子目录)。 - 类型推断 ——
/api/、/guides/等下的页面会自动分类;其他所有内容回退到concept。
多语言选择启用
{
"plugins": {
"okf": { "localeStrategy": "folders" },
"llms": { "i18n": true }
}
}
设置这两个标志后,默认语言的文件保留在 bundle 根目录(因此现有消费者不会中断),非默认语言获得 <locale>/ 子目录(OKF)或 .<locale> 后缀(LLMS):
site/okf/okf.yaml ← 默认语言
site/okf/concepts/...md ← 默认语言概念
site/okf/ja/okf.yaml ← 日语
site/okf/ja/concepts/...md
site/llms.txt ← 默认语言(en)
site/llms.ja.txt ← 日语
测试覆盖
packages/plugins/okf/tests/index.test.ts(8 个测试)+ packages/plugins/okf/tests/brute.test.ts(15 个测试),包括 12 个真实 fs 场景。23/23 通过。
新测试验证默认启用契约(没有 plugins.okf 条目的项目仍会生成 bundle)。
🧪 测试基础设施(分类)
暴力测试过去是 scripts/ 中两个文件中的 1000 多行,测试编号重复(Phase 3 PR 3.A 重用了 26、27、29)。
它们现在在 tests/ 下分类:
tests/
├── shared.js ← 装置辅助函数
├── runner.js ← 带 TUI 分段的编排器
├── cli-contracts/
│ ├── exit-codes.test.js ← F6、M-12
│ ├── plugin-add-remove.test.js ← F7、M-3
│ └── validate-workspace.test.js ← M-1、F8、F9
└── (每个包的测试保留在 packages/*/test/ 下)
编排器由 pnpm prep(或直接通过 node tests/runner.js)调用,并在其自己的 TUI 分段中运行每个套件。
按 --only=exit-codes,container-normaliser 过滤以仅运行已命名的套件。
结果
9 个分类文件中 342 个断言,全部通过(外加每个包的单元测试在 4 个包中贡献了 89 个,合计 13 个文件 / 431 个断言):
| 套件 | 结果 | 覆盖范围 |
|---|---|---|
cli-contracts/exit-codes |
7 / 7 | F6、M-12 |
cli-contracts/plugin-add-remove |
9 / 9 | F7、M-3(TS、MJS、JSON、CJS、JS) |
cli-contracts/validate-workspace |
6 / 6 | M-1、F8、F9 |
packages/parser/test/container-normaliser |
60 / 60 | F1–F5 + 确定性 |
packages/utils/test/{path,html-escape} |
31 / 31 | safePath、escHtml 等 |
scripts/brute-test-security |
88 / 88 | Phase 1 CVE 套件(13 个场景) |
scripts/brute-test |
114 / 114 | 29 个功能集成场景 |
packages/plugins/okf |
23 / 23 | 单元 + 12 个真实 fs 场景 + 图形按需启用 / 弃用别名 |
packages/plugins/llms |
6 / 6 | 默认语言 + i18n 选择启用(新) |
更新 — pnpm prep 发布流水线加固
发布流水线增加了三项改进:
- 每个包的单元测试现已纳入发布门禁。 在
tests/runner.js之后运行pnpm -r run test --if-present,因此@docmd/parser、@docmd/utils、@docmd/plugin-okf或@docmd/plugin-mermaid中的回归会以与分类运行器回归相同的方式使发布流水线失败。再也不会有任何包级测试套件被静默忽略。 --verbose/--full标志。 默认输出保持折叠(每个测试步骤一行),干净的运行一目了然。传递--verbose可内联流式输出原始测试输出 —— 在迭代刚刚失败的测试并希望断言文本处于上下文中时非常有用。- 尾部
Summary块。 干净运行时,每个 section 都会贡献一项统计信息(lint 计数、包总数、测试计数、Docker 版本),并在末尾渲染为一个绿色块。失败时,同一位置变为红色Issues块,列出每个失败及其文件 :行详情,并带上限溢出。查看发布结论只需要看一个地方。
🔌 插件作者说明
@docmd/plugin-llms 和 @docmd/plugin-okf 在 0.8.8 中都是 默认启用的核心插件。
构建自动加载它们。
要退出,请使用已对所有其他核心插件有效的 enabled: false 形式:
{
"plugins": {
"okf": { "enabled": false },
"llms": { "enabled": false }
}
}
展望 —— Node 20+ 在 v0.9.0 中
下一个次要版本将最低要求提升到 Node.js 20+(从 18+),将引擎要求、发布的 Docker 运行时和 CI 环境统一到 Node 22 上。Node 18 于 2025 年 4 月达到生命周期终点;这使 docmd 保持在受支持的运行时上,并留有充足的空间。
此版本(0.8.8)不受影响 —— >=18 最低要求将保持到 0.9.0 发布。
| 当前(0.8.x) | 下一个(0.9.0) | |
|---|---|---|
| 最低 Node | 18+ | 20+ |
| Docker 基础镜像 | 20-alpine |
20-alpine |
| Node 类型 | ^24 | ^24 |
| CI 目标 | 24 | 24 |
受影响的版本
此版本中的所有修复都针对 0.8.7。没有破坏性更改——每个修复都是增量的或纯错误更正。