@dsh-workbench/module-tools

中文文档
1.2.12发布于 2026/11/13MITES Module近 30 天下载 1.2万

模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。

中文使用文档
文档目录12 节
  1. 六条命令走一遍
  2. 命令
  3. 十四条判据
  4. seam :模块只走执行缝门面
  5. arch :模块包的分层方言
  6. lint :模块包的语法卫生方言
  7. design.json :三张表
  8. 读源配方
  9. 协议: package.json 里的五段
  10. 模板
  11. 自己的自检
  12. 相关文档

@dsh-workbench/module-tools

工作台模块的工具箱:建骨架、构建、过门禁、管设计底稿、发货前核清单。 模块作者只跟这几条命令打交道 —— 模块包里不再各带一份构建脚本。

六条命令走一遍

pnpm exec dsh-wb-module init my-unit                    # 建骨架(唯一模板,已默认)
pnpm build        # dist/host.js + 套了 loader 壳的 client.js
pnpm check        # 十四条判据
pnpm design check # 设计底稿有没有空格、readme.json / README / DEVELOPING 有没有落后

要发货再加一步 pnpm exec dsh-wb-module publish:跑门禁、核 files 与 npm pack 清单,默认 只 dry run,不真发。 维护者确认要真发时跑 pnpm exec dsh-wb-module release:全门禁(build / check / design / typecheck / test / selfcheck)、 验收必须全勾、预检(登录 / license / git 远端)、按 --target 发到团队源 / 公网 npm / 安装包 / git, 最后从出口侧核包(--dry-run 只预览,--verify 只核已发布的包)。配了可写 git 索引源时, 发完还会写索引条目(--index auto|none|<目录|git 地址>,artifacts-in-repo 连产物一起入库, catalog-only 只更索引);索引没更上不影响包已发布这件事,结果里会如实写。 发布可选 --index-source <id> 指定发到哪个索引源;不指定时用唯一可发布的源,有多个可发布源会要求指定。 空仓库先跑 dsh-wb-module market-init <git 地址>:生成索引目录 market/ 与 README 并推首个 commit (用仓库声明的默认分支,读不到时用 --ref 指定)。在工作台里配了可写索引源之后, release 默认出口就是 market:产物与索引一起进那个仓库。 发布过程中每一步都写进 $DSH_WORKBENCH_HOME/releases/<unit>.json(默认 ~/.dsh/workbench);中断后带 --resume 接着发 (沿用记录里的出口、版本与索引计划,产物没变就不重跑门禁)。 这一版在 CHANGELOG.md 里没有记录时,带 --changelog-note "这一版改了什么" (--changelog-type 选分类,缺省 changed)发一次就写进去,文件不在会先建骨架; 没带 note 又没记录时 release 在跑任何门禁之前就拦下。 日志写在确认之后,发失败不回滚:这一版已经记下了,下一次发布直接用。 package.json 缺 files 里的 CHANGELOG.md 一样拦下;author 没写时从本机 git config 补进 manifest,索引与产物都带上。 标签只住在市集条目上(metadata.labels),不写进 package.json:清单的键是协议定死的 (apiVersion / unit / title / card / tools / host / needs),往里塞私货 check 会判红。 标签可选:--label <短词>(可重复,最多两个)并进索引条目的 metadata.labels; 不传就沿用老清单里已经写过的(dsh.workbench.market.labels,只在读那一侧认)。 分类 --category <短词> 是市场发布的要求:条目还没有分类时必须给一个(工作台的发布审批会拦), 已有分类可以不带;它写进 metadata.category,发现页的分类 chip 就是按它汇总的,新模块带一个新分类,页面自动多一个。 标题 / 署名 / 说明仍保持索引里现有的那份不动。 要换署名时带 --author "姓名 <邮箱>"(或只给姓名):它写回 package.json,索引、产物与提交都用它, --refresh 时也一样。发布本身不改市集里的标题 / 署名 / 说明:这些元数据只由 --refresh(或显式 --author)写, 避免一台还没更新的机器发新版时把别人刚改的元数据盖回去。一个包名归第一次发布的人(条目文件里的 owner):换署名发同名会被拒, --refresh 也一样 —— 真要换人,先改那一条的 owner。 同一个市场里,一个模块 id(dsh.workbench.unit)只能被一个条目使用;产物目录按包名走 (artifacts/<包名>/):换一个包名沿用旧 unit、或两个条目撞同一个 id / 目录,都会被拒。

