@dsh-workbench/module-tools 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  ```bash
9
9
  dsh-wb-module init <unit> [--dir <目录>] [--template minimal|diy] [--description "一句话"]
10
10
  dsh-wb-module build # 出 dist/host.js 与套了 loader 壳的 client.js
11
- dsh-wb-module check [--only <判据>] # 十条判据;--only 可以单跑
11
+ dsh-wb-module check [--only <判据>] # 十一条判据;--only 可以单跑
12
12
  dsh-wb-module design <init|check|render|accept>
13
13
  dsh-wb-module publish # 发货前的清单:门禁 + 三张表 + npm pack 清单(只 dry run)
14
14
  ```
@@ -16,11 +16,12 @@ dsh-wb-module publish # 发货前的清单:门禁 + 三张表 +
16
16
  `--cwd <目录>` 指定包目录(默认当前目录)。模块目录里更省事的是骨架自带的脚本:
17
17
  `pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck` / `pnpm design`。
18
18
 
19
- ## 十条判据(`check`)
19
+ ## 十一条判据(`check`)
20
20
 
21
21
  | 判据 | 盯什么 |
22
22
  |---|---|
23
- | `contracts` | 模块契约:单元 id 唯一、两个面、四个脚本、`files`、peer 下限 |
23
+ | `contracts` | 模块契约:单元 id 唯一、两个面、四个脚本、`files`、peer 下限;以及**清单 ↔ 代码**(真正注册的工具)与**底稿 ↔ 清单**(`design.json` 的能力名)两处对账(见下) |
24
+ | `protocol` | 模块声明协议:`dsh.workbench` 的 `apiVersion` 要在、静态半形状要对、不许有协议不认识的键(`scope` 现在**就是**不认识的键之一 —— 见下) |
24
25
  | `bundles` | 产物边界:宿主面里不许出现浏览器侧的东西 |
25
26
  | `source` | 源码边界:不直接引 dsh 的包、不引不是种子词的 `@deepseek-ai/*` |
26
27
  | `primitives` | dsh 原语只从模块表(`kit`)拿 |
@@ -38,12 +39,65 @@ dsh-wb-module publish # 发货前的清单:门禁 + 三张表 +
38
39
  三张表(面板 / 能力 / 联动)+ 状态矩阵 + 数据源记录 + 验收清单住在模块根目录的 `design.json`,
39
40
  README 里那一段由它生成(夹在 `<!-- dsh-wb:design:start/end -->` 之间)。
40
41
 
42
+ `design.json` 是**设计底稿**:只装人话(`oneLine` / `shape` / `approvalText` / 面板那几张表的文字 /
43
+ `datasource`),**不装机器字段**。**"这条能力在哪些工作区出现"不在底稿、也不在清单**:
44
+ 那是**用户**的事(他的面板板),模块作者声明不了(用户口径「模块开发者不能定义 scope,
45
+ scope 永远是用户来决定的」,见 [ADR-0038](../../docs/adr/0038-module-declaration-protocol.md)
46
+ §2.4 / §6.16)。
47
+ `contracts` 会把底稿的能力名与清单的工具名做**键集合对账**(不重不漏,只对名字):
48
+ 底稿多一条、清单多一把都点名判红;没有 `design.json` 的模块不查。
49
+
41
50
  ```bash
42
51
  dsh-wb-module design check # 空着的格一格格点名叫出来 + README 有没有落后
43
52
  dsh-wb-module design render # 用 design.json 重新生成 README 那一段
44
53
  dsh-wb-module design accept # 用户逐条点头之后,把验收清单打勾
45
54
  ```
46
55
 
