@dsh-workbench/module-tools 0.3.11 → 1.0.0-beta.1
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/LICENSE +21 -0
- package/README.md +223 -177
- package/bin/dsh-wb-module.mjs +89 -34
- package/package.json +22 -3
- package/src/arch.mjs +158 -129
- package/src/build.mjs +6 -30
- package/src/changelog.mjs +166 -0
- package/src/check-flags.mjs +0 -7
- package/src/check.mjs +115 -53
- package/src/checks/alias.mjs +0 -9
- package/src/checks/arch.mjs +3 -25
- package/src/checks/bundles.mjs +2 -34
- package/src/checks/cards.mjs +26 -30
- package/src/checks/contracts.mjs +72 -219
- package/src/checks/kit.mjs +2 -42
- package/src/checks/lint.mjs +9 -82
- package/src/checks/markers.mjs +48 -17
- package/src/checks/primitives.mjs +0 -39
- package/src/checks/protocol.mjs +0 -18
- package/src/checks/source.mjs +103 -51
- package/src/checks/standalone.mjs +4 -14
- package/src/checks/style.mjs +84 -230
- package/src/css-injection.mjs +0 -11
- package/src/css-shell.mjs +3 -31
- package/src/datasource-file.mjs +104 -0
- package/src/declared-tools.mjs +0 -24
- package/src/design.mjs +116 -38
- package/src/discover.mjs +2 -10
- package/src/font-scale.mjs +1 -61
- package/src/init.mjs +2 -17
- package/src/lint.mjs +24 -144
- package/src/market-index.mjs +569 -0
- package/src/market-init.mjs +80 -0
- package/src/primitives-surface.mjs +2 -28
- package/src/publish.mjs +11 -25
- package/src/release-changelog.mjs +29 -0
- package/src/release-journal.mjs +131 -0
- package/src/release-report.mjs +99 -0
- package/src/release-util.mjs +87 -0
- package/src/release.mjs +1250 -0
- package/src/seed.mjs +0 -25
- package/src/shell-type-files.mjs +3 -30
- package/src/sync.mjs +41 -24
- package/template/AGENTS.md.tpl +33 -0
- package/template/CHANGELOG.md.tpl +7 -0
- package/template/README.md.tpl +6 -33
- package/template/package.json.tpl +2 -2
- package/template/src/client.tsx.tpl +1 -18
- package/template/src/host.ts.tpl +2 -15
- package/template/types/globals.d.ts +1 -14
- package/template-diy/AGENTS.md.tpl +33 -0
- package/template-diy/CHANGELOG.md.tpl +7 -0
- package/template-diy/README.md.tpl +4 -133
- package/template-diy/package.json.tpl +2 -2
- package/template-diy/src/client.tsx.tpl +1 -29
- package/template-diy/src/host.ts.tpl +1 -15
- package/template-diy/src/plugin.test.ts.tpl +1 -1
- package/template-diy/types/globals.d.ts +1 -14
- package/template-diy/types/styles.d.ts +0 -10
package/src/checks/cards.mjs
CHANGED
|
@@ -19,9 +19,6 @@ const FORBIDDEN = [
|
|
|
19
19
|
|
|
20
20
|
const WORKSPACE = { path: '/tmp/repo', workspaceId: 'wb-card-probe' };
|
|
21
21
|
|
|
22
|
-
// 刷新那一帧的旧时间戳(`at`)。真壳在 `ready` 那一步写 `Date.now()`,同一个 identity 重读时
|
|
23
|
-
// 把它原样带进 loading 相(`use-api-resource.ts:102`);这里给一个固定的过去时刻,判据不依赖
|
|
24
|
-
// "现在几点"(同一个值每次跑都一样,负例才好断言)。
|
|
25
22
|
const STALE_AT = 1_700_000_000_000;
|
|
26
23
|
|
|
27
24
|
function anyStub(path = 'stub') {
|
|
@@ -103,51 +100,52 @@ const SNAPSHOTS = [
|
|
|
103
100
|
resource: { phase: 'error', error: { code: 'network', message: '宿主没有响应' } },
|
|
104
101
|
workspace: WORKSPACE,
|
|
105
102
|
},
|
|
106
|
-
|
|
107
|
-
// `at` 都在,而且是旧那份。`use-api-resource.ts:100-104` 在同一个 identity 重读时给的就是这一相
|
|
108
|
-
// (`{ phase: 'loading', value: prev.value, ...(prev.at === undefined ? {} : { at: prev.at }) }`,
|
|
109
|
-
// 而 `prev.at` 是 `ready` 那一步写的 `Date.now()`,所以真实这一相两格一起在)。卡片若把
|
|
110
|
-
// "loading 就没有值"写死,脏就出在这一帧;若把旧值当新值、或拿旧 `at` 当"刚读到"去算,脏也出在这一帧。
|
|
103
|
+
|
|
111
104
|
{
|
|
112
105
|
name: 'loading(刷新:保留旧值)',
|
|
113
106
|
resource: { phase: 'loading', value: containerStub('api', 2), at: STALE_AT },
|
|
114
107
|
workspace: WORKSPACE,
|
|
115
108
|
},
|
|
116
|
-
|
|
117
|
-
// `use-unit-board.ts:47` 对 `capability === null` 直接不算 `blockerOfCapability`,喂 null 就退化成
|
|
118
|
-
// P1 的 idle 那一屏、一格新代码都走不到。`gated` 由能力状态按 `gateOf`(`unit-board.ts:13-17`)
|
|
119
|
-
// 算出来(missing-plugin ⇒ false),`resource` 照 `ready` 一份 —— 于是它跟 `ready` 只差闸这一格,
|
|
120
|
-
// 阻断屏与正文同时在场时谁优先是模块的事。
|
|
109
|
+
|
|
121
110
|
{
|
|
122
111
|
name: 'unreadable(能力闸:缺插件)',
|
|
123
112
|
resource: { phase: 'ready', value: containerStub('api', 2) },
|
|
124
113
|
workspace: WORKSPACE,
|
|
125
114
|
capability: (unit) => ({
|
|
126
115
|
unit,
|
|
127
|
-
status: 'missing
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
status: 'missing',
|
|
117
|
+
needs: [
|
|
118
|
+
{
|
|
119
|
+
id: 'extension:demo-plugin',
|
|
120
|
+
kind: 'extension',
|
|
121
|
+
name: 'demo-plugin',
|
|
122
|
+
status: 'missing',
|
|
123
|
+
detail: '逐态探针:缺 demo-plugin',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
130
126
|
detail: '逐态探针:缺 demo-plugin',
|
|
131
127
|
}),
|
|
132
128
|
},
|
|
133
|
-
|
|
134
|
-
// 差的是两格:`status` 换掉,且 `fix` / `command` 都在(真壳 `probe-verdict.ts:109-116`:命令落后
|
|
135
|
-
// 的插件状态是 `outdated`,于是 `fix: 'update'`;`:158-160` 再带上那条升级命令)。这两格正好把
|
|
136
|
-
// `blockerOfCapability` 里 `install` 那一段走通:`fixAction`(`blocker.fix.update`)+ `copyAction`
|
|
137
|
-
// (`blocker.copy` + `command` 原文,`blocker-table.ts:109-123`),加上照旧的 retry —— 缺插件那一帧
|
|
138
|
-
// 两格都没给,install 是空的,只走到 retry。`sourceUnknown` 在 `fix` 在时不成立,所以不开市场那条。
|
|
129
|
+
|
|
139
130
|
{
|
|
140
131
|
name: 'unreadable(能力闸:缺命令)',
|
|
141
132
|
resource: { phase: 'ready', value: containerStub('api', 2) },
|
|
142
133
|
workspace: WORKSPACE,
|
|
143
134
|
capability: (unit) => ({
|
|
144
135
|
unit,
|
|
145
|
-
status: '
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
status: 'outdated',
|
|
137
|
+
needs: [
|
|
138
|
+
{
|
|
139
|
+
id: 'extension:demo-plugin',
|
|
140
|
+
kind: 'extension',
|
|
141
|
+
name: 'demo-plugin',
|
|
142
|
+
status: 'outdated',
|
|
143
|
+
missing: ['demo.command'],
|
|
144
|
+
detail: '逐态探针:demo-plugin 装在,缺 demo.command',
|
|
145
|
+
command: "'opencli' plugin update 'demo-plugin'",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
148
|
detail: '逐态探针:demo-plugin 装在,缺 demo.command',
|
|
149
|
-
fix: 'update',
|
|
150
|
-
command: 'opencli plugin update demo-plugin',
|
|
151
149
|
}),
|
|
152
150
|
},
|
|
153
151
|
];
|
|
@@ -403,9 +401,7 @@ export async function run() {
|
|
|
403
401
|
if (realKit === undefined) return fakeKit();
|
|
404
402
|
const hooks = { ...(realKit.hooks ?? {}) };
|
|
405
403
|
hooks.useApiResource = () => current.snapshot.resource;
|
|
406
|
-
|
|
407
|
-
// `capability` 才换成那一份,`gated` 照 `gateOf(capability, 'capability')` 算 —— 判据不当
|
|
408
|
-
// 第二份真相:状态的语义归外壳,这里只是把"外壳会给的那一格"摆出来。
|
|
404
|
+
|
|
409
405
|
hooks.useCapabilityGate = (unit) => {
|
|
410
406
|
const capability = current.snapshot.capability?.(unit) ?? null;
|
|
411
407
|
return {
|
package/src/checks/contracts.mjs
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
// 跨包的**模块契约**检查(S2 的第一块:门禁读包的自述清单,不扫源码)。
|
|
2
|
-
//
|
|
3
|
-
// 为什么需要它:dsh 的组合阶段**不校验** `dsh.client.external` —— 实测把一个不存在的
|
|
4
|
-
// 外部声明(`@dsh-workbench/core-nope/client`)写进模块包,它被原样收进启动图、
|
|
5
|
-
// 宿主侧一声不吭、程序照起;错的声明要等浏览器 `require` 那一刻才炸。
|
|
6
|
-
//
|
|
7
|
-
// 判据全部来自各包 package.json 里的 `dsh` 字段(**自述清单**):
|
|
8
|
-
// · `dsh.bundle.patch` 指的补丁文件真的在;
|
|
9
|
-
// · `dsh.client.platform` 是认识的平台;
|
|
10
|
-
// · `dsh.client` 的 inject / external 每一条都能落到图里的一行(seed 词 / 平台包 / workspace 包);
|
|
11
|
-
// · `dsh.workbench`(**这是个模块**):
|
|
12
|
-
// - `unit` 形状合法、全局唯一(撞了就是装上去当场抛);
|
|
13
|
-
// - `dsh.workbenchFaces.host` / `.client` 指的源码入口文件真的在 ——
|
|
14
|
-
// 它是**工具链字段**,不在 `dsh.workbench` 协议里(ADR-0038 §7 第 7 条):
|
|
15
|
-
// `source` 判据靠这两个入口做可达性分析,所以它必须是源码路径、必须声明;
|
|
16
|
-
// - 对壳有 peer 依赖(它跑在外壳交出来的服务上);
|
|
17
|
-
// - 有自己的接线自检脚本(模块侧也必须机器判据,不能只靠人眼)。
|
|
18
|
-
//
|
|
19
|
-
// 形状(所有判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
|
|
20
|
-
// 打印与退出码归 `../check.mjs` 那一层。
|
|
21
|
-
|
|
22
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
23
2
|
import { createRequire } from 'node:module';
|
|
24
3
|
import { join, relative } from 'node:path';
|
|
25
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
|
-
import { readDeclaration } from '@dsh-workbench/shared/host';
|
|
5
|
+
import { buildSupplyIndex, readDeclaration, recipeProblemOf } from '@dsh-workbench/shared/host';
|
|
27
6
|
import {
|
|
28
7
|
DECLARED_TOOLS_FILE,
|
|
29
8
|
declaredToolsProblemOf,
|
|
@@ -31,11 +10,12 @@ import {
|
|
|
31
10
|
declaredToolsApplies,
|
|
32
11
|
} from '../declared-tools.mjs';
|
|
33
12
|
import { discoverPackages, workspaceRootOf } from '../discover.mjs';
|
|
13
|
+
import { readDesign } from '../design.mjs';
|
|
14
|
+
import { datasourceProblemOf } from '../datasource-file.mjs';
|
|
34
15
|
import { SEED_SHORT } from '../seed.mjs';
|
|
35
16
|
|
|
36
17
|
const ROOT = process.env.DSH_WB_ROOT ?? fileURLToPath(new URL('../../../../', import.meta.url));
|
|
37
18
|
|
|
38
|
-
/** dsh 浏览器侧的固定 seed 词表(模块表里天生就有的那几个)—— 唯一真相在 `../seed.mjs`。 */
|
|
39
19
|
const SEED = SEED_SHORT;
|
|
40
20
|
|
|
41
21
|
const PLATFORMS = new Set(['web']);
|
|
@@ -50,10 +30,6 @@ function text(value) {
|
|
|
50
30
|
return typeof value === 'string' ? value.trim() : '';
|
|
51
31
|
}
|
|
52
32
|
|
|
53
|
-
// 把模块的**宿主产物**挂起来,拿到它的面(清单 + needsApproval / approvalDetail)。
|
|
54
|
-
// 与模块自己的 smoke 用的是同一条路:apply() 要一个 workbenchUnits,register 里把面接住。
|
|
55
|
-
// 挂不起来(产物缺失、apply 抛、mount 需要真的 store)一律返回 undefined —— 那是别的判据的事,
|
|
56
|
-
// 这里只负责"清单说它会写,那它有没有认领审批"。
|
|
57
33
|
async function unitFaceOf(dir) {
|
|
58
34
|
const entry = join(dir, 'dist/host.js');
|
|
59
35
|
if (!existsSync(entry)) return undefined;
|
|
@@ -82,58 +58,6 @@ async function unitFaceOf(dir) {
|
|
|
82
58
|
}
|
|
83
59
|
}
|
|
84
60
|
|
|
85
|
-
function isRegistry(value) {
|
|
86
|
-
return /^(https?|file):\/\//.test(value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function envProblemsOf(name, env) {
|
|
90
|
-
const problems = [];
|
|
91
|
-
const libs = env.libs ?? [];
|
|
92
|
-
const plugins = env.plugins ?? [];
|
|
93
|
-
if (text(env.command) === '' && libs.length === 0 && plugins.length === 0) {
|
|
94
|
-
problems.push(`${name}: env 是空的 —— 要么删掉它,要么写清这个模块靠什么才跑得起来(command / libs / plugins)`);
|
|
95
|
-
}
|
|
96
|
-
libs.forEach((need, index) => {
|
|
97
|
-
const at = `env.libs[${index}]`;
|
|
98
|
-
if (text(need?.name) === '') problems.push(`${name}: ${at} 没写 name`);
|
|
99
|
-
if (text(need?.registryEnv) === '') {
|
|
100
|
-
problems.push(`${name}: ${at} 没写 registryEnv —— 用户的内网源没法覆盖它,换源时只能改代码`);
|
|
101
|
-
}
|
|
102
|
-
if (!isRegistry(text(need?.registryDefault))) {
|
|
103
|
-
problems.push(
|
|
104
|
-
`${name}: ${at}.registryDefault 不是 http(s):// 或 file:// 的源地址 —— ${JSON.stringify(need?.registryDefault)}`,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
if (text(need?.purpose) === '') {
|
|
108
|
-
problems.push(`${name}: ${at} 没写 purpose —— "装它干嘛"就是给用户看的那半句,不能空`);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
plugins.forEach((need, index) => {
|
|
112
|
-
const at = `env.plugins[${index}]`;
|
|
113
|
-
if (text(need?.name) === '') problems.push(`${name}: ${at} 没写 name`);
|
|
114
|
-
for (const command of need?.commands ?? []) {
|
|
115
|
-
if (text(command) === '') problems.push(`${name}: ${at}.commands 里有空字符串`);
|
|
116
|
-
}
|
|
117
|
-
const source = need?.installSource;
|
|
118
|
-
if (source === undefined) return;
|
|
119
|
-
if (source.kind === 'npm') {
|
|
120
|
-
if (text(source.package) === '') problems.push(`${name}: ${at}.installSource 是 npm 却没写 package`);
|
|
121
|
-
if (text(source.registryEnv) === '') problems.push(`${name}: ${at}.installSource 没写 registryEnv`);
|
|
122
|
-
} else if (source.kind === 'git') {
|
|
123
|
-
if (text(source.url) === '') problems.push(`${name}: ${at}.installSource 是 git 却没写 url`);
|
|
124
|
-
} else if (source.kind === 'local') {
|
|
125
|
-
if (text(source.env) === '') problems.push(`${name}: ${at}.installSource 是 local 却没写 env`);
|
|
126
|
-
} else {
|
|
127
|
-
problems.push(
|
|
128
|
-
`${name}: ${at}.installSource.kind 不认识 —— ${JSON.stringify(source.kind)}(只有 npm / git / local)`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
return problems;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// opencli 的隔离运行时(唯一真相在 packages/shared/src/host/opencli-runtime.ts;module-tools 不依赖
|
|
136
|
-
// shared,所以这里按同一套约定重算一遍 —— 认同一组环境变量,测试与 selfcheck 靠它注入 fixture)。
|
|
137
61
|
const OPENCLI_CATALOG_ENV = 'DSH_WB_OPENCLI_CATALOG';
|
|
138
62
|
const OPENCLI_NPM_PACKAGE = '@jackwener/opencli';
|
|
139
63
|
|
|
@@ -216,10 +140,6 @@ function sourceFilesOf(dir) {
|
|
|
216
140
|
return found;
|
|
217
141
|
}
|
|
218
142
|
|
|
219
|
-
// `plugin: '<字面量>'` 与同一行里 `?? '<字面量>'` 的兜底:都该是**清单里声明过**的那个插件名。
|
|
220
|
-
// 插件名只该有一处真相(能拿到 `deps.manifest` 的地方就用它),但模块级代码拿不到 manifest ⇒ 只能写字面量。
|
|
221
|
-
// 这条判据拦的是"改了清单没改代码"(或反过来):那样外壳会按错的名字去装 / 去探那个插件,而用户只看到
|
|
222
|
-
// "缺命令"这种没头没尾的状态。只查 `plugin:` 那几行,别的字面量不管(不猜)。
|
|
223
143
|
function pluginLiteralsOf(line) {
|
|
224
144
|
if (!/\bplugin\s*:/.test(line)) return [];
|
|
225
145
|
const found = [];
|
|
@@ -251,8 +171,7 @@ function fakeLongTasks(created) {
|
|
|
251
171
|
return {
|
|
252
172
|
create(options) {
|
|
253
173
|
created.push(options);
|
|
254
|
-
|
|
255
|
-
// 把注册表原样返回的模块就会被这条判据冤枉成"缺方法"——所以 `stop` / `live` 也在这里。
|
|
174
|
+
|
|
256
175
|
return {
|
|
257
176
|
start: () => ({}),
|
|
258
177
|
snapshot: () => null,
|
|
@@ -266,10 +185,6 @@ function fakeLongTasks(created) {
|
|
|
266
185
|
};
|
|
267
186
|
}
|
|
268
187
|
|
|
269
|
-
// 契约里 mount 一定拿得到 deps.store(外壳给的是 WorkspaceStore)。假的 deps 少给这一样,
|
|
270
|
-
// 用到 store 的模块(存偏好是最常见的用法)就会在这里抛、被当成"mount 坏了",
|
|
271
|
-
// 然后在审批那条判据上被冤枉成"没给出合法口径"。所以假 store 也给它一个:
|
|
272
|
-
// for / machine / scan 都回同一个内存文件区,写进去的东西后面读得回来。
|
|
273
188
|
function fakeStore() {
|
|
274
189
|
const files = new Map();
|
|
275
190
|
const one = () => ({
|
|
@@ -286,11 +201,6 @@ function fakeStore() {
|
|
|
286
201
|
return { for: () => one(), machine: () => one(), scan: () => [one()] };
|
|
287
202
|
}
|
|
288
203
|
|
|
289
|
-
// mount 也一定拿得到 shell 与 workspaceRegistry(外壳给的是 dsh 交出来的服务)。
|
|
290
|
-
// 少给这两样,模块的**降级写法**就会生效 —— 而"拿不到 shell 就只交 routes + registerTools"
|
|
291
|
-
// 是合法写法(实测 pduiv2 的 coding-issues / coding-analysis 就这么写),降级后的面本来就不带
|
|
292
|
-
// 审批钩子,判据却会把这读成"没认领审批":红的是判据自己的环境,不是模块。
|
|
293
|
-
// (与上面 `store` 那笔账同一条纪律:假 deps 少给一样,模块就在这条判据上被冤枉。)
|
|
294
204
|
function fakeShell() {
|
|
295
205
|
return {
|
|
296
206
|
resolve: () => undefined,
|
|
@@ -316,7 +226,7 @@ function fakeWorkspaceRegistry() {
|
|
|
316
226
|
|
|
317
227
|
function mountOf(face, created, manifest, tools) {
|
|
318
228
|
if (face === undefined || typeof face.mount !== 'function') return undefined;
|
|
319
|
-
|
|
229
|
+
|
|
320
230
|
const shell = fakeShell();
|
|
321
231
|
const workspaceRegistry = fakeWorkspaceRegistry();
|
|
322
232
|
const toolRegistry = {
|
|
@@ -343,6 +253,17 @@ function mountOf(face, created, manifest, tools) {
|
|
|
343
253
|
},
|
|
344
254
|
manifests: () => [],
|
|
345
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 未装');
|
|
265
|
+
},
|
|
266
|
+
},
|
|
346
267
|
store: fakeStore(),
|
|
347
268
|
longTasks: fakeLongTasks(created),
|
|
348
269
|
});
|
|
@@ -355,16 +276,6 @@ const LONG_TASK_METHODS = ['start', 'snapshot', 'active', 'current', 'live', 'st
|
|
|
355
276
|
|
|
356
277
|
const LONG_TASK_RENDERERS = ['useLongTask', 'LongTaskRow', 'longTaskDot'];
|
|
357
278
|
|
|
358
|
-
// 审批探针要用的**合成调用**。
|
|
359
|
-
//
|
|
360
|
-
// 为什么不是 `{ arguments: {} }`:外壳调 needsApproval 时拿到的是 agent 真传的参数,
|
|
361
|
-
// 而"这次要不要点头"本来就允许依赖参数 —— "带 confirm: true 才真的写"是最常见的合法口径。
|
|
362
|
-
// 空参数探会把这类模块判成"没认领审批"(实测 pduiv2 那 4 条审批报错就是这么来的,
|
|
363
|
-
// ADR-0038 §6.15)。所以能拿到工具的 `parameters`(JSON Schema)时,按**必填项**造一个
|
|
364
|
-
// 形状合法的调用:枚举取第一个值,string / number / boolean / array / object 各给一个占位值。
|
|
365
|
-
//
|
|
366
|
-
// 拿不到(registerTools() 没注册出这把工具、或注册时没带 parameters)就退回空参数,
|
|
367
|
-
// 并在判红时把这件事写进那句话 —— 那种情况下判据只能验到"钩子在不在"这一层。
|
|
368
279
|
function placeholderOf(schema) {
|
|
369
280
|
if (typeof schema !== 'object' || schema === null) return null;
|
|
370
281
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
@@ -389,36 +300,12 @@ function probeCallOf(tool) {
|
|
|
389
300
|
return { arguments: args, shaped: true };
|
|
390
301
|
}
|
|
391
302
|
|
|
392
|
-
// 审批头行(dsh 那张卡的 `.headline`)的预算。
|
|
393
|
-
//
|
|
394
|
-
// 为什么要有这条:`.headline` 是**纯文本的一格**,没有 `white-space` —— 换行会被塌成空格,
|
|
395
|
-
// 长句就不是"多几行"而是"一大坨"。三段都会落进那一格(外壳按 `明细 —— 问句 —— 附注` 拼):
|
|
396
|
-
// · `approvalDetail` 的 zh/en = 明细(模块写的"这次要动什么",本该是一句短标题)
|
|
397
|
-
// · `question` 的 zh/en = 问句(外壳拼在明细后面的那句"允许吗")
|
|
398
|
-
// · `fallback` 的 zh/en = 兜底整句(明细缺失时顶上来)
|
|
399
|
-
// 长内容(文件路径 / diff / 列表 / 正文 / 多字段)该走模块的 `approval.render`:
|
|
400
|
-
// 那是卡上**另一格**(`conversation.approval.detail`),有排版、能换行、能画 diff。
|
|
401
|
-
// 实测(2026-10-16 一条真实审批卡):明细里塞了一整条 commit message、问句 77 字、附注 75 字,
|
|
402
|
-
// 屏幕上 5 行 300 字。
|
|
403
303
|
export const HEAD_BUDGETS = { detail: 20, question: 40, fallback: 40 };
|
|
404
304
|
|
|
405
|
-
// 英文那一侧只用来抓"真正的一坨":按中文的 3 倍算。长度主要看中文那侧(用户读的是它),
|
|
406
|
-
// 实测同一句的英文是中文的 2.2–2.8 倍(machine 直译的风格),按 2 倍算会把正常英文问句全判红。
|
|
407
305
|
const EN_RATIO = 3;
|
|
408
306
|
|
|
409
|
-
// 长度预算是**两轮**的:
|
|
410
|
-
// · 第 1 轮(占位短值,见 probeCallOf):紧预算 —— 头行本来就该是一句短句;
|
|
411
|
-
// · 第 2 轮(长值,见 budgetProbeOf):宽 3 倍 —— 专抓"把可变长字段塞进头行"。
|
|
412
|
-
// 宽 3 倍是故意的:对象名、文件名这类标识符本来就有长有短,不该因为"名字长"判红;
|
|
413
|
-
// 而一条 commit message、一段正文(60 字)塞进标题,3 倍也照样兜不住 —— 那才是要抓的。
|
|
414
307
|
const LONG_SCALE = 3;
|
|
415
308
|
|
|
416
|
-
// 只按必填项造的探针(`probeCallOf`)给的是**占位短值**(`probe` / `1` / `true`)——
|
|
417
|
-
// 于是"把可变长字段塞进头行"这件事它是**瞎的**:实测那条真实审批卡的明细是
|
|
418
|
-
// `改对象文件:对象「diep-dify」的「tencent-test-values.yaml」 仓库:helm 提交信息:<一整条 commit message>`(157 字),
|
|
419
|
-
// 而探针上只有 20 字(`probe`)⇒ 判据照绿。所以第 2 轮把**自由文本字段**换成真长度(60 字):
|
|
420
|
-
// 名字里带 message / note / reason / body / text / content / comment / title / description / summary
|
|
421
|
-
// 的字段(这些就是"人可以写一段话"的地方),其余 string(标识符)给 12 字的正常长度。
|
|
422
309
|
const FREE_TEXT = /message|note|reason|body|text|content|comment|title|description|summary/i;
|
|
423
310
|
const LONG_TEXT = '这里是一段真实长度的正文用来量头行预算不要把这种内容放进标题里'.repeat(2);
|
|
424
311
|
const LONG_ID = '真实长度的名字十二个字';
|
|
@@ -488,11 +375,6 @@ function installed(specifier, from) {
|
|
|
488
375
|
}
|
|
489
376
|
}
|
|
490
377
|
|
|
491
|
-
/**
|
|
492
|
-
* 这个包是不是"裸"的:上面没有任何 workspace(`pnpm-workspace.yaml` 或 `packages/`),
|
|
493
|
-
* 自己也没装依赖。裸目录里 `init` 出来的骨架就是这样 —— 它报的 inject/external 落不到图里
|
|
494
|
-
* **不是声明写错**,而是"它压根不在一个 workspace 里",而原文把这句话说成了"声明有问题"。
|
|
495
|
-
*/
|
|
496
378
|
function barePackage(pkg) {
|
|
497
379
|
return workspaceRootOf(pkg.dir) === pkg.dir && !existsSync(join(pkg.dir, 'node_modules'));
|
|
498
380
|
}
|
|
@@ -502,7 +384,6 @@ function packageOf(specifier) {
|
|
|
502
384
|
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
503
385
|
}
|
|
504
386
|
|
|
505
|
-
/** 深比较两份 JSON:对象**键序无关**,数组**顺序有关**(工具那一串的顺序是声明的一部分)。 */
|
|
506
387
|
function reconcileTools(name, manifest, toolNames) {
|
|
507
388
|
const declared = manifest.tools.map((tool) => tool.name).sort();
|
|
508
389
|
const actual = [...new Set(toolNames.filter((one) => one !== ''))].sort();
|
|
@@ -519,10 +400,11 @@ export async function run() {
|
|
|
519
400
|
const packages = discoverPackages({ root: process.cwd() });
|
|
520
401
|
const known = new Set(packages.map((pkg) => pkg.manifest.name));
|
|
521
402
|
const problems = [];
|
|
403
|
+
const warnings = [];
|
|
522
404
|
const unresolvedIn = new Set();
|
|
523
405
|
const units = new Map();
|
|
524
|
-
|
|
525
|
-
|
|
406
|
+
const supplyDeclarations = [];
|
|
407
|
+
|
|
526
408
|
const probeNotes = [];
|
|
527
409
|
const headlineNotes = [];
|
|
528
410
|
let declared = 0;
|
|
@@ -534,10 +416,6 @@ export async function run() {
|
|
|
534
416
|
if (dsh === undefined) continue;
|
|
535
417
|
declared += 1;
|
|
536
418
|
|
|
537
|
-
// `dsh.bundle.patch` 允许是**一个字符串,也允许是一个数组**(dsh 自己的 dsh-web-app 就是数组:
|
|
538
|
-
// 它的 cordis.patch.yml + 四个 preset 文件全挂在这一处)。这里原来只按字符串处理,
|
|
539
|
-
// 于是第一个用数组的包会把判据**打崩**(ERR_INVALID_ARG_TYPE,堆栈指在 path.join 上,
|
|
540
|
-
// 而真正的问题是"声明形状没接住")—— 外壳加 DIY 的 preset 时踩到。
|
|
541
419
|
const patches = dsh.bundle?.patch;
|
|
542
420
|
for (const patch of patches === undefined ? [] : Array.isArray(patches) ? patches : [patches]) {
|
|
543
421
|
if (!existsSync(join(pkg.dir, patch))) {
|
|
@@ -589,10 +467,6 @@ export async function run() {
|
|
|
589
467
|
}
|
|
590
468
|
}
|
|
591
469
|
|
|
592
|
-
// 清单里的工具名与「代码里那份名字」的关系(ADR-0038 的 `tools[].key` 一节):
|
|
593
|
-
// 给了 key 就走生成物那一档 —— `src/declared-tools.ts` 必须存在、且与清单一致(过期 = 红),
|
|
594
|
-
// 并且源码里不许再有工具名的字面量(名字只写在清单里,代码引用 `DECLARED_TOOLS.<key>.name`,
|
|
595
|
-
// 键写错是编译错)。一个 key 都没写的模块不适用(那是不用生成物那一档)。
|
|
596
470
|
const declaredProblem = declaredToolsProblemOf(pkg.dir, dsh.workbench);
|
|
597
471
|
if (declaredProblem !== undefined) {
|
|
598
472
|
problems.push(`${name}: ${declaredProblem}`);
|
|
@@ -660,17 +534,21 @@ export async function run() {
|
|
|
660
534
|
}
|
|
661
535
|
}
|
|
662
536
|
|
|
663
|
-
// 外部供给:清单里声明了 env,就要把"靠什么跑起来"和"没装好时怎么办"都说清。
|
|
664
|
-
// 外壳只按 env 去装(app/host.ts 把 plugins/libs 交给隔离运行时跑 probe/install),
|
|
665
|
-
// 它不知道这个能力叫什么 —— 所以"照做"那句安装命令必须由模块自己给。
|
|
666
537
|
const read = readDeclaration(name, dsh.workbench);
|
|
667
538
|
const manifest = read.manifest;
|
|
668
539
|
const created = [];
|
|
669
|
-
|
|
540
|
+
|
|
670
541
|
const tools = [];
|
|
671
542
|
const face = await unitFaceOf(pkg.dir);
|
|
672
543
|
const mounted = face === undefined ? undefined : mountOf(face, created, manifest, tools);
|
|
673
|
-
const
|
|
544
|
+
const needs = manifest?.needs;
|
|
545
|
+
warnings.push(...read.warnings);
|
|
546
|
+
if (manifest === undefined) {
|
|
547
|
+
for (const one of read.problems) problems.push(one);
|
|
548
|
+
}
|
|
549
|
+
if (manifest !== undefined) {
|
|
550
|
+
supplyDeclarations.push({ id: manifest.id, needs: needs ?? [] });
|
|
551
|
+
}
|
|
674
552
|
|
|
675
553
|
if (manifest !== undefined) {
|
|
676
554
|
if (face === undefined) {
|
|
@@ -694,15 +572,12 @@ export async function run() {
|
|
|
694
572
|
}
|
|
695
573
|
}
|
|
696
574
|
|
|
697
|
-
if (
|
|
698
|
-
problems.push(...envProblemsOf(name, env));
|
|
575
|
+
if (needs !== undefined) {
|
|
699
576
|
const products = productsTextOf(pkg.dir);
|
|
577
|
+
const extensions = needs.filter((need) => need?.kind === 'extension');
|
|
700
578
|
|
|
701
|
-
// 声明的插件命令必须真的存在:拿 opencli 自带目录(按 site 归组,别名也算)与这台机器上
|
|
702
|
-
// 已装的插件包对一遍。**插件两头都找不到的就不管**(那说明这台机器还没装它,没得对,别误伤)——
|
|
703
|
-
// 拦的是"插件明明在,命令名却写错了":那样用户那边的"缺命令"永远补不上。
|
|
704
579
|
const catalog = catalogBySite();
|
|
705
|
-
|
|
580
|
+
extensions.forEach((plugin, index) => {
|
|
706
581
|
const pluginName = text(plugin?.name);
|
|
707
582
|
const declared = (plugin?.commands ?? []).map((one) => text(one)).filter((one) => one !== '');
|
|
708
583
|
if (pluginName === '' || declared.length === 0) return;
|
|
@@ -714,15 +589,13 @@ export async function run() {
|
|
|
714
589
|
if (missing.length === 0) return;
|
|
715
590
|
const knownList = [...known].sort();
|
|
716
591
|
problems.push(
|
|
717
|
-
`${name}:
|
|
592
|
+
`${name}: needs 里第 ${index} 条 extension(${pluginName})声明的命令 ${missing.join(' / ')} 在插件里找不到 —— ` +
|
|
718
593
|
`它真有这些:${knownList.slice(0, 8).join(' / ')}${knownList.length > 8 ? ' …' : ''}。` +
|
|
719
594
|
`命令名写错的话,用户那边的"缺命令"永远补不上(用 \`opencli ${pluginName} --help\` 看原名)`,
|
|
720
595
|
);
|
|
721
596
|
});
|
|
722
597
|
|
|
723
|
-
const declaredPlugins = new Set(
|
|
724
|
-
(env.plugins ?? []).map((one) => text(one?.name)).filter((one) => one !== ''),
|
|
725
|
-
);
|
|
598
|
+
const declaredPlugins = new Set(extensions.map((one) => text(one?.name)).filter((one) => one !== ''));
|
|
726
599
|
if (declaredPlugins.size > 0) {
|
|
727
600
|
for (const file of sourceFilesOf(pkg.dir)) {
|
|
728
601
|
readFileSync(file, 'utf8')
|
|
@@ -732,7 +605,7 @@ export async function run() {
|
|
|
732
605
|
if (declaredPlugins.has(literal)) continue;
|
|
733
606
|
problems.push(
|
|
734
607
|
`${name}: ${relative(pkg.dir, file)}:${index + 1} 里的插件名「${literal}」不在清单的 ` +
|
|
735
|
-
`
|
|
608
|
+
`needs(kind=extension)里(清单里是 ${[...declaredPlugins].join(' / ')})—— 改了清单没改代码` +
|
|
736
609
|
'(或反过来)会让外壳按错的名字去装 / 去探那个插件。插件名只有一处真相:' +
|
|
737
610
|
'能拿到 deps.manifest 的地方就用它,拿不到的地方至少与清单保持一致',
|
|
738
611
|
);
|
|
@@ -741,17 +614,21 @@ export async function run() {
|
|
|
741
614
|
}
|
|
742
615
|
}
|
|
743
616
|
|
|
744
|
-
|
|
617
|
+
const usesLibPort = sourceFilesOf(pkg.dir).some((file) =>
|
|
618
|
+
/(?:\blibs\.load\b|\bworkflowFromLibs\b|\bLibReader\b)/.test(readFileSync(file, 'utf8')),
|
|
619
|
+
);
|
|
620
|
+
if (
|
|
621
|
+
needs.some((need) => need?.kind === 'lib') &&
|
|
622
|
+
!products.includes('libInstallCommand') &&
|
|
623
|
+
!usesLibPort
|
|
624
|
+
) {
|
|
745
625
|
problems.push(
|
|
746
|
-
`${name}: 声明了
|
|
626
|
+
`${name}: 声明了 lib 类 need 却既没用 libInstallCommand,也没走 core 的 LibReader(libs.load)—— ` +
|
|
747
627
|
`lib 没装好时用户只看到"没装",没有能照做的那条安装命令`,
|
|
748
628
|
);
|
|
749
629
|
}
|
|
750
630
|
}
|
|
751
631
|
|
|
752
|
-
// 「他能自己配什么」有两种:视图内选择(route 参数,刷新回默认)与偏好(prefs + store,刷新还在)。
|
|
753
|
-
// 设计表里写了哪一种就得真的接上哪一种 —— 否则用户改完刷新回到默认,而表上写着"能配"。
|
|
754
|
-
// (**与 env 无关**:没有 env 的模块一样要查,所以放在 env 块外面。)
|
|
755
632
|
const configRows = designRowsOf(pkg.dir, 'config');
|
|
756
633
|
const hostProduct = hostProductOf(pkg.dir);
|
|
757
634
|
const wiredPrefs = /\bprefs\s*:/.test(hostProduct);
|
|
@@ -769,9 +646,25 @@ export async function run() {
|
|
|
769
646
|
);
|
|
770
647
|
}
|
|
771
648
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
649
|
+
const design = readDesign(pkg.dir);
|
|
650
|
+
const datasourceProblem = datasourceProblemOf(pkg.dir, design);
|
|
651
|
+
if (datasourceProblem !== undefined) problems.push(`${name}: ${datasourceProblem}`);
|
|
652
|
+
|
|
653
|
+
const reads =
|
|
654
|
+
design !== undefined && Array.isArray(design.reads) && design.reads.length > 0
|
|
655
|
+
? design.reads
|
|
656
|
+
: design !== undefined && design.datasource !== undefined
|
|
657
|
+
? [design.datasource]
|
|
658
|
+
: [];
|
|
659
|
+
for (const read of reads) {
|
|
660
|
+
const recipe = read?.recipe;
|
|
661
|
+
if (recipe === undefined) continue;
|
|
662
|
+
const problem = recipeProblemOf(recipe);
|
|
663
|
+
if (problem !== undefined) {
|
|
664
|
+
problems.push(`${name}: design.json 的配方不合法(${problem})—— 跑 \`dsh-wb-module design check\` 看点名`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
775
668
|
const capabilityRows = designRowsOf(pkg.dir, 'capabilities');
|
|
776
669
|
if (capabilityRows !== undefined && manifest !== undefined) {
|
|
777
670
|
const inDesign = capabilityRows.map((one) => text(one?.name)).filter((one) => one !== '');
|
|
@@ -792,22 +685,6 @@ export async function run() {
|
|
|
792
685
|
}
|
|
793
686
|
}
|
|
794
687
|
|
|
795
|
-
// 长任务:跑一会儿的事,注册了就得让用户看得见那一行。
|
|
796
|
-
//
|
|
797
|
-
// 外壳按 mount 结果里的 longTasks 投影出 `${unit}/runs`(kit 的 useLongTask 读的就是它)。
|
|
798
|
-
// 契约里的状态是**五种**:running / needs-login / stopped / ok / failed(`longTaskDot` 把它们映成
|
|
799
|
-
// 进行中 / 要登录 / 灰点 / 完成 / 出错 —— `stopped` 落进默认分支,是"用户按停的",不是成功也不是出错)。
|
|
800
|
-
// 契约里也**有"停止"**:注册表多一个 `stop(id)`,`run` 多收一个 `signal`。
|
|
801
|
-
// `start` 还可以带一个**可选**的 `session`(起这一趟的那个会话 id,模块从 `sessionIdOf(exec)` 拿):
|
|
802
|
-
// 不记 = 用户在界面上按的 —— 外壳那两把 `workbench_long_tasks_*` 按它判"谁的"(自己起的免审批)。
|
|
803
|
-
// 判据**不查**这一条(它只查形状;"起的时候记没记来源"是形状之外的事)。
|
|
804
|
-
// 但"停止"在注册表这一侧的射程只有两件:外壳把 `signal` abort 掉、把这一趟标成 `stopped`;
|
|
805
|
-
// **真停不停得了看生产方认不认这个 signal** —— 那是模块的知识,判据查不了这一条
|
|
806
|
-
// (内部是 opencli 子进程那类编排,今天最多做到"我们不再等它")。判据能查的是形状与"画得出来":
|
|
807
|
-
// · 注册表形状要全(少了方法,外壳那条 runs 路由 / 那颗停止钮就按不动);
|
|
808
|
-
// · create 的 key 非空、run 与 view 都是函数(没有 view,卡片上那行就没有可渲染的快照);
|
|
809
|
-
// · 真的注册了长任务,客户端产物里就得出现 kit 那套(useLongTask / LongTaskRow / longTaskDot),
|
|
810
|
-
// 否则用户点完只看到"没反应"。
|
|
811
688
|
if (created.length > 0 || mounted?.longTasks !== undefined) {
|
|
812
689
|
const registry = mounted?.longTasks;
|
|
813
690
|
if (registry !== undefined) {
|
|
@@ -844,11 +721,6 @@ export async function run() {
|
|
|
844
721
|
}
|
|
845
722
|
}
|
|
846
723
|
|
|
847
|
-
// 联动层:agent 动完 / 读完,面板那一格怎么知道要重读。
|
|
848
|
-
//
|
|
849
|
-
// 只查**形状**:`context.render` 不能拿假参数去调 —— 成熟模块在参数形状不对时是**故意 throw** 的
|
|
850
|
-
// (coding-issues 的 render 就是"缺陷引用的形状不对 → throw"),拿假参数调会误伤正确的模块。
|
|
851
|
-
// `factsOf` / `warmReads` 可以安全地拿空参数调一次,看它回的东西形状对不对。
|
|
852
724
|
if (mounted !== undefined) {
|
|
853
725
|
const factsOf = mounted.factsOf;
|
|
854
726
|
if (factsOf !== undefined && typeof factsOf !== 'function') {
|
|
@@ -909,21 +781,6 @@ export async function run() {
|
|
|
909
781
|
}
|
|
910
782
|
}
|
|
911
783
|
|
|
912
|
-
// 声明会写的工具,必须同时认领"这次要不要点头"。
|
|
913
|
-
//
|
|
914
|
-
// 外壳每次调用前都会问套件:writes:true 的工具,needsApproval 不给 verdict.kind==='ask'
|
|
915
|
-
// 就**当场 deny**(原文:"在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去")——
|
|
916
|
-
// 那条错误只有真跑一次才看得见,用户看到的是一次莫名其妙的拒绝。所以门禁在出包前先看一遍。
|
|
917
|
-
//
|
|
918
|
-
// **这条判据只能验到哪一层**(原样写给以后改它的人):
|
|
919
|
-
// · 硬事实:一个 writes:true 的工具,mount() 有没有把 needsApproval 交出来;
|
|
920
|
-
// · 判据能造出合成调用时:对那次调用给的 verdict 形状对不对;verdict 是 ask 时
|
|
921
|
-
// approvalDetail 说没说清"这次会发生什么";
|
|
922
|
-
// · **验不了的**:真实写入调用会不会真的 ask。答案依赖参数的模块("带 confirm: true 才真的写")
|
|
923
|
-
// 在合成调用上依法回 `{ kind: 'read' }` —— 外壳那条路(shared/host/tool-approval.ts)在 read
|
|
924
|
-
// 时**根本不会问 approvalDetail**,所以这里也不再逼它给。这类模块列进 notes 让作者自己确认。
|
|
925
|
-
// · 空参数探会误报:合成调用按声明里的 `parameters` 必填项造(见 probeCallOf),
|
|
926
|
-
// 造不出来时退回空参数并把这件事写进判红那句。
|
|
927
784
|
const writeTools = (manifest?.tools ?? []).filter((one) => one?.writes === true);
|
|
928
785
|
if (writeTools.length > 0) {
|
|
929
786
|
if (mounted === undefined) {
|
|
@@ -960,12 +817,9 @@ export async function run() {
|
|
|
960
817
|
);
|
|
961
818
|
continue;
|
|
962
819
|
}
|
|
963
|
-
|
|
964
|
-
// `{ kind: 'ask', reason, question{zh,en}, fallback{zh,en} }`(要问,且问得清)。
|
|
965
|
-
// 别的返回值(**包括 undefined**)外壳会当场 deny —— 那才是要拦的。
|
|
820
|
+
|
|
966
821
|
const read = verdict?.kind === 'read';
|
|
967
|
-
|
|
968
|
-
// 合成调用上它合法,但和 read 一样验不到"真的调用会不会问",进 notes 让作者自己确认。
|
|
822
|
+
|
|
969
823
|
const deny = verdict?.kind === 'deny' && text(verdict.reason) !== '';
|
|
970
824
|
const ask =
|
|
971
825
|
verdict?.kind === 'ask' &&
|
|
@@ -984,9 +838,7 @@ export async function run() {
|
|
|
984
838
|
);
|
|
985
839
|
continue;
|
|
986
840
|
}
|
|
987
|
-
|
|
988
|
-
// (`approvalFor` 只在 verdict 是 ask 时才调 detailOf)。合成调用本来就不是一次真实写入,
|
|
989
|
-
// 拿它逼模块给"这次会发生什么"就是让判据替外壳问一个外壳不会问的问题。
|
|
841
|
+
|
|
990
842
|
if (read || deny) {
|
|
991
843
|
readOnProbe.push(tool.name);
|
|
992
844
|
continue;
|
|
@@ -1003,8 +855,7 @@ export async function run() {
|
|
|
1003
855
|
? text(facts.blocked) !== ''
|
|
1004
856
|
: text(facts.zh) !== '' && text(facts.en) !== '');
|
|
1005
857
|
problems.push(...headlineProblemsOf(name, tool.name, verdict, facts));
|
|
1006
|
-
|
|
1007
|
-
// 专抓"标题里带了可变长字段"—— 第一轮用的是占位短值,对这件事是瞎的(见 budgetProbeOf)。
|
|
858
|
+
|
|
1008
859
|
const longArgs = budgetProbeOf(registered.get(tool.name));
|
|
1009
860
|
if (longArgs !== undefined) {
|
|
1010
861
|
const longCall = { ...call, arguments: longArgs };
|
|
@@ -1077,7 +928,7 @@ export async function run() {
|
|
|
1077
928
|
}
|
|
1078
929
|
for (const file of suspects) {
|
|
1079
930
|
const text = readFileSync(file, 'utf8');
|
|
1080
|
-
const hit =
|
|
931
|
+
const hit = /(?:@[a-z0-9-]+\/)?dsh-workbench-module-[a-z0-9-]+/.exec(text);
|
|
1081
932
|
if (hit !== null) {
|
|
1082
933
|
problems.push(
|
|
1083
934
|
`${SHELL}: ${relative(ROOT, file)} 里点名了模块包 ${hit[0]} —— ` +
|
|
@@ -1087,10 +938,11 @@ export async function run() {
|
|
|
1087
938
|
}
|
|
1088
939
|
}
|
|
1089
940
|
|
|
941
|
+
problems.push(...buildSupplyIndex(supplyDeclarations).problems);
|
|
942
|
+
|
|
1090
943
|
if (problems.length > 0) {
|
|
1091
944
|
const notes = [`${declared} 个带 dsh 声明的包,其中 ${modules} 个模块`, ...probeNotes, ...headlineNotes];
|
|
1092
|
-
|
|
1093
|
-
// 出来的骨架)。不点出来的话,屏幕上的话会把责任推给那两条声明,而真正该做的是换到工坊里跑。
|
|
945
|
+
|
|
1094
946
|
const bare = [...unresolvedIn].filter(barePackage);
|
|
1095
947
|
if (bare.length > 0) {
|
|
1096
948
|
notes.push(
|
|
@@ -1099,7 +951,7 @@ export async function run() {
|
|
|
1099
951
|
`做法:在工坊里造($DSH_HOME/workbench/diy/packages/module-<名字>),或者先在这个工作区里 pnpm install。`,
|
|
1100
952
|
);
|
|
1101
953
|
}
|
|
1102
|
-
return { status: 'fail', notes, problems };
|
|
954
|
+
return { status: 'fail', notes: [...notes, ...warnings.map((one) => `(警告)${one}`)], problems };
|
|
1103
955
|
}
|
|
1104
956
|
|
|
1105
957
|
return {
|
|
@@ -1111,6 +963,7 @@ export async function run() {
|
|
|
1111
963
|
`单元:${unitList}`,
|
|
1112
964
|
...probeNotes,
|
|
1113
965
|
...headlineNotes,
|
|
966
|
+
...warnings.map((one) => `(警告)${one}`),
|
|
1114
967
|
],
|
|
1115
968
|
problems: [],
|
|
1116
969
|
};
|