骨架自带脚本,日常不用记长命令:build · typecheck · test · selfcheck · check · lint · design。

命令

命令作用
dsh-wb-module init <unit> [--dir <目录>] [--template diy] [--description "一句话"]建模块骨架(只有 diy 一个模板,也是默认)
dsh-wb-module market-init <git 地址|目录> [--ref <分支>] [--path <索引路径>]初始化市场索引仓库:写 market/ + README,推首个 commit
dsh-wb-module build出 dist/host.js(单文件 ESM)与 client.js(套 loader 壳)
dsh-wb-module check [--only <判据>] [--package <包名或 unit>]十四条判据;--only 单跑,也当查询口用;--package 只报这个模块的问题(见下);--list [--json|--markdown] 只列判据元数据
dsh-wb-module lint [--fix]用模块自己那份 eslint.config.mjs;--fix 写回文件
dsh-wb-module sync按清单生成 src/declared-tools.ts,按 design.json 的 key + recipe 生成 src/datasource.ts
dsh-wb-module design <init|migrate|check|render|accept>设计底稿:填、拆旧 README、查空格、生成 README 与 DEVELOPING、验收打勾
dsh-wb-module migrate [--dry-run]把模块清单迁到当前代:apiVersion → 3、缺/空的 workbenchFaces 补默认;源码类问题只给提示(check 会点名)
dsh-wb-module changelog check [版本]查这一版有没有记录;发布时用 release --changelog-note 写进去
dsh-wb-module publish发货前清单:门禁 + files + npm pack(只 dry run)
dsh-wb-module release真发布:team / public / tarball / git / market / local;全门禁 + 验收闸 + 预检接管 + 发后核包 + 更新市场索引(--dry-run / --verify)

