@dsh-workbench/module-tools 0.3.1 → 0.3.3
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 +135 -8
- package/bin/dsh-wb-module.mjs +50 -4
- package/package.json +12 -4
- package/src/arch.mjs +234 -0
- package/src/build.mjs +4 -1
- package/src/check.mjs +2 -0
- package/src/checks/arch.mjs +127 -0
- package/src/checks/lint.mjs +351 -0
- package/src/design.mjs +46 -5
- package/src/lint.mjs +309 -0
- package/template/README.md.tpl +18 -3
- package/template/arch.config.mjs +2 -0
- package/template/eslint.config.mjs +2 -0
- package/template/package.json.tpl +2 -0
- package/template/src/client.tsx.tpl +4 -18
- package/template/src/components/HelloCard.tsx.tpl +28 -0
- package/template-diy/README.md.tpl +44 -19
- package/template-diy/arch.config.mjs +2 -0
- package/template-diy/design.json.tpl +1 -0
- package/template-diy/eslint.config.mjs +2 -0
- package/template-diy/package.json.tpl +2 -0
- package/template-diy/src/client.tsx.tpl +10 -149
- package/template-diy/src/components/OverviewCard.module.css.tpl +5 -0
- package/template-diy/src/components/OverviewCard.test.ts.tpl +118 -0
- package/template-diy/src/components/OverviewCard.tsx.tpl +92 -0
- package/template-diy/src/host.ts.tpl +1 -14
- package/template-diy/src/model/overview.test.ts.tpl +49 -0
- package/template-diy/src/model/overview.ts.tpl +61 -0
- package/template-diy/src/plugin.test.ts.tpl +4 -140
- package/template-diy/types/styles.d.ts +14 -0
package/README.md
CHANGED
|
@@ -8,15 +8,16 @@
|
|
|
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 <判据>] #
|
|
11
|
+
dsh-wb-module check [--only <判据>] # 十三条判据;--only 可以单跑
|
|
12
|
+
dsh-wb-module lint [--fix] # 跑模块自己那份 eslint 配置;--fix 写回
|
|
12
13
|
dsh-wb-module design <init|check|render|accept>
|
|
13
14
|
dsh-wb-module publish # 发货前的清单:门禁 + 三张表 + npm pack 清单(只 dry run)
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
`--cwd <目录>` 指定包目录(默认当前目录)。模块目录里更省事的是骨架自带的脚本:
|
|
17
|
-
`pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck` / `pnpm design`。
|
|
18
|
+
`pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck` / `pnpm check` / `pnpm lint` / `pnpm design`。
|
|
18
19
|
|
|
19
|
-
##
|
|
20
|
+
## 十三条判据(`check`)
|
|
20
21
|
|
|
21
22
|
| 判据 | 盯什么 |
|
|
22
23
|
|---|---|
|
|
@@ -31,13 +32,128 @@ dsh-wb-module publish # 发货前的清单:门禁 + 三张表 +
|
|
|
31
32
|
| `markers` | 公开包无内部标识(内网域名、企业内部名) |
|
|
32
33
|
| `style` | 样式归属:模块不许定义外壳类(`wb-*`)的规则(判据看**复合选择器**,`:global(.wb-x)` 也算,只有同一个复合选择器里带模块自己的类才放行);**裸 `<a>` 与裸交互控件(`<button>` / `<input>` / `<select>`)都判红** —— 前者用 kit 的 `Link`,后者用 `Button` / `TextField` / `SelectField` / `ChoiceGroup`;只有 `<textarea>` 与 `input[type=hidden\|file]` 例外(两处都没有那种形态);**手写的「标签 + 控件」行(标签元素紧邻 `SelectField` / `TextField` / `ChoiceGroup` / `Input`)与模块自己给 `field`/`form` 容器设的行距(`gap` / 上下 `margin`)也判红** —— 这一层归 kit 的 `FormRow(s)`(面 → 行 → 控件);现存违规收进 `FORM_ROW_EXEMPT`(棘轮:迁一处删一行,且表里已经不再命中的行会当场报红);**手写的操作区(借了 `wb-formActions` 槽类手摆按钮)也判红** —— 操作区归 kit 的 `FormActions`(顺序 / 一个区一颗 `primary` / `danger` 的位置 / 间距 / 窄容器换行都由它定,调用点只给数据与语义),现存违规收进 `FORM_ACTIONS_EXEMPT`(同一套棘轮:迁一处删一行,表里不再命中的行当场报红) |
|
|
33
34
|
| `alias` | 模块不指外壳:源码里没有指向外壳 `@/` 的引用 |
|
|
35
|
+
| `arch` | 模块自己的**分层形状**(下面单开一节):层序表外的目录 / 反向依赖 / 跨运行时 / 巨文件 / 模型层没测试 / 样式里手搓数值与 `!important`。**只跑带 `arch.config.mjs` 的模块** —— 没有这份文件就是 `○ 不适用`,不是红 |
|
|
36
|
+
| `lint` | 模块自己的**语法卫生**(下面单开一节):`as any` / 残留 `console` / `debugger` / 内联颜色字面量 / 字符串里的 `!important` / react-hooks。**只跑带 `eslint.config.mjs` 的模块** —— 没有那份文件就是 `○ 不适用`,不是红;规则住在 module-tools 的方言里、随工具链同代 |
|
|
34
37
|
|
|
35
38
|
三态输出:`✔ 通过` / `○ 不适用` / `✖ 不过`;有任何不过就退出 1。
|
|
36
39
|
|
|
40
|
+
### `arch`:模块包的架构方言
|
|
41
|
+
|
|
42
|
+
新加的一条(十三条判据里的第十二条)。此前那十一条全在盯**边界**(声明 / 产物 / 可达性 / 卡片渲染 / 样式归属),
|
|
43
|
+
没有一条在看**代码结构** —— 卡片一复杂就往 `src/client.tsx` 里堆,宿主侧顺手 import 一个组件
|
|
44
|
+
(`dist/host.js` 在 Node 里起不来)也没人拦。`arch` 把 arch-guard 套到模块包上补这一层。
|
|
45
|
+
|
|
46
|
+
模板里那份 `arch.config.mjs` **只有一行**:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
import { moduleArch } from '@dsh-workbench/module-tools/arch';
|
|
50
|
+
export default moduleArch();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
方言(层序表、两个运行时、阈值、允许的依赖)**只有一份**,住在
|
|
54
|
+
`@dsh-workbench/module-tools/arch` 的 `moduleArch()` 里,随工具链与外壳同代更新 —— 不要求每个模块
|
|
55
|
+
抄一份配置,也不认 arch-guard 的「应用 / 库 / FSD」范式(模块是一个插件包 + 两个面的小库,
|
|
56
|
+
`src/messages.ts` 这种根文件在库范式下会报 S01「无处安放」)。
|
|
57
|
+
|
|
58
|
+
层序(数字越小越底层,S21:只许依赖层号 ≤ 自己的文件):
|
|
59
|
+
|
|
60
|
+
| 落点 | 层 | 是什么 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `src/model/**` | 1 | 纯逻辑:类型 / 换算 / 状态机。两个运行时都可达,不碰 react / kit / dsh / `node:*` |
|
|
63
|
+
| `src/messages.ts` | 1 | 文案(zh / en) |
|
|
64
|
+
| `src/components/**` | 2 | 浏览器侧的视图件;样式与它同目录(`X.module.css`) |
|
|
65
|
+
| `src/host.ts` | 10 | 宿主面 |
|
|
66
|
+
| `src/client.tsx` | 10 | 浏览器面 |
|
|
67
|
+
| `**/*.test.ts(x)` | 99 | 测试(S21 跳过) |
|
|
68
|
+
|
|
69
|
+
目录**按需要出现**(空壳模块可以只有 `components/`),但多一个目录就是多一处 S01 —— 要加层就在
|
|
70
|
+
自己那份配置里明写。两个运行时靠 S23 隔离:**`src/components/**` 整片算 client 面**,所以宿主侧
|
|
71
|
+
import 一个组件是硬红(层号拦不住它:components 第 2 层、host 第 10 层,S21 认为合法)。
|
|
72
|
+
|
|
73
|
+
内置口径:阈值 `fileLines 200` / `viewLines 200` / `functionLines 80`(严于 arch-guard 默认的
|
|
74
|
+
500 / 500 / 150);允许的运行时依赖只有 `react` · `@dsh-workbench/core` · `@dsh-workbench/shared`;
|
|
75
|
+
样式目录 `src/components` + CSS Modules;`src/model/**` 有文件就必须有测试;模块**不拥有**主题
|
|
76
|
+
(不声明令牌表,`--dsw-*` 由 dsh 运行时给)。
|
|
77
|
+
|
|
78
|
+
要开口子**从参数进**(别改判据;每条口子都有名字,评审看得见):
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { moduleArch } from '@dsh-workbench/module-tools/arch';
|
|
82
|
+
export default moduleArch({
|
|
83
|
+
fileLines: 300, // 放宽体积阈值(fileLines / viewLines / functionLines)
|
|
84
|
+
deps: ['some-sdk'], // 追加放行的运行时依赖
|
|
85
|
+
layers: { adapters: 2 }, // 追加 / 覆盖内部目录表
|
|
86
|
+
roles: [{ id: 'lib:version', pattern: 'src/version.ts', layer: 1 }], // 追加角色
|
|
87
|
+
// structure: { … }, // 整体替换结构声明(面在别处 / 只有一个运行时)
|
|
88
|
+
exceptions: [{ rule: 'S16', glob: 'src/components/Legacy.tsx', reason: '待拆,见 #123' }],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`exceptions` 每条**必须写 `reason`**(arch-guard 的硬要求)。红了怎么读、常见红、怎么从参数开口子,
|
|
93
|
+
见随外壳包出货的模块作者指引 `skills/workbench-module-diy/REVIEW.md`。
|
|
94
|
+
|
|
95
|
+
### `lint`:模块包的语法卫生方言
|
|
96
|
+
|
|
97
|
+
第十三条。`arch` 管**结构**(层序 / 反向依赖 / 跨运行时 / 巨文件),`lint` 管**单文件语法卫生**:
|
|
98
|
+
`as any` 绕过契约、残留的 `console.log` / `debugger`、TS 内联样式里的颜色字面量与 `!important`、
|
|
99
|
+
react-hooks 那几条(渲染期写 ref / 漏依赖 / 条件调用)。两半合起来"手搓外观、绕过契约"才闭合 ——
|
|
100
|
+
`*.module.css` 那一半归 arch-guard 的 D 族,TS 里的这一半归这里。
|
|
101
|
+
|
|
102
|
+
模板里那份 `eslint.config.mjs` **只有两行**:
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
import { moduleLint } from '@dsh-workbench/module-tools/lint';
|
|
106
|
+
export default moduleLint({ rootDir: import.meta.dirname });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`moduleLint(options)` 返回一份**标准 flat config 数组**(不是私有格式)—— 所以它同时是"判据跑的那份
|
|
110
|
+
配置"与"编辑器 / `eslint` 认的那份配置",没有第二份真相。方言(规则、ignores、那一组 react 与
|
|
111
|
+
react-hooks 取舍)**只有一份**,住在 `@dsh-workbench/module-tools/lint`,随工具链与外壳同代更新;
|
|
112
|
+
依赖(`eslint` / `@eslint/js` / `typescript-eslint` / 两个 react 插件)装在 **module-tools 一家**,
|
|
113
|
+
模块与工坊根的清单**一个字都不用改**。
|
|
114
|
+
|
|
115
|
+
`rootDir` 把 typescript-eslint 的 `tsconfigRootDir` 钉在这份配置所在的目录。**不传也能跑**(一个
|
|
116
|
+
进程里只有一份配置时 parser 自己猜得对);但本仓根上还有一份 `eslint.config.mjs`,ESLint 10 的扁平
|
|
117
|
+
配置按目录就近选,一个进程里同时出现多份候选时它猜不出来(每个文件一条
|
|
118
|
+
`No tsconfigRootDir was set, and multiple candidate TSConfigRootDirs are present`),所以模板与骨架
|
|
119
|
+
都带着它。
|
|
120
|
+
|
|
121
|
+
要开口子**从命名参数进**(`ignores` / `rules` / `rootDir`),也可以展开 `moduleLint()` 再追自己的一块:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { moduleLint } from '@dsh-workbench/module-tools/lint';
|
|
125
|
+
export default [
|
|
126
|
+
...moduleLint({ rootDir: import.meta.dirname, rules: { 'no-console': 'off' } }),
|
|
127
|
+
{ files: ['src/legacy/**'], rules: { '@typescript-eslint/no-explicit-any': 'off' } },
|
|
128
|
+
];
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**指纹是逐块比整份方言**:`moduleLint()`(不带 options)产出里除 `/overrides` 与 `/root`(options
|
|
132
|
+
驱动的那两块)之外**全部 8 块**,在模块那份配置里必须一块不少,且名字 / `files` / `rules` 与
|
|
133
|
+
`moduleLint()` 产出的**逐项一致** —— `@eslint/js/recommended` · `typescript-eslint/base` ·
|
|
134
|
+
`typescript-eslint/eslint-recommended` · `typescript-eslint/recommended`,加 `dsh-wb/module-lint`
|
|
135
|
+
那四块。全局 ignores 单列按**超集**比:多忽略几条可以(那是 `moduleLint({ ignores })` 这个口子),
|
|
136
|
+
少一条就点名缺哪条 glob。少一块、同名出现两次、删一条规则、改一个规则值都当场红,主句是
|
|
137
|
+
`… 里的方言与 moduleLint() 产出的不一致 —— <明细>`。所以**只抄一个名字不行**(`export default
|
|
138
|
+
[{ name: 'dsh-wb/module-lint', rules: {} }]` 也红),`export default []`、语法错的配置、以及
|
|
139
|
+
`moduleLint().filter((b) => b.name !== undefined)` 这类"外科手术式"筛法(会筛掉全局 ignores 或
|
|
140
|
+
基础块)都红;要开口子走 `moduleLint({ rules, ignores })` 或展开 `moduleLint()` 再追加自己的一块。
|
|
141
|
+
**残余(只剩这一条)**:模块自己 import 这个方言、再把整份照抄一遍,判据分不出来(它比的是形状,
|
|
142
|
+
不是"这段代码从哪来")—— 这条是**抬高门槛 + 让改动必须明着写**,不是"防住蓄意伪造"。
|
|
143
|
+
|
|
144
|
+
`scripts/**` 与 `*.config.*` **不判 `no-console`**:模块自己的 `scripts/smoke.mjs` 那几处
|
|
145
|
+
`console.log` 是它的本职工作,不是 debug 残留。`src/components/vendor/**` 是**唯一**允许
|
|
146
|
+
`!important` 的地方(方言里写成 `**/components/vendor/**`,与 arch 那份 `vendorDir` 同一处)。
|
|
147
|
+
|
|
148
|
+
`dsh-wb-module lint [--fix]` 单独跑:`--fix` 先写回再跑一遍看还剩什么。**`check` 永远不动手改文件**
|
|
149
|
+
—— 门禁是判据,不是修复器。
|
|
150
|
+
|
|
151
|
+
为什么依赖装在 module-tools 一家、为什么用"文件在不在"做 opt-in、`warn` 为什么不判红:见 ADR-0060。
|
|
152
|
+
|
|
37
153
|
## 三张表:`design.json`
|
|
38
154
|
|
|
39
|
-
三张表(面板 / 能力 / 联动)+ 状态矩阵 + 数据源记录 + 验收清单住在模块根目录的 `design.json`,
|
|
40
|
-
README 里那一段由它生成(夹在 `<!-- dsh-wb:design:start/end -->`
|
|
155
|
+
三张表(面板 / 能力 / 联动)+ 状态矩阵 + 数据源记录 + 写通道记录 + 验收清单住在模块根目录的 `design.json`,
|
|
156
|
+
README 里那一段由它生成(夹在 `<!-- dsh-wb:design:start/end -->` 之间);验收清单由 agent 自己在真卡片上逐条走通后打勾。
|
|
41
157
|
|
|
42
158
|
`design.json` 是**设计底稿**:只装人话(`oneLine` / `shape` / `approvalText` / 面板那几张表的文字 /
|
|
43
159
|
`datasource`),**不装机器字段**。**"这条能力在哪些工作区出现"不在底稿、也不在清单**:
|
|
@@ -46,10 +162,14 @@ scope 永远是用户来决定的」)。
|
|
|
46
162
|
`contracts` 会把底稿的能力名与清单的工具名做**键集合对账**(不重不漏,只对名字):
|
|
47
163
|
底稿多一条、清单多一把都点名判红;没有 `design.json` 的模块不查。
|
|
48
164
|
|
|
165
|
+
有写能力的模块,还要在 `writes[]` 里落它的**命令 / 入参 / 出参列 / 预览档 / 回读 / 失败形状**
|
|
166
|
+
(一条写一行;只有一条写时写成 `write: { … }`)—— 与 `datasource` 那张表对称,渲染成 README 里
|
|
167
|
+
「写通道探明记录」那一节。**没有写通道的模块不用写**,`design check` 不会因此变红。
|
|
168
|
+
|
|
49
169
|
```bash
|
|
50
170
|
dsh-wb-module design check # 空着的格一格格点名叫出来 + README 有没有落后
|
|
51
171
|
dsh-wb-module design render # 用 design.json 重新生成 README 那一段
|
|
52
|
-
dsh-wb-module design accept #
|
|
172
|
+
dsh-wb-module design accept # 自己在真卡片上逐条走通后,把验收清单打勾
|
|
53
173
|
```
|
|
54
174
|
|
|
55
175
|
## 协议:`dsh` 字段里的五段
|
|
@@ -103,6 +223,13 @@ dsh-wb-module design accept # 用户逐条点头之后,把验收清单打勾
|
|
|
103
223
|
| `minimal`(默认) | 空壳:卡壳、文案、测试接好了,没有数据源 |
|
|
104
224
|
| `diy` | 一份真能跑的样板:宿主侧一条只读路由真读盘、卡片五态,**三张表也是填好的** |
|
|
105
225
|
|
|
226
|
+
两个模板都是**分层多文件**骨架,不是单文件:纯逻辑(类型 / 状态机 / 换算)住 `src/model/**`、
|
|
227
|
+
视图件与它同目录的样式住 `src/components/**`、文案住 `src/messages.ts`,`src/host.ts` /
|
|
228
|
+
`src/client.tsx` 只剩两个面(`client.tsx` 只注册、把 props 交给组件)。目录按需要出现 ——
|
|
229
|
+
`minimal` 没有纯逻辑就不硬造 `model/`。两个模板各自带一份一行工厂的 `arch.config.mjs` 与一份两行的
|
|
230
|
+
`eslint.config.mjs`,`package.json` 里多 `check`(方言里的 M09 要求有这个脚本名)与 `lint` 两条脚本。
|
|
231
|
+
为什么这么分、怎么给自己的模块加配置,见 `## 十三条判据` 里的 `arch` 与 `lint` 那两节。
|
|
232
|
+
|
|
106
233
|
`init` 会连模板里的 `*.tpl` 一起拷过去、按**已知 token 名**逐个盖章
|
|
107
234
|
(`__UNIT__` / `__NAME__` / `__DESC__` / `__SHELL_RANGE__`)、剥掉 `.tpl` 后缀,
|
|
108
235
|
再用 `design.json` 生成 README 那一段。
|
|
@@ -118,10 +245,10 @@ dsh-wb-module design accept # 用户逐条点头之后,把验收清单打勾
|
|
|
118
245
|
## 自己的自检
|
|
119
246
|
|
|
120
247
|
```bash
|
|
121
|
-
node scripts/selfcheck.mjs
|
|
248
|
+
node scripts/selfcheck/index.mjs
|
|
122
249
|
```
|
|
123
250
|
|
|
124
|
-
工具箱的回归网:两个模板各 `init` 一遍、各跑全套(门禁 +
|
|
251
|
+
工具箱的回归网:两个模板各 `init` 一遍、各跑全套(门禁 + 骨架自带的脚本);判据的**正例与负例**都要对;
|
|
125
252
|
打包清单不含 `.tpl` 残留;`design` 的空格、README 同步、`accept`;`publish` 的清单。
|
|
126
253
|
|
|
127
254
|
## 去哪看"为什么"
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -1,19 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
3
4
|
import { build } from '../src/build.mjs';
|
|
4
|
-
import { check } from '../src/check.mjs';
|
|
5
|
+
import { CHECKS, check } from '../src/check.mjs';
|
|
5
6
|
import { design } from '../src/design.mjs';
|
|
6
7
|
import { init } from '../src/init.mjs';
|
|
7
8
|
import { publish } from '../src/publish.mjs';
|
|
8
9
|
import { sync } from '../src/sync.mjs';
|
|
9
10
|
|
|
11
|
+
// `dsh-wb-module lint [--fix]`:跑模块**自己那份** `eslint.config.mjs`(方言住在 `src/lint.mjs`)。
|
|
12
|
+
// ESLint 的构造只有一处 —— `src/checks/lint.mjs` 的 `lintPackage`,判据与这条命令共用它;
|
|
13
|
+
// 这里只管打印与退出码(与 `check` 那条路同一分工)。
|
|
14
|
+
//
|
|
15
|
+
// 为什么 eslint 是**懒 import**:`build` / `init` 是工坊里的热路径,而 eslint 那一坨加载要几百毫秒 ——
|
|
16
|
+
// 静态 import 会让每条命令都替这条 lint 付一次。写成命令里的动态 import,只有真跑 lint 才付。
|
|
17
|
+
async function lint({ root, args }) {
|
|
18
|
+
const [{ CONFIG_FILE, lintPackage }, { discoverModulePackages }] = await Promise.all([
|
|
19
|
+
import('../src/checks/lint.mjs'),
|
|
20
|
+
import('../src/discover.mjs'),
|
|
21
|
+
]);
|
|
22
|
+
const fix = args.includes('--fix');
|
|
23
|
+
const modulePackages = discoverModulePackages({ root });
|
|
24
|
+
const declared = modulePackages.filter((pkg) => existsSync(join(pkg.dir, CONFIG_FILE)));
|
|
25
|
+
if (declared.length === 0) {
|
|
26
|
+
console.log(`没有模块包带 ${CONFIG_FILE}(模块包 ${modulePackages.length} 个)—— 没什么可 lint 的。`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let failed = 0;
|
|
31
|
+
for (const pkg of declared) {
|
|
32
|
+
const result = await lintPackage(pkg, { fix });
|
|
33
|
+
if (result.error !== undefined) {
|
|
34
|
+
console.error(`✖ ${result.error}`);
|
|
35
|
+
failed += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const fixed = fix ? `(修了 ${result.fixed} 条 / 还剩 ${result.errors + result.warnings} 条)` : '';
|
|
39
|
+
console.log(
|
|
40
|
+
`${result.errors > 0 ? '✖' : '✔'} ${result.name}:${result.files} 个文件 · ` +
|
|
41
|
+
`${result.errors} error · ${result.warnings} warn${fixed}`,
|
|
42
|
+
);
|
|
43
|
+
for (const problem of result.problems) console.log(` · ${problem}`);
|
|
44
|
+
for (const warning of result.warnLines) console.log(` · ${warning}`);
|
|
45
|
+
if (result.errors > 0) failed += 1;
|
|
46
|
+
}
|
|
47
|
+
if (failed > 0) process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
10
50
|
const [command, ...rest] = process.argv.slice(2);
|
|
11
51
|
const cwd = process.env.DSH_WB_MODULE_CWD ?? process.cwd();
|
|
12
52
|
|
|
13
|
-
const commands = { build, check, init, design, publish, sync };
|
|
53
|
+
const commands = { build, check, init, design, publish, sync, lint };
|
|
14
54
|
|
|
15
55
|
if (!command || !commands[command]) {
|
|
16
|
-
console.error(
|
|
56
|
+
console.error(`用法:dsh-wb-module <build|check|init|design|publish|sync|lint> [--cwd <目录>]
|
|
57
|
+
dsh-wb-module check [--only ${Object.keys(CHECKS).join('|')}]
|
|
58
|
+
dsh-wb-module init <unit> [--dir <目录>] [--template minimal|diy] [--description <文字>]
|
|
59
|
+
dsh-wb-module design <init|check|render|accept> # 三张表:填、查空格、生成 README、自验打勾
|
|
60
|
+
dsh-wb-module sync # 按清单生成 src/declared-tools.ts(工具名只有清单一处写者)
|
|
61
|
+
dsh-wb-module lint [--fix] # 模块自己那份 eslint.config.mjs;--fix 写回文件
|
|
62
|
+
dsh-wb-module publish # 发货前的清单:门禁 + npm pack 清单 + files 漏项(只 dry run)`);
|
|
17
63
|
process.exit(2);
|
|
18
64
|
}
|
|
19
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"dsh-wb-module": "./bin/dsh-wb-module.mjs"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
|
-
".": "./src/index.mjs"
|
|
11
|
+
".": "./src/index.mjs",
|
|
12
|
+
"./arch": "./src/arch.mjs",
|
|
13
|
+
"./lint": "./src/lint.mjs"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
16
|
"bin",
|
|
@@ -20,10 +22,16 @@
|
|
|
20
22
|
"access": "public"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
25
|
+
"@arch-guard/core": "^0.11.0",
|
|
26
|
+
"@eslint/js": "^10.0.1",
|
|
23
27
|
"esbuild": "^0.28.2",
|
|
24
|
-
"
|
|
28
|
+
"eslint": "^10.11.0",
|
|
29
|
+
"eslint-plugin-react": "^7.37.5",
|
|
30
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
31
|
+
"typescript-eslint": "^8.71.0",
|
|
32
|
+
"@dsh-workbench/shared": "0.3.3"
|
|
25
33
|
},
|
|
26
34
|
"scripts": {
|
|
27
|
-
"selfcheck": "node scripts/selfcheck.mjs"
|
|
35
|
+
"selfcheck": "node scripts/selfcheck/index.mjs"
|
|
28
36
|
}
|
|
29
37
|
}
|
package/src/arch.mjs
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// 模块包的**架构方言**(arch-guard 的模块版)。
|
|
2
|
+
//
|
|
3
|
+
// 为什么要有这一份:模块包是"用户自己写"的代码,而模板给的是一个**单文件**起步的形状 ——
|
|
4
|
+
// 卡片一复杂(多路取数、多态渲染、几个子件、几条文案)那个文件就会长到几百行,而门禁
|
|
5
|
+
// (`check` 原本那十一条)只看产物边界与声明形状,**没有一条在看代码结构**。arch-guard 正好补这一层,
|
|
6
|
+
// 但它认识的是"应用 / 库 / FSD"三种范式,模块包(一个插件包、两个面的小库)不在其中:
|
|
7
|
+
// 直接把库范式套上去,`src/messages.ts` 这种根文件会报 S01「无处安放」,而两个运行时的
|
|
8
|
+
// 互相 import 它也不管。所以这里把模块包的方言**写死一处**:层序表、两个运行时、阈值、
|
|
9
|
+
// 允许的依赖、样式与文案的口径。
|
|
10
|
+
//
|
|
11
|
+
// 为什么不是让每个模板抄一份 arch.config.mjs:那样 ① 每个模块的方言会各自漂,
|
|
12
|
+
// ② 方言跟着**工具链**走才对 —— 工坊那三个包本来就"与外壳同代"(seed-workshop.sh 的
|
|
13
|
+
// 代际自愈),把方言放在 module-tools 里,它随代际一起更新,用户不用重抄一份配置。
|
|
14
|
+
// 模板里那份 `arch.config.mjs` 只留一行 `moduleArch()`,要改(加目录、加依赖、放宽阈值)
|
|
15
|
+
// 就在**这一处**改。
|
|
16
|
+
//
|
|
17
|
+
// 层序(数字越小越底层,S21:只许依赖层号 ≤ 自己的文件):
|
|
18
|
+
//
|
|
19
|
+
// src/model/** 1 纯逻辑:类型 / 换算 / 状态机。两个运行时都可达,不碰 react / kit / node:fs
|
|
20
|
+
// src/messages.ts 1 文案(zh / en)。同样是"谁都能用的数据"
|
|
21
|
+
// src/declared-tools.ts 1 `dsh-wb-module sync` 生成的工具名(工具链自己的产物,见下)
|
|
22
|
+
// src/components/** 2 浏览器侧的视图件;样式与它同目录(`*.module.css`)
|
|
23
|
+
// src/host.ts 10 宿主面(`dsh.workbenchFaces.host`)
|
|
24
|
+
// src/client.tsx 10 浏览器面(`dsh.workbenchFaces.client`)
|
|
25
|
+
// **/*.test.ts(x) 99 测试(库范式自带这条排他角色,S21 直接跳过;S16 的体积阈值也不管它)
|
|
26
|
+
// src/**/__tests__/** 99 测试的另一个家(方言自己声明的,见 TESTS_DIR_ROLE 那段)
|
|
27
|
+
//
|
|
28
|
+
// 两个运行时**互相隔离**:`structure.runtimes` + `faces` 让 S23 认得出"宿主侧文件 import 了
|
|
29
|
+
// 浏览器面"(反过来也一样)。这一条以前没有任何判据管 —— `check:source` 只管 dsh 的包在不在
|
|
30
|
+
// 客户端侧可达。
|
|
31
|
+
//
|
|
32
|
+
// 阈值刻意**严于** arch-guard 的默认(500 行 / 150 行函数):模块包是"一个人写的一块面板",
|
|
33
|
+
// 一个文件 200 行、一个函数 80 行就该拆了 —— 这条不是审美,是"复杂模块不会退化成一个巨文件"
|
|
34
|
+
// 的可判定下限。要更宽就在自己那份配置里传 `fileLines`。
|
|
35
|
+
//
|
|
36
|
+
// 三条内置豁免条目 / 两条规则(都要写 reason,arch-guard 的硬要求):D04 的 `--dsw-*` 令牌
|
|
37
|
+
// (我们**不拥有**主题,见 STYLE_EXCEPTION 那段)与 S16 的测试文件(脚手架不是产品代码,
|
|
38
|
+
// 见 TEST_LINES_EXCEPTIONS 那段 —— 后缀式与 `__tests__/**` 各一条)。
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
cssModulesKit,
|
|
42
|
+
deps,
|
|
43
|
+
designSystem,
|
|
44
|
+
hygiene,
|
|
45
|
+
i18n,
|
|
46
|
+
library,
|
|
47
|
+
metrics,
|
|
48
|
+
noneI18nKit,
|
|
49
|
+
styles,
|
|
50
|
+
} from '@arch-guard/core/presets';
|
|
51
|
+
import { DECLARED_TOOLS_FILE } from './declared-tools.mjs';
|
|
52
|
+
|
|
53
|
+
// 两个面**就是** `dsh.workbenchFaces` 指的那两个文件(协议要求,见 ADR-0038)。改这两个名字
|
|
54
|
+
// 等于改声明,判据(contracts / source)先红 —— 所以这里是常量,不是选项。
|
|
55
|
+
const HOST_FACE = 'src/host.ts';
|
|
56
|
+
const CLIENT_FACE = 'src/client.tsx';
|
|
57
|
+
|
|
58
|
+
// 内部目录表(`library()` 的 modules)。目录名是约定:模型 / 视图,别的一个都不许有 ——
|
|
59
|
+
// 多出来的目录会被 S01 报"不在目录契约内",那正是要的:要加一层就明写在配置里。
|
|
60
|
+
const LAYERS = { model: 1, components: 2 };
|
|
61
|
+
|
|
62
|
+
// `src/messages.ts` 是文件不是目录,而 `modules` 只认目录前缀(文件级 key 实测仍报 S01),
|
|
63
|
+
// 所以它走 `addRoles`。层号与 model 同层:文案不依赖任何东西,两端都能读。
|
|
64
|
+
const MESSAGES_ROLE = { id: 'lib:messages', pattern: 'src/messages.ts', layer: 1 };
|
|
65
|
+
|
|
66
|
+
// `src/declared-tools.ts` 是 `dsh-wb-module sync` 按清单生成的工具名(`tools[].key` 那一档,
|
|
67
|
+
// 见 declared-tools.mjs)—— 与 messages.ts 一样是"谁都能读的常量",所以同层、同样走 `addRoles`
|
|
68
|
+
// (它也是文件不是目录)。这一条必须内置:那是**工具链自己的产物**,不认它等于"照我们推荐的路走
|
|
69
|
+
// 就会撞 S01"(实测:作者得翻源码才知道 roles 的形状,再手写一行补上)。
|
|
70
|
+
// 路径直接取 declared-tools 那份常量,不在这里抄一遍字符串(工具名只有一处写者,这条也一样)。
|
|
71
|
+
const DECLARED_TOOLS_ROLE = { id: 'lib:declared-tools', pattern: DECLARED_TOOLS_FILE, layer: 1 };
|
|
72
|
+
|
|
73
|
+
const DEFAULT_THRESHOLDS = { fileLines: 200, viewLines: 200, functionLines: 80 };
|
|
74
|
+
|
|
75
|
+
// 测试文件不吃 S16 的体积阈值(类级豁免:arch-guard 的 exceptions 只认"某条规则对某类文件不适用")。
|
|
76
|
+
// 它与产品代码不是同一种东西 —— 夹具、逐条断言、一条用例十来行,200 行对它是错的口径;而"拆成两个
|
|
77
|
+
// 测试文件"只会把脚手架摞进 src/(作者反馈:一个模块被这条逼出 `src/host/testing.ts`,测试脚手架住进
|
|
78
|
+
// 了宿主层目录)。它本来就有一条排他角色(layer 99,S21 直接跳过),这条豁免与那条同一个理由。
|
|
79
|
+
// 为什么不给"测试单列一个阈值":S16 只读 thresholds 的 fileLines / viewLines / functionLines
|
|
80
|
+
// (arch-guard structure.js),要按角色分档得改上游;先用类级豁免把口径说清楚。
|
|
81
|
+
//
|
|
82
|
+
// 两条覆盖面合起来 = 方言对"什么算测试"的**全部**声明,一份口径:后缀式的 `.test.` / `.spec.`
|
|
83
|
+
// (库范式那条排他角色收的七种扩展,这里一种不落)与 `__tests__/**` 这个家(`metrics.testGlobs`
|
|
84
|
+
// 里写着的那一条)。少于这个并集就会冒出第二份口径:同一个方言里"什么算测试"有两套答案。
|
|
85
|
+
const TEST_LINES_REASON =
|
|
86
|
+
'测试文件是脚手架,不是产品代码:夹具、逐条断言、一条用例十来行,200 行这个阈值对它是错的口径;' +
|
|
87
|
+
'按它拆文件只会把测试脚手架摞进 src/ 的层里(作者反馈:一个模块被逼出过 src/host/testing.ts)。' +
|
|
88
|
+
'测试在方言里本来就有一条排他角色(layer 99,S21 跳过它),这条豁免与那条同一个理由。' +
|
|
89
|
+
'要形状与体积上的约束,请写进模块自己的测试(用例数、覆盖的行为),不要用文件行数卡它。';
|
|
90
|
+
|
|
91
|
+
const TEST_LINES_EXCEPTIONS = [
|
|
92
|
+
{ rule: 'S16', glob: '**/*.{test,spec}.{ts,tsx,mts,cts,js,mjs,cjs}', reason: TEST_LINES_REASON },
|
|
93
|
+
// `__tests__/**` 也要豁免:那是方言自己声明过的测试家(`metrics.testGlobs`),
|
|
94
|
+
// 但库范式那条排他角色只认后缀式命名 —— 不豁免的话,把测试放进 `__tests__/` 反而吃 S16。
|
|
95
|
+
{ rule: 'S16', glob: 'src/**/__tests__/**', reason: TEST_LINES_REASON },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
// `__tests__/**` 里的文件(夹具 / 用例)也要有个角色:方言已经在 `metrics.testGlobs` 里把这里
|
|
99
|
+
// 声明成测试的家,而库范式只给了后缀式命名一条角色 —— 于是 `src/__tests__/x.ts` 会撞 S01,
|
|
100
|
+
// `src/model/__tests__/x.ts` 会被当成模型层(S16 与 S21 都按产品代码判它)。`exclusive` 是必须的:
|
|
101
|
+
// 它排在库范式的目录角色后面,命中即清掉前面的命中,否则 `src/model/__tests__/**` 会"同时命中两个角色"
|
|
102
|
+
// 再撞一次 S01。层号跟后缀式测试同一条(99 ⇒ S21 跳过):脚手架能引产品代码,反过来不行。
|
|
103
|
+
const TESTS_DIR_ROLE = {
|
|
104
|
+
id: 'lib:tests-dir',
|
|
105
|
+
pattern: 'src/**/__tests__/**',
|
|
106
|
+
layer: 99,
|
|
107
|
+
exclusive: true,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// 我们**不拥有**主题:`--dsw-*` 那套令牌由 dsh 在运行时给(与外壳同一口径,
|
|
111
|
+
// 见 ADR-0028 / .scratch/dsh-ui-alignment/spec.md)。所以这里声明的样式目录里,
|
|
112
|
+
// "引用的令牌查不闭合"是正常的 —— 理由就写在下面这条里(与上面那条 S16 一起,是两条内置豁免)。
|
|
113
|
+
const STYLE_EXCEPTION = {
|
|
114
|
+
rule: 'D04',
|
|
115
|
+
glob: 'src/components/**',
|
|
116
|
+
reason:
|
|
117
|
+
'主题令牌由 dsh 在运行时提供(--dsw-*):模块包**不拥有**令牌表,声明一份自己的' +
|
|
118
|
+
'就是编造一处不存在的真相。所以组件样式里引用的 --dsw-* 在本地查不闭合是正常的;' +
|
|
119
|
+
'dsh 改名会由外壳升级暴露,而不是由模块本地的表兜住。',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// 模块包**运行时**允许的依赖:react(外壳经模块表提供)+ 外壳自己那两个包。
|
|
123
|
+
// `@dsh-workbench/module-tools` 是构建/门禁工具,是 devDependency,不进这张表。
|
|
124
|
+
const ALLOWED_DEPS = ['react', '@dsh-workbench/core', '@dsh-workbench/shared'];
|
|
125
|
+
|
|
126
|
+
// 我们**不拥有**主题:颜色 / 圆角 / 字号 / 时长一律引用 dsh 的 `--dsw-*` 令牌,
|
|
127
|
+
// 所以这里没有 tokenDir / palette(声明一个自己的令牌表就是编造一处不存在的真相,
|
|
128
|
+
// 与外壳那份 arch.config.mjs 同一个理由)。留白的三族白名单 = 一个手搓数值都不收(`0` 本来就被跳过)。
|
|
129
|
+
const DESIGN = {
|
|
130
|
+
styleDir: 'src/components',
|
|
131
|
+
tokenDir: '',
|
|
132
|
+
paletteFile: '',
|
|
133
|
+
vendorDir: 'src/components/vendor',
|
|
134
|
+
themeFile: '',
|
|
135
|
+
valueWhitelists: [
|
|
136
|
+
{ rule: 'D12', allow: ['0'] },
|
|
137
|
+
{ rule: 'D13', allow: ['0'] },
|
|
138
|
+
{ rule: 'D14', allow: ['0'] },
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 模块包的 arch-guard 配置。模板里那一行就是默认值:
|
|
144
|
+
*
|
|
145
|
+
* ```js
|
|
146
|
+
* import { moduleArch } from '@dsh-workbench/module-tools/arch';
|
|
147
|
+
* export default moduleArch();
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* 复杂模块要开口子时**从参数进**(每条口子都有名字,评审时看得见),别改判据:
|
|
151
|
+
*
|
|
152
|
+
* - `fileLines` / `viewLines` / `functionLines`:放宽体积阈值(默认 200 / 200 / 80)
|
|
153
|
+
* - `deps`:追加允许的运行时依赖(比如宿主侧接了某个 SDK)
|
|
154
|
+
* - `roles`:追加角色(自定义目录 / 文件;`{ id, pattern, layer }`)。
|
|
155
|
+
* 内置已有三条**文件级**角色:`src/messages.ts`、`src/declared-tools.ts`,以及 `__tests__/`
|
|
156
|
+
* 目录下的文件(`src/` 内任意一层)—— 后两条一条是 `sync` 生成的、一条是方言声明的测试家,
|
|
157
|
+
* 工具链自己认,不用你补
|
|
158
|
+
* - `layers`:**追加或覆盖**内部目录表(默认 `{ model: 1, components: 2 }`)。宿主侧想住
|
|
159
|
+
* `src/host/` 就在自己那份配置里加 `{ host: 10 }`;但**两个面的文件路径不是可调项**
|
|
160
|
+
* (`src/host.ts` / `src/client.tsx` 同时钉在 `library({ entry })`、`structure.faces` 与清单的
|
|
161
|
+
* `dsh.workbenchFaces` 上,要换面得整体替换 `structure` 并改清单 —— 先红的是 contracts)
|
|
162
|
+
* - `structure`:整体替换 `structure` 声明(面在别处、或者只有一个运行时的模块)
|
|
163
|
+
* - `exceptions`:结构性豁免,**每条必须写 reason**(arch-guard 的硬要求);
|
|
164
|
+
* 内置已有三条(`--dsw-*` 令牌 / 测试文件不吃 S16 的两条覆盖面),不必重复声明
|
|
165
|
+
*/
|
|
166
|
+
export function moduleArch(options = {}) {
|
|
167
|
+
const {
|
|
168
|
+
fileLines,
|
|
169
|
+
viewLines,
|
|
170
|
+
functionLines,
|
|
171
|
+
deps: extraDeps = [],
|
|
172
|
+
roles = [],
|
|
173
|
+
layers = {},
|
|
174
|
+
structure,
|
|
175
|
+
exceptions = [],
|
|
176
|
+
} = options;
|
|
177
|
+
|
|
178
|
+
const thresholds = {
|
|
179
|
+
...DEFAULT_THRESHOLDS,
|
|
180
|
+
...(fileLines === undefined ? {} : { fileLines }),
|
|
181
|
+
...(viewLines === undefined ? {} : { viewLines }),
|
|
182
|
+
...(functionLines === undefined ? {} : { functionLines }),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
sourceForm: 'react',
|
|
187
|
+
presets: [
|
|
188
|
+
// 层序表 + 角色完备(S01 未命中任何角色的文件当场报)+ 体积(S16)。
|
|
189
|
+
library({ src: 'src', entry: ['host.ts', 'client.tsx'], modules: { ...LAYERS, ...layers } }),
|
|
190
|
+
// 运行时依赖 fail-closed:没登记的依赖报 P01。
|
|
191
|
+
deps({ allow: [...ALLOWED_DEPS, ...extraDeps] }),
|
|
192
|
+
// 手搓数值 / !important / 选择器 / CSS Modules 的绑定闭合。令牌表留白(理由见 DESIGN)。
|
|
193
|
+
designSystem({ ...DESIGN }),
|
|
194
|
+
// 组件样式走 `*.module.css`(ADR-0029 的模块版:样式跟组件走)。
|
|
195
|
+
styles(cssModulesKit()),
|
|
196
|
+
// 反退化:TODO / 调试残留 / 空 catch / 未完成标记。
|
|
197
|
+
hygiene(),
|
|
198
|
+
// 文案走模块自己的 `messages`(zh / en),不用 i18next —— 与外壳同一口径。
|
|
199
|
+
i18n(noneI18nKit()),
|
|
200
|
+
// 模型层必须有落地的测试(`src/model/**` 命中文件但一份测试都没有就红)。
|
|
201
|
+
// 门禁链不在这里查(`check` 不跑 vitest,那是 `pnpm test` 的事,与外壳同一条口径)。
|
|
202
|
+
metrics({
|
|
203
|
+
tests: {
|
|
204
|
+
requireTestsFor: ['src/model/**'],
|
|
205
|
+
testGlobs: ['src/**/*.test.ts', 'src/**/*.test.tsx', 'src/**/__tests__/**'],
|
|
206
|
+
checkChain: { script: 'check', require: [] },
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
overrides: {
|
|
211
|
+
include: ['src/**'],
|
|
212
|
+
addRoles: [MESSAGES_ROLE, DECLARED_TOOLS_ROLE, TESTS_DIR_ROLE, ...roles],
|
|
213
|
+
thresholds,
|
|
214
|
+
// 两个运行时 + 两个面:S23 据此判"跨运行时走了对面那个面"。
|
|
215
|
+
// 不写 `value`(面值形状):模块的面导出的是 `apply` / `inject` / `name`,
|
|
216
|
+
// 那是 dsh 插件的契约,不是我们在这里能收敛成某个前缀的东西。
|
|
217
|
+
structure: structure ?? {
|
|
218
|
+
runtimes: [
|
|
219
|
+
{ name: 'host', entries: [HOST_FACE] },
|
|
220
|
+
{ name: 'client', entries: [CLIENT_FACE] },
|
|
221
|
+
],
|
|
222
|
+
faces: [
|
|
223
|
+
{ pattern: HOST_FACE, runtime: 'host' },
|
|
224
|
+
{ pattern: CLIENT_FACE, runtime: 'client' },
|
|
225
|
+
// 组件目录整片算"浏览器侧的面":于是**宿主侧的文件 import 一个组件**会被 S23 当场报
|
|
226
|
+
// (层号拦不住它 —— components 在第 2 层、host 面在第 10 层,S21 认为"向下依赖"合法)。
|
|
227
|
+
// 那正是最典型的一种错:宿主产物里拖进一个只会渲染的东西,`dist/host.js` 在 Node 里起不来。
|
|
228
|
+
{ pattern: 'src/components/**', runtime: 'client' },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
exceptions: [STYLE_EXCEPTION, ...TEST_LINES_EXCEPTIONS, ...exceptions],
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
package/src/build.mjs
CHANGED
|
@@ -188,5 +188,8 @@ export async function build({ root }) {
|
|
|
188
188
|
const wrapped = `// ⚠️ 本文件由 dsh-wb-module build 生成,请勿手改。改 src/ 后跑:dsh-wb-module build\nwindow.__ModuleLoader__.load({\n id: ${JSON.stringify(id)},\n factory(require) {\n const module = { exports: {} };\n const exports = module.exports;\n${indented}\n return module.exports;\n },\n});\n`;
|
|
189
189
|
|
|
190
190
|
writeFileSync(join(root, 'client.js'), wrapped);
|
|
191
|
-
|
|
191
|
+
// `wrapped.length` 是 **UTF-16 码元数**(字符数),不是字节数 —— 产物里有中文头注释与模块自己的
|
|
192
|
+
// 中文文案,报出来会比真实文件小(实测 `module-hello/client.js`:3095 字符 / 3131 字节)。
|
|
193
|
+
// 报字节就按字节算。
|
|
194
|
+
console.log(`${id}:client.js 已更新(${Buffer.byteLength(wrapped, 'utf8')} 字节)`);
|
|
192
195
|
}
|
package/src/check.mjs
CHANGED
|
@@ -26,6 +26,8 @@ export const CHECKS = {
|
|
|
26
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
|
+
arch: { title: '模块架构(分层 / 边界 / 设计系统)', load: () => import('./checks/arch.mjs') },
|
|
30
|
+
lint: { title: '模块代码卫生(eslint)', load: () => import('./checks/lint.mjs') },
|
|
29
31
|
primitives: { title: 'dsh 原语只从模块表拿', load: () => import('./checks/primitives.mjs') },
|
|
30
32
|
kit: { title: '外壳 kit 面', load: () => import('./checks/kit.mjs') },
|
|
31
33
|
cards: { title: '卡片渲染', load: () => import('./checks/cards.mjs') },
|