@dsh-workbench/module-tools 0.3.3 → 0.3.5

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.
@@ -0,0 +1,119 @@
1
+ // kit 成员 → 类型文件:外壳那两个包的类型树(`@dsh-workbench/shared` 的 `dist` 与外壳自己的 `dist/types`)。
2
+ //
3
+ // 为什么要扫:kit 的成员是**运行期**从外壳产物 `client.js` 里跑出来的(见 `checks/kit.mjs`),
4
+ // 产物里没有"这个成员来自哪个模块";而类型树里每个成员的声明都躺在某份 `.d.ts` 里 ——
5
+ // 扫一遍就能把"名字"落到"文件",于是 agent「想问哪个直接去看那份文件」有确切路径。
6
+ //
7
+ // 为什么是名字扫描而不是真正的类型解析:这些 `.d.ts` 是 tsc 生成的、声明形状规整
8
+ // (`export declare const X` / `export interface X` / `export { X } from '…'`),而 kit 的成员
9
+ // 本来就是运行期对象上的键名。扫描不引 TypeScript 依赖、贵在稳;代价是"同名声明出现在多处"时
10
+ // 要挑一个(挑法见 `preferred`),以及极少数成员指到的是 barrel 而不是最终实现文件。
11
+ // 实测本机 190 个 kit 成员 190 个都落得到文件(见 `.scratch/dsh-primitives-face/verify.md` §2)。
12
+
13
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
14
+ import { createRequire } from 'node:module';
15
+ import { dirname, join } from 'node:path';
16
+
17
+ const PATTERNS = [
18
+ /^export declare (?:const|function|class) (\w+)/gm,
19
+ /^export (?:interface|type) (\w+)/gm,
20
+ /^export \{([^}]*)\}/gm,
21
+ /^declare (?:const|function|class) (\w+)/gm,
22
+ /^(?:interface|type) (\w+)/gm,
23
+ ];
24
+
25
+ function walk(dir, acc = []) {
26
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
27
+ const full = join(dir, entry.name);
28
+ if (entry.isDirectory()) walk(full, acc);
29
+ else if (full.endsWith('.d.ts')) acc.push(full);
30
+ }
31
+ return acc;
32
+ }
33
+
34
+ function namesIn(clause) {
35
+ if (!clause.includes(',') && !clause.includes(' ')) return [clause];
36
+ return clause
37
+ .split(',')
38
+ .map((raw) => raw.trim().split(/\s+as\s+/).pop().trim())
39
+ .filter((name) => /^[A-Za-z_$][\w$]*$/.test(name));
40
+ }
41
+
42
+ /** 外壳那两个包的类型树(`shared` 没装就只给外壳那棵)。 */
43
+ export function shellTypeRoots(shellClientPath) {
44
+ const shellRoot = dirname(shellClientPath);
45
+ const roots = {};
46
+ const shellTypes = join(shellRoot, 'dist', 'types');
47
+ if (existsSync(shellTypes)) roots.shell = shellTypes;
48
+ // 打印用"从 node_modules 看"的那条路径(pnpm 的真身在 `.pnpm/` 哈希目录里);读文件两条都行。
49
+ const viaNodeModules = join(shellRoot, 'node_modules', '@dsh-workbench', 'shared', 'dist');
50
+ try {
51
+ // 解析 "." 那个入口(= `dist/index.js`)再取 dirname —— shared 的 `exports` 里**没有**
52
+ // `./package.json` 这一条,直接 resolve 它的 package.json 会 ERR_PACKAGE_PATH_NOT_EXPORTED。
53
+ const sharedEntry = createRequire(join(shellRoot, 'package.json')).resolve('@dsh-workbench/shared');
54
+ const sharedTypes = dirname(sharedEntry);
55
+ if (existsSync(sharedTypes)) roots.shared = existsSync(viaNodeModules) ? viaNodeModules : sharedTypes;
56
+ } catch {
57
+ // 没装 shared:只有外壳自己那棵,仍然能落一部分成员
58
+ }
59
+ return { shellRoot, roots };
60
+ }
61
+
62
+ /**
63
+ * 同名多处时挑哪个:组件 > hooks > 库 > 其他 > **套件内部(`app/` / `modules/`)垫底**,同档挑路径短的。
64
+ *
65
+ * 垫底那一档是 2026-10-20 补的(ADR-0067 的 kit JSDoc 判据撞出来的):kit 的 `readText`(共享包
66
+ * `config/storage.ts`)与外壳内部 `modules/panels/model/diy-report.ts` 里的同名函数撞了,而 `modules/`
67
+ * 那条路径在 `node_modules` 那份类型树旁边**更短**,于是 `--file readText` 一直指到别人的实现上。
68
+ * kit 的成员只从 `client.tsx` 那几处 spread 来(共享包 + 外壳 `shared/**`),套件内部不会是它 ——
69
+ * 所以垫底只影响撞名时的取舍,掉不到"本来该选的那份"。
70
+ */
71
+ function preferred(candidates) {
72
+ const rank = (file) =>
73
+ file.includes('/components/')
74
+ ? 0
75
+ : file.includes('/hooks/')
76
+ ? 1
77
+ : file.includes('/lib/')
78
+ ? 2
79
+ : /\/(app|modules)\//.test(file)
80
+ ? 9
81
+ : 3;
82
+ return [...candidates].sort((a, b) => rank(a.file) - rank(b.file) || a.file.length - b.file.length)[0];
83
+ }
84
+
85
+ /**
86
+ * 把 kit 的成员名落到类型文件。
87
+ * 返回 `{ roots, files: Map<名字, 绝对路径>, missing: 名字[] }` —— `roots` 是两棵类型树的绝对根。
88
+ */
89
+ export function memberFilesOf(shellClientPath, names) {
90
+ const { roots } = shellTypeRoots(shellClientPath);
91
+ const index = new Map();
92
+ for (const root of Object.values(roots)) {
93
+ for (const file of walk(root)) {
94
+ let text;
95
+ try {
96
+ text = readFileSync(file, 'utf8');
97
+ } catch {
98
+ continue;
99
+ }
100
+ for (const pattern of PATTERNS) {
101
+ for (const match of text.matchAll(pattern)) {
102
+ for (const name of namesIn(match[1])) {
103
+ if (!index.has(name)) index.set(name, []);
104
+ const found = index.get(name);
105
+ if (!found.some((one) => one.file === file)) found.push({ file });
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ const files = new Map();
112
+ const missing = [];
113
+ for (const name of names) {
114
+ const candidates = index.get(name);
115
+ if (candidates === undefined || candidates.length === 0) missing.push(name);
116
+ else files.set(name, preferred(candidates).file);
117
+ }
118
+ return { roots, files, missing };
119
+ }
@@ -23,7 +23,7 @@
23
23
  "client": {
24
24
  "platform": "web",
25
25
  "inject": ["@dsh-workbench/core"],
26
- "external": ["@dsh-workbench/core/client"]
26
+ "external": ["@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives"]
27
27
  },
28
28
  "workbench": {
29
29
  "apiVersion": 1,
@@ -37,6 +37,7 @@
37
37
  "peerDependencies": { "@dsh-workbench/core": "__SHELL_RANGE__", "react": "*" },
38
38
  "peerDependenciesMeta": { "react": { "optional": true } },
39
39
  "devDependencies": {
40
+ "@deepseek-ai/dsh-client-ui-primitives": "__DSH_UI_VERSION__",
40
41
  "@dsh-workbench/core": "__SHELL_RANGE__",
41
42
  "@dsh-workbench/module-tools": "__SHELL_RANGE__",
42
43
  "@types/node": "^24.19.0",
@@ -24,7 +24,7 @@
24
24
  "client": {
25
25
  "platform": "web",
26
26
  "inject": ["@dsh-workbench/core"],
27
- "external": ["@dsh-workbench/core/client"]
27
+ "external": ["@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives"]
28
28
  },
29
29
  "workbench": {
30
30
  "apiVersion": 1,
@@ -38,6 +38,7 @@
38
38
  "peerDependencies": { "@dsh-workbench/core": "__SHELL_RANGE__", "react": "*" },
39
39
  "peerDependenciesMeta": { "react": { "optional": true } },
40
40
  "devDependencies": {
41
+ "@deepseek-ai/dsh-client-ui-primitives": "__DSH_UI_VERSION__",
41
42
  "@dsh-workbench/core": "__SHELL_RANGE__",
42
43
  "@dsh-workbench/module-tools": "__SHELL_RANGE__",
43
44
  "@types/node": "^24.19.0",