@dsh-workbench/module-tools 0.3.2 → 0.3.3
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 +134 -7
- package/bin/dsh-wb-module.mjs +50 -4
- package/package.json +11 -3
- package/src/arch.mjs +234 -0
- package/src/build.mjs +4 -1
- package/src/check.mjs +2 -0
- package/src/checks/arch.mjs +127 -0
- package/src/checks/lint.mjs +351 -0
- package/src/design.mjs +46 -5
- package/src/lint.mjs +309 -0
- package/template/README.md.tpl +18 -3
- package/template/arch.config.mjs +2 -0
- package/template/eslint.config.mjs +2 -0
- package/template/package.json.tpl +2 -0
- package/template/src/client.tsx.tpl +4 -18
- package/template/src/components/HelloCard.tsx.tpl +28 -0
- package/template-diy/README.md.tpl +44 -19
- package/template-diy/arch.config.mjs +2 -0
- package/template-diy/design.json.tpl +1 -0
- package/template-diy/eslint.config.mjs +2 -0
- package/template-diy/package.json.tpl +2 -0
- package/template-diy/src/client.tsx.tpl +10 -149
- package/template-diy/src/components/OverviewCard.module.css.tpl +5 -0
- package/template-diy/src/components/OverviewCard.test.ts.tpl +118 -0
- package/template-diy/src/components/OverviewCard.tsx.tpl +92 -0
- package/template-diy/src/host.ts.tpl +1 -14
- package/template-diy/src/model/overview.test.ts.tpl +49 -0
- package/template-diy/src/model/overview.ts.tpl +61 -0
- package/template-diy/src/plugin.test.ts.tpl +4 -140
- package/template-diy/types/styles.d.ts +14 -0
|
@@ -3,35 +3,23 @@ import { tmpdir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterAll, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
|
|
6
|
-
const shell = vi.hoisted(() => ({
|
|
7
|
-
resource: { phase: 'loading' } as {
|
|
8
|
-
readonly phase: string;
|
|
9
|
-
readonly value?: unknown;
|
|
10
|
-
readonly error?: { readonly code: string; readonly message: string };
|
|
11
|
-
},
|
|
12
|
-
readEnabled: undefined as boolean | undefined,
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
6
|
vi.mock('@dsh-workbench/core/client', () => ({
|
|
16
7
|
kit: {
|
|
17
|
-
hooks: {
|
|
18
|
-
useApiResource: (_route: string, _query: unknown, _refreshToken: number, options?: { readonly enabled?: boolean }) => {
|
|
19
|
-
shell.readEnabled = options?.enabled;
|
|
20
|
-
return shell.resource;
|
|
21
|
-
},
|
|
22
|
-
},
|
|
8
|
+
hooks: { useApiResource: () => ({ phase: 'loading' }) },
|
|
23
9
|
components: {
|
|
24
10
|
Card: () => null,
|
|
25
11
|
CenteredState: () => null,
|
|
26
12
|
EmptyState: () => null,
|
|
27
13
|
StateBlock: () => null,
|
|
28
14
|
IconPanelScreen: () => null,
|
|
15
|
+
Rows: () => null,
|
|
16
|
+
Row: () => null,
|
|
29
17
|
},
|
|
30
18
|
},
|
|
31
19
|
}));
|
|
32
20
|
|
|
33
21
|
import { apply, inject, name, OVERVIEW_ROUTE, readOverview } from './host';
|
|
34
|
-
import { apply as applyClient,
|
|
22
|
+
import { apply as applyClient, inject as injectClient } from './client';
|
|
35
23
|
|
|
36
24
|
interface HostFace {
|
|
37
25
|
readonly unit: string;
|
|
@@ -99,42 +87,11 @@ function overview(search: Record<string, string>): Promise<unknown> {
|
|
|
99
87
|
return hostFace().mount().routes[OVERVIEW_ROUTE](new URLSearchParams(search));
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
function textOf(value: unknown, out: string[] = []): string[] {
|
|
103
|
-
if (typeof value === 'string') {
|
|
104
|
-
out.push(value);
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
if (typeof value === 'number') {
|
|
108
|
-
out.push(String(value));
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
if (typeof value === 'boolean' || value === null || value === undefined) return out;
|
|
112
|
-
if (Array.isArray(value)) {
|
|
113
|
-
for (const one of value) textOf(one, out);
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
if (typeof value === 'object') {
|
|
117
|
-
const record = value as Record<string, unknown>;
|
|
118
|
-
const source = 'type' in record && 'props' in record ? (record.props as Record<string, unknown>) : record;
|
|
119
|
-
for (const one of Object.values(source)) {
|
|
120
|
-
if (typeof one !== 'function') textOf(one, out);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
90
|
const scratch = mkdtempSync(join(tmpdir(), 'dsh-wb-diy-'));
|
|
127
91
|
afterAll(() => {
|
|
128
92
|
rmSync(scratch, { recursive: true, force: true });
|
|
129
93
|
});
|
|
130
94
|
|
|
131
|
-
const cardProps = {
|
|
132
|
-
world: { current: { workspace: { path: '/tmp/__UNIT__-workspace' } } },
|
|
133
|
-
refreshToken: 1,
|
|
134
|
-
onRefresh: () => {},
|
|
135
|
-
t: (key: string) => key,
|
|
136
|
-
};
|
|
137
|
-
|
|
138
95
|
describe('__NAME__ 的面', () => {
|
|
139
96
|
it('插件名就是包名', () => {
|
|
140
97
|
expect(name).toBe('__NAME__');
|
|
@@ -200,96 +157,3 @@ describe('宿主侧的只读路由', () => {
|
|
|
200
157
|
expect(readOverview(join(scratch, 'sample'), 5).bytes).toBe(160);
|
|
201
158
|
});
|
|
202
159
|
});
|
|
203
|
-
|
|
204
|
-
describe('卡片:待机 / 加载 / 就绪 / 就绪但空 / 出错', () => {
|
|
205
|
-
it('没有选中工作区时不发请求,画待机那一屏', () => {
|
|
206
|
-
const face = clientFace();
|
|
207
|
-
shell.resource = { phase: 'idle' };
|
|
208
|
-
const withoutProject = { ...cardProps, world: { current: { workspace: null } } };
|
|
209
|
-
|
|
210
|
-
expect(textOf(face.card(withoutProject)).join(' ')).toContain('__UNIT__.needProject');
|
|
211
|
-
expect(shell.readEnabled).toBe(false);
|
|
212
|
-
|
|
213
|
-
shell.resource = { phase: 'loading' };
|
|
214
|
-
expect(textOf(face.card(cardProps)).join(' ')).not.toContain('__UNIT__.needProject');
|
|
215
|
-
expect(shell.readEnabled).toBe(true);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('四态各画得出来', () => {
|
|
219
|
-
const face = clientFace();
|
|
220
|
-
|
|
221
|
-
shell.resource = { phase: 'loading' };
|
|
222
|
-
expect(textOf(face.card(cardProps)).join(' ')).toContain('__UNIT__.loading');
|
|
223
|
-
|
|
224
|
-
shell.resource = { phase: 'error', error: { code: 'internal', message: 'boom' } };
|
|
225
|
-
expect(textOf(face.card(cardProps)).join(' ')).toContain('internal: boom');
|
|
226
|
-
|
|
227
|
-
shell.resource = {
|
|
228
|
-
phase: 'ready',
|
|
229
|
-
value: {
|
|
230
|
-
ok: true,
|
|
231
|
-
path: '/real/workbench',
|
|
232
|
-
files: 2,
|
|
233
|
-
dirs: 1,
|
|
234
|
-
bytes: 2048,
|
|
235
|
-
largest: [{ name: 'big.bin', bytes: 2048, mtime: '2026-01-02T03:04:05.000Z' }],
|
|
236
|
-
},
|
|
237
|
-
};
|
|
238
|
-
const ready = textOf(face.card(cardProps)).join(' ');
|
|
239
|
-
expect(ready).toContain('/real/workbench');
|
|
240
|
-
expect(ready).toContain('big.bin');
|
|
241
|
-
expect(ready).toContain('2.0 KB');
|
|
242
|
-
expect(ready).toContain('__UNIT__.fileUnit');
|
|
243
|
-
expect(ready).toContain('__UNIT__.largest');
|
|
244
|
-
|
|
245
|
-
shell.resource = {
|
|
246
|
-
phase: 'ready',
|
|
247
|
-
value: { ok: true, path: '/real/empty', files: 0, dirs: 0, bytes: 0, largest: [] },
|
|
248
|
-
};
|
|
249
|
-
expect(textOf(face.card(cardProps)).join(' ')).toContain('__UNIT__.empty');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
it('刷新中旧数据还在(不退回加载态)', () => {
|
|
253
|
-
const face = clientFace();
|
|
254
|
-
shell.resource = {
|
|
255
|
-
phase: 'loading',
|
|
256
|
-
value: { ok: true, path: '/real/workbench', files: 1, dirs: 0, bytes: 10, largest: [] },
|
|
257
|
-
};
|
|
258
|
-
const text = textOf(face.card(cardProps)).join(' ');
|
|
259
|
-
expect(text).toContain('/real/workbench');
|
|
260
|
-
expect(text).not.toContain('__UNIT__.loading');
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('状态映射', () => {
|
|
266
|
-
it('phase 与读数一起解出那一屏(待机 / 加载 / 就绪 / 就绪但空 / 出错)', () => {
|
|
267
|
-
const at = (input: Parameters<typeof overviewStateOf>[0]): string => overviewStateOf(input).kind;
|
|
268
|
-
|
|
269
|
-
expect(at({ phase: 'idle', value: undefined, error: undefined })).toBe('idle');
|
|
270
|
-
expect(at({ phase: 'loading', value: undefined, error: undefined })).toBe('loading');
|
|
271
|
-
expect(
|
|
272
|
-
at({
|
|
273
|
-
phase: 'ready',
|
|
274
|
-
value: { ok: true, path: '/p', files: 2, dirs: 1, bytes: 10, largest: [] },
|
|
275
|
-
error: undefined,
|
|
276
|
-
}),
|
|
277
|
-
).toBe('ready');
|
|
278
|
-
expect(
|
|
279
|
-
at({
|
|
280
|
-
phase: 'ready',
|
|
281
|
-
value: { ok: true, path: '/p', files: 0, dirs: 0, bytes: 0, largest: [] },
|
|
282
|
-
error: undefined,
|
|
283
|
-
}),
|
|
284
|
-
).toBe('empty');
|
|
285
|
-
expect(at({ phase: 'error', value: undefined, error: { code: 'internal', message: 'boom' } })).toBe('error');
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
describe('格式化', () => {
|
|
290
|
-
it('字节格式化不炸也不乱', () => {
|
|
291
|
-
expect(formatBytes(0)).toBe('0 B');
|
|
292
|
-
expect(formatBytes(2048)).toBe('2.0 KB');
|
|
293
|
-
expect(formatBytes(3145728)).toBe('3.0 MB');
|
|
294
|
-
});
|
|
295
|
-
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件样式(CSS Modules)的类名表。
|
|
3
|
+
*
|
|
4
|
+
* 构建时类名会被哈希,`import styles from './X.module.css'` 拿到的是**表**而不是文本
|
|
5
|
+
* (构建让它进产物里的那份内联样式);少了这条声明,`styles.xxx` 会被当成 string 的属性,
|
|
6
|
+
* `pnpm typecheck` 直接红。
|
|
7
|
+
*
|
|
8
|
+
* 单独一个文件:`types/globals.d.ts` 里有 import,是模块,里面的 `declare module`
|
|
9
|
+
* 会变成模块增强而不是环境声明 —— 与外壳 `packages/workbench/types/assets.d.ts` 同一个理由。
|
|
10
|
+
*/
|
|
11
|
+
declare module '*.module.css' {
|
|
12
|
+
const classes: Record<string, string>;
|
|
13
|
+
export default classes;
|
|
14
|
+
}
|