56
+ ## 协议:`dsh` 字段里的五段
57
+
58
+ 一个模块包在 `package.json` 里声明五件事,缺哪一段在哪一层炸都不一样:
59
+
60
+ | 段 | 是什么 | 谁读 |
61
+ |---|---|---|
62
+ | `dsh.bundle.patch` | 把自己插进 profile 组合图的 cordis 补丁 | dsh 的组合器(**装载**) |
63
+ | `dsh.client{platform,inject,external}` | 浏览器侧接线 | dsh 的客户端组合器(**dsh 自己不校验**,错的声明要等浏览器 `require` 才炸 —— 所以 `contracts` 替你查) |
64
+ | `dsh.workbench` | **模块声明协议**(下详) | 门禁 · 市场 · 外壳 |
65
+ | `dsh.workbenchFaces{host,client}` | **工具链元数据**(**不在协议里**):两个 half 的**源码**入口 | 门禁:`source` 拿它算可达性、`contracts` 查它在位;运行期与装机预检都不读 |
66
+ | `peerDependencies['@dsh-workbench/core']` | 服务面:外壳 provide 的服务在不在 | dsh 的 `evaluatePluginCompatibility` |
67
+
68
+ ### `dsh.workbench`(协议 v1)
69
+
70
+ ```jsonc
71
+ "workbench": {
72
+ "apiVersion": 1, // 必填。形状契约,缺了外壳不认这个模块
73
+ "unit": "hello", // 单元 id(小写连字符),进持久化配置
74
+ "card": { "titleKey": "hello.cardTitle", "order": 90 }, // 没有卡片写 null
75
+ "tools": [{ "name": "hello_read", "writes": false }], // 没工具写 []
76
+ "host": { "inject": [] }, // 这个单元要外壳的哪几个服务(判"能不能挂")
77
+ "env": { "command": "opencli", "plugins": [], "libs": [] } // 可选:靠什么才跑得起来
78
+ },
79
+ "workbenchFaces": { "host": "src/host.ts", "client": "src/client.tsx" } // 工具链那一格(不在协议里):两半的源码入口
80
+ ```
81
+
82
+ 五条口径:
83
+
84
+ 1. **声明了 `apiVersion` = 宣布静态半写全了** —— `unit` / `card` / `tools` / `host`
85
+ 四个键都必须有(`card` 没有卡片写 `null`),`env` 没有外部供给时省略。
86
+ 2. **不许有协议不认识的键**:生字段会被外壳**静默忽略**(拼错一个键就等于那句话没写),
87
+ 所以 `protocol` 判据先拦。**`scope` 不在协议里**:它曾经是(`card.scope` / `tools[].scope`),
88
+ 现已删除 —— 一个能力在哪些工作区出现**只由用户的板决定**,作者写它 = 写了一个生字段、当场红
89
+ (`protocol` 会点名 `"scope"`)。见 [ADR-0038](../../docs/adr/0038-module-declaration-protocol.md) §2.4 / §6.16。
90
+ 3. **清单声明的工具 = 代码真正注册的工具**(U1b 起):清单是工具面的**唯一**来源,
91
+ `contracts` 拿 `mount()` 里 `registerTools()` 真正注册的名字对账,两边漂了就红。
92
+ 4. **底稿 ↔ 清单只对账键集合**(U1c 起):`design.json` 的 `capabilities[].name` 与清单的
93
+ `tools[].name` 不重不漏,**只对名字**(内容各写一处,没有可比的东西);没有 `design.json`
94
+ 的模块不查 —— 底稿是可选的设计材料。见 [ADR-0038](../../docs/adr/0038-module-declaration-protocol.md) §2.5 / §6.3。
95
+ 5. **`dsh.workbenchFaces` 不是协议的一部分,但门禁必填**(A-2 起):它是 `dsh.workbench` 的
96
+ **兄弟键**,写两半的**源码**入口;`source` 判据靠它算"这段源码属于哪一半"。
97
+ 写进 `dsh.workbench` 里会被判**生字段**(不留兼容),它也不能指向出货产物
98
+ —— 从 `dist/host.js` 反推不出是哪份源码把 dsh 的包拖进来的。见
99
+ [ADR-0038](../../docs/adr/0038-module-declaration-protocol.md) §7 第 7 条。
100
+
47
101
  ## 两个模板
48
102
 
49
103
  | 模板 | 是什么 |
@@ -5,14 +5,15 @@ import { check } from '../src/check.mjs';
5
5
  import { design } from '../src/design.mjs';
6
6
  import { init } from '../src/init.mjs';
7
7
  import { publish } from '../src/publish.mjs';
8
+ import { sync } from '../src/sync.mjs';
8
9
 
9
10
  const [command, ...rest] = process.argv.slice(2);
10
11
  const cwd = process.env.DSH_WB_MODULE_CWD ?? process.cwd();
11
12
 
12
- const commands = { build, check, init, design, publish };
13
+ const commands = { build, check, init, design, publish, sync };
13
14
 
14
15
  if (!command || !commands[command]) {
15
- console.error('用法:dsh-wb-module <build|check|init|design|publish> [--cwd <目录>]\n dsh-wb-module check [--only contracts|bundles|source|primitives|kit|cards|standalone|markers|style|alias]\n dsh-wb-module init <unit> [--dir <目录>] [--template minimal|diy] [--description <文字>]\n dsh-wb-module design <init|check|render|accept> # 三张表:填、查空格、生成 README、验收打勾\n dsh-wb-module publish # 发货前的清单:门禁 + npm pack 清单 + files 漏项(只 dry run)');
16
+ console.error('用法:dsh-wb-module <build|check|init|design|publish|sync> [--cwd <目录>]\n dsh-wb-module check [--only contracts|bundles|source|primitives|kit|cards|standalone|markers|style|alias]\n dsh-wb-module init <unit> [--dir <目录>] [--template minimal|diy] [--description <文字>]\n dsh-wb-module design <init|check|render|accept> # 三张表:填、查空格、生成 README、验收打勾\n dsh-wb-module sync # 按清单生成 src/declared-tools.ts(工具名只有清单一处写者)\n dsh-wb-module publish # 发货前的清单:门禁 + npm pack 清单 + files 漏项(只 dry run)');
16
17
  process.exit(2);
17
18
  }
18
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/module-tools",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,7 +20,8 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "esbuild": "^0.28.2"
23
+ "esbuild": "^0.28.2",
24
+ "@dsh-workbench/shared": "0.1.18"
24
25
  },
