@dsh-workbench/module-tools 1.1.12 → 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/src/checks/kit.mjs
CHANGED
|
@@ -170,8 +170,9 @@ export async function run() {
|
|
|
170
170
|
if (missing.length > 0) {
|
|
171
171
|
problems.push(
|
|
172
172
|
`${name}: 产物用了这份外壳(${shellClient})里没有的 ${missing.join('、')} —— ` +
|
|
173
|
-
`顶层解构不会抛(拿到 undefined),卡片首帧渲染才炸 "Element type is invalid"
|
|
174
|
-
|
|
173
|
+
`顶层解构不会抛(拿到 undefined),卡片首帧渲染才炸 "Element type is invalid";` +
|
|
174
|
+
`外壳会把这张卡收进错误态(只炸这一张,但用户看到的是错误页而不是你的卡)。` +
|
|
175
|
+
`要么去掉/换掉这些成员,要么把 peer 下限提到真正需要的那版`,
|
|
175
176
|
);
|
|
176
177
|
}
|
|
177
178
|
}
|
package/src/checks/lint.mjs
CHANGED
|
@@ -111,7 +111,7 @@ async function loadConfig(pkg) {
|
|
|
111
111
|
return {
|
|
112
112
|
error:
|
|
113
113
|
`${pkg.manifest.name}: ${CONFIG_FILE} 跑不起来 —— ${messageOf(error)}` +
|
|
114
|
-
'(先确认这份配置能 import、没有语法错,再确认它 import
|
|
114
|
+
'(先确认这份配置能 import、没有语法错,再确认它 import 的东西在工作区里解析得到)',
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -251,7 +251,7 @@ export async function run() {
|
|
|
251
251
|
|
|
252
252
|
if (problems.length > 0) {
|
|
253
253
|
|
|
254
|
-
const only = process.env.DSH_WB_ONLY === 'lint';
|
|
254
|
+
const only = process.env.DSH_WB_ONLY === 'lint' || process.env.DSH_WB_PACKAGE_FILTER !== '';
|
|
255
255
|
const shown = only ? problems : problems.slice(0, MAX_PROBLEMS);
|
|
256
256
|
const notes = [
|
|
257
257
|
'红了怎么修:先看位置与规则名,再决定"改代码"还是"就地 eslint-disable-next-line";' +
|
|
@@ -115,9 +115,9 @@ function faceOf(modulePackages) {
|
|
|
115
115
|
const notes = [
|
|
116
116
|
`这份工程里没装 ${PRIMITIVES_SPECIFIER} —— 列不出清单。`,
|
|
117
117
|
` 它在 @deepseek-ai/dsh-client-ui-primitives 那个包的 lib/types/**/*.d.ts 里(源码不随包发布)。`,
|
|
118
|
-
`
|
|
118
|
+
` 在模块清单里加这一行:`,
|
|
119
119
|
` "devDependencies": { "${PRIMITIVES_SPECIFIER}": "${want}" }`,
|
|
120
|
-
`
|
|
120
|
+
` 模板默认带这一行;加完在 dsh.client.external 里也声明 "${PRIMITIVES_SPECIFIER}"(两处都要)。`,
|
|
121
121
|
];
|
|
122
122
|
if (pinned !== undefined) notes.push(` 外壳(${SHELL}${shellVersion === undefined ? '' : `@${shellVersion}`})钉的是 ${pinned} —— 对齐它。`);
|
|
123
123
|
return { notes, payload: { installed: null, pinned: pinned ?? null, modules, snippet: devDependencySnippet(want) } };
|
package/src/discover.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
3
3
|
|
|
4
4
|
function manifestOf(dir) {
|
|
5
5
|
const file = join(dir, 'package.json');
|
|
@@ -11,41 +11,211 @@ function manifestOf(dir) {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function stripComment(line) {
|
|
15
|
+
const at = line.indexOf('#');
|
|
16
|
+
return at === -1 ? line : line.slice(0, at);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function unquote(value) {
|
|
20
|
+
return value.trim().replace(/^['"]|['"]$/g, '').trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function flowPatterns(text) {
|
|
24
|
+
const inner = text.trim().replace(/^\[/, '').replace(/\]$/, '');
|
|
25
|
+
if (inner.trim() === '') return [];
|
|
26
|
+
return inner
|
|
27
|
+
.split(',')
|
|
28
|
+
.map(unquote)
|
|
29
|
+
.filter((one) => one !== '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function patternsFromYaml(text) {
|
|
33
|
+
const lines = text.split(/\r?\n/);
|
|
34
|
+
const patterns = [];
|
|
35
|
+
let state = 'idle';
|
|
36
|
+
for (const raw of lines) {
|
|
37
|
+
const line = stripComment(raw);
|
|
38
|
+
if (line.trim() === '') continue;
|
|
39
|
+
if (state === 'idle') {
|
|
40
|
+
const match = /^packages\s*:\s*(.*)$/.exec(line);
|
|
41
|
+
if (match === null) continue;
|
|
42
|
+
const rest = match[1].trim();
|
|
43
|
+
if (rest.startsWith('[')) {
|
|
44
|
+
const flow = flowPatterns(rest);
|
|
45
|
+
patterns.push(...flow);
|
|
46
|
+
return { declared: true, patterns, empty: flow.length === 0 };
|
|
47
|
+
}
|
|
48
|
+
if (rest !== '') return { declared: true, patterns: flowPatterns(rest) };
|
|
49
|
+
state = 'block';
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const item = /^\s*-\s*(.+?)\s*$/.exec(line);
|
|
53
|
+
if (item !== null) {
|
|
54
|
+
const value = unquote(item[1]);
|
|
55
|
+
if (value !== '') patterns.push(value);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
return { declared: true, patterns };
|
|
59
|
+
}
|
|
60
|
+
return state === 'block' ? { declared: true, patterns } : { declared: false, patterns: [] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function patternsOf(root) {
|
|
64
|
+
const yaml = join(root, 'pnpm-workspace.yaml');
|
|
65
|
+
if (existsSync(yaml)) {
|
|
66
|
+
const parsed = patternsFromYaml(readFileSync(yaml, 'utf8'));
|
|
67
|
+
if (parsed.declared) {
|
|
68
|
+
if (parsed.patterns.length === 0 && parsed.empty !== true) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`pnpm-workspace.yaml 里有 packages 但没有解析出任何条目:${yaml} —— ` +
|
|
71
|
+
'支持缩进/不缩进的块列表、行内数组与 ! 排除;花括号、字符类等写法请改成显式目录',
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return parsed;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const manifest = manifestOf(root);
|
|
78
|
+
const workspaces = manifest?.workspaces;
|
|
79
|
+
if (Array.isArray(workspaces)) {
|
|
80
|
+
return { declared: true, patterns: workspaces.filter((one) => typeof one === 'string') };
|
|
81
|
+
}
|
|
82
|
+
if (workspaces !== null && typeof workspaces === 'object' && Array.isArray(workspaces.packages)) {
|
|
83
|
+
return { declared: true, patterns: workspaces.packages.filter((one) => typeof one === 'string') };
|
|
84
|
+
}
|
|
85
|
+
return { declared: false, patterns: [] };
|
|
86
|
+
}
|
|
87
|
+
|
|
14
88
|
export function workspaceRootOf(start) {
|
|
15
89
|
if (process.env.DSH_WB_ROOT) return process.env.DSH_WB_ROOT;
|
|
16
90
|
let dir = start;
|
|
91
|
+
let legacy;
|
|
17
92
|
for (;;) {
|
|
18
|
-
if (existsSync(join(dir, '
|
|
93
|
+
if (existsSync(join(dir, 'pnpm-workspace.yaml'))) return dir;
|
|
94
|
+
const manifest = manifestOf(dir);
|
|
95
|
+
if (manifest !== undefined) {
|
|
96
|
+
const workspaces = manifest.workspaces;
|
|
97
|
+
if (Array.isArray(workspaces) || (workspaces !== null && typeof workspaces === 'object')) return dir;
|
|
98
|
+
}
|
|
99
|
+
if (legacy === undefined && existsSync(join(dir, 'packages'))) legacy = dir;
|
|
19
100
|
const parent = dirname(dir);
|
|
20
|
-
if (parent === dir) return start;
|
|
101
|
+
if (parent === dir) return legacy ?? start;
|
|
21
102
|
dir = parent;
|
|
22
103
|
}
|
|
23
104
|
}
|
|
24
105
|
|
|
106
|
+
function isDir(dir) {
|
|
107
|
+
try {
|
|
108
|
+
return statSync(dir).isDirectory();
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function segmentMatches(name, segment) {
|
|
115
|
+
if (/[{}[\]()|+@!]/.test(segment)) {
|
|
116
|
+
throw new Error(`工作区 glob 里这个写法暂不支持:${segment}(改成 *、** 或显式目录)`);
|
|
117
|
+
}
|
|
118
|
+
const pattern = segment
|
|
119
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
120
|
+
.replace(/\*/g, '[^/]*')
|
|
121
|
+
.replace(/\?/g, '[^/]');
|
|
122
|
+
return new RegExp(`^${pattern}$`).test(name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function childDirs(dir) {
|
|
126
|
+
try {
|
|
127
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
128
|
+
.filter((entry) => entry.name !== 'node_modules' && !entry.name.startsWith('.'))
|
|
129
|
+
.map((entry) => join(dir, entry.name))
|
|
130
|
+
.filter((child) => isDir(child));
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function walkDirs(dir, out) {
|
|
137
|
+
out.push(dir);
|
|
138
|
+
for (const child of childDirs(dir)) walkDirs(child, out);
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function expandPattern(root, pattern) {
|
|
143
|
+
if (/[{}[\]()|+]/.test(pattern)) {
|
|
144
|
+
throw new Error(`工作区 glob 里这个写法暂不支持:${pattern}(改成 *、** 或显式目录)`);
|
|
145
|
+
}
|
|
146
|
+
const parts = pattern.replace(/\\/g, '/').split('/').filter((one) => one !== '' && one !== '.');
|
|
147
|
+
let current = [root];
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
const next = [];
|
|
150
|
+
for (const base of current) {
|
|
151
|
+
if (part === '**') {
|
|
152
|
+
walkDirs(base, next);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (part.includes('*') || part.includes('?')) {
|
|
156
|
+
for (const child of childDirs(base)) {
|
|
157
|
+
if (segmentMatches(basename(child), part)) next.push(child);
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const exact = join(base, part);
|
|
162
|
+
if (isDir(exact)) next.push(exact);
|
|
163
|
+
}
|
|
164
|
+
current = next;
|
|
165
|
+
}
|
|
166
|
+
return current;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const cache = new Map();
|
|
170
|
+
|
|
171
|
+
function legacyPackages(root) {
|
|
172
|
+
return childDirs(join(root, 'packages'));
|
|
173
|
+
}
|
|
174
|
+
|
|
25
175
|
export function discoverPackages({ root = process.cwd() } = {}) {
|
|
26
176
|
const workspace = workspaceRootOf(root);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
177
|
+
const parsed = patternsOf(workspace);
|
|
178
|
+
const positive = parsed.patterns.filter((one) => !one.startsWith('!'));
|
|
179
|
+
const negative = parsed.patterns.filter((one) => one.startsWith('!')).map((one) => one.slice(1));
|
|
180
|
+
const key = [workspace, root, parsed.patterns.join('|')].join('\0');
|
|
181
|
+
const cached = cache.get(key);
|
|
182
|
+
if (cached !== undefined) return cached.map((pkg) => ({ ...pkg }));
|
|
31
183
|
|
|
32
|
-
|
|
33
|
-
|
|
184
|
+
const dirs = [];
|
|
185
|
+
const push = (dir) => {
|
|
186
|
+
if (existsSync(dir)) dirs.push(dir);
|
|
187
|
+
};
|
|
188
|
+
if (!parsed.declared) {
|
|
189
|
+
for (const dir of legacyPackages(workspace)) push(dir);
|
|
190
|
+
} else {
|
|
191
|
+
const excluded = new Set();
|
|
192
|
+
for (const pattern of negative) {
|
|
193
|
+
for (const dir of expandPattern(workspace, pattern)) excluded.add(realpathSync(dir));
|
|
194
|
+
}
|
|
195
|
+
for (const pattern of positive) {
|
|
196
|
+
for (const dir of expandPattern(workspace, pattern)) {
|
|
197
|
+
if (excluded.has(realpathSync(dir))) continue;
|
|
198
|
+
push(dir);
|
|
199
|
+
}
|
|
34
200
|
}
|
|
35
201
|
}
|
|
36
|
-
|
|
202
|
+
const isolated = process.env.DSH_WB_ROOT?.trim();
|
|
203
|
+
const inside = isolated === undefined || isolated === '' || resolve(root).startsWith(resolve(workspace) + sep);
|
|
204
|
+
if (inside) push(root);
|
|
205
|
+
push(workspace);
|
|
206
|
+
|
|
37
207
|
const seen = new Set();
|
|
38
208
|
const found = [];
|
|
39
209
|
for (const dir of dirs) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
seen.add(key);
|
|
210
|
+
const resolved = existsSync(dir) ? realpathSync(dir) : dir;
|
|
211
|
+
if (seen.has(resolved)) continue;
|
|
212
|
+
seen.add(resolved);
|
|
44
213
|
const manifest = manifestOf(dir);
|
|
45
214
|
if (manifest === undefined || typeof manifest.name !== 'string') continue;
|
|
46
215
|
found.push({ dir, manifest });
|
|
47
216
|
}
|
|
48
|
-
|
|
217
|
+
cache.set(key, found);
|
|
218
|
+
return found.map((pkg) => ({ ...pkg }));
|
|
49
219
|
}
|
|
50
220
|
|
|
51
221
|
export function discoverModulePackages(options = {}) {
|
package/src/init.mjs
CHANGED
|
@@ -4,7 +4,6 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
const TEMPLATES = {
|
|
7
|
-
minimal: '../template',
|
|
8
7
|
diy: '../template-diy',
|
|
9
8
|
};
|
|
10
9
|
const UNIT_ID = /^[a-z][a-z0-9-]*$/;
|
|
@@ -26,7 +25,7 @@ const dshUiVersion = (root) => {
|
|
|
26
25
|
|
|
27
26
|
}
|
|
28
27
|
console.error(
|
|
29
|
-
`读不到外壳 manifest 里钉的 ${DSH_UI}:模板保持 ${FALLBACK_DSH_UI}
|
|
28
|
+
`读不到外壳 manifest 里钉的 ${DSH_UI}:模板保持 ${FALLBACK_DSH_UI}(与外壳对账过的那版);工程里装过依赖时 init 会盖上当下那版`,
|
|
30
29
|
);
|
|
31
30
|
return { version: FALLBACK_DSH_UI, source: '兜底常量(没读到外壳 manifest)' };
|
|
32
31
|
};
|
|
@@ -46,7 +45,7 @@ const shellRange = (root) => {
|
|
|
46
45
|
const own = versionIn(fileURLToPath(new URL('../package.json', import.meta.url)));
|
|
47
46
|
if (own !== undefined) return `^${own}`;
|
|
48
47
|
console.error(
|
|
49
|
-
`读不到 @dsh-workbench/core 与 module-tools 的版本:模板里那三处 @dsh-workbench/* 保持 ${FALLBACK_RANGE}
|
|
48
|
+
`读不到 @dsh-workbench/core 与 module-tools 的版本:模板里那三处 @dsh-workbench/* 保持 ${FALLBACK_RANGE}(与以前一样);工程里装过依赖时 init 会盖上当下范围`,
|
|
50
49
|
);
|
|
51
50
|
return FALLBACK_RANGE;
|
|
52
51
|
};
|
|
@@ -54,13 +53,13 @@ const shellRange = (root) => {
|
|
|
54
53
|
export async function init({ root, args = [] }) {
|
|
55
54
|
const unit = args[0];
|
|
56
55
|
if (unit === undefined || !UNIT_ID.test(unit)) {
|
|
57
|
-
console.error('用法:dsh-wb-module init <unit> [--dir <目录>] [--template
|
|
56
|
+
console.error('用法:dsh-wb-module init <unit> [--dir <目录>] [--template diy] [--description <文字>]');
|
|
58
57
|
console.error(' unit 是小写短横线标识(它同时是单元 id、包名后缀、卡片 class 前缀)');
|
|
59
|
-
console.error(' --template diy
|
|
58
|
+
console.error(' --template diy:唯一模板(真数据源 + 卡片 + 可跑流程/工具),也是默认');
|
|
60
59
|
process.exit(2);
|
|
61
60
|
}
|
|
62
61
|
const picked = args[args.indexOf('--template') + 1];
|
|
63
|
-
const which = args.includes('--template') ? picked : '
|
|
62
|
+
const which = args.includes('--template') ? picked : 'diy';
|
|
64
63
|
const template = TEMPLATES[which];
|
|
65
64
|
if (template === undefined) {
|
|
66
65
|
console.error(`不认识的模板:${String(which)}(可用:${Object.keys(TEMPLATES).join(' / ')})`);
|
|
@@ -68,8 +67,9 @@ export async function init({ root, args = [] }) {
|
|
|
68
67
|
}
|
|
69
68
|
const at = args.indexOf('--dir');
|
|
70
69
|
const target = at === -1 ? join(root, `module-${unit}`) : args[at + 1];
|
|
70
|
+
const describedAt = args.indexOf('--description');
|
|
71
71
|
const description =
|
|
72
|
-
args[
|
|
72
|
+
describedAt === -1 ? `${unit} 模块:独立 dsh 插件包` : (args[describedAt + 1] ?? `${unit} 模块:独立 dsh 插件包`);
|
|
73
73
|
if (existsSync(target) && readdirSync(target).length > 0) {
|
|
74
74
|
console.error(`${target} 已经存在且不是空目录,不覆盖`);
|
|
75
75
|
process.exit(2);
|
|
@@ -114,6 +114,5 @@ export async function init({ root, args = [] }) {
|
|
|
114
114
|
console.log(`${target} 建好了:${unit} 模块骨架(模板 ${which};build / check / design / selfcheck 已接工具)`);
|
|
115
115
|
console.log(` 需求底稿在 ${tables};空格用 dsh-wb-module design check 查,README 由它生成`);
|
|
116
116
|
console.log(` 模板默认带了 ${DSH_UI}@${dshUi.version}(${dshUi.source})—— 这是 dsh 原语的类型面 + external 声明两处;`);
|
|
117
|
-
console.log(' 装完先在这个 workspace 里跑一次 pnpm install,pnpm typecheck 才认得到它(骨架的判据不依赖它)。');
|
|
118
117
|
console.log(' 确实用不到就连 devDependencies 与 dsh.client.external 两处一起删,判据不拦。');
|
|
119
118
|
}
|
package/src/lint.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import js from '@eslint/js';
|
|
2
2
|
import react from 'eslint-plugin-react';
|
|
3
3
|
import reactHooks from 'eslint-plugin-react-hooks';
|
|
4
|
+
import globals from 'globals';
|
|
4
5
|
import tseslint from 'typescript-eslint';
|
|
5
6
|
|
|
6
7
|
export const FINGERPRINT = 'dsh-wb/module-lint';
|
|
@@ -58,23 +59,10 @@ const PLATFORM_READ = {
|
|
|
58
59
|
"MemberExpression[object.name='process'][property.name='platform'], " +
|
|
59
60
|
"MemberExpression[object.object.name='process'][object.property.name='versions'][property.name='electron'], " +
|
|
60
61
|
"MemberExpression[object.name='window'][property.name='dshDesktop'], " +
|
|
61
|
-
"MemberExpression[property.name='hostOs'], " +
|
|
62
62
|
"MemberExpression[object.name='navigator'][property.name='userAgent']",
|
|
63
63
|
message:
|
|
64
|
-
'
|
|
65
|
-
'process.platform / versions.electron / dshDesktop /
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const NODE_GLOBALS = {
|
|
69
|
-
Buffer: 'readonly',
|
|
70
|
-
URL: 'readonly',
|
|
71
|
-
URLSearchParams: 'readonly',
|
|
72
|
-
console: 'readonly',
|
|
73
|
-
fetch: 'readonly',
|
|
74
|
-
module: 'readonly',
|
|
75
|
-
process: 'readonly',
|
|
76
|
-
setTimeout: 'readonly',
|
|
77
|
-
clearTimeout: 'readonly',
|
|
64
|
+
'平台/外壳差异走能力面:宿主面读 deps.surface(HostSurface),浏览器面读 kit.hooks.usePlatform();' +
|
|
65
|
+
'模块源码不直接读 process.platform / versions.electron / dshDesktop / navigator.userAgent',
|
|
78
66
|
};
|
|
79
67
|
|
|
80
68
|
const SCRIPT_RULES = {
|
|
@@ -174,7 +162,7 @@ export function moduleLint(options = {}) {
|
|
|
174
162
|
{
|
|
175
163
|
name: 'dsh-wb/module-lint/scripts',
|
|
176
164
|
files: SCRIPT_FILES,
|
|
177
|
-
languageOptions: { globals:
|
|
165
|
+
languageOptions: { globals: { ...globals.node } },
|
|
178
166
|
rules: SCRIPT_RULES,
|
|
179
167
|
},
|
|
180
168
|
...overrideRules,
|
package/src/market-index.mjs
CHANGED
|
@@ -415,7 +415,7 @@ export function resolveIndexPlan({ args = [], home, exec }) {
|
|
|
415
415
|
if (plan === undefined) {
|
|
416
416
|
if (asked === 'auto' || asked === '') return undefined;
|
|
417
417
|
throw new MarketIndexError(
|
|
418
|
-
|
|
418
|
+
`这台机器还没配可写的 git 索引源。给我一个能推的 git 仓库(或它在本机的 clone 路径),我配成索引源并建一份 ${MARKET_DIR}/。`,
|
|
419
419
|
'index-write',
|
|
420
420
|
);
|
|
421
421
|
}
|
package/src/migrate.mjs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const API_VERSION = 3;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_FACES = { host: 'src/host.ts', client: 'src/client.tsx' };
|
|
7
|
+
|
|
8
|
+
export async function migrate({ root, args = [] }) {
|
|
9
|
+
const dryRun = args.includes('--dry-run');
|
|
10
|
+
const file = join(root, 'package.json');
|
|
11
|
+
if (!existsSync(file)) {
|
|
12
|
+
console.error(`这里没有 package.json:${root}`);
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let manifest;
|
|
17
|
+
try {
|
|
18
|
+
manifest = JSON.parse(readFileSync(file, 'utf8'));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`package.json 不是合法 JSON:${error.message}`);
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const dsh = manifest.dsh;
|
|
25
|
+
const workbench = dsh?.workbench;
|
|
26
|
+
if (workbench === undefined) {
|
|
27
|
+
console.error('这个包没声明 dsh.workbench:先 cd 到模块目录再跑(或加 --cwd <模块目录>)');
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const changes = [];
|
|
32
|
+
if (workbench.apiVersion !== API_VERSION) {
|
|
33
|
+
changes.push(`dsh.workbench.apiVersion:${JSON.stringify(workbench.apiVersion)} → ${String(API_VERSION)}`);
|
|
34
|
+
workbench.apiVersion = API_VERSION;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (dsh.workbenchFaces === undefined) {
|
|
38
|
+
changes.push(`dsh.workbenchFaces:补默认 ${DEFAULT_FACES.host} / ${DEFAULT_FACES.client}`);
|
|
39
|
+
dsh.workbenchFaces = { ...DEFAULT_FACES };
|
|
40
|
+
} else {
|
|
41
|
+
for (const [kind, fallback] of Object.entries(DEFAULT_FACES)) {
|
|
42
|
+
if (typeof dsh.workbenchFaces[kind] === 'string' && dsh.workbenchFaces[kind] !== '') continue;
|
|
43
|
+
changes.push(`dsh.workbenchFaces.${kind}:补 ${fallback}`);
|
|
44
|
+
dsh.workbenchFaces[kind] = fallback;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [kind, rel] of Object.entries(dsh.workbenchFaces)) {
|
|
49
|
+
if (!existsSync(join(root, rel))) {
|
|
50
|
+
console.warn(`⚠ workbenchFaces.${kind} 指的 ${rel} 不存在 —— 先把这个入口建出来`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (changes.length === 0) {
|
|
55
|
+
console.log('已经是当前代:清单不用改。');
|
|
56
|
+
} else if (dryRun) {
|
|
57
|
+
console.log(`会改 ${String(changes.length)} 处(--dry-run 没写):`);
|
|
58
|
+
for (const one of changes) console.log(` · ${one}`);
|
|
59
|
+
} else {
|
|
60
|
+
writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
61
|
+
console.log(`改好 ${String(changes.length)} 处:`);
|
|
62
|
+
for (const one of changes) console.log(` · ${one}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (workbench.card === null || workbench.card === undefined) {
|
|
66
|
+
console.log('清单 card 是 null / 缺:如果 client.js 真登记了卡,跑 `check --only cards` 看 —— 补 `"card": { "order": … }` 或去掉那张卡的登记。');
|
|
67
|
+
}
|
|
68
|
+
console.log('源码类迁移不自动改:`check` 会逐条点名(kit 成员、裸控件、apiVersion 之外的协议字段),照提示改源码。');
|
|
69
|
+
}
|
package/src/release-journal.mjs
CHANGED
|
@@ -2,6 +2,8 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
|
|
5
|
+
import { workbenchRootOf } from './release-util.mjs';
|
|
6
|
+
|
|
5
7
|
const FINGERPRINT_FILES = [
|
|
6
8
|
'package.json',
|
|
7
9
|
'design.json',
|
|
@@ -13,7 +15,7 @@ const FINGERPRINT_FILES = [
|
|
|
13
15
|
];
|
|
14
16
|
|
|
15
17
|
function statePathOf(home, unit) {
|
|
16
|
-
return join(home, '
|
|
18
|
+
return join(workbenchRootOf(home), 'releases', `${unit}.json`);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export function readState(home, unit) {
|
package/src/release-target.mjs
CHANGED
|
@@ -159,7 +159,7 @@ export function registryInfoOf(root, manifest, args, target, fallbackRegistry) {
|
|
|
159
159
|
if (registry === '') {
|
|
160
160
|
throw new NeedsUser(
|
|
161
161
|
'registry',
|
|
162
|
-
'
|
|
162
|
+
'这台机器还没配团队源。让公司把 registry 和 token 写进 `.npmrc`;或者我先给你打一个安装包。',
|
|
163
163
|
undefined,
|
|
164
164
|
{ target },
|
|
165
165
|
);
|
package/src/release-util.mjs
CHANGED
|
@@ -80,8 +80,14 @@ export function dshHomeOf(home) {
|
|
|
80
80
|
return home ?? process.env.DSH_HOME?.trim() ?? join(homedir(), '.dsh');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
export function workbenchRootOf(home) {
|
|
84
|
+
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
85
|
+
if (configured !== undefined && configured !== '') return configured;
|
|
86
|
+
return join(dshHomeOf(home), 'workbench');
|
|
87
|
+
}
|
|
88
|
+
|
|
83
89
|
export function artifactsDirOf(home, unit) {
|
|
84
|
-
return join(
|
|
90
|
+
return join(workbenchRootOf(home), 'artifacts', unit);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
export function publishTagOf(version) {
|
|
@@ -1,33 +1,25 @@
|
|
|
1
1
|
# __NAME__ · agent 工作说明
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
工作台模块(单元 `__UNIT__`)。模块行为与数据看 `README.md`;这份只说工具链怎么用。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 干活循环
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
pnpm install
|
|
9
|
-
pnpm build # dist/host.js + client.js
|
|
10
|
-
pnpm typecheck
|
|
11
|
-
pnpm test
|
|
12
|
-
pnpm selfcheck
|
|
13
|
-
pnpm check # 十三条模块判据
|
|
14
|
-
pnpm lint
|
|
15
|
-
pnpm design check
|
|
16
|
-
```
|
|
7
|
+
改 `src/**` 后:
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
1. `pnpm build` —— 产出 `dist/host.js` + 套了 loader 壳的 `client.js`;产物由它生成。
|
|
10
|
+
2. `pnpm check` —— 十三条判据,读的就是上一步的产物。单跑一条用 `pnpm check --only <判据名>`;
|
|
11
|
+
**全绿**才算这件活做完。怎么读红、常见红:`@dsh-workbench/core/skills/workbench-module-diy/REVIEW.md`。
|
|
12
|
+
3. `pnpm test` —— `src/**/__tests__/` 下、与改动同目录的 `*.test.ts`;新逻辑配对应用例,**全绿**才算完。
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
dsh-wb-module release --target team|public|tarball|git
|
|
22
|
-
```
|
|
14
|
+
`pnpm selfcheck` = build + `design check` + smoke;`design.json` 里的(待填)填完才绿。
|
|
23
15
|
|
|
24
|
-
|
|
16
|
+
## 工具链约定
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
18
|
+
- 清单是工具名唯一真相:`dsh-wb-module sync` 生成 `src/declared-tools.ts`,代码引 `DECLARED_TOOLS`。
|
|
19
|
+
- 判据读构建产物:改了 `src/**` 先 build 再 check。
|
|
20
|
+
- 发布:`dsh-wb-module release --target team|public|tarball|git`;`--dry-run` 只预览,`--verify` 只核已发布的包。
|
|
29
21
|
|
|
30
22
|
## 先读
|
|
31
23
|
|
|
32
|
-
- `README.md
|
|
33
|
-
-
|
|
24
|
+
- `README.md`:模块行为、数据源、三张表。
|
|
25
|
+
- `@dsh-workbench/core/skills/workbench-module-diy/`:路由 / 工具 / 卡片 / 文案的写法与约定(随外壳出货)。
|