@dsh-workbench/module-tools 1.1.13 → 1.1.14-beta.0
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 +36 -23
- package/bin/dsh-wb-module.mjs +18 -4
- package/package.json +4 -4
- package/src/arch.mjs +36 -25
- package/src/check.mjs +50 -3
- package/src/checks/arch.mjs +2 -2
- package/src/checks/contracts.mjs +88 -41
- package/src/checks/kit.mjs +3 -2
- package/src/checks/lint.mjs +2 -2
- package/src/checks/primitives.mjs +2 -2
- package/src/discover.mjs +186 -16
- package/src/init.mjs +7 -8
- package/src/lint.mjs +4 -16
- package/src/market-index.mjs +1 -1
- package/src/migrate.mjs +69 -0
- package/src/release-journal.mjs +3 -1
- package/src/release-target.mjs +1 -1
- package/src/release-util.mjs +7 -1
- package/template-diy/AGENTS.md.tpl +14 -22
- package/template-diy/README.md.tpl +61 -65
- package/template-diy/design.json.tpl +19 -21
- package/template-diy/package.json.tpl +6 -2
- package/template-diy/scripts/smoke.assert.mjs.tpl +28 -0
- package/template-diy/scripts/smoke.mjs.tpl +150 -30
- package/template-diy/src/__tests__/plugin.test.ts.tpl +453 -0
- package/template-diy/src/components/NotesSection.tsx.tpl +74 -0
- package/template-diy/src/components/OverviewBody.module.css.tpl +5 -0
- package/template-diy/src/components/OverviewBody.tsx.tpl +64 -0
- package/template-diy/src/components/OverviewCard.module.css.tpl +9 -1
- package/template-diy/src/components/OverviewCard.tsx.tpl +108 -67
- package/template-diy/src/components/PrefsSection.tsx.tpl +36 -0
- package/template-diy/src/components/RunSection.tsx.tpl +69 -0
- package/template-diy/src/components/{OverviewCard.test.ts.tpl → __tests__/OverviewCard.test.ts.tpl} +51 -1
- package/template-diy/src/declared-tools.ts.tpl +6 -0
- package/template-diy/src/host/notes.ts.tpl +92 -0
- package/template-diy/src/host/overview.ts.tpl +57 -0
- package/template-diy/src/host/sample.ts.tpl +151 -0
- package/template-diy/src/host.ts.tpl +88 -66
- package/template-diy/src/messages.ts.tpl +28 -0
- package/template-diy/src/model/__tests__/flow.test.ts.tpl +36 -0
- package/template-diy/src/model/__tests__/notes.test.ts.tpl +49 -0
- package/template-diy/src/model/{overview.test.ts.tpl → __tests__/overview.test.ts.tpl} +1 -1
- package/template-diy/src/model/__tests__/prefs.test.ts.tpl +59 -0
- package/template-diy/src/model/flow.ts.tpl +47 -0
- package/template-diy/src/model/notes.ts.tpl +52 -0
- package/template-diy/src/model/overview.ts.tpl +3 -1
- package/template-diy/src/model/prefs.ts.tpl +61 -0
- package/template/AGENTS.md.tpl +0 -33
- package/template/CHANGELOG.md.tpl +0 -7
- package/template/README.md.tpl +0 -30
- package/template/arch.config.mjs +0 -2
- package/template/cordis.patch.yml.tpl +0 -3
- package/template/eslint.config.mjs +0 -2
- package/template/package.json.tpl +0 -50
- package/template/scripts/smoke.mjs.tpl +0 -192
- package/template/src/client.tsx.tpl +0 -22
- package/template/src/components/HelloCard.tsx.tpl +0 -28
- package/template/src/host.ts.tpl +0 -21
- package/template/src/messages.ts.tpl +0 -12
- package/template/src/plugin.test.ts.tpl +0 -56
- package/template/tsconfig.json +0 -20
- package/template/types/globals.d.ts +0 -14
- package/template/vitest.config.ts +0 -14
- package/template-diy/src/plugin.test.ts.tpl +0 -159
package/README.md
CHANGED
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
## 六条命令走一遍
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
pnpm exec dsh-wb-module init my-unit
|
|
10
|
-
cd my-unit && pnpm install
|
|
9
|
+
pnpm exec dsh-wb-module init my-unit # 建骨架(唯一模板,已默认)
|
|
11
10
|
pnpm build # dist/host.js + 套了 loader 壳的 client.js
|
|
12
11
|
pnpm check # 十三条判据
|
|
13
12
|
pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
14
13
|
```
|
|
15
14
|
|
|
16
|
-
要发货再加一步 `pnpm publish`:跑门禁、核 `files` 与 `npm pack`
|
|
15
|
+
要发货再加一步 `pnpm exec dsh-wb-module publish`:跑门禁、核 `files` 与 `npm pack` 清单,默认 **只 dry run,不真发**。
|
|
17
16
|
维护者确认要真发时跑 `pnpm exec dsh-wb-module release`:全门禁(build / check / design / typecheck / test / selfcheck)、
|
|
18
17
|
验收必须全勾、预检(登录 / license / git 远端)、按 `--target` 发到团队源 / 公网 npm / 安装包 / git,
|
|
19
18
|
最后从出口侧核包(`--dry-run` 只预览,`--verify` 只核已发布的包)。配了可写 git 索引源时,
|
|
@@ -23,7 +22,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
23
22
|
空仓库先跑 `dsh-wb-module market-init <git 地址>`:生成索引目录 `market/` 与 README 并推首个 commit
|
|
24
23
|
(用仓库声明的默认分支,读不到时用 `--ref` 指定)。在工作台里配了可写索引源之后,
|
|
25
24
|
`release` 默认出口就是 `market`:产物与索引一起进那个仓库。
|
|
26
|
-
发布过程中每一步都写进
|
|
25
|
+
发布过程中每一步都写进 `$DSH_WORKBENCH_HOME/releases/<unit>.json`(默认 `~/.dsh/workbench`);中断后带 `--resume` 接着发
|
|
27
26
|
(沿用记录里的出口、版本与索引计划,产物没变就不重跑门禁)。
|
|
28
27
|
这一版在 `CHANGELOG.md` 里没有记录时,带 `--changelog-note "这一版改了什么"`
|
|
29
28
|
(`--changelog-type` 选分类,缺省 changed)发一次就写进去,文件不在会先建骨架;
|
|
@@ -48,18 +47,26 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
48
47
|
|
|
49
48
|
| 命令 | 作用 |
|
|
50
49
|
|---|---|
|
|
51
|
-
| `dsh-wb-module init <unit> [--dir <目录>] [--template
|
|
50
|
+
| `dsh-wb-module init <unit> [--dir <目录>] [--template diy] [--description "一句话"]` | 建模块骨架(只有 `diy` 一个模板,也是默认) |
|
|
52
51
|
| `dsh-wb-module market-init <git 地址\|目录> [--ref <分支>] [--path <索引路径>]` | 初始化市场索引仓库:写 `market/` + README,推首个 commit |
|
|
53
52
|
| `dsh-wb-module build` | 出 `dist/host.js`(单文件 ESM)与 `client.js`(套 loader 壳) |
|
|
54
|
-
| `dsh-wb-module check [--only <判据>]` | 十三条判据;`--only` 单跑,也当查询口用;`--list [--json\|--markdown]` 只列判据元数据 |
|
|
53
|
+
| `dsh-wb-module check [--only <判据>] [--package <包名或 unit>]` | 十三条判据;`--only` 单跑,也当查询口用;`--package` 只报这个模块的问题(见下);`--list [--json\|--markdown]` 只列判据元数据 |
|
|
55
54
|
| `dsh-wb-module lint [--fix]` | 用模块自己那份 `eslint.config.mjs`;`--fix` 写回文件 |
|
|
56
55
|
| `dsh-wb-module sync` | 按清单生成 `src/declared-tools.ts`,按 `design.json` 的 `key + recipe` 生成 `src/datasource.ts` |
|
|
57
56
|
| `dsh-wb-module design <init\|check\|render\|accept>` | 设计底稿:填、查空格、生成 README、验收打勾 |
|
|
57
|
+
| `dsh-wb-module migrate [--dry-run]` | 把模块清单迁到当前代:`apiVersion` → 3、缺/空的 `workbenchFaces` 补默认;源码类问题只给提示(check 会点名) |
|
|
58
58
|
| `dsh-wb-module changelog check [版本]` | 查这一版有没有记录;发布时用 `release --changelog-note` 写进去 |
|
|
59
59
|
| `dsh-wb-module publish` | 发货前清单:门禁 + `files` + `npm pack`(只 dry run) |
|
|
60
60
|
| `dsh-wb-module release` | 真发布:team / public / tarball / git;全门禁 + 验收闸 + 预检接管 + 发后核包 + 更新市场索引(`--dry-run` / `--verify`) |
|
|
61
61
|
|
|
62
|
-
`--cwd <目录>`
|
|
62
|
+
`--cwd <目录>` 指定**工作目录**,默认当前目录。注意判据仍按**工作区**发现模块:往上找
|
|
63
|
+
`pnpm-workspace.yaml`(或 `package.json` 的 `workspaces`),按它声明的 glob 展开包目录
|
|
64
|
+
(`packages/**`、`apps/*`、多层目录都行;没有声明时退回 `packages/*`)——`--cwd` 不会把范围缩到一个模块。
|
|
65
|
+
只想看自己那个模块用 `check --package <包名或 unit>`。
|
|
66
|
+
|
|
67
|
+
`--package` 是**开发期便利**,不是门禁:它仍全量扫描、只过滤报告与退出码,所以
|
|
68
|
+
「我这个模块绿」不等于「工作区绿」。门禁 / 发布 / release 不要带它。要只让判据看见一个包,
|
|
69
|
+
用环境变量 `DSH_WB_ROOT=<包目录>`(工具链自己的自检就是这么隔离的)。
|
|
63
70
|
|
|
64
71
|
## 十三条判据
|
|
65
72
|
|
|
@@ -70,7 +77,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
70
77
|
<!-- dsh-wb-module:begin checks -->
|
|
71
78
|
| 判据 | 层 | 摘要 |
|
|
72
79
|
|---|---|---|
|
|
73
|
-
| `contracts` | manifest | 单元 id、两个面、四个脚本、
|
|
80
|
+
| `contracts` | manifest | 单元 id、两个面、四个脚本、peer 依赖存在,以及清单与代码注册对账 |
|
|
74
81
|
| `protocol` | manifest | dsh.workbench 的 apiVersion、静态半形状与未知键 |
|
|
75
82
|
| `bundles` | build | 宿主产物不含浏览器侧、浏览器产物不内联外壳 CSS 与夹具 |
|
|
76
83
|
| `source` | source | 零注释、包与运行时入口分开、不手写进程/锁/原子写 |
|
|
@@ -112,11 +119,14 @@ export default moduleArch();
|
|
|
112
119
|
| `src/messages.ts` | 1 | 文案(zh / en) |
|
|
113
120
|
| `src/components/**` | 2 | 浏览器侧视图件;样式与它同目录(`X.module.css`) |
|
|
114
121
|
| `src/host.ts` · `src/client.tsx` | 10 | 两个面 |
|
|
115
|
-
|
|
|
122
|
+
| `src/host/**` | 10 | 宿主侧内部实现(按需出现):`moduleArch()` 默认就认这一层;对外仍只有 `src/host.ts` 一个面 |
|
|
123
|
+
| `src/**/__tests__/**` | 99 | 测试:住**被测代码同目录**的 `__tests__/`;`.test.ts(x)` 散在 `src/` 里判 S74 |
|
|
116
124
|
|
|
117
125
|
两个运行时靠另一条规则隔离:`src/components/**` 整片算浏览器面,宿主侧 import 一个组件是硬红
|
|
118
|
-
(层号拦不住它:components 第 2 层、host 第 10
|
|
119
|
-
|
|
126
|
+
(层号拦不住它:components 第 2 层、host 第 10 层)。`src/host/**` 与两个面同层(10),
|
|
127
|
+
所以浏览器面从它 import 时层号不报红 —— 运行时边界要自己守。内置阈值比 arch-guard 默认更严:
|
|
128
|
+
文件与视图各 200 行、函数 80 行;`src/model/**` 有文件就必须有测试(`__tests__/` 下 —— M08),
|
|
129
|
+
测试文件不在 `__tests__/` 里判 S74。
|
|
120
130
|
|
|
121
131
|
要开口子**从参数进**(别改判据;每条口子都有名字,评审看得见):
|
|
122
132
|
|
|
@@ -133,6 +143,10 @@ export default moduleArch({
|
|
|
133
143
|
`exceptions` 每条必须写 `reason`。怎么读红、常见红、还有哪些口子,见
|
|
134
144
|
[REVIEW.md](../core/skills/workbench-module-diy/REVIEW.md)。
|
|
135
145
|
|
|
146
|
+
宿主面写到 200 行还收不住时,把内部实现拆到 `src/host/**`(`diy` 模板就这么做,
|
|
147
|
+
`moduleArch()` 默认就认这一层)。**不要**把它做成第三个
|
|
148
|
+
`dsh.workbenchFaces` 入口 —— 两个面不可调。
|
|
149
|
+
|
|
136
150
|
### `lint`:模块包的语法卫生方言
|
|
137
151
|
|
|
138
152
|
`arch` 管结构,`lint` 管单文件:`as any` 绕过契约、残留的 `console.log` / `debugger`、
|
|
@@ -144,8 +158,8 @@ export default moduleLint({ rootDir: import.meta.dirname });
|
|
|
144
158
|
```
|
|
145
159
|
|
|
146
160
|
`moduleLint()` 返回一份**标准 flat config**:它同时是判据跑的那份与编辑器 / `eslint` 认的那份,
|
|
147
|
-
没有第二份真相。依赖(`eslint`、`typescript-eslint`、两个 react
|
|
148
|
-
|
|
161
|
+
没有第二份真相。依赖(`eslint`、`typescript-eslint`、两个 react 插件、`globals`)装在 module-tools 一家,
|
|
162
|
+
模块清单一个字都不用改(依赖由 module-tools 自己带)。
|
|
149
163
|
|
|
150
164
|
要开口子从命令参数进:`moduleLint({ rootDir, ignores, rules })`,或者展开它再追自己的一块:
|
|
151
165
|
|
|
@@ -280,20 +294,19 @@ dsh-wb-module design accept # 在真卡片上逐条走通后打勾
|
|
|
280
294
|
5. **`dsh.workbenchFaces` 不是协议的一部分,但门禁必填**:它指向两半的**源码**,不能指向出货
|
|
281
295
|
产物 —— 从 `dist/host.js` 反推不出是哪份源码把 dsh 的包拖进来的。
|
|
282
296
|
|
|
283
|
-
##
|
|
297
|
+
## 模板
|
|
284
298
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
| `diy` | 一份真能跑的样板:宿主侧一条只读路由真读盘、卡片五态、三张表也是填好的 |
|
|
299
|
+
只有**一个**模板(`diy`,也是默认):一份真能跑的样板 —— 宿主侧三条只读路由真读盘、卡片五态与写失败提示、
|
|
300
|
+
开箱(`(待填)`)的 `design.json`(填完 `design check` 才绿),外加一条可跑的长任务 / 两把工具(一只读一读写)/ 动作通道 / prefs。维护两份模板的成本
|
|
301
|
+
没人付,所以不再有 `minimal` 空壳;要空骨架就从 `diy` 删掉数据源那几段。
|
|
289
302
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
`
|
|
303
|
+
它是**分层多文件**骨架:纯逻辑住 `src/model/**`、视图件与同目录样式住 `src/components/**`、
|
|
304
|
+
文案住 `src/messages.ts`,测试住被测代码同目录的 `__tests__/`,两个面只剩注册与交 props;宿主侧内部实现住 `src/host/**`
|
|
305
|
+
(`moduleArch()` 默认就认这一层)。带一份 `arch.config.mjs` 与两行的
|
|
293
306
|
`eslint.config.mjs`。
|
|
294
307
|
|
|
295
308
|
`init` 连模板里的 `*.tpl` 一起拷、按已知 token 名逐个盖章(`__UNIT__` / `__NAME__` / `__DESC__` /
|
|
296
|
-
`__SHELL_RANGE__`)、剥掉后缀,再用 `design.json` 生成 README 那一段。
|
|
309
|
+
`__SHELL_RANGE__` / `__DSH_UI_VERSION__`)、剥掉后缀,再用 `design.json` 生成 README 那一段。
|
|
297
310
|
`__SHELL_RANGE__` 取 init 所在工程实装的那份外壳版本;读不到就退回 module-tools 自己的版本;
|
|
298
311
|
两者都读不到才保持 `^0.1.0` 并在 stderr 上说一句。
|
|
299
312
|
|
|
@@ -303,7 +316,7 @@ dsh-wb-module design accept # 在真卡片上逐条走通后打勾
|
|
|
303
316
|
node scripts/selfcheck/index.mjs
|
|
304
317
|
```
|
|
305
318
|
|
|
306
|
-
|
|
319
|
+
工具箱的回归网:模板 `init` 一遍、跑全套;判据的正例与负例都要对;打包清单不含
|
|
307
320
|
`.tpl` 残留;`design` 的空格、README 同步、`accept`;`publish` 的清单;`release` 的验收闸、
|
|
308
321
|
dry-run、真发后的元数据与发布失败回滚。
|
|
309
322
|
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { changelog } from '../src/changelog.mjs';
|
|
|
8
8
|
import { design } from '../src/design.mjs';
|
|
9
9
|
import { init } from '../src/init.mjs';
|
|
10
10
|
import { marketInit } from '../src/market-init.mjs';
|
|
11
|
+
import { migrate } from '../src/migrate.mjs';
|
|
11
12
|
import { publish } from '../src/publish.mjs';
|
|
12
13
|
import { release } from '../src/release.mjs';
|
|
13
14
|
import { sync } from '../src/sync.mjs';
|
|
@@ -45,14 +46,25 @@ async function lint({ root, args }) {
|
|
|
45
46
|
if (failed > 0) process.exit(1);
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const commands = { build, check, init, 'market-init': marketInit, design, publish, release, sync, lint, changelog };
|
|
49
|
+
const commands = { build, check, init, 'market-init': marketInit, design, migrate, publish, release, sync, lint, changelog };
|
|
49
50
|
|
|
50
51
|
const CHECK_HELP = `
|
|
51
52
|
查询口:
|
|
52
53
|
check --only kit [--file <名字>] 这份外壳的 kit 里有什么
|
|
53
54
|
check --only primitives [--all|--files|--json] [--file <名字>]
|
|
54
55
|
这份外壳的 dsh 原语里有什么
|
|
55
|
-
check --list [--json|--markdown]
|
|
56
|
+
check --list [--json|--markdown] 只列判据元数据
|
|
57
|
+
|
|
58
|
+
范围:
|
|
59
|
+
check --package <包名|unit> 只报这个模块的问题(开发期便利,不是门禁)
|
|
60
|
+
全量扫描、只过滤报告与退出码`;
|
|
61
|
+
|
|
62
|
+
const MIGRATE_HELP = `
|
|
63
|
+
用法:
|
|
64
|
+
migrate [--cwd <模块目录>] [--dry-run]
|
|
65
|
+
|
|
66
|
+
把模块清单迁到当前代:apiVersion → 3、缺/空的 workbenchFaces 补默认入口。
|
|
67
|
+
源码类问题(kit 成员移除、裸控件、card: null 对账)不自动改 —— check 会逐条点名。`;
|
|
56
68
|
|
|
57
69
|
const RELEASE_HELP = `
|
|
58
70
|
选项:
|
|
@@ -86,9 +98,10 @@ release --changelog-note "这一版改了什么"(文件不在会先建)。`;
|
|
|
86
98
|
const COMMANDS = {
|
|
87
99
|
build: '出 dist/host.js(单文件 ESM)与 client.js(套 loader 壳)',
|
|
88
100
|
check: '十三条判据;--only 单跑,也当查询口用;--list 只列判据元数据',
|
|
89
|
-
init: '建模块骨架(--dir / --template
|
|
101
|
+
init: '建模块骨架(--dir / --template diy / --description;只有 diy 一个模板)',
|
|
90
102
|
'market-init': '初始化市场索引仓库(写 market/ 与 README,推首个 commit)',
|
|
91
103
|
design: '设计底稿:init 填 / check 查空格 / render 生成 README / accept 打勾',
|
|
104
|
+
migrate: '把模块清单迁到当前代(apiVersion / workbenchFaces);源码类问题只给提示',
|
|
92
105
|
publish: '发货前清单:门禁 + files + npm pack(只 dry run)',
|
|
93
106
|
release: '真发布:全门禁 + 验收闸 + 预检接管 + 发到出口 + 发后核包 + 更新市场索引(中断可 --resume)',
|
|
94
107
|
sync: '按清单生成 src/declared-tools.ts,按 design.json 生成 src/datasource.ts',
|
|
@@ -121,7 +134,7 @@ const program = new Command('dsh-wb-module')
|
|
|
121
134
|
.helpOption('-h, --help', '打印帮助')
|
|
122
135
|
.addHelpText(
|
|
123
136
|
'after',
|
|
124
|
-
'\n --cwd <目录>
|
|
137
|
+
'\n --cwd <目录> 指定工作目录(默认当前目录;判据仍按工作区发现,可用 DSH_WB_MODULE_CWD)\n 任何命令后跟 --help 都只打印帮助。\n',
|
|
125
138
|
);
|
|
126
139
|
|
|
127
140
|
for (const [name, description] of Object.entries(COMMANDS)) {
|
|
@@ -135,6 +148,7 @@ for (const [name, description] of Object.entries(COMMANDS)) {
|
|
|
135
148
|
if (name === 'release') sub.addHelpText('after', `${RELEASE_HELP}\n`);
|
|
136
149
|
if (name === 'market-init') sub.addHelpText('after', `${MARKET_INIT_HELP}\n`);
|
|
137
150
|
if (name === 'changelog') sub.addHelpText('after', `${CHANGELOG_HELP}\n`);
|
|
151
|
+
if (name === 'migrate') sub.addHelpText('after', `${MIGRATE_HELP}\n`);
|
|
138
152
|
sub.action(async (_options, command) => {
|
|
139
153
|
await dispatch(name, command.args);
|
|
140
154
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.14-beta.0",
|
|
4
4
|
"description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"bin",
|
|
17
17
|
"src",
|
|
18
|
-
"template",
|
|
19
18
|
"template-diy"
|
|
20
19
|
],
|
|
21
20
|
"publishConfig": {
|
|
22
21
|
"access": "public"
|
|
23
22
|
},
|
|
24
23
|
"dependencies": {
|
|
25
|
-
"@arch-guard/core": "^0.11.
|
|
24
|
+
"@arch-guard/core": "^0.11.3",
|
|
26
25
|
"@eslint/js": "^10.0.1",
|
|
27
26
|
"commander": "^15.0.0",
|
|
28
27
|
"esbuild": "^0.28.2",
|
|
@@ -30,10 +29,11 @@
|
|
|
30
29
|
"eslint-plugin-react": "^7.37.5",
|
|
31
30
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
32
31
|
"execa": "^10.2.0",
|
|
32
|
+
"globals": "^17.13.0",
|
|
33
33
|
"semver": "^7.8.5",
|
|
34
34
|
"typescript": "5.9.3",
|
|
35
35
|
"typescript-eslint": "^8.71.0",
|
|
36
|
-
"@dsh-workbench/shared": "1.1.
|
|
36
|
+
"@dsh-workbench/shared": "1.1.14-beta.0"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
package/src/arch.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { SEED_SPECIFIERS } from './seed.mjs';
|
|
|
17
17
|
const HOST_FACE = 'src/host.ts';
|
|
18
18
|
const CLIENT_FACE = 'src/client.tsx';
|
|
19
19
|
|
|
20
|
-
const LAYERS = { model: 1, components: 2 };
|
|
20
|
+
const LAYERS = { model: 1, components: 2, host: 10 };
|
|
21
21
|
|
|
22
22
|
const MESSAGES_ROLE = { id: 'lib:messages', pattern: 'src/messages.ts', layer: 1 };
|
|
23
23
|
|
|
@@ -25,18 +25,6 @@ const DECLARED_TOOLS_ROLE = { id: 'lib:declared-tools', pattern: DECLARED_TOOLS_
|
|
|
25
25
|
|
|
26
26
|
const DEFAULT_THRESHOLDS = { fileLines: 200, viewLines: 200, functionLines: 80 };
|
|
27
27
|
|
|
28
|
-
const TEST_LINES_REASON =
|
|
29
|
-
'测试文件是脚手架,不是产品代码:夹具、逐条断言、一条用例十来行,200 行这个阈值对它是错的口径;' +
|
|
30
|
-
'按它拆文件只会把测试脚手架摞进 src/ 的层里(作者反馈:一个模块被逼出过 src/host/testing.ts)。' +
|
|
31
|
-
'测试在方言里本来就有一条排他角色(layer 99,S21 跳过它),这条豁免与那条同一个理由。' +
|
|
32
|
-
'要形状与体积上的约束,请写进模块自己的测试(用例数、覆盖的行为),不要用文件行数卡它。';
|
|
33
|
-
|
|
34
|
-
const TEST_LINES_EXCEPTIONS = [
|
|
35
|
-
{ rule: 'S16', glob: '**/*.{test,spec}.{ts,tsx,mts,cts,js,mjs,cjs}', reason: TEST_LINES_REASON },
|
|
36
|
-
|
|
37
|
-
{ rule: 'S16', glob: 'src/**/__tests__/**', reason: TEST_LINES_REASON },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
28
|
const TESTS_DIR_ROLE = {
|
|
41
29
|
id: 'lib:tests-dir',
|
|
42
30
|
pattern: 'src/**/__tests__/**',
|
|
@@ -44,15 +32,6 @@ const TESTS_DIR_ROLE = {
|
|
|
44
32
|
exclusive: true,
|
|
45
33
|
};
|
|
46
34
|
|
|
47
|
-
const STYLE_EXCEPTION = {
|
|
48
|
-
rule: 'D04',
|
|
49
|
-
glob: 'src/components/**',
|
|
50
|
-
reason:
|
|
51
|
-
'主题令牌由 dsh 在运行时提供(--dsw-*):模块包**不拥有**令牌表,声明一份自己的' +
|
|
52
|
-
'就是编造一处不存在的真相。所以组件样式里引用的 --dsw-* 在本地查不闭合是正常的;' +
|
|
53
|
-
'dsh 改名会由外壳升级暴露,而不是由模块本地的表兜住。',
|
|
54
|
-
};
|
|
55
|
-
|
|
56
35
|
const SEED_PACKAGES = [
|
|
57
36
|
...new Set(
|
|
58
37
|
SEED_SPECIFIERS.map((name) => (name.startsWith('@') ? name.split('/').slice(0, 2).join('/') : name.split('/')[0])),
|
|
@@ -74,6 +53,11 @@ const ALLOWED_DEPS = [
|
|
|
74
53
|
|
|
75
54
|
const DESIGN = {
|
|
76
55
|
styleDir: 'src/components',
|
|
56
|
+
externalTokens: [
|
|
57
|
+
{ prefix: '--dsw-', provider: '@deepseek-ai/dsh-client-ui-theme' },
|
|
58
|
+
{ prefix: '--ds-', provider: '@deepseek-ai/dsh-client-ui-theme' },
|
|
59
|
+
{ prefix: '--wb-', provider: '@dsh-workbench/core' },
|
|
60
|
+
],
|
|
77
61
|
tokenDir: '',
|
|
78
62
|
paletteFile: '',
|
|
79
63
|
vendorDir: 'src/components/vendor',
|
|
@@ -201,7 +185,34 @@ const protocolsStayCanonical = createRule({
|
|
|
201
185
|
},
|
|
202
186
|
});
|
|
203
187
|
|
|
204
|
-
const
|
|
188
|
+
const TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?(?:\.tpl)?$/;
|
|
189
|
+
const TESTS_HOME = /(?:^|\/)__tests__\//;
|
|
190
|
+
|
|
191
|
+
const testsLiveInFolders = createRule({
|
|
192
|
+
id: 'S74',
|
|
193
|
+
domain: 'structure',
|
|
194
|
+
level: 'L2',
|
|
195
|
+
severity: 'error',
|
|
196
|
+
title: '测试文件住在 __tests__ 目录里',
|
|
197
|
+
hint: '测试与被测代码物理分开:挪进同目录的 __tests__/,相对路径的 import 各退一级。',
|
|
198
|
+
run(ctx) {
|
|
199
|
+
const findings = [];
|
|
200
|
+
for (const record of ctx.records) {
|
|
201
|
+
if (!TEST_FILE.test(record.rel)) continue;
|
|
202
|
+
if (TESTS_HOME.test(record.rel)) continue;
|
|
203
|
+
findings.push({
|
|
204
|
+
rule: 'S74',
|
|
205
|
+
file: record.rel,
|
|
206
|
+
line: 1,
|
|
207
|
+
text: `测试文件不在 __tests__ 目录里:${record.rel}`,
|
|
208
|
+
hint: '挪进同目录的 __tests__/,相对路径的 import 各退一级。',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return findings;
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const MODULE_RULES = [modelStaysPure, componentsStayBrowser, protocolsStayCanonical, testsLiveInFolders];
|
|
205
216
|
|
|
206
217
|
export function moduleArch(options = {}) {
|
|
207
218
|
const {
|
|
@@ -259,7 +270,7 @@ export function moduleArch(options = {}) {
|
|
|
259
270
|
},
|
|
260
271
|
}),
|
|
261
272
|
|
|
262
|
-
|
|
273
|
+
|
|
263
274
|
],
|
|
264
275
|
overrides: {
|
|
265
276
|
include: ['src/**'],
|
|
@@ -279,7 +290,7 @@ export function moduleArch(options = {}) {
|
|
|
279
290
|
{ pattern: 'src/components/**', runtime: 'client' },
|
|
280
291
|
],
|
|
281
292
|
},
|
|
282
|
-
exceptions: [
|
|
293
|
+
exceptions: [...exceptions],
|
|
283
294
|
},
|
|
284
295
|
};
|
|
285
296
|
}
|
package/src/check.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { discoverPackages } from './discover.mjs';
|
|
2
|
+
|
|
1
3
|
export const CHECKS = {
|
|
2
4
|
contracts: {
|
|
3
5
|
title: '模块契约',
|
|
4
|
-
summary: '单元 id、两个面、四个脚本、
|
|
6
|
+
summary: '单元 id、两个面、四个脚本、peer 依赖存在,以及清单与代码注册对账',
|
|
5
7
|
layer: 'manifest',
|
|
6
8
|
load: () => import('./checks/contracts.mjs'),
|
|
7
9
|
},
|
|
@@ -135,6 +137,29 @@ export async function check({ only, args = [] } = {}) {
|
|
|
135
137
|
}
|
|
136
138
|
const names = only === undefined ? Object.keys(CHECKS) : [only];
|
|
137
139
|
const jsonMode = args.includes('--json');
|
|
140
|
+
const packageAt = args.indexOf('--package');
|
|
141
|
+
const wanted = packageAt === -1 ? undefined : args[packageAt + 1];
|
|
142
|
+
let filter;
|
|
143
|
+
let moduleNames = [];
|
|
144
|
+
if (wanted !== undefined) {
|
|
145
|
+
const packages = discoverPackages({ root: process.cwd() }).filter(
|
|
146
|
+
(pkg) => pkg.manifest.dsh?.workbench !== undefined,
|
|
147
|
+
);
|
|
148
|
+
const matched = packages.filter((pkg) => {
|
|
149
|
+
const name = pkg.manifest.name;
|
|
150
|
+
const unit = pkg.manifest.dsh?.workbench?.unit;
|
|
151
|
+
return name === wanted || name.endsWith(`/${wanted}`) || unit === wanted;
|
|
152
|
+
});
|
|
153
|
+
if (matched.length === 0) {
|
|
154
|
+
const available = packages.map((pkg) => `${pkg.manifest.name}(${String(pkg.manifest.dsh?.workbench?.unit ?? '')})`);
|
|
155
|
+
console.error(`没有这个模块:${wanted}(这个工作区里有:${available.join(' / ')})`);
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
|
158
|
+
filter = new Set(matched.map((pkg) => pkg.manifest.name));
|
|
159
|
+
moduleNames = packages.map((pkg) => pkg.manifest.name).filter((name) => !filter.has(name));
|
|
160
|
+
console.log(`(只查 ${[...filter].join(' / ')}:问题按包名过滤;完整门禁别带 --package)`);
|
|
161
|
+
}
|
|
162
|
+
process.env.DSH_WB_PACKAGE_FILTER = filter === undefined ? '' : '' + [...filter].length;
|
|
138
163
|
const tally = { pass: 0, skip: 0, fail: 0 };
|
|
139
164
|
const skipped = [];
|
|
140
165
|
let ran = 0;
|
|
@@ -149,7 +174,26 @@ export async function check({ only, args = [] } = {}) {
|
|
|
149
174
|
|
|
150
175
|
process.env.DSH_WB_ONLY = only === undefined ? '' : name;
|
|
151
176
|
const module = await entry.load();
|
|
152
|
-
|
|
177
|
+
let result = await module.run();
|
|
178
|
+
if (filter !== undefined && result.status === 'fail') {
|
|
179
|
+
const kept = result.problems.filter((problem) => {
|
|
180
|
+
const own = [...filter].some(
|
|
181
|
+
(pkgName) => problem.startsWith(`${pkgName}:`) || problem.startsWith(`${pkgName} `),
|
|
182
|
+
);
|
|
183
|
+
if (own) return true;
|
|
184
|
+
return !moduleNames.some(
|
|
185
|
+
(pkgName) => problem.startsWith(`${pkgName}:`) || problem.startsWith(`${pkgName} `),
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
const notes = [
|
|
189
|
+
...result.notes,
|
|
190
|
+
`(--package:只列 ${[...filter].join(' / ')} 的问题,其余模块的问题这次不算)`,
|
|
191
|
+
];
|
|
192
|
+
result =
|
|
193
|
+
kept.length === 0
|
|
194
|
+
? { ...result, status: 'pass', problems: [], notes }
|
|
195
|
+
: { ...result, problems: kept, notes };
|
|
196
|
+
}
|
|
153
197
|
|
|
154
198
|
const title = typeof module.title === 'string' && module.title !== '' ? module.title : entry.title;
|
|
155
199
|
ran += 1;
|
|
@@ -178,11 +222,14 @@ export async function check({ only, args = [] } = {}) {
|
|
|
178
222
|
}
|
|
179
223
|
|
|
180
224
|
console.log(`${ran} 条判据:${tally.pass} 通过 · ${tally.skip} 不适用 · ${tally.fail} 不过`);
|
|
225
|
+
if (filter !== undefined) {
|
|
226
|
+
console.log(` 这次只报 ${[...filter].join(' / ')};其余模块的红被过滤掉了 —— 门禁 / 发布请不带 --package 重跑。`);
|
|
227
|
+
}
|
|
181
228
|
|
|
182
229
|
if (skipped.length > 0) {
|
|
183
230
|
console.log(` 这 ${skipped.length} 条这次一条都没查(不是"没问题"):`);
|
|
184
231
|
for (const one of skipped) console.log(` ○ ${one}`);
|
|
185
|
-
console.log(' 换个 cwd
|
|
232
|
+
console.log(' 换个 cwd 它们才跑得起来(工作区里「独立构建」扫不到外壳源码、「公开包无内部标识」只扫');
|
|
186
233
|
console.log(' 要发出去的包)—— 想验这两条,回工作台仓库根跑 `dsh-wb-module check`。');
|
|
187
234
|
}
|
|
188
235
|
if (tally.fail > 0) process.exit(1);
|
package/src/checks/arch.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const HOW_TO_JOIN =
|
|
|
11
11
|
`在模块包里放一份 ${CONFIG_FILE},内容一行就够:\n` +
|
|
12
12
|
` import { moduleArch } from '@dsh-workbench/module-tools/arch';\n` +
|
|
13
13
|
' export default moduleArch();\n' +
|
|
14
|
-
' 形状(层序表 / 两个运行时 / 阈值)见
|
|
14
|
+
' 形状(层序表 / 两个运行时 / 阈值)见 @dsh-workbench/module-tools/arch 的文档与 README;' +
|
|
15
15
|
'要开口子就从 moduleArch() 的参数进(deps / layers / roles / exceptions)。';
|
|
16
16
|
|
|
17
17
|
const severityIndex = (rules) => new Map(rules.map((rule) => [rule.id, rule.severity]));
|
|
@@ -69,7 +69,7 @@ export async function run() {
|
|
|
69
69
|
const message = error instanceof Error ? error.message : String(error);
|
|
70
70
|
problems.push(
|
|
71
71
|
`${name}: ${CONFIG_FILE} 跑不起来 —— ${message}(先确认这份配置能 import、没有语法错,` +
|
|
72
|
-
'再确认它 import
|
|
72
|
+
'再确认它 import 的东西在工作区里解析得到)',
|
|
73
73
|
);
|
|
74
74
|
continue;
|
|
75
75
|
}
|
package/src/checks/contracts.mjs
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
3
4
|
import { join, relative } from 'node:path';
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildSupplyIndex,
|
|
8
|
+
createHostKit,
|
|
9
|
+
hostSurface,
|
|
10
|
+
readDeclaration,
|
|
11
|
+
recipeProblemOf,
|
|
12
|
+
} from '@dsh-workbench/shared/host';
|
|
6
13
|
import {
|
|
7
14
|
DECLARED_TOOLS_FILE,
|
|
8
15
|
declaredToolsProblemOf,
|
|
@@ -61,9 +68,15 @@ async function unitFaceOf(dir) {
|
|
|
61
68
|
const OPENCLI_CATALOG_ENV = 'DSH_WB_OPENCLI_CATALOG';
|
|
62
69
|
const OPENCLI_NPM_PACKAGE = '@jackwener/opencli';
|
|
63
70
|
|
|
64
|
-
function
|
|
71
|
+
function workbenchRootOf() {
|
|
72
|
+
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
73
|
+
if (configured !== undefined && configured !== '') return configured;
|
|
65
74
|
const home = process.env.DSH_HOME?.trim();
|
|
66
|
-
return `${home === undefined || home === '' ?
|
|
75
|
+
return `${home === undefined || home === '' ? join(homedir(), '.dsh') : home}/workbench`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isolatedRootOf() {
|
|
79
|
+
return `${workbenchRootOf()}/opencli`;
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
function catalogPathOf() {
|
|
@@ -224,8 +237,26 @@ function fakeWorkspaceRegistry() {
|
|
|
224
237
|
};
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
function fakeDataReader() {
|
|
241
|
+
const missing = async () => ({
|
|
242
|
+
ok: false,
|
|
243
|
+
error: { code: 'invalid-request', message: 'gate: 没有注入 DataReader(模块只能走 deps.data 读数据)' },
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
read: missing,
|
|
247
|
+
request: missing,
|
|
248
|
+
requestMany: async (_recipe, calls) => {
|
|
249
|
+
const out = {};
|
|
250
|
+
for (const name of Object.keys(calls)) out[name] = await missing();
|
|
251
|
+
return out;
|
|
252
|
+
},
|
|
253
|
+
submit: missing,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
227
257
|
function mountOf(face, created, manifest, tools) {
|
|
228
|
-
if (face === undefined
|
|
258
|
+
if (face === undefined) return {};
|
|
259
|
+
if (typeof face.mount !== 'function') return { error: '宿主面没有 mount(deps)' };
|
|
229
260
|
|
|
230
261
|
const shell = fakeShell();
|
|
231
262
|
const workspaceRegistry = fakeWorkspaceRegistry();
|
|
@@ -236,39 +267,45 @@ function mountOf(face, created, manifest, tools) {
|
|
|
236
267
|
},
|
|
237
268
|
};
|
|
238
269
|
try {
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
270
|
+
return {
|
|
271
|
+
result: face.mount({
|
|
272
|
+
manifest,
|
|
273
|
+
ctx: {
|
|
274
|
+
get: (key) => {
|
|
275
|
+
if (key === 'tools') return toolRegistry;
|
|
276
|
+
if (key === 'shell') return shell;
|
|
277
|
+
if (key === 'workspaceRegistry') return workspaceRegistry;
|
|
278
|
+
return undefined;
|
|
279
|
+
},
|
|
280
|
+
effect: (fn) => {
|
|
281
|
+
fn();
|
|
282
|
+
},
|
|
283
|
+
provide: () => {},
|
|
284
|
+
logger: { warn: () => {}, info: () => {} },
|
|
247
285
|
},
|
|
248
|
-
|
|
249
|
-
|
|
286
|
+
manifests: () => [],
|
|
287
|
+
ownedUnits: () => [],
|
|
288
|
+
data: fakeDataReader(),
|
|
289
|
+
...createHostKit({ libInstallCommand: () => '', surface: hostSurface() }),
|
|
290
|
+
opencliCommand: 'opencli',
|
|
291
|
+
env: {},
|
|
292
|
+
git: {
|
|
293
|
+
origin: async () => ({ kind: 'no-origin' }),
|
|
294
|
+
branch: async () => ({ kind: 'no-branch' }),
|
|
295
|
+
head: async () => ({ kind: 'no-head' }),
|
|
250
296
|
},
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
opencliCommand: 'opencli',
|
|
256
|
-
env: {},
|
|
257
|
-
git: {
|
|
258
|
-
origin: async () => ({ kind: 'no-origin' }),
|
|
259
|
-
branch: async () => ({ kind: 'no-branch' }),
|
|
260
|
-
head: async () => ({ kind: 'no-head' }),
|
|
261
|
-
},
|
|
262
|
-
libs: {
|
|
263
|
-
load: async () => {
|
|
264
|
-
throw new Error('gate: libs 未装');
|
|
297
|
+
libs: {
|
|
298
|
+
load: async () => {
|
|
299
|
+
throw new Error('gate: libs 未装');
|
|
300
|
+
},
|
|
265
301
|
},
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
302
|
+
store: fakeStore(),
|
|
303
|
+
longTasks: fakeLongTasks(created),
|
|
304
|
+
longTaskRuns: () => new Map(),
|
|
305
|
+
}),
|
|
306
|
+
};
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
272
309
|
}
|
|
273
310
|
}
|
|
274
311
|
|
|
@@ -502,7 +539,8 @@ export async function run() {
|
|
|
502
539
|
if (typeof rel !== 'string') {
|
|
503
540
|
problems.push(
|
|
504
541
|
`${name}: dsh.workbenchFaces.${face} 没声明 —— 这两个源码入口是工具链字段` +
|
|
505
|
-
`(不在 dsh.workbench 协议里):source 判据靠它们判"哪一半"
|
|
542
|
+
`(不在 dsh.workbench 协议里):source 判据靠它们判"哪一半",缺了它判不了` +
|
|
543
|
+
`(\`dsh-wb-module migrate\` 会补默认的 ${face === 'host' ? 'src/host.ts' : 'src/client.tsx'})`,
|
|
506
544
|
);
|
|
507
545
|
} else if (!existsSync(join(pkg.dir, rel))) {
|
|
508
546
|
problems.push(`${name}: dsh.workbenchFaces.${face} 指的文件不存在 —— ${rel}`);
|
|
@@ -540,7 +578,9 @@ export async function run() {
|
|
|
540
578
|
|
|
541
579
|
const tools = [];
|
|
542
580
|
const face = await unitFaceOf(pkg.dir);
|
|
543
|
-
const
|
|
581
|
+
const mountedOf = face === undefined ? undefined : mountOf(face, created, manifest, tools);
|
|
582
|
+
const mounted = mountedOf?.result;
|
|
583
|
+
const mountError = mountedOf?.error;
|
|
544
584
|
const needs = manifest?.needs;
|
|
545
585
|
warnings.push(...read.warnings);
|
|
546
586
|
if (manifest === undefined) {
|
|
@@ -556,6 +596,11 @@ export async function run() {
|
|
|
556
596
|
`${name}: 声明了 ${manifest.tools.length} 个能力,但宿主面挂不起来(dist/host.js 缺、或 apply 抛了)` +
|
|
557
597
|
`—— 声明的能力对不了账,先把面修好`,
|
|
558
598
|
);
|
|
599
|
+
} else if (mountError !== undefined) {
|
|
600
|
+
problems.push(
|
|
601
|
+
`${name}: 宿主面 mount(deps) 抛了 —— ${mountError}。` +
|
|
602
|
+
`先修 mount 里用到的那份 deps(宿主交的服务)与初始化,能力才对得了账`,
|
|
603
|
+
);
|
|
559
604
|
} else {
|
|
560
605
|
try {
|
|
561
606
|
mounted?.registerTools();
|
|
@@ -784,10 +829,12 @@ export async function run() {
|
|
|
784
829
|
const writeTools = (manifest?.tools ?? []).filter((one) => one?.writes === true);
|
|
785
830
|
if (writeTools.length > 0) {
|
|
786
831
|
if (mounted === undefined) {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
832
|
+
if (mountError === undefined) {
|
|
833
|
+
problems.push(
|
|
834
|
+
`${name}: 清单里有会写的工具,但宿主面挂不起来(apply / mount 抛了)—— ` +
|
|
835
|
+
`这条审批口径验不了,先把挂载依赖修好(模块自己的 smoke 也是这么挂的)`,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
791
838
|
continue;
|
|
792
839
|
}
|
|
793
840
|
const registered = new Map(tools.map((tool) => [text(tool?.name), tool]));
|
|
@@ -948,7 +995,7 @@ export async function run() {
|
|
|
948
995
|
notes.push(
|
|
949
996
|
`上面有 ${bare.length} 个包是**裸**的:它上面没有 pnpm-workspace.yaml、自己也没有 node_modules —— ` +
|
|
950
997
|
`那几条 inject/external 正是因为找不到任何同伴包才落不到图里。` +
|
|
951
|
-
|
|
998
|
+
`它上面既没有 pnpm-workspace.yaml、自己也没有 node_modules —— 所以找不到任何同伴包。`,
|
|
952
999
|
);
|
|
953
1000
|
}
|
|
954
1001
|
return { status: 'fail', notes: [...notes, ...warnings.map((one) => `(警告)${one}`)], problems };
|