@dsh-workbench/module-tools 1.1.13 → 1.1.14-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.
Files changed (64) hide show
  1. package/README.md +36 -23
  2. package/bin/dsh-wb-module.mjs +18 -4
  3. package/package.json +4 -4
  4. package/src/arch.mjs +36 -25
  5. package/src/check.mjs +50 -3
  6. package/src/checks/arch.mjs +2 -2
  7. package/src/checks/contracts.mjs +88 -41
  8. package/src/checks/kit.mjs +3 -2
  9. package/src/checks/lint.mjs +2 -2
  10. package/src/checks/primitives.mjs +2 -2
  11. package/src/discover.mjs +186 -16
  12. package/src/init.mjs +7 -8
  13. package/src/lint.mjs +4 -16
  14. package/src/market-index.mjs +1 -1
  15. package/src/migrate.mjs +69 -0
  16. package/src/release-journal.mjs +3 -1
  17. package/src/release-target.mjs +1 -1
  18. package/src/release-util.mjs +7 -1
  19. package/template-diy/AGENTS.md.tpl +14 -22
  20. package/template-diy/README.md.tpl +61 -65
  21. package/template-diy/design.json.tpl +19 -21
  22. package/template-diy/package.json.tpl +6 -2
  23. package/template-diy/scripts/smoke.assert.mjs.tpl +28 -0
  24. package/template-diy/scripts/smoke.mjs.tpl +150 -30
  25. package/template-diy/src/__tests__/plugin.test.ts.tpl +453 -0
  26. package/template-diy/src/components/NotesSection.tsx.tpl +74 -0
  27. package/template-diy/src/components/OverviewBody.module.css.tpl +5 -0
  28. package/template-diy/src/components/OverviewBody.tsx.tpl +64 -0
  29. package/template-diy/src/components/OverviewCard.module.css.tpl +9 -1
  30. package/template-diy/src/components/OverviewCard.tsx.tpl +108 -67
  31. package/template-diy/src/components/PrefsSection.tsx.tpl +36 -0
  32. package/template-diy/src/components/RunSection.tsx.tpl +69 -0
  33. package/template-diy/src/components/{OverviewCard.test.ts.tpl → __tests__/OverviewCard.test.ts.tpl} +51 -1
  34. package/template-diy/src/declared-tools.ts.tpl +6 -0
  35. package/template-diy/src/host/notes.ts.tpl +92 -0
  36. package/template-diy/src/host/overview.ts.tpl +57 -0
  37. package/template-diy/src/host/sample.ts.tpl +151 -0
  38. package/template-diy/src/host.ts.tpl +88 -66
  39. package/template-diy/src/messages.ts.tpl +28 -0
  40. package/template-diy/src/model/__tests__/flow.test.ts.tpl +36 -0
  41. package/template-diy/src/model/__tests__/notes.test.ts.tpl +49 -0
  42. package/template-diy/src/model/{overview.test.ts.tpl → __tests__/overview.test.ts.tpl} +1 -1
  43. package/template-diy/src/model/__tests__/prefs.test.ts.tpl +59 -0
  44. package/template-diy/src/model/flow.ts.tpl +47 -0
  45. package/template-diy/src/model/notes.ts.tpl +52 -0
  46. package/template-diy/src/model/overview.ts.tpl +3 -1
  47. package/template-diy/src/model/prefs.ts.tpl +61 -0
  48. package/template/AGENTS.md.tpl +0 -33
  49. package/template/CHANGELOG.md.tpl +0 -7
  50. package/template/README.md.tpl +0 -30
  51. package/template/arch.config.mjs +0 -2
  52. package/template/cordis.patch.yml.tpl +0 -3
  53. package/template/eslint.config.mjs +0 -2
  54. package/template/package.json.tpl +0 -50
  55. package/template/scripts/smoke.mjs.tpl +0 -192
  56. package/template/src/client.tsx.tpl +0 -22
  57. package/template/src/components/HelloCard.tsx.tpl +0 -28
  58. package/template/src/host.ts.tpl +0 -21
  59. package/template/src/messages.ts.tpl +0 -12
  60. package/template/src/plugin.test.ts.tpl +0 -56
  61. package/template/tsconfig.json +0 -20
  62. package/template/types/globals.d.ts +0 -14
  63. package/template/vitest.config.ts +0 -14
  64. package/template-diy/src/plugin.test.ts.tpl +0 -159