--cwd <目录> 指定工作目录,默认当前目录。注意判据仍按工作区发现模块:往上找 pnpm-workspace.yaml(或 package.json 的 workspaces),按它声明的 glob 展开包目录 (packages/**、apps/*、多层目录都行;没有声明时退回 packages/*)——--cwd 不会把范围缩到一个模块。 只想看自己那个模块用 check --package <包名或 unit>。

--package 是开发期便利,不是门禁:它仍全量扫描、只过滤报告与退出码,所以 「我这个模块绿」不等于「工作区绿」。门禁 / 发布 / release 不要带它。要只让判据看见一个包, 用环境变量 DSH_WB_ROOT=<包目录>(工具链自己的自检就是这么隔离的)。

十四条判据

dsh-wb-module check 一次跑完,三态输出:✔ 通过 / ○ 不适用 / ✖ 不过;有任何不过就退出 1。

表由 dsh-wb-module check --list --markdown 生成;层 是 manifest / source / build / render / meta。

判据层摘要
contractsmanifest单元 id、两个面、四个脚本、peer 依赖存在,以及清单与代码注册对账
protocolmanifestdsh.workbench 的 apiVersion、静态半形状与未知键
bundlesbuild宿主产物不含浏览器侧、浏览器产物不内联外壳 CSS 与夹具
sourcesource零注释、包与运行时入口分开、不手写进程/锁/原子写
seamsource子进程只走 deps.seam 门面;不取原始 shell / 不裸 child_process;清单 writes 与请求一致
archsource模块自己的层序、运行时边界与设计系统配置
lintsource模块自己的 eslint.config.mjs 与规则集
primitivesbuilddsh 包只从 kit 取,模块自己声明 external
kitbuild模块产物对得上所装外壳的 kit 面与 peer 下限
cardsrender八态 × zh/en 渲染:不抛、不空白、不冒裸键名
standalonebuild外壳源码不在场时仍能 build + typecheck
markersmeta公开包不含私有源、私网 IP、内部域名后缀
stylesource不定义 wb-* 规则;裸标签控件改走 kit;令牌名对现读的清单校验
aliassource模块源码里没有指向外壳 @/ 的引用

style 还查令牌名:模块源码里每个 var(--dsw-* | --wb-*) 都要在现读的清单里 —— --dsw-* 从装着的 dsh 主题读、--wb-* 从外壳出货的 client.js 读;读不到的那一份不判红,只记一条 「这次没验」。拼错的名字按最近的几个候选点名(workbench_diy_components 的 scope: 'tokens' 是同一个来源)。

style 还有两族更窄的判据:手写的「标签 + 控件」行与手写的操作区判红(归 kit 的 FormRow(s) 与 FormActions),裸 <form> 与手写整张表单也判红(归 Form)。 现存违规收在各自的豁免表里,是棘轮:迁一处删一行,表里已经不再命中的行也会当场报红。 判据故意窄 —— 模块自己用 div 拼一张不借槽类的表单不判,判宽了就是每次改界面都先撞门禁。

seam:模块只走执行缝门面

模块在宿主侧起子进程,只能走外壳放进 mount(deps) 的门面 deps.seam。判据查三件事:

  • 宿主侧可达的源码里不许出现 runShell / shellOf、ctx.get('shell') / ctx.get('workbenchShell') / ctx.get('seam')、node:child_process / child_process;
  • dsh.workbench.host.writes 只认 "unit" / "workspace" 两档(protocol 判据也查形状,这里再兜一次);
  • 源码里以 scope: 'unit' / scope: 'workspace' 请求的档,清单 host.writes 里必须有。

修法:清单里写 "host": { "inject": [], "writes": ["unit"] },代码里 deps.seam?.run({ scope: 'unit', command })。unit 档可写 <运行时根>/units/<unit>/(模块自己的数据目录, HOME 与 npm/pnpm 缓存也注入在那里),workspace 档写当前会话工作区、还要带上会话对象; 不写 writes = 不给门面(deps.seam 是 undefined)。模块宿主代码与外壳同进程 —— 这层是 约定 + 门禁 + 归因,不是安全边界。完整写法见 BUILD.md §5。

arch:模块包的分层方言

卡片一复杂就往 src/client.tsx 里堆,宿主侧顺手 import 一个组件(dist/host.js 在 Node 里 根本起不来)—— 边界判据拦不住这些,arch 补这一层。模板里的配置只有一行:

import { moduleArch } from '@dsh-workbench/module-tools/arch';
export default moduleArch();

方言只有一份,随工具链与外壳同代,不要求每个模块抄一份。层序:

落点层是什么
src/model/**1纯逻辑:类型 / 换算 / 状态机。不碰 react / kit / dsh / node:*
src/messages.ts1文案(zh / en)
src/components/**2浏览器侧视图件;样式与它同目录(X.module.css)
src/host.ts · src/client.tsx10两个面
src/host/**10宿主侧内部实现(按需出现):moduleArch() 默认就认这一层;对外仍只有 src/host.ts 一个面
src/**/__tests__/**99测试:住被测代码同目录的 __tests__/;.test.ts(x) 散在 src/ 里判 S74

两个运行时靠另一条规则隔离:src/components/** 整片算浏览器面,宿主侧 import 一个组件是硬红 (层号拦不住它:components 第 2 层、host 第 10 层)。src/host/** 与两个面同层(10), 所以浏览器面从它 import 时层号不报红 —— 运行时边界要自己守。内置阈值比 arch-guard 默认更严: 文件与视图各 200 行、函数 80 行;src/model/** 有文件就必须有测试(__tests__/ 下 —— M08), 测试文件不在 __tests__/ 里判 S74。

要开口子从参数进(别改判据;每条口子都有名字,评审看得见):

export default moduleArch({
  fileLines: 300,
  deps: ['some-sdk'],
  layers: { adapters: 2 },
  roles: [{ id: 'lib:version', pattern: 'src/version.ts', layer: 1 }],
  exceptions: [{ rule: 'S16', glob: 'src/components/Legacy.tsx', reason: '待拆,见 #123' }],
});

exceptions 每条必须写 reason。怎么读红、常见红、还有哪些口子,见 REVIEW.md。

宿主面写到 200 行还收不住时,把内部实现拆到 src/host/**(diy 模板就这么做, moduleArch() 默认就认这一层)。不要把它做成第三个 dsh.workbenchFaces 入口 —— 两个面不可调。

lint:模块包的语法卫生方言

arch 管结构,lint 管单文件:as any 绕过契约、残留的 console.log / debugger、 TS 内联样式里的颜色字面量与 !important、react-hooks 那几条。模板里的配置只有两行:

import { moduleLint } from '@dsh-workbench/module-tools/lint';
export default moduleLint({ rootDir: import.meta.dirname });

moduleLint() 返回一份标准 flat config:它同时是判据跑的那份与编辑器 / eslint 认的那份, 没有第二份真相。依赖(eslint、typescript-eslint、两个 react 插件、globals)装在 module-tools 一家, 模块清单一个字都不用改(依赖由 module-tools 自己带)。

要开口子从命令参数进:moduleLint({ rootDir, ignores, rules }),或者展开它再追自己的一块:

export default [
  ...moduleLint({ rootDir: import.meta.dirname, rules: { 'no-console': 'off' } }),
  { files: ['src/legacy/**'], rules: { '@typescript-eslint/no-explicit-any': 'off' } },
];

指纹是逐块比整份方言:方言那 8 块在模块配置里必须一块不少、逐项一致;少一块、同名两次、 删一条规则或改一个值都红。只写一个同名块、export default []、语法错的配置都会红。 scripts/** 与 *.config.* 不判 no-console(脚本打印是本职工作)。

dsh-wb-module lint --fix 先写回再跑一遍看还剩什么。check 永远不动手改文件 —— 门禁是判据,不是修复器。

模板生成三份文档:readme.json 是终端用户文案源(标题、简介、卡片布局草图 shots[]、会看到什么、能做什么、agent 能做什么、数据从哪来、排障、FAQ,后五段是短条目),README.md 只由它生成;DEVELOPING.md 是作者文档(实现、路由、判据、数据源与三张表),由 design.json 生成。design render 刷新这两份;design check 会拦住空文案、占位符和技术词;旧模块先跑 design migrate,它把旧 README.md 原样保存为 DEVELOPING.md,再生成 readme.json 与用户 README。

design.json:三张表

面板 / 能力 / 联动三张表、状态矩阵、数据源记录、写通道记录、验收清单都住在模块根目录的 design.json,DEVELOPING.md 里那一段由它生成(夹在 <!-- dsh-wb:design:start/end --> 之间)。README.md 的用户文案不看 design.json,只看模块根的 readme.json。

它是设计底稿:三张表与探明记录装人话(oneLine / shape / approvalText / 三张表的文字); 读源那一条可以多带 key 与 recipe 两个机器字段 —— 它们只用于生成 src/datasource.ts, 不改变"人话那一份"(read / params / credentials / notLoggedIn / timeout)照写。「这条能力在哪些工作区出现」不在底稿、也不在清单 —— 那是用户的面板板说了算。 contracts 会把底稿的能力名与清单的工具名做键集合对账(不重不漏,只对名字);没有 design.json 的模块不查。

有写能力的模块另在 writes[] 里落命令 / 入参 / 出参 / 预览档 / 回读 / 失败形状, 渲染成 DEVELOPING.md 里的「写通道探明记录」。没有写通道就不用写,不会因此变红。 写也能落配方:writes[] 里加一对 key + recipe,sync 把它和读一起生成进 DATASOURCE(data.submit(DATASOURCE.<key>));配方盖不住的写写 custom: { why }(warn)。

读源配方

一条读可以带 key(lowerCamelCase)和 recipe:

"datasource": {
  "key": "balance",
  "recipe": {
    "origin": "https://x.example.com",
    "request": { "method": "GET", "path": "/api/balance" },
    "response": { "kind": "json", "path": "data" }
  },
  "read": "GET /api/balance(登录态 cookie)",
  "params": "无",
  "credentials": "同源 cookie",
  "notLoggedIn": "401 → auth",
  "timeout": "25 秒"
}

sync 会把它们生成成 src/datasource.ts(export const DATASOURCE = { balance: … } as const), 模块代码只写 data.request(DATASOURCE.balance)。一条读要一次打多条同源接口时,再加一格 calls:

"datasource": {
  "key": "wallet",
  "recipe": { "origin": "https://x.example.com", "request": { "method": "GET", "path": "/api/subscription/self" } },
  "calls": {
    "balance": { "path": "/api/wallet/balance", "response": { "path": "data.balance" } },
    "usage": { "query": { "days": 7 } }
  }
}

生成的 src/datasource.ts 多一个 export const DATASOURCE_CALLS = { wallet: { … } } as const, 代码写 data.requestMany(DATASOURCE.wallet, DATASOURCE_CALLS.wallet):一次换登录态、并发打, 每格各自成败。calls 的键就是结果名;path / query / headers / response 覆盖配方同名默认值。 几条规矩:

  • key 与 recipe 要么都写、要么都不写;只写一个不生成、也不校验(旧条目一字不动)。
  • 写走同一份生成物:writes[] 的 key + recipe 也进 DATASOURCE;读写的 key 不许重复, design check 点名。
  • recipe 与 custom 二选一。custom: { why } 表示"配方盖不住,仍用旧的 browser 脚本", design check 记 warn,脚本留在代码里。
  • design check 校验配方与批量:枚举、origin / path、request.headers 里不许写凭据头、 maxBytes、GET 不带 body;calls 非空、≤ 8 条、逐格同一套校验,且必须配 recipe。 生成物过期会被 contracts 点名,跑 sync 刷新。
  • 形状与六种配方见模块 DIY 的 DATASOURCE.md。
dsh-wb-module design check    # 空着的格一格格点名叫出来 + README / DEVELOPING 有没有落后
dsh-wb-module design render   # 用 readme.json 重新生成 README,用 design.json 刷新 DEVELOPING 那一段
dsh-wb-module design accept   # 在真卡片上逐条走通后打勾

协议:package.json 里的五段

段是什么谁读
dsh.bundle.patch把自己插进 profile 组合图的 cordis 补丁dsh 的组合器(装载)
dsh.client{platform,inject,external}浏览器侧接线dsh 的客户端组合器(dsh 不校验,错的声明要等浏览器 require 才炸 —— 所以 contracts 替你查)
dsh.workbench模块声明协议门禁 · 市场 · 外壳
dsh.workbenchFaces{host,client}工具链元数据(不在协议里):两个面的源码入口门禁:source 拿它算可达性、contracts 查它在位;运行期不读
peerDependencies['@dsh-workbench/core']服务面:外壳 provide 的服务在不在dsh 的兼容性预检
"workbench": {
  "apiVersion": 3,
  "unit": "hello",
  "title": { "zh": "示例模块", "en": "Sample module" },
  "card": { "order": 90 },
  "tools": [{ "name": "hello_read", "writes": false }],
  "host": { "inject": [] },
  "needs": [
    {
      "kind": "extension",
      "runtime": "opencli",
      "name": "acme-workflow",
      "commands": ["acme-read"],
      "purpose": "读写工单",
      "sources": [{ "kind": "npm", "package": "opencli-plugin-acme", "registryEnv": "ACME_REGISTRY" }]
    }
  ]
},
"workbenchFaces": { "host": "src/host.ts", "client": "src/client.tsx" }

五条口径:

  1. 声明了 apiVersion = 静态半写全了:unit / title / card / tools / host 五个键都要有 (没有卡片写 null),needs 没有外部需要时省略。名字是包里的字面量(中英各一份), 不是词表键 —— 宿主在模块载入之前就读得到它。 needs 四种 kind:bin / extension / lib / env;每条必写 purpose; 未知 kind 或 sources[].kind 判红,need 里未知字段只算警告(x- 前缀忽略)。
  2. 不许有协议不认识的键:生字段会被外壳静默忽略(拼错一个键就等于那句话没写), protocol 先拦。scope 不在协议里 —— 能力出现在哪些工作区只由用户的板决定。
  3. 清单声明的工具 = 代码真正注册的工具:清单是工具面的唯一来源,两边漂了就红。
  4. 底稿 ↔ 清单只对账键集合,只对名字,内容各写一处。
  5. dsh.workbenchFaces 不是协议的一部分,但门禁必填:它指向两半的源码,不能指向出货 产物 —— 从 dist/host.js 反推不出是哪份源码把 dsh 的包拖进来的。

模板

只有一个模板(diy,也是默认):一份真能跑的样板 —— 宿主侧三条只读路由真读盘、卡片五态与写失败提示、 开箱((待填))的 design.json(填完 design check 才绿),外加一条可跑的长任务 / 两把工具(一只读一读写)/ 动作通道 / prefs。维护两份模板的成本 没人付,所以不再有 minimal 空壳;要空骨架就从 diy 删掉数据源那几段。

它是分层多文件骨架:纯逻辑住 src/model/**、视图件与同目录样式住 src/components/**、 文案住 src/messages.ts,测试住被测代码同目录的 __tests__/,两个面只剩注册与交 props;宿主侧内部实现住 src/host/** (moduleArch() 默认就认这一层)。带一份 arch.config.mjs 与两行的 eslint.config.mjs。

init 连模板里的 *.tpl 一起拷、按已知 token 名逐个盖章(__UNIT__ / __NAME__ / __DESC__ / __SHELL_RANGE__ / __DSH_UI_VERSION__)、剥掉后缀,再用 readme.json 生成 README、用 design.json 生成 DEVELOPING 那一段。 __SHELL_RANGE__ 取 init 所在工程实装的那份外壳版本;读不到就退回 module-tools 自己的版本; 两者都读不到才保持 ^0.1.0 并在 stderr 上说一句。

自己的自检

node scripts/selfcheck/index.mjs

工具箱的回归网:模板 init 一遍、跑全套;判据的正例与负例都要对;打包清单不含 .tpl 残留;design 的空格、README / DEVELOPING 同步、accept;publish 的清单;release 的验收闸、 dry-run、真发后的元数据与发布失败回滚。

相关文档

想了解读
模块怎么写、DIY 整套流程workbench-module-diy
构建骨架、接线(bundle / client / 注册)BUILD.md · WIRING.md
kit 组件与 hooksKIT.md
面板、状态矩阵、界面规则PANEL.md
工具、审批、长任务TOOLS.md
判据红了怎么读REVIEW.md
最小可安装样本module-hello
契约类型与共享组件shared

模块的 apply(ctx) / mount(deps) 只拿工作台自己的类型(HostContext / ClientContext / ToolDefinition / ToolRegistry / ToolRunContext,都在 @dsh-workbench/core/contract); 要用 dsh 的官方类型,把对应包放进模块自己的 devDependencies,不要放进 dependencies。

版本与发布记录

@dsh-workbench/module-tools 共发布 106 个版本,首次发布于 2026/10/02;过去 12 个月发布了 106 个版本。

版本发布日期距今
1.2.12latest2026/11/13今天
1.2.112026/11/13今天
1.2.102026/11/13今天
1.2.92026/11/13今天
1.2.82026/11/13今天
1.2.8-beta.2beta2026/11/13今天

关于 @dsh-workbench/module-tools 的常见问题

@dsh-workbench/module-tools 是什么?

模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。

如何安装 @dsh-workbench/module-tools?

在项目目录运行 npm install @dsh-workbench/module-tools,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 dsh-wb-module,可以用 npx dsh-wb-module 直接运行。

@dsh-workbench/module-tools 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/dsh-workbench__module-tools 是否存在。

@dsh-workbench/module-tools 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@dsh-workbench/module-tools 可以商用吗?

@dsh-workbench/module-tools 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dsh-workbench/module-tools 的下载量有多少?

根据 npm 官方下载统计,@dsh-workbench/module-tools 在 2026/10/13 – 2026/11/11 这 30 天内被下载 12,210 次,日均约 407 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dsh-workbench/module-tools 还在维护吗?

最新版本 1.2.12 发布于 2026/11/13(今天);至今共发布 106 个版本,其中过去 12 个月发布了 106 个;首次发布于 2026/10/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。