@dsh-workbench/module-tools 0.1.14 → 0.1.16
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 +71 -0
- package/bin/dsh-wb-module.mjs +4 -2
- package/package.json +3 -2
- package/src/build.mjs +2 -11
- package/src/check.mjs +88 -7
- package/src/checks/alias.mjs +51 -29
- package/src/checks/bundles.mjs +44 -31
- package/src/checks/cards.mjs +119 -107
- package/src/checks/contracts.mjs +621 -116
- package/src/checks/kit.mjs +127 -64
- package/src/checks/markers.mjs +55 -32
- package/src/checks/primitives.mjs +82 -57
- package/src/checks/source.mjs +106 -92
- package/src/checks/standalone.mjs +54 -24
- package/src/checks/style.mjs +133 -94
- package/src/css-injection.mjs +27 -0
- package/src/design.mjs +424 -0
- package/src/discover.mjs +1 -1
- package/src/init.mjs +6 -1
- package/src/publish.mjs +134 -0
- package/template/scripts/smoke.mjs.tpl +104 -4
- package/template-diy/README.md.tpl +282 -0
- package/template-diy/cordis.patch.yml.tpl +3 -0
- package/template-diy/design.json.tpl +38 -0
- package/template-diy/package.json.tpl +43 -0
- package/template-diy/scripts/smoke.mjs.tpl +232 -0
- package/template-diy/src/client.tsx.tpl +258 -0
- package/template-diy/src/host.ts.tpl +131 -0
- package/template-diy/src/messages.ts.tpl +28 -0
- package/template-diy/src/plugin.test.ts.tpl +359 -0
- package/template-diy/tsconfig.json +20 -0
- package/template-diy/types/globals.d.ts +27 -0
- package/template-diy/vitest.config.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @dsh-workbench/module-tools
|
|
2
|
+
|
|
3
|
+
工作台**模块的工具箱**:建骨架、构建、过门禁、管三张表、发货前核清单。
|
|
4
|
+
模块作者(以及替用户造模块的 agent)只跟这几条命令打交道。
|
|
5
|
+
|
|
6
|
+
## 命令
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
dsh-wb-module init <unit> [--dir <目录>] [--template minimal|diy] [--description "一句话"]
|
|
10
|
+
dsh-wb-module build # 出 dist/host.js 与套了 loader 壳的 client.js
|
|
11
|
+
dsh-wb-module check [--only <判据>] # 十条判据;--only 可以单跑
|
|
12
|
+
dsh-wb-module design <init|check|render|accept>
|
|
13
|
+
dsh-wb-module publish # 发货前的清单:门禁 + 三张表 + npm pack 清单(只 dry run)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`--cwd <目录>` 指定包目录(默认当前目录)。模块目录里更省事的是骨架自带的脚本:
|
|
17
|
+
`pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck` / `pnpm design`。
|
|
18
|
+
|
|
19
|
+
## 十条判据(`check`)
|
|
20
|
+
|
|
21
|
+
| 判据 | 盯什么 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `contracts` | 模块契约:单元 id 唯一、两个面、四个脚本、`files`、peer 下限 |
|
|
24
|
+
| `bundles` | 产物边界:宿主面里不许出现浏览器侧的东西 |
|
|
25
|
+
| `source` | 源码边界:不直接引 dsh 的包、不引不是种子词的 `@deepseek-ai/*` |
|
|
26
|
+
| `primitives` | dsh 原语只从模块表(`kit`)拿 |
|
|
27
|
+
| `kit` | 这份外壳的 kit 成员到底有哪些(缺什么会当场点名) |
|
|
28
|
+
| `cards` | 卡片渲染:五态都要画得出来,空数据不许抛 |
|
|
29
|
+
| `standalone` | 独立构建:产物不依赖工坊里跑 |
|
|
30
|
+
| `markers` | 公开包无内部标识(内网域名、企业内部名) |
|
|
31
|
+
| `style` | 样式归属:模块不许定义外壳前缀(`wb-*`)的规则 |
|
|
32
|
+
| `alias` | 模块不指外壳:源码里没有指向外壳 `@/` 的引用 |
|
|
33
|
+
|
|
34
|
+
三态输出:`✔ 通过` / `○ 不适用` / `✖ 不过`;有任何不过就退出 1。
|
|
35
|
+
|
|
36
|
+
## 三张表:`design.json`
|
|
37
|
+
|
|
38
|
+
三张表(面板 / 能力 / 联动)+ 状态矩阵 + 数据源记录 + 验收清单住在模块根目录的 `design.json`,
|
|
39
|
+
README 里那一段由它生成(夹在 `<!-- dsh-wb:design:start/end -->` 之间)。
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dsh-wb-module design check # 空着的格一格格点名叫出来 + README 有没有落后
|
|
43
|
+
dsh-wb-module design render # 用 design.json 重新生成 README 那一段
|
|
44
|
+
dsh-wb-module design accept # 用户逐条点头之后,把验收清单打勾
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 两个模板
|
|
48
|
+
|
|
49
|
+
| 模板 | 是什么 |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `minimal`(默认) | 空壳:卡壳、文案、测试接好了,没有数据源 |
|
|
52
|
+
| `diy` | 一份真能跑的样板:宿主侧一条只读路由真读盘、卡片五态、`?wbMock=1` 三档夹具,**三张表也是填好的** |
|
|
53
|
+
|
|
54
|
+
`init` 会连模板里的 `*.tpl` 一起拷过去、把 `__UNIT__` / `__NAME__` / `__DESC__` 填上、剥掉 `.tpl` 后缀,
|
|
55
|
+
再用 `design.json` 生成 README 那一段。
|
|
56
|
+
|
|
57
|
+
## 自己的自检
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node scripts/selfcheck.mjs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
工具箱的回归网:两个模板各 `init` 一遍、各跑全套(门禁 + 五个脚本);判据的**正例与负例**都要对;
|
|
64
|
+
打包清单不含 `.tpl` 残留;`design` 的空格、README 同步、`accept`;`publish` 的清单。
|
|
65
|
+
|
|
66
|
+
## 去哪看"为什么"
|
|
67
|
+
|
|
68
|
+
模块怎么写、DIY 那套流程怎么走,见外壳包里的指引:`@dsh-workbench/core` 的
|
|
69
|
+
`skills/workbench-module-diy/**`(`SKILL.md` / `BUILD.md` / `WIRING.md` / `DESIGN.md` / `PANEL.md` /
|
|
70
|
+
`TOOLS.md` / `LINKAGE.md` / `DATASOURCE.md` / `REVIEW.md` / `TOOLING.md` / `STEPS.md`)。
|
|
71
|
+
架构上的取舍写在工作台仓库的 `docs/adr/**`。
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { build } from '../src/build.mjs';
|
|
4
4
|
import { check } from '../src/check.mjs';
|
|
5
|
+
import { design } from '../src/design.mjs';
|
|
5
6
|
import { init } from '../src/init.mjs';
|
|
7
|
+
import { publish } from '../src/publish.mjs';
|
|
6
8
|
|
|
7
9
|
const [command, ...rest] = process.argv.slice(2);
|
|
8
10
|
const cwd = process.env.DSH_WB_MODULE_CWD ?? process.cwd();
|
|
9
11
|
|
|
10
|
-
const commands = { build, check, init };
|
|
12
|
+
const commands = { build, check, init, design, publish };
|
|
11
13
|
|
|
12
14
|
if (!command || !commands[command]) {
|
|
13
|
-
console.error('用法:dsh-wb-module <build|check|init> [--cwd <目录>] [--only contracts|bundles|source|cards]\n dsh-wb-module init <unit> [--dir <目录>] [--description <文字>]');
|
|
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)');
|
|
14
16
|
process.exit(2);
|
|
15
17
|
}
|
|
16
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
15
|
"src",
|
|
16
|
-
"template"
|
|
16
|
+
"template",
|
|
17
|
+
"template-diy"
|
|
17
18
|
],
|
|
18
19
|
"publishConfig": {
|
|
19
20
|
"access": "public"
|
package/src/build.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { builtinModules } from 'node:module';
|
|
|
5
5
|
import { isAbsolute, join, relative } from 'node:path';
|
|
6
6
|
import { build as esbuild } from 'esbuild';
|
|
7
7
|
import { SEED_SPECIFIERS } from './seed.mjs';
|
|
8
|
+
import { cssInjectionCode } from './css-injection.mjs';
|
|
8
9
|
|
|
9
10
|
const SHELL_CLIENT = '@dsh-workbench/core/client';
|
|
10
11
|
const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.js', '.json'];
|
|
@@ -102,17 +103,7 @@ function cssInjector({ root }) {
|
|
|
102
103
|
}
|
|
103
104
|
return {
|
|
104
105
|
loader: 'js',
|
|
105
|
-
contents: [
|
|
106
|
-
`const css = ${JSON.stringify(css)};`,
|
|
107
|
-
`const key = ${JSON.stringify(key)};`,
|
|
108
|
-
"if (typeof document !== 'undefined' && document.querySelector('style[data-dsh-wb-css=\"' + key + '\"]') === null) {",
|
|
109
|
-
" const style = document.createElement('style');",
|
|
110
|
-
" style.setAttribute('data-dsh-wb-css', key);",
|
|
111
|
-
" style.textContent = css;",
|
|
112
|
-
" document.head.appendChild(style);",
|
|
113
|
-
"}",
|
|
114
|
-
`export default ${map};`,
|
|
115
|
-
].join('\n'),
|
|
106
|
+
contents: [`${cssInjectionCode(key, css)}`, `export default ${map};`].join('\n'),
|
|
116
107
|
};
|
|
117
108
|
});
|
|
118
109
|
},
|
package/src/check.mjs
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
|
+
// 十条判据的**聚合层**:跑、收集三态、打印、决定退出码。
|
|
2
|
+
//
|
|
3
|
+
// ── 为什么判据改成"导出函数",打印与退出码都搬到这里 ──────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// 在这之前,每条 `checks/*.mjs` 都是"import 即执行 + 自己 console.log + 自己 process.exit"
|
|
6
|
+
// 的脚本。那样"不适用"就没有一等形状 —— 只能靠"这条判据不许 exit(0)"这条**纪律**维持:
|
|
7
|
+
// 谁哪天写个 `process.exit(0)`,排在它后面的判据就静默不跑,而屏幕看着还是全绿
|
|
8
|
+
// (standalone 在仓外工作区就是这么吞掉 markers / style / alias 三条的,见
|
|
9
|
+
// `.scratch/workbench-diy/spec.md` §20)。
|
|
10
|
+
//
|
|
11
|
+
// 另一件后果是聚合层拿不到"跑了哪些、跳了哪些、为什么跳",只能看判据自己打的字。
|
|
12
|
+
//
|
|
13
|
+
// 新形状:每条判据导出 `run()`,返回
|
|
14
|
+
// { status: 'pass' | 'fail' | 'skip', notes: string[], problems: string[] }
|
|
15
|
+
// ——"不适用"于是是一等结果,而**截断在形状上就不可能**:
|
|
16
|
+
// 判据手里没有 exit,退出码只有这一层按汇总给。
|
|
17
|
+
//
|
|
18
|
+
// 打印形状(三态,稳定):
|
|
19
|
+
// 通过 ✔ <title>:<notes 拼起来>
|
|
20
|
+
// 不适用 ○ <title>:(不适用)<notes>
|
|
21
|
+
// 失败 ✖ <title> <N> 条不过: 然后逐条 ` · <problem>`
|
|
22
|
+
// 最后一行是汇总;有任何 fail 就 exit 1。
|
|
1
23
|
|
|
2
|
-
|
|
24
|
+
// 十个名字与 title 一字不变 —— selfcheck 会把它与各判据模块导出的 `title` 逐个对一遍。
|
|
25
|
+
export const CHECKS = {
|
|
3
26
|
contracts: { title: '模块契约', load: () => import('./checks/contracts.mjs') },
|
|
4
27
|
bundles: { title: '模块产物边界', load: () => import('./checks/bundles.mjs') },
|
|
5
28
|
source: { title: '模块源码边界', load: () => import('./checks/source.mjs') },
|
|
@@ -12,14 +35,72 @@ const CHECKS = {
|
|
|
12
35
|
alias: { title: '模块不指外壳', load: () => import('./checks/alias.mjs') },
|
|
13
36
|
};
|
|
14
37
|
|
|
38
|
+
/** 判据只许回这三态;聚合层的打印与汇总都按它分支。 */
|
|
39
|
+
const STATES = ['pass', 'fail', 'skip'];
|
|
40
|
+
|
|
41
|
+
/** 一屏一条:三态各一种前缀,notes 的第一条跟在标题后面,其余各占一行。 */
|
|
42
|
+
function report(title, result) {
|
|
43
|
+
const notes = Array.isArray(result.notes) ? result.notes : [];
|
|
44
|
+
if (result.status === 'fail') {
|
|
45
|
+
const problems = Array.isArray(result.problems) ? result.problems : [];
|
|
46
|
+
console.log(`✖ ${title} ${problems.length} 条不过:`);
|
|
47
|
+
for (const problem of problems) console.log(` · ${problem}`);
|
|
48
|
+
for (const note of notes) console.log(` ${note}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (result.status === 'skip') {
|
|
52
|
+
console.log(`○ ${title}:(不适用)${notes[0] ?? ''}`);
|
|
53
|
+
} else {
|
|
54
|
+
console.log(`✔ ${title}:${notes[0] ?? ''}`);
|
|
55
|
+
}
|
|
56
|
+
for (const note of notes.slice(1)) console.log(` ${note}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
15
59
|
export async function check({ only } = {}) {
|
|
16
60
|
const names = only === undefined ? Object.keys(CHECKS) : [only];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
61
|
+
const tally = { pass: 0, skip: 0, fail: 0 };
|
|
62
|
+
const skipped = [];
|
|
63
|
+
let ran = 0;
|
|
64
|
+
try {
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
const entry = CHECKS[name];
|
|
67
|
+
if (entry === undefined) {
|
|
68
|
+
console.error(`不认识的判据:${name}(有:${Object.keys(CHECKS).join(' / ')})`);
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
// 告诉这一条"这次是不是只跑它":有的判据在单跑时顺带当查询口(`--only kit` 会把
|
|
72
|
+
// 这份外壳的 kit 成员列出来),聚合跑时那些清单属于噪音,不该倒。
|
|
73
|
+
process.env.DSH_WB_ONLY = only === undefined ? '' : name;
|
|
74
|
+
const module = await entry.load();
|
|
75
|
+
const result = await module.run();
|
|
76
|
+
// title 的发声处是判据模块自己;表里那份是兜底(两边必须一字不差,selfcheck 钉着)。
|
|
77
|
+
const title = typeof module.title === 'string' && module.title !== '' ? module.title : entry.title;
|
|
78
|
+
ran += 1;
|
|
79
|
+
if (!STATES.includes(result.status)) {
|
|
80
|
+
report(title, {
|
|
81
|
+
status: 'fail',
|
|
82
|
+
notes: [],
|
|
83
|
+
problems: [`返回了不认识的 status:${String(result.status)} —— 只许 ${STATES.join(' / ')}`],
|
|
84
|
+
});
|
|
85
|
+
tally.fail += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
tally[result.status] += 1;
|
|
89
|
+
if (result.status === 'skip') skipped.push(`${title}(${result.notes[0] ?? ''})`);
|
|
90
|
+
report(title, result);
|
|
22
91
|
}
|
|
23
|
-
|
|
92
|
+
} finally {
|
|
93
|
+
process.env.DSH_WB_ONLY = '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
console.log(`${ran} 条判据:${tally.pass} 通过 · ${tally.skip} 不适用 · ${tally.fail} 不过`);
|
|
97
|
+
// 「不适用」不是「通过」。汇总行里那两个数字长得很像,读的人(尤其 agent)很容易把
|
|
98
|
+
// "8 通过 · 2 不适用" 念成"全过了" —— 而工坊里那两条是**结构性**跑不了,不是这次没问题。
|
|
99
|
+
if (skipped.length > 0) {
|
|
100
|
+
console.log(` 这 ${skipped.length} 条这次一条都没查(不是"没问题"):`);
|
|
101
|
+
for (const one of skipped) console.log(` ○ ${one}`);
|
|
102
|
+
console.log(' 换个 cwd 它们才跑得起来(工坊里「独立构建」扫不到外壳源码、「公开包无内部标识」只扫');
|
|
103
|
+
console.log(' 要发出去的包)—— 想验这两条,回工作台仓库根跑 `dsh-wb-module check`。');
|
|
24
104
|
}
|
|
105
|
+
if (tally.fail > 0) process.exit(1);
|
|
25
106
|
}
|
package/src/checks/alias.mjs
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
// 模块的源码只许引**自己的** `@/modules/<unit>/*`,不许出现任何指向外壳的 `@/`。
|
|
2
|
+
//
|
|
3
|
+
// 为什么:模块出包后用户手上没有外壳源码,`@/app/...` 这类别名只在**本仓的构建配置**里
|
|
4
|
+
// 解析得到 —— 装到别人那里就是构建期找不到文件(`check:standalone` 会把它变成红灯,
|
|
5
|
+
// 这条判据则直接指到源码那一行)。
|
|
6
|
+
//
|
|
7
|
+
// 形状(十条判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
|
|
8
|
+
// 打印与退出码归 `../check.mjs` 那一层。
|
|
9
|
+
|
|
1
10
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
11
|
import { join, relative } from 'node:path';
|
|
3
12
|
import { discoverModulePackages } from '../discover.mjs';
|
|
@@ -14,36 +23,49 @@ function walk(dir, acc = []) {
|
|
|
14
23
|
return acc;
|
|
15
24
|
}
|
|
16
25
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
export const title = '模块不指外壳';
|
|
27
|
+
|
|
28
|
+
export async function run() {
|
|
29
|
+
const modules = discoverModulePackages({ root: process.cwd() });
|
|
30
|
+
const problems = [];
|
|
31
|
+
for (const pkg of modules) {
|
|
32
|
+
const unit = pkg.manifest.dsh?.workbench?.unit;
|
|
33
|
+
const src = join(pkg.dir, 'src');
|
|
34
|
+
const viaPlatform = /(^|\/)platform-/.test(
|
|
35
|
+
Object.keys({ ...(pkg.manifest.dependencies ?? {}), ...(pkg.manifest.devDependencies ?? {}) }).join(' '),
|
|
36
|
+
);
|
|
37
|
+
if (unit === undefined) continue;
|
|
38
|
+
for (const file of walk(src)) {
|
|
39
|
+
const text = readFileSync(file, 'utf8');
|
|
40
|
+
text.split('\n').forEach((line, index) => {
|
|
41
|
+
for (const match of line.matchAll(SPECIFIER)) {
|
|
42
|
+
const specifier = match[1];
|
|
43
|
+
const own = specifier.startsWith(`@/modules/${unit}/`);
|
|
44
|
+
const selfPlatform = specifier.startsWith('@/platforms/') && !viaPlatform;
|
|
45
|
+
if (!own && !selfPlatform) {
|
|
46
|
+
problems.push(
|
|
47
|
+
`${pkg.manifest.name}: ${relative(pkg.dir, file)}:${index + 1} 用了 ${specifier} —— ` +
|
|
48
|
+
`模块只许引自己的 @/modules/${unit}/*;共享代码走包名,平台知识走包名或本包的 @/platforms/*(用了平台包就不许再用)`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
38
51
|
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
41
54
|
}
|
|
42
|
-
}
|
|
43
55
|
|
|
44
|
-
if (problems.length > 0) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
if (problems.length > 0) {
|
|
57
|
+
return { status: 'fail', notes: [], problems };
|
|
58
|
+
}
|
|
59
|
+
if (modules.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
status: 'skip',
|
|
62
|
+
notes: ['没有模块包(没有声明 dsh.workbench 的包)—— 没有源码要查 @/'],
|
|
63
|
+
problems: [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
status: 'pass',
|
|
68
|
+
notes: [`${modules.length} 个模块的 src 里没有指向外壳的 @/`],
|
|
69
|
+
problems: [],
|
|
70
|
+
};
|
|
48
71
|
}
|
|
49
|
-
console.log(`✔ 模块不指外壳:${modules.length} 个模块的 src 里没有指向外壳的 @/`);
|
package/src/checks/bundles.mjs
CHANGED
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
//
|
|
14
14
|
// 覆盖范围跟着**自述清单**走:凡 `package.json` 里声明了 `dsh.workbench` 的包都被检查,
|
|
15
15
|
// 新加一个模块不用改这个脚本。
|
|
16
|
+
//
|
|
17
|
+
// 形状(十条判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
|
|
18
|
+
// 打印与退出码归 `../check.mjs` 那一层。
|
|
16
19
|
|
|
17
20
|
import { existsSync, readFileSync } from 'node:fs';
|
|
18
21
|
import { join } from 'node:path';
|
|
19
22
|
import { discoverModulePackages } from '../discover.mjs';
|
|
20
23
|
|
|
21
|
-
|
|
22
24
|
const CLIENT_FORBIDDEN = [
|
|
23
25
|
['node 内置', /\bnode:[a-z]/],
|
|
24
26
|
['process.env(宿主环境)', /\bprocess\.env\b/],
|
|
@@ -31,41 +33,52 @@ const HOST_FORBIDDEN = [
|
|
|
31
33
|
['react', /require\("react"\)/],
|
|
32
34
|
];
|
|
33
35
|
|
|
34
|
-
const
|
|
36
|
+
export const title = '模块产物边界';
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
export async function run() {
|
|
39
|
+
const modulePackages = discoverModulePackages({ root: process.cwd() });
|
|
40
|
+
const problems = [];
|
|
41
|
+
let checked = 0;
|
|
38
42
|
|
|
39
|
-
for (const pkg of modulePackages
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
for (const pkg of modulePackages) {
|
|
44
|
+
const name = pkg.manifest.name;
|
|
45
|
+
const artifacts = [
|
|
46
|
+
{ what: '浏览器产物', file: 'client.js', rules: CLIENT_FORBIDDEN },
|
|
47
|
+
{ what: '宿主产物', file: 'dist/host.js', rules: HOST_FORBIDDEN },
|
|
48
|
+
];
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
for (const artifact of artifacts) {
|
|
51
|
+
const full = join(pkg.dir, artifact.file);
|
|
52
|
+
if (!existsSync(full)) {
|
|
53
|
+
problems.push(`${name}: 找不到${artifact.what} ${artifact.file} —— 先 build 再 check`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
checked += 1;
|
|
57
|
+
const source = readFileSync(full, 'utf8');
|
|
58
|
+
for (const [label, pattern] of artifact.rules) {
|
|
59
|
+
if (pattern.test(source)) {
|
|
60
|
+
problems.push(
|
|
61
|
+
`${name}: ${artifact.what} ${artifact.file} 里出现了${label} —— ` +
|
|
62
|
+
`两个运行时混了(判据是能力:浏览器侧不许碰宿主传输层,宿主侧不许碰 DOM/react)`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
|
-
}
|
|
64
68
|
|
|
65
|
-
if (problems.length > 0) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
if (problems.length > 0) {
|
|
70
|
+
return { status: 'fail', notes: [], problems };
|
|
71
|
+
}
|
|
72
|
+
if (modulePackages.length === 0) {
|
|
73
|
+
return {
|
|
74
|
+
status: 'skip',
|
|
75
|
+
notes: ['没有模块包(没有声明 dsh.workbench 的包)—— 没有产物可查'],
|
|
76
|
+
problems: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
status: 'pass',
|
|
81
|
+
notes: [`${checked} 份产物 · 浏览器侧无 node 内置 · 宿主侧无 DOM/react`],
|
|
82
|
+
problems: [],
|
|
83
|
+
};
|
|
69
84
|
}
|
|
70
|
-
|
|
71
|
-
console.log(`✔ 模块产物边界通过:${checked} 份产物 · 浏览器侧无 node 内置 · 宿主侧无 DOM/react`);
|