@@ -0,0 +1,61 @@
1
+ import { UNIT_ID } from './notes';
2
+
3
+ export const PREF_FILE = 'prefs.json';
4
+ export const PREFS_ROUTE = `${UNIT_ID}/prefs`;
5
+ export const PREFS_RESOURCE = UNIT_ID;
6
+ export const LIMIT_DEFAULT = 5;
7
+ export const LIMIT_MIN = 1;
8
+ export const LIMIT_MAX = 50;
9
+ export const LIMIT_CHOICES = [5, 10, 20] as const;
10
+
11
+ export interface PrefStore {
12
+ read(name: string): unknown | null;
13
+ write(name: string, value: unknown): void;
14
+ remove(name: string): void;
15
+ }
16
+
17
+ export interface Prefs {
18
+ readonly limit: number;
19
+ }
20
+
21
+ export const DEFAULT_PREFS: Prefs = { limit: LIMIT_DEFAULT };
22
+
23
+ function recordOf(value: unknown): Record<string, unknown> {
24
+ return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};
25
+ }
26
+
27
+ function limitOf(raw: unknown): number | undefined {
28
+ const parsed = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : Number.NaN;
29
+ if (!Number.isInteger(parsed)) return undefined;
30
+ if (parsed < LIMIT_MIN || parsed > LIMIT_MAX) return undefined;
31
+ return parsed;
32
+ }
33
+
34
+ export function prefsOf(store: PrefStore): Prefs {
35
+ const stored = recordOf(store.read(PREF_FILE));
36
+ const limit = limitOf(stored['limit']);
37
+ return { limit: limit ?? LIMIT_DEFAULT };
38
+ }
39
+
40
+ export function applyPrefs(store: PrefStore, payload: unknown): { readonly prefs: Prefs } {
41
+ if (typeof payload !== 'object' || payload === null) {
42
+ throw new Error('偏好写需要一个对象:{ limit?, reset? }');
43
+ }
44
+ const body = payload as Record<string, unknown>;
45
+ if (body['reset'] === true) {
46
+ store.remove(PREF_FILE);
47
+ return { prefs: DEFAULT_PREFS };
48
+ }
49
+ if (!Object.prototype.hasOwnProperty.call(body, 'limit')) {
50
+ throw new Error(`这次偏好写里没有 limit —— 可用 ${String(LIMIT_MIN)}..${String(LIMIT_MAX)} 的整数`);
51
+ }
52
+ const limit = limitOf(body['limit']);
53
+ if (limit === undefined) {
54
+ throw new Error(
55
+ `「最大的几个文件」只认 ${String(LIMIT_MIN)}..${String(LIMIT_MAX)} 的整数,收到「${String(body['limit'])}」`,
56
+ );
57
+ }
58
+ const prefs: Prefs = { limit };
59
+ store.write(PREF_FILE, prefs);
60
+ return { prefs };
61
+ }
@@ -1,33 +0,0 @@
1
- # __NAME__ · agent 工作说明
2
-
3
- 这个包是工作台模块(单元 `__UNIT__`)。`README.md` 讲模块本身;这份讲 agent 在这份代码里怎么干活。
4
-
5
- ## 命令
6
-
7
- ```bash
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
- ```
17
-
18
- 发布(维护者确认之后):
19
-
20
- ```bash
21
- dsh-wb-module release --target team|public|tarball|git
22
- ```
23
-
24
- `--dry-run` 只预览,`--verify` 只核已发布的包。
25
-
26
- ## 不要
27
-
28
- - 手改 `client.js` / `dist/**`:构建产物,改 `src/**` 后重新 build。
29
-
30
- ## 先读
31
-
32
- - `README.md`:模块行为与数据源。
33
- - 工作台模块指引:`@dsh-workbench/core/skills/workbench-module-diy/`(dsh / 工作台的约束随外壳出货)。
@@ -1,7 +0,0 @@
1
- # 更新日志
2
-
3
- ## v0.1.0
4
-
5
- ### ✨ 新增
6
-
7
- - 第一个版本
@@ -1,30 +0,0 @@
1
- # __NAME__
2
-
3
- `__UNIT__` 模块:一个独立的 dsh 插件包,只认外壳的服务名,不认识外壳源码。
4
-
5
- ```bash
6
- dsh-wb-module build # 出 dist/host.js 与套了 loader 壳的 client.js
7
- dsh-wb-module check # 契约 / 声明协议 / 产物边界 / 源码边界 / 架构 / dsh 原语 / kit 面 / 卡片渲染 / 独立构建 / 内部标识 / 样式归属 / 不指外壳 / 代码卫生(eslint)
8
- dsh-wb-module check --only cards # 十三条可以单跑
9
- ```
10
-
11
- 包自带的六个脚本(`build` / `typecheck` / `test` / `selfcheck` 是 `check` 的契约判据要求的那四个;`check` 跑全部判据,`lint` 只跑源码卫生那一条):
12
-
13
- ```bash
14
- pnpm build # 构建产物
15
- pnpm typecheck # tsc --noEmit
16
- pnpm test # vitest(src/**/*.test.ts)
17
- pnpm selfcheck # 构建 + 把 client.js 当浏览器加载器跑一遍,看卡片真进没进外壳
18
- pnpm check # dsh-wb-module check
19
- pnpm lint # dsh-wb-module lint(`--fix` 就地改文件;`check` 永不动手)
20
- ```
21
-
22
- 形状:`src/components/HelloCard.tsx` 是卡片,`src/client.tsx` 只注册,`src/host.ts` 是宿主面,
23
- `src/messages.ts` 是文案;有纯逻辑就放 `src/model/**` 并配一份同目录测试。
24
-
25
- ## 开发与发布
26
-
27
- 开发命令见上面的六个脚本;dsh / 工作台的模块约束见工作台模块指引(随外壳出货);agent 的工作说明见 `AGENTS.md`。
28
-
29
- 发布(维护者确认之后):`dsh-wb-module release --target team|public|tarball|git`;
30
- `--dry-run` 只预览,`--verify` 只核已发布的包。
@@ -1,2 +0,0 @@
1
- import { moduleArch } from '@dsh-workbench/module-tools/arch';
2
- export default moduleArch();
@@ -1,3 +0,0 @@
1
- - insert:
2
- - id: workbench-module-__UNIT__
3
- name: '__NAME__'
@@ -1,2 +0,0 @@
1
- import { moduleLint } from '@dsh-workbench/module-tools/lint';
2
- export default moduleLint({ rootDir: import.meta.dirname });
@@ -1,50 +0,0 @@
1
- {
2
- "name": "__NAME__",
3
- "version": "0.1.0",
4
- "private": true,
5
- "description": "__DESC__",
6
- "type": "module",
7
- "exports": {
8
- ".": "./dist/host.js",
9
- "./client": "./client.js"
10
- },
11
- "files": ["dist", "client.js", "cordis.patch.yml", "CHANGELOG.md"],
12
- "scripts": {
13
- "build": "dsh-wb-module build",
14
- "typecheck": "tsc --noEmit",
15
- "test": "vitest run",
16
- "selfcheck": "dsh-wb-module build && node scripts/smoke.mjs",
17
- "check": "dsh-wb-module check",
18
- "lint": "dsh-wb-module lint",
19
- "prepack": "dsh-wb-module build"
20
- },
21
- "dsh": {
22
- "bundle": { "patch": "./cordis.patch.yml" },
23
- "client": {
24
- "platform": "web",
25
- "inject": ["@dsh-workbench/core"],
26
- "external": ["@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives"]
27
- },
28
- "workbench": {
29
- "apiVersion": 3,
30
- "unit": "__UNIT__",
31
- "title": { "zh": "__UNIT__ 模块", "en": "__UNIT__ module" },
32
- "card": { "order": 90 },
33
- "tools": [],
34
- "host": { "inject": [] }
35
- },
36
- "workbenchFaces": { "host": "src/host.ts", "client": "src/client.tsx" }
37
- },
38
- "peerDependencies": { "@dsh-workbench/core": "__SHELL_RANGE__", "react": "*" },
39
- "peerDependenciesMeta": { "react": { "optional": true } },
40
- "devDependencies": {
41
- "@deepseek-ai/dsh-client-ui-primitives": "__DSH_UI_VERSION__",
42
- "@dsh-workbench/core": "__SHELL_RANGE__",
43
- "@dsh-workbench/module-tools": "__SHELL_RANGE__",
44
- "@types/node": "^24.19.0",
45
- "@types/react": "^19.3.0",
46
- "react": "^19.3.0",
47
- "typescript": "5.9",
48
- "vitest": "^5.0.2"
49
- }
50
- }
@@ -1,192 +0,0 @@
1
- // 模块包的接线自检:把 client.js 当成浏览器里的加载器那样跑一遍,再把宿主产物当 ESM 跑一遍。
2
- //
3
- // 为什么值得有:宿主侧挂没挂上,curl /panels 就能看见;浏览器侧挂没挂上,过去只能靠"刷个页面看看"。
4
- // 这个脚本复刻 dsh 加载器的**契约**(`window.__ModuleLoader__.load({ id, factory })` + `factory(require)`),
5
- // 于是"模块注册的卡片到底进没进外壳"变成一条机器判据,而不再是人的眼睛。
6
- //
7
- // ★ 这份脚本只验**形状**:它认不出成员名、也不认识你的数据源,所以换数据源、换卡片内容都不用改它。
8
- // 你自己的那条读该不该跑通,属于你自己 vitest 与 `pnpm selfcheck` 的事。
9
- //
10
- // 负例在最后一段:服务不在的时候必须**报出来**,不许静默通过 —— 静默的模块是最难查的那种坏。
11
-
12
- const PLUGIN_ID = '__NAME__';
13
- const UNIT_ID = '__UNIT__';
14
- const failures = [];
15
-
16
- function check(name, ok, detail) {
17
- if (ok) {
18
- console.log(`✅ ${name}`);
19
- return;
20
- }
21
- failures.push(name);
22
- console.log(`❌ ${name}${detail === undefined ? '' : ` —— ${detail}`}`);
23
- }
24
-
25
- /**
26
- * 什么属性都拿得到、什么调用都返回自己的替身 —— 这样"模块用了 kit 的哪个成员"就不再是
27
- * 这份脚本要知道的事(`check:cards` 用的是同一招)。真红只留给模块自己的代码。
28
- */
29
- function anyStub(path = 'stub') {
30
- const target = function () {
31
- return anyStub(`${path}()`);
32
- };
33
- return new Proxy(target, {
34
- get(_target, key) {
35
- if (key === Symbol.toPrimitive) return () => 0;
36
- if (key === Symbol.iterator) return function* () {};
37
- if (key === 'then') return undefined;
38
- if (key === 'length') return 0;
39
- if (key === 'toString') return () => path;
40
- return anyStub(`${path}.${String(key)}`);
41
- },
42
- apply() {
43
- return anyStub(`${path}()`);
44
- },
45
- });
46
- }
47
-
48
- /** 外壳 kit 的替身:**三个桶都必须在** —— 卡片是在模块顶层解构 kit 的,少一个桶是 import 那一刻就抛。 */
49
- function fakeKit() {
50
- return {
51
- hooks: new Proxy({}, { get: () => () => anyStub('hook') }),
52
- components: new Proxy({}, { get: () => () => null }),
53
- api: new Proxy({}, { get: () => () => anyStub('api') }),
54
- lib: anyStub('lib'),
55
- };
56
- }
57
-
58
- function moduleRequire(name) {
59
- if (name === 'react') {
60
- // 必须是**真的元素对象**:返回 null 的话,"卡片画得出来"这类断言会永远假红。
61
- return { createElement: (type, props, ...children) => ({ type, props: props ?? {}, children }) };
62
- }
63
- if (name === '@dsh-workbench/core/client') return { kit: fakeKit() };
64
- throw new Error(`模块包不该 require ${JSON.stringify(name)}:模块表里只有外壳的 seed 词与外部声明`);
65
- }
66
-
67
- let boot = null;
68
- globalThis.window = {
69
- __ModuleLoader__: {
70
- load(entry) {
71
- boot = entry;
72
- },
73
- },
74
- };
75
-
76
- await import(new URL('../client.js', import.meta.url).href);
77
-
78
- check('client.js 调用 __ModuleLoader__.load()', boot !== null);
79
- check('模块 id 等于包名', boot?.id === PLUGIN_ID, boot?.id);
80
- if (boot === null) {
81
- console.error(`\n${failures.length} 条没过`);
82
- process.exit(1);
83
- }
84
-
85
- const pluginExports = boot.factory(moduleRequire);
86
- check(
87
- '注入 workbenchUnits(外壳交出来的那个服务名)',
88
- Array.isArray(pluginExports.inject) && pluginExports.inject.includes('workbenchUnits'),
89
- String(pluginExports.inject),
90
- );
91
- check('导出了 apply', typeof pluginExports.apply === 'function');
92
-
93
- const faces = [];
94
- const ctx = {
95
- get: (name) =>
96
- name === 'workbenchUnits'
97
- ? {
98
- registerUnit(face) {
99
- faces.push(face);
100
- return () => {};
101
- },
102
- }
103
- : undefined,
104
- effect: (fn) => {
105
- fn();
106
- },
107
- };
108
-
109
- pluginExports.apply(ctx);
110
- check('卡片真的注册进了外壳', faces.length === 1, `注册了 ${faces.length} 个`);
111
- check(`注册的是 ${UNIT_ID} 单元`, faces[0]?.unit === UNIT_ID, faces[0]?.unit);
112
- check('卡片是个能画的组件', typeof faces[0]?.card === 'function');
113
- check(
114
- '面只交 unit(标题与顺序住在 package.json 的 dsh.workbench 里,由外壳按 unit 交给浏览器)',
115
- faces[0]?.manifest === undefined,
116
- JSON.stringify(faces[0]?.manifest),
117
- );
118
- check(
119
- '卡片在空数据下也画得出来(不许抛)',
120
- (() => {
121
- try {
122
- return (
123
- faces[0].card({ world: { current: {} }, refreshToken: 0, onRefresh: () => {}, t: (key) => key }) != null
124
- );
125
- } catch {
126
- return false;
127
- }
128
- })(),
129
- );
130
-
131
- const warnings = [];
132
- const realWarn = console.warn;
133
- console.warn = (...args) => warnings.push(args.join(' '));
134
- pluginExports.apply({
135
- get: () => undefined,
136
- effect: (fn) => {
137
- fn();
138
- },
139
- });
140
- console.warn = realWarn;
141
-
142
- check(
143
- '负例:外壳没交服务时必须报出来(不许静默通过)',
144
- warnings.some((line) => line.includes('workbenchUnits')),
145
- warnings.join(' | '),
146
- );
147
-
148
- // ── 宿主产物:面注册得对不对 ──────────────────────────────────────────────
149
- const hostExports = await import(new URL('../dist/host.js', import.meta.url).href);
150
- check('宿主产物导出了 apply', typeof hostExports.apply === 'function');
151
- check(
152
- '宿主产物也注入 workbenchUnits',
153
- Array.isArray(hostExports.inject) && hostExports.inject.includes('workbenchUnits'),
154
- );
155
-
156
- const hostFaces = [];
157
- hostExports.apply({
158
- get: (name) =>
159
- name === 'workbenchUnits'
160
- ? {
161
- register(face) {
162
- hostFaces.push(face);
163
- return () => {};
164
- },
165
- }
166
- : undefined,
167
- effect: (fn) => {
168
- fn();
169
- },
170
- logger: { warn: () => {} },
171
- });
172
-
173
- check('宿主面注册进了外壳', hostFaces.length === 1, `注册了 ${hostFaces.length} 个`);
174
- // 给一份最小 deps:宿主面的 `mount(deps)` 通常要 `deps.ctx.get('shell')` 之类,
175
- // 不给 deps 的话**任何真的用了 `mount(deps)` 的模块**都会在这里崩 —— 而那不是模块的错。
176
- let mounted = {};
177
- try {
178
- mounted = hostFaces[0]?.mount({ ctx: { get: () => undefined }, logger: { warn: () => {} } }) ?? {};
179
- } catch (error) {
180
- failures.push(`宿主面的 mount(deps) 抛了:${error.message}`);
181
- }
182
-
183
- const routes = mounted.routes ?? {};
184
- const strays = Object.keys(routes).filter((key) => key !== UNIT_ID && !key.startsWith(`${UNIT_ID}/`));
185
- check(`每条路由都住在自己那一格(${UNIT_ID} 或 ${UNIT_ID}/…)`, strays.length === 0, strays.join(', '));
186
- check(
187
- 'registerTools() 交得出一份清单(这份模块没有工具时是空数组)',
188
- Array.isArray(mounted.registerTools?.()),
189
- );
190
-
191
- console.log(`\n${failures.length === 0 ? '模块自检通过' : `${failures.length} 条没过`}`);
192
- if (failures.length > 0) process.exit(1);
@@ -1,22 +0,0 @@
1
- import type { ClientContext, UnitClientApi, UnitClientFace } from '@dsh-workbench/core/contract';
2
- import { HelloCard } from './components/HelloCard';
3
- import { messages } from './messages';
4
-
5
- const UNIT_ID = '__UNIT__';
6
-
7
- const face: UnitClientFace = {
8
- unit: UNIT_ID,
9
- messages,
10
- card: (props) => HelloCard({ t: props.t, onRefresh: props.onRefresh }),
11
- };
12
-
13
- export const inject = ['workbenchUnits'];
14
-
15
- export function apply(ctx: ClientContext): void {
16
- const units = ctx.get<UnitClientApi>('workbenchUnits');
17
- if (units === undefined) {
18
- console.warn(`[${UNIT_ID}] 外壳没有提供 workbenchUnits 服务:这张卡片装不进来`);
19
- return;
20
- }
21
- ctx.effect(() => units.registerUnit(face), `__NAME__: 注册 ${UNIT_ID}`);
22
- }
@@ -1,28 +0,0 @@
1
- import { createElement as h } from 'react';
2
- import { kit } from '@dsh-workbench/core/client';
3
-
4
- const UNIT_ID = '__UNIT__';
5
-
6
- const { Card } = kit.components;
7
-
8
- type Rendered = ReturnType<typeof h>;
9
- type Translate = (key: string, params?: Record<string, unknown>) => string;
10
-
11
- export interface HelloCardProps {
12
- readonly t: Translate;
13
- readonly onRefresh: () => void;
14
- }
15
-
16
- export function HelloCard(props: HelloCardProps): Rendered {
17
- return h(
18
- Card,
19
- {
20
- t: props.t,
21
- title: props.t(`${UNIT_ID}.cardTitle`),
22
- refreshing: false,
23
- onRefresh: props.onRefresh,
24
- },
25
- h('p', null, props.t(`${UNIT_ID}.heading`)),
26
- h('p', null, props.t(`${UNIT_ID}.body`)),
27
- );
28
- }
@@ -1,21 +0,0 @@
1
- import type { HostContext, UnitModuleFace, UnitRegistry } from '@dsh-workbench/core/contract';
2
-
3
- const UNIT_ID = '__UNIT__';
4
-
5
- export const name = '__NAME__';
6
-
7
- export const inject = ['workbenchUnits'];
8
-
9
- const face: UnitModuleFace = {
10
- unit: UNIT_ID,
11
- mount: () => ({ routes: {}, registerTools: () => [] }),
12
- };
13
-
14
- export function apply(ctx: HostContext): void {
15
- const units = ctx.get<UnitRegistry>('workbenchUnits');
16
- if (units === undefined) {
17
- ctx.logger?.warn?.(`[${UNIT_ID}] 外壳没有提供 workbenchUnits 服务:这个模块装不进来`);
18
- return;
19
- }
20
- ctx.effect(() => units.register(face), `__NAME__: 注册 ${UNIT_ID}`);
21
- }
@@ -1,12 +0,0 @@
1
- export const messages = {
2
- zh: {
3
- '__UNIT__.cardTitle': '新模块',
4
- '__UNIT__.heading': '这张卡还是空的',
5
- '__UNIT__.body': '改 src/client.tsx 画你要的内容,然后跑 dsh-wb-module build。',
6
- },
7
- en: {
8
- '__UNIT__.cardTitle': 'New module',
9
- '__UNIT__.heading': 'This card is still empty',
10
- '__UNIT__.body': 'Edit src/client.tsx to render what you need, then run dsh-wb-module build.',
11
- },
12
- };
@@ -1,56 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { apply, inject, name } from './host';
3
- import { apply as applyClient, inject as injectClient } from './client';
4
-
5
- vi.mock('@dsh-workbench/core/client', () => ({
6
- kit: { components: { Card: () => null } },
7
- }));
8
-
9
- function fakeCtx(service: unknown) {
10
- const faces: unknown[] = [];
11
- return {
12
- faces,
13
- ctx: {
14
- get: () => service,
15
- effect: (effect: () => void | (() => void)) => {
16
- effect();
17
- return () => {};
18
- },
19
- },
20
- };
21
- }
22
-
23
- describe('__NAME__ 的面', () => {
24
- it('插件名就是包名', () => {
25
- expect(name).toBe('__NAME__');
26
- });
27
-
28
- it('两侧都只注入外壳交出来的那个服务', () => {
29
- expect(inject).toEqual(['workbenchUnits']);
30
- expect(injectClient).toEqual(['workbenchUnits']);
31
- });
32
-
33
- it('宿主半侧把自己的面注册进外壳', () => {
34
- const { ctx, faces } = fakeCtx({
35
- register: (face: unknown) => {
36
- faces.push(face);
37
- return () => {};
38
- },
39
- });
40
- apply(ctx as never);
41
- expect(faces).toHaveLength(1);
42
- expect((faces[0] as { unit: string }).unit).toBe('__UNIT__');
43
- });
44
-
45
- it('浏览器半侧把自己的卡片注册进外壳', () => {
46
- const { ctx, faces } = fakeCtx({
47
- registerUnit: (face: unknown) => {
48
- faces.push(face);
49
- return () => {};
50
- },
51
- });
52
- applyClient(ctx as never);
53
- expect(faces).toHaveLength(1);
54
- expect((faces[0] as { unit: string }).unit).toBe('__UNIT__');
55
- });
56
- });
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022", "DOM"],
7
- "jsx": "react",
8
- "jsxFactory": "h",
9
- "jsxFragmentFactory": "Fragment",
10
- "strict": true,
11
- "noUnusedLocals": true,
12
- "noImplicitOverride": true,
13
- "noEmit": true,
14
- "skipLibCheck": true,
15
- "isolatedModules": true,
16
- "verbatimModuleSyntax": true,
17
- "paths": { "@/modules/__UNIT__/*": ["./src/*"] }
18
- },
19
- "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.d.ts"]
20
- }
@@ -1,14 +0,0 @@
1
- import type * as ReactTypes from 'react';
2
-
3
- declare global {
4
- namespace JSX {
5
- interface Element extends ReactTypes.JSX.Element {}
6
- interface ElementClass extends ReactTypes.JSX.ElementClass {}
7
- interface IntrinsicElements extends ReactTypes.JSX.IntrinsicElements {}
8
-
9
- type ElementType = ReactTypes.JSX.ElementType;
10
- interface IntrinsicAttributes extends ReactTypes.JSX.IntrinsicAttributes {}
11
- interface ElementAttributesProperty extends ReactTypes.JSX.ElementAttributesProperty {}
12
- interface ElementChildrenAttribute extends ReactTypes.JSX.ElementChildrenAttribute {}
13
- }
14
- }
@@ -1,14 +0,0 @@
1
- import { fileURLToPath } from 'node:url';
2
- import { defineConfig } from 'vitest/config';
3
-
4
- const here = (relative: string): string => fileURLToPath(new URL(relative, import.meta.url));
5
-
6
- export default defineConfig({
7
- resolve: {
8
- alias: [
9
- { find: /^@\/modules\/__UNIT__\//, replacement: `${here('./src')}/` },
10
- { find: /^@\/platforms\/[^/]+\//, replacement: `${here('./src/platforms')}/` },
11
- ],
12
- },
13
- test: { environment: 'node', include: ['src/**/*.test.ts', 'src/**/*.test.tsx'] },
14
- });