25
26
  "scripts": {
26
27
  "selfcheck": "node scripts/selfcheck.mjs"
package/src/check.mjs CHANGED
@@ -1,5 +1,4 @@
1
- // 十条判据的**聚合层**:跑、收集三态、打印、决定退出码。
2
- //
1
+ // 判据的**聚合层**:跑、收集三态、打印、决定退出码。有几条以 `CHECKS` 表为准。
3
2
  // ── 为什么判据改成"导出函数",打印与退出码都搬到这里 ──────────────────────────
4
3
  //
5
4
  // 在这之前,每条 `checks/*.mjs` 都是"import 即执行 + 自己 console.log + 自己 process.exit"
@@ -21,9 +20,10 @@
21
20
  // 失败 ✖ <title> <N> 条不过: 然后逐条 ` · <problem>`
22
21
  // 最后一行是汇总;有任何 fail 就 exit 1。
23
22
 
24
- // 十个名字与 title 一字不变 —— selfcheck 会把它与各判据模块导出的 `title` 逐个对一遍。
23
+ // 名字与 title 一字不变 —— selfcheck 会把它与各判据模块导出的 `title` 逐个对一遍。
25
24
  export const CHECKS = {
26
25
  contracts: { title: '模块契约', load: () => import('./checks/contracts.mjs') },
26
+ protocol: { title: '模块声明协议', load: () => import('./checks/protocol.mjs') },
27
27
  bundles: { title: '模块产物边界', load: () => import('./checks/bundles.mjs') },
28
28
  source: { title: '模块源码边界', load: () => import('./checks/source.mjs') },
29
29
  primitives: { title: 'dsh 原语只从模块表拿', load: () => import('./checks/primitives.mjs') },
@@ -4,7 +4,7 @@
4
4
  // 解析得到 —— 装到别人那里就是构建期找不到文件(`check:standalone` 会把它变成红灯,
5
5
  // 这条判据则直接指到源码那一行)。
6
6
  //
7
- // 形状(十条判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
7
+ // 形状(所有判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
8
8
  // 打印与退出码归 `../check.mjs` 那一层。
9
9
 
10
10
  import { readdirSync, readFileSync, statSync } from 'node:fs';
@@ -14,7 +14,7 @@
14
14
  // 覆盖范围跟着**自述清单**走:凡 `package.json` 里声明了 `dsh.workbench` 的包都被检查,
15
15
  // 新加一个模块不用改这个脚本。
16
16
  //
17
- // 形状(十条判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
17
+ // 形状(所有判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
18
18
  // 打印与退出码归 `../check.mjs` 那一层。
19
19
 
20
20
  import { existsSync, readFileSync } from 'node:fs';
@@ -12,7 +12,7 @@
12
12
  // `window.__ModuleLoader__.load({id, factory})`,用假的 loader / require 把它拉起来。
13
13
  // 覆盖范围跟着 `dsh.workbench` 自述清单走 —— 新模块不用改这里。
14
14
  //
15
- // 形状(十条判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
15
+ // 形状(所有判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
16
16
  // 打印与退出码归 `../check.mjs` 那一层。
17
17
 
18
18
  import { existsSync, readFileSync } from 'node:fs';
@@ -223,6 +223,28 @@ export async function run() {
223
223
  continue;
224
224
  }
225
225
 
226
+ // 清单的 `card.titleKey` 是**面板那一格的标题**,它的文案住在模块自己交的字典里
227
+ // (`registerUnit` 那个面的 `messages`,`zh` / `en` 都必填)。键拼错、或字典漏了那一条 ⇒
228
+ // 面板上显示的就是原始键名(`demo.cardTitle`),而且一声不响 —— 这条判据盯它。
229
+ const titleKey = pkg.manifest.dsh?.workbench?.card?.titleKey;
230
+ if (typeof titleKey === 'string' && titleKey !== '') {
231
+ const messages = registered?.messages;
232
+ if (messages === undefined || messages === null || typeof messages !== 'object') {
233
+ problems.push(
234
+ `${name}: 清单的 card.titleKey「${titleKey}」没有落点 —— registerUnit 交的面里没有 messages,` +
235
+ '面板上那一格会显示原始键名',
236
+ );
237
+ } else {
238
+ const missing = ['zh', 'en'].filter((one) => typeof messages[one]?.[titleKey] !== 'string');
239
+ if (missing.length > 0) {
240
+ problems.push(
241
+ `${name}: 清单的 card.titleKey「${titleKey}」在模块字典的 ${missing.join(' / ')} 里没有 —— ` +
242
+ '面板上那一格会显示原始键名。清单写了 titleKey,字典就得有这一条(zh / en 都要)',
243
+ );
244
+ }
245
+ }
246
+ }
247
+
226
248
  const props = {
227
249
  world: { current: {} },
228
250
  t: (key) => key,
@@ -288,7 +310,7 @@ export async function run() {
288
310
  }
289
311
  return {
290
312
  status: 'pass',
291
- notes: [`${rows.length} 张卡片在**空数据**下都画得出来(不许抛)`, ...rows],
313
+ notes: [`${rows.length} 张卡片在**空数据**下都画得出来(不许抛);清单的 card.titleKey 在字典里都有落点`, ...rows],
292
314
  problems: [],
293
315
  };
294
316
  }