sloth-d2c-mcp 1.0.4-beta70 → 1.0.4-beta71
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/cli/run.js +5 -0
- package/dist/build/component-mapping/adapter-manager.js +45 -0
- package/dist/build/component-mapping/adapters/base-adapter.js +137 -0
- package/dist/build/component-mapping/adapters/ios-adapter.js +697 -0
- package/dist/build/component-mapping/adapters/web-adapter.js +536 -0
- package/dist/build/component-mapping/index.js +32 -0
- package/dist/build/component-mapping/storage.js +142 -0
- package/dist/build/component-mapping/types.js +4 -0
- package/dist/build/config-manager/index.js +80 -0
- package/dist/build/core/prompt-builder.js +110 -0
- package/dist/build/core/sampling.js +382 -0
- package/dist/build/core/types.js +1 -0
- package/dist/build/index.js +320 -81
- package/dist/build/server.js +510 -14
- package/dist/build/utils/file-manager.js +301 -10
- package/dist/build/utils/image-matcher.js +154 -0
- package/dist/build/utils/opencv-loader.js +70 -0
- package/dist/interceptor-web/dist/build-report.json +7 -7
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web平台适配器 - 支持React/Vue/Web Components
|
|
3
|
+
*/
|
|
4
|
+
import { BasePlatformAdapter } from './base-adapter.js';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
export class WebPlatformAdapter extends BasePlatformAdapter {
|
|
7
|
+
platform = 'web';
|
|
8
|
+
async scanComponents(projectPath, options) {
|
|
9
|
+
const components = [];
|
|
10
|
+
// 扫描React组件
|
|
11
|
+
const reactComponents = await this.scanReactComponents(projectPath, options);
|
|
12
|
+
components.push(...reactComponents);
|
|
13
|
+
// 扫描Vue组件
|
|
14
|
+
// const vueComponents = await this.scanVueComponents(projectPath, options)
|
|
15
|
+
// components.push(...vueComponents)
|
|
16
|
+
// TODO: 扫描Storybook (如果存在)
|
|
17
|
+
// const storybookComponents = await this.scanStorybook(projectPath)
|
|
18
|
+
// components.push(...storybookComponents)
|
|
19
|
+
return this.deduplicateComponents(components);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 扫描React组件
|
|
23
|
+
*/
|
|
24
|
+
async scanReactComponents(projectPath, options) {
|
|
25
|
+
const components = [];
|
|
26
|
+
const files = await this.findFiles(projectPath, ['.tsx', '.jsx'], options);
|
|
27
|
+
console.log(`[scanReactComponents] 找到 ${files.length} 个 React 文件`);
|
|
28
|
+
// 只扫描路径中包含 "component" 的文件(不区分大小写)
|
|
29
|
+
const componentFiles = files.filter((file) => {
|
|
30
|
+
const normalizedPath = file.toLowerCase();
|
|
31
|
+
return normalizedPath.includes('component');
|
|
32
|
+
});
|
|
33
|
+
console.log(`[scanReactComponents] 过滤后,${componentFiles.length} 个文件在 component 目录中`);
|
|
34
|
+
for (const file of componentFiles) {
|
|
35
|
+
try {
|
|
36
|
+
const content = await this.readFile(file);
|
|
37
|
+
const extractedComponents = this.extractReactComponents(file, content, projectPath);
|
|
38
|
+
console.log(`[scanReactComponents] 文件 ${file} 提取到 ${extractedComponents.length} 个组件`);
|
|
39
|
+
components.push(...extractedComponents);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.warn(`解析React组件失败 ${file}:`, error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return components;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从文件内容中提取React组件
|
|
49
|
+
*/
|
|
50
|
+
extractReactComponents(filePath, content, projectPath) {
|
|
51
|
+
// 将绝对路径转换为相对路径
|
|
52
|
+
const relativePath = path.relative(projectPath, filePath).replace(/\\/g, '/');
|
|
53
|
+
const components = [];
|
|
54
|
+
// 匹配函数组件:
|
|
55
|
+
// 1. export function ComponentName({...}: Type) {
|
|
56
|
+
// 2. export function ComponentName(...) {
|
|
57
|
+
// 3. export const ComponentName = ({...}: Type) => {
|
|
58
|
+
// 4. export const ComponentName = (...) => {
|
|
59
|
+
const functionComponentRegex = /export\s+(?:const|function)\s+([A-Z][a-zA-Z0-9]*)\s*(?:=\s*)?(?:\([^)]*\)|{[\s\S]*?})\s*(?::\s*[A-Z][a-zA-Z0-9]*)?\s*(?:=>|{)/g;
|
|
60
|
+
// 匹配类组件: export class ComponentName extends
|
|
61
|
+
const classComponentRegex = /export\s+class\s+([A-Z][a-zA-Z0-9]*)\s+extends/g;
|
|
62
|
+
// 匹配 default export: export default function ComponentName 或 export default ComponentName
|
|
63
|
+
const defaultExportRegex = /export\s+default\s+(?:function\s+)?([A-Z][a-zA-Z0-9]*)/g;
|
|
64
|
+
let match;
|
|
65
|
+
// 提取函数组件(具名导出)
|
|
66
|
+
while ((match = functionComponentRegex.exec(content)) !== null) {
|
|
67
|
+
const componentName = match[1];
|
|
68
|
+
const props = this.extractReactProps(content, componentName);
|
|
69
|
+
const metadata = this.extractReactMetadata(content, componentName);
|
|
70
|
+
components.push({
|
|
71
|
+
id: this.generateComponentId(filePath, componentName),
|
|
72
|
+
name: componentName,
|
|
73
|
+
path: relativePath,
|
|
74
|
+
platform: 'web',
|
|
75
|
+
type: this.inferComponentType(componentName),
|
|
76
|
+
props,
|
|
77
|
+
metadata: {
|
|
78
|
+
...metadata,
|
|
79
|
+
framework: 'React',
|
|
80
|
+
importType: 'named', // 具名导出
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// 提取类组件(具名导出)
|
|
85
|
+
while ((match = classComponentRegex.exec(content)) !== null) {
|
|
86
|
+
const componentName = match[1];
|
|
87
|
+
const props = this.extractReactProps(content, componentName);
|
|
88
|
+
const metadata = this.extractReactMetadata(content, componentName);
|
|
89
|
+
components.push({
|
|
90
|
+
id: this.generateComponentId(filePath, componentName),
|
|
91
|
+
name: componentName,
|
|
92
|
+
path: relativePath,
|
|
93
|
+
platform: 'web',
|
|
94
|
+
type: this.inferComponentType(componentName),
|
|
95
|
+
props,
|
|
96
|
+
metadata: {
|
|
97
|
+
...metadata,
|
|
98
|
+
framework: 'React',
|
|
99
|
+
importType: 'named', // 具名导出
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// 提取 default export 组件(默认导出)
|
|
104
|
+
while ((match = defaultExportRegex.exec(content)) !== null) {
|
|
105
|
+
const componentName = match[1];
|
|
106
|
+
// 检查是否已经添加过(避免重复)
|
|
107
|
+
const existingComponent = components.find((c) => c.name === componentName);
|
|
108
|
+
if (existingComponent) {
|
|
109
|
+
// 如果已经存在,更新为默认导出
|
|
110
|
+
existingComponent.metadata.importType = 'default';
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const props = this.extractReactProps(content, componentName);
|
|
114
|
+
const metadata = this.extractReactMetadata(content, componentName);
|
|
115
|
+
components.push({
|
|
116
|
+
id: this.generateComponentId(filePath, componentName),
|
|
117
|
+
name: componentName,
|
|
118
|
+
path: relativePath,
|
|
119
|
+
platform: 'web',
|
|
120
|
+
type: this.inferComponentType(componentName),
|
|
121
|
+
props,
|
|
122
|
+
metadata: {
|
|
123
|
+
...metadata,
|
|
124
|
+
framework: 'React',
|
|
125
|
+
importType: 'default', // 默认导出
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log(`[extractReactComponents] 从 ${filePath} 提取到 ${components.length} 个组件:`, components.map(c => `${c.name} (${c.metadata.importType || 'named'})`));
|
|
131
|
+
return components;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 提取React组件的Props
|
|
135
|
+
*/
|
|
136
|
+
extractReactProps(content, componentName) {
|
|
137
|
+
const props = [];
|
|
138
|
+
// 尝试匹配TypeScript接口定义
|
|
139
|
+
const interfaceRegex = new RegExp(`interface\\s+${componentName}Props\\s*{([^}]+)}`, 's');
|
|
140
|
+
const interfaceMatch = content.match(interfaceRegex);
|
|
141
|
+
if (interfaceMatch) {
|
|
142
|
+
const propsContent = interfaceMatch[1];
|
|
143
|
+
const propLines = propsContent.split('\n').filter((line) => line.trim());
|
|
144
|
+
for (const line of propLines) {
|
|
145
|
+
const propMatch = line.match(/(\w+)(\?)?:\s*([^;]+)/);
|
|
146
|
+
if (propMatch) {
|
|
147
|
+
props.push({
|
|
148
|
+
name: propMatch[1],
|
|
149
|
+
type: propMatch[3].trim(),
|
|
150
|
+
required: !propMatch[2], // 没有?表示必需
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 尝试匹配解构参数
|
|
156
|
+
// 匹配: ComponentName = ({ prop1, prop2 }) => 或 ComponentName: ({ prop1, prop2 }) =>
|
|
157
|
+
// 支持类型注解: ComponentName = ({ prop1 }: { prop1?: string }) =>
|
|
158
|
+
const destructureRegex = new RegExp(`${componentName}\\s*[=:]\\s*\\(\\s*\\{([^}]+)\\}\\s*.*?\\s*\\)\\s*=>`);
|
|
159
|
+
const destructureMatch = content.match(destructureRegex);
|
|
160
|
+
if (destructureMatch && props.length === 0) {
|
|
161
|
+
const propsStr = destructureMatch[1];
|
|
162
|
+
if (propsStr) {
|
|
163
|
+
// 处理解构参数,可能包含类型注解,如: { className }: { className?: string }
|
|
164
|
+
// 只取第一个花括号内的内容(参数名部分),去掉类型注解
|
|
165
|
+
const paramPart = propsStr.split('}:')[0].trim();
|
|
166
|
+
const propNames = paramPart.split(',').map((p) => p.trim().split(':')[0].trim());
|
|
167
|
+
propNames.forEach((name) => {
|
|
168
|
+
if (name && !name.startsWith('...')) {
|
|
169
|
+
props.push({
|
|
170
|
+
name,
|
|
171
|
+
type: 'any',
|
|
172
|
+
required: false,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return props;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 提取React组件元数据
|
|
182
|
+
*/
|
|
183
|
+
extractReactMetadata(content, componentName) {
|
|
184
|
+
console.log(`\n[extractReactMetadata] ========== 开始提取组件 ${componentName} 的元数据 ==========`);
|
|
185
|
+
const metadata = {
|
|
186
|
+
tags: [],
|
|
187
|
+
dependencies: [],
|
|
188
|
+
styleProps: {},
|
|
189
|
+
};
|
|
190
|
+
// 提取JSDoc注释 - 只提取 /** */ 部分,不包含后面的代码
|
|
191
|
+
// 使用更精确的正则:匹配 /** 到 */ 的内容,且后面跟着组件定义
|
|
192
|
+
const jsDocPatterns = [
|
|
193
|
+
// 匹配: /** ... */ \n export function ComponentName
|
|
194
|
+
new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)\\s*(?:export\\s+)?(?:const|function)\\s+${componentName}\\b`),
|
|
195
|
+
// 匹配: /** ... */ \n export default function ComponentName
|
|
196
|
+
new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)\\s*export\\s+default\\s+(?:function\\s+)?${componentName}\\b`),
|
|
197
|
+
];
|
|
198
|
+
let jsDocContent = null;
|
|
199
|
+
for (let i = 0; i < jsDocPatterns.length; i++) {
|
|
200
|
+
const pattern = jsDocPatterns[i];
|
|
201
|
+
const match = content.match(pattern);
|
|
202
|
+
if (match && match[1]) {
|
|
203
|
+
jsDocContent = match[1];
|
|
204
|
+
console.log(`[extractReactMetadata] 使用模式 ${i + 1} 匹配到 JSDoc (前100字符):`, jsDocContent.substring(0, 100));
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (jsDocContent) {
|
|
209
|
+
console.log(`[extractReactMetadata] 完整 JSDoc 内容:\n${jsDocContent}`);
|
|
210
|
+
// 提取完整的描述(支持多行)
|
|
211
|
+
const descriptionLines = [];
|
|
212
|
+
const lines = jsDocContent.split('\n');
|
|
213
|
+
console.log(`[extractReactMetadata] JSDoc 共 ${lines.length} 行`);
|
|
214
|
+
for (let i = 0; i < lines.length; i++) {
|
|
215
|
+
const line = lines[i];
|
|
216
|
+
const trimmedLine = line.trim();
|
|
217
|
+
console.log(`[extractReactMetadata] 处理第 ${i + 1} 行: "${line}"`);
|
|
218
|
+
// 跳过 /** 和 */ 行
|
|
219
|
+
if (trimmedLine === '/**' || trimmedLine === '*/') {
|
|
220
|
+
console.log(`[extractReactMetadata] -> 跳过开始/结束标记`);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// 跳过 @ 标签行
|
|
224
|
+
if (trimmedLine.startsWith('* @') || trimmedLine.startsWith('@')) {
|
|
225
|
+
console.log(`[extractReactMetadata] -> 遇到 @ 标签,停止提取描述`);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
// 提取 signature(格式: * signature: hash)
|
|
229
|
+
const signatureMatch = line.match(/^\s*\*\s*signature:\s*(.+)$/i);
|
|
230
|
+
if (signatureMatch) {
|
|
231
|
+
metadata.signature = signatureMatch[1].trim();
|
|
232
|
+
console.log(`[extractReactMetadata] -> ✅ 提取到 signature: ${metadata.signature}`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// 提取描述行(以 * 开头,后面跟内容)
|
|
236
|
+
if (trimmedLine.startsWith('*')) {
|
|
237
|
+
const descLine = line.replace(/^\s*\*\s*/, '').trim();
|
|
238
|
+
if (descLine && !descLine.startsWith('@') && !descLine.toLowerCase().startsWith('signature:')) {
|
|
239
|
+
console.log(`[extractReactMetadata] -> 提取描述内容: "${descLine}"`);
|
|
240
|
+
descriptionLines.push(descLine);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (descriptionLines.length > 0) {
|
|
245
|
+
metadata.description = descriptionLines.join(' ').trim();
|
|
246
|
+
console.log(`[extractReactMetadata] ✅ 最终 description: "${metadata.description}"`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log(`[extractReactMetadata] ⚠️ 未提取到 description`);
|
|
250
|
+
}
|
|
251
|
+
// 提取@tags
|
|
252
|
+
const tagMatches = jsDocContent.matchAll(/@(\w+)(?:\s+([^\n]*))?/g);
|
|
253
|
+
for (const match of tagMatches) {
|
|
254
|
+
metadata.tags.push(match[1]);
|
|
255
|
+
console.log(`[extractReactMetadata] 提取到 tag: @${match[1]}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
console.log(`[extractReactMetadata] ⚠️ 未找到 JSDoc 注释`);
|
|
260
|
+
}
|
|
261
|
+
// 如果没有找到 JSDoc,尝试提取单行注释
|
|
262
|
+
if (!metadata.description) {
|
|
263
|
+
console.log(`[extractReactMetadata] 尝试提取单行注释...`);
|
|
264
|
+
const singleLineCommentRegex = new RegExp(`//\\s*([^\\n]+)\\s*\\n\\s*(?:export\\s+)?(?:const|function)\\s+${componentName}`, 's');
|
|
265
|
+
const singleLineMatch = content.match(singleLineCommentRegex);
|
|
266
|
+
if (singleLineMatch) {
|
|
267
|
+
metadata.description = singleLineMatch[1].trim();
|
|
268
|
+
console.log(`[extractReactMetadata] ✅ 从单行注释提取到 description: "${metadata.description}"`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// 提取import依赖
|
|
272
|
+
const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g;
|
|
273
|
+
let importMatch;
|
|
274
|
+
while ((importMatch = importRegex.exec(content)) !== null) {
|
|
275
|
+
metadata.dependencies.push(importMatch[1]);
|
|
276
|
+
}
|
|
277
|
+
// 提取样式相关的props
|
|
278
|
+
metadata.styleProps = this.extractStyleProps(content);
|
|
279
|
+
// 最终总结
|
|
280
|
+
console.log(`[extractReactMetadata] ========== 提取完成 ${componentName} ==========`);
|
|
281
|
+
console.log(`[extractReactMetadata] 结果摘要:`);
|
|
282
|
+
console.log(` - description: ${metadata.description || '(无)'}`);
|
|
283
|
+
console.log(` - signature: ${metadata.signature ? metadata.signature.substring(0, 16) + '...' : '(无)'}`);
|
|
284
|
+
console.log(` - tags: [${metadata.tags.join(', ')}]`);
|
|
285
|
+
console.log(` - dependencies: ${metadata.dependencies.length} 个`);
|
|
286
|
+
console.log(`[extractReactMetadata] =============================================\n`);
|
|
287
|
+
return metadata;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 提取样式属性
|
|
291
|
+
*/
|
|
292
|
+
extractStyleProps(content) {
|
|
293
|
+
const styleProps = {};
|
|
294
|
+
// 查找常见的样式属性
|
|
295
|
+
const stylePatterns = [
|
|
296
|
+
{ key: 'width', pattern: /width[:\s]*['"]?(\d+(?:px|%|rem|em)?)/i },
|
|
297
|
+
{ key: 'height', pattern: /height[:\s]*['"]?(\d+(?:px|%|rem|em)?)/i },
|
|
298
|
+
{ key: 'padding', pattern: /padding[:\s]*['"]?([^'";\n]+)/i },
|
|
299
|
+
{ key: 'margin', pattern: /margin[:\s]*['"]?([^'";\n]+)/i },
|
|
300
|
+
{ key: 'backgroundColor', pattern: /background-?color[:\s]*['"]?([^'";\n]+)/i },
|
|
301
|
+
{ key: 'borderRadius', pattern: /border-?radius[:\s]*['"]?([^'";\n]+)/i },
|
|
302
|
+
];
|
|
303
|
+
for (const { key, pattern } of stylePatterns) {
|
|
304
|
+
const match = content.match(pattern);
|
|
305
|
+
if (match) {
|
|
306
|
+
styleProps[key] = match[1].trim();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return styleProps;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* 扫描Vue组件
|
|
313
|
+
*/
|
|
314
|
+
async scanVueComponents(projectPath, options) {
|
|
315
|
+
const components = [];
|
|
316
|
+
const files = await this.findFiles(projectPath, ['.vue'], options);
|
|
317
|
+
// 只扫描路径中包含 "component" 的文件(不区分大小写)
|
|
318
|
+
const componentFiles = files.filter((file) => {
|
|
319
|
+
const normalizedPath = file.toLowerCase();
|
|
320
|
+
return normalizedPath.includes('component');
|
|
321
|
+
});
|
|
322
|
+
console.log(`[scanVueComponents] 过滤后,${componentFiles.length} 个文件在 component 目录中`);
|
|
323
|
+
for (const file of componentFiles) {
|
|
324
|
+
try {
|
|
325
|
+
const content = await this.readFile(file);
|
|
326
|
+
const component = this.extractVueComponent(file, content, projectPath);
|
|
327
|
+
if (component) {
|
|
328
|
+
components.push(component);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.warn(`解析Vue组件失败 ${file}:`, error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return components;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 从.vue文件提取组件信息
|
|
339
|
+
*/
|
|
340
|
+
extractVueComponent(filePath, content, projectPath) {
|
|
341
|
+
const componentName = path.basename(filePath, '.vue');
|
|
342
|
+
// 将绝对路径转换为相对路径
|
|
343
|
+
const relativePath = path.relative(projectPath, filePath).replace(/\\/g, '/');
|
|
344
|
+
// 提取props定义
|
|
345
|
+
const props = this.extractVueProps(content);
|
|
346
|
+
// 提取元数据
|
|
347
|
+
const metadata = {
|
|
348
|
+
tags: [],
|
|
349
|
+
dependencies: [],
|
|
350
|
+
styleProps: this.extractStyleProps(content),
|
|
351
|
+
framework: 'Vue',
|
|
352
|
+
};
|
|
353
|
+
// 提取import和注释
|
|
354
|
+
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
355
|
+
if (scriptMatch) {
|
|
356
|
+
const scriptContent = scriptMatch[1];
|
|
357
|
+
// 提取 JSDoc 注释
|
|
358
|
+
const jsDocRegex = /\/\*\*([^*]|\*(?!\/))*\*\//s;
|
|
359
|
+
const jsDocMatch = scriptContent.match(jsDocRegex);
|
|
360
|
+
if (jsDocMatch) {
|
|
361
|
+
const jsDoc = jsDocMatch[0];
|
|
362
|
+
const descriptionLines = [];
|
|
363
|
+
const lines = jsDoc.split('\n');
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
if (line.trim() === '/**' || line.trim() === '*/')
|
|
366
|
+
continue;
|
|
367
|
+
if (line.trim().startsWith('* @'))
|
|
368
|
+
break;
|
|
369
|
+
const descLine = line.replace(/^\s*\*\s*/, '').trim();
|
|
370
|
+
if (descLine && !descLine.startsWith('@')) {
|
|
371
|
+
descriptionLines.push(descLine);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (descriptionLines.length > 0) {
|
|
375
|
+
metadata.description = descriptionLines.join(' ').trim();
|
|
376
|
+
}
|
|
377
|
+
// 提取@tags
|
|
378
|
+
const tagMatches = jsDoc.matchAll(/@(\w+)(?:\s+([^\n]*))?/g);
|
|
379
|
+
for (const match of tagMatches) {
|
|
380
|
+
metadata.tags.push(match[1]);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// 如果没有找到 JSDoc,尝试提取单行注释
|
|
384
|
+
if (!metadata.description) {
|
|
385
|
+
const singleLineCommentRegex = /\/\/\s*([^\n]+)/;
|
|
386
|
+
const singleLineMatch = scriptContent.match(singleLineCommentRegex);
|
|
387
|
+
if (singleLineMatch) {
|
|
388
|
+
metadata.description = singleLineMatch[1].trim();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// 提取import
|
|
392
|
+
const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g;
|
|
393
|
+
let importMatch;
|
|
394
|
+
while ((importMatch = importRegex.exec(scriptContent)) !== null) {
|
|
395
|
+
metadata.dependencies.push(importMatch[1]);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
id: this.generateComponentId(filePath),
|
|
400
|
+
name: componentName,
|
|
401
|
+
path: relativePath,
|
|
402
|
+
platform: 'web',
|
|
403
|
+
type: this.inferComponentType(componentName),
|
|
404
|
+
props,
|
|
405
|
+
metadata,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* 提取Vue组件的Props
|
|
410
|
+
*/
|
|
411
|
+
extractVueProps(content) {
|
|
412
|
+
const props = [];
|
|
413
|
+
// 匹配defineProps (Vue 3 Composition API)
|
|
414
|
+
const definePropsRegex = /defineProps<\{([^}]+)\}>/s;
|
|
415
|
+
const definePropsMatch = content.match(definePropsRegex);
|
|
416
|
+
if (definePropsMatch) {
|
|
417
|
+
const propsContent = definePropsMatch[1];
|
|
418
|
+
const propLines = propsContent.split('\n').filter((line) => line.trim());
|
|
419
|
+
for (const line of propLines) {
|
|
420
|
+
const propMatch = line.match(/(\w+)(\?)?:\s*([^;,\n]+)/);
|
|
421
|
+
if (propMatch) {
|
|
422
|
+
props.push({
|
|
423
|
+
name: propMatch[1],
|
|
424
|
+
type: propMatch[3].trim(),
|
|
425
|
+
required: !propMatch[2],
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// 匹配props对象 (Vue 2 Options API)
|
|
431
|
+
const propsObjectRegex = /props:\s*\{([^}]+)\}/s;
|
|
432
|
+
const propsObjectMatch = content.match(propsObjectRegex);
|
|
433
|
+
if (propsObjectMatch && props.length === 0) {
|
|
434
|
+
const propsContent = propsObjectMatch[1];
|
|
435
|
+
const propMatches = propsContent.matchAll(/(\w+):\s*\{([^}]+)\}/g);
|
|
436
|
+
for (const match of propMatches) {
|
|
437
|
+
const propName = match[1];
|
|
438
|
+
const propConfig = match[2];
|
|
439
|
+
const typeMatch = propConfig.match(/type:\s*(\w+)/);
|
|
440
|
+
const requiredMatch = propConfig.match(/required:\s*(true|false)/);
|
|
441
|
+
props.push({
|
|
442
|
+
name: propName,
|
|
443
|
+
type: typeMatch ? typeMatch[1] : 'any',
|
|
444
|
+
required: requiredMatch ? requiredMatch[1] === 'true' : false,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return props;
|
|
449
|
+
}
|
|
450
|
+
async parseComponentMetadata(filePath) {
|
|
451
|
+
const content = await this.readFile(filePath);
|
|
452
|
+
if (filePath.endsWith('.vue')) {
|
|
453
|
+
const metadata = {
|
|
454
|
+
tags: [],
|
|
455
|
+
dependencies: [],
|
|
456
|
+
styleProps: this.extractStyleProps(content),
|
|
457
|
+
framework: 'Vue',
|
|
458
|
+
};
|
|
459
|
+
// 提取import和注释
|
|
460
|
+
const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
461
|
+
if (scriptMatch) {
|
|
462
|
+
const scriptContent = scriptMatch[1];
|
|
463
|
+
// 提取 JSDoc 注释
|
|
464
|
+
const jsDocRegex = /\/\*\*([^*]|\*(?!\/))*\*\//s;
|
|
465
|
+
const jsDocMatch = scriptContent.match(jsDocRegex);
|
|
466
|
+
if (jsDocMatch) {
|
|
467
|
+
const jsDoc = jsDocMatch[0];
|
|
468
|
+
const descriptionLines = [];
|
|
469
|
+
const lines = jsDoc.split('\n');
|
|
470
|
+
for (const line of lines) {
|
|
471
|
+
if (line.trim() === '/**' || line.trim() === '*/')
|
|
472
|
+
continue;
|
|
473
|
+
if (line.trim().startsWith('* @'))
|
|
474
|
+
break;
|
|
475
|
+
const descLine = line.replace(/^\s*\*\s*/, '').trim();
|
|
476
|
+
if (descLine && !descLine.startsWith('@')) {
|
|
477
|
+
descriptionLines.push(descLine);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (descriptionLines.length > 0) {
|
|
481
|
+
metadata.description = descriptionLines.join(' ').trim();
|
|
482
|
+
}
|
|
483
|
+
// 提取@tags
|
|
484
|
+
const tagMatches = jsDoc.matchAll(/@(\w+)(?:\s+([^\n]*))?/g);
|
|
485
|
+
for (const match of tagMatches) {
|
|
486
|
+
metadata.tags.push(match[1]);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// 如果没有找到 JSDoc,尝试提取单行注释
|
|
490
|
+
if (!metadata.description) {
|
|
491
|
+
const singleLineCommentRegex = /\/\/\s*([^\n]+)/;
|
|
492
|
+
const singleLineMatch = scriptContent.match(singleLineCommentRegex);
|
|
493
|
+
if (singleLineMatch) {
|
|
494
|
+
metadata.description = singleLineMatch[1].trim();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// 提取import
|
|
498
|
+
const importRegex = /import\s+.*\s+from\s+['"]([^'"]+)['"]/g;
|
|
499
|
+
let importMatch;
|
|
500
|
+
while ((importMatch = importRegex.exec(scriptContent)) !== null) {
|
|
501
|
+
metadata.dependencies.push(importMatch[1]);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return metadata;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
// React/JSX
|
|
508
|
+
const componentName = filePath.match(/([A-Z][a-zA-Z0-9]*)\.(tsx?|jsx?)$/)?.[1] || '';
|
|
509
|
+
return this.extractReactMetadata(content, componentName);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
generateImportCode(component, targetPath) {
|
|
513
|
+
if (!targetPath) {
|
|
514
|
+
return `import { ${component.name} } from '${component.path}'`;
|
|
515
|
+
}
|
|
516
|
+
const relativePath = this.getRelativePath(targetPath, component.path);
|
|
517
|
+
return `import { ${component.name} } from '${relativePath}'`;
|
|
518
|
+
}
|
|
519
|
+
generateUsageCode(component, props, styles) {
|
|
520
|
+
const propsStr = Object.entries(props)
|
|
521
|
+
.map(([key, value]) => {
|
|
522
|
+
if (typeof value === 'string') {
|
|
523
|
+
return `${key}="${value}"`;
|
|
524
|
+
}
|
|
525
|
+
else if (typeof value === 'boolean') {
|
|
526
|
+
return value ? key : `${key}={false}`;
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
return `${key}={${JSON.stringify(value)}}`;
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
.join(' ');
|
|
533
|
+
const styleStr = Object.keys(styles).length > 0 ? ` style={${JSON.stringify(styles)}}` : '';
|
|
534
|
+
return `<${component.name}${propsStr ? ' ' + propsStr : ''}${styleStr} />`;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组件映射系统 - 主入口
|
|
3
|
+
*/
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export * from './adapters/base-adapter.js';
|
|
6
|
+
export * from './adapters/web-adapter.js';
|
|
7
|
+
export * from './adapters/ios-adapter.js';
|
|
8
|
+
export * from './storage.js';
|
|
9
|
+
export * from './adapter-manager.js';
|
|
10
|
+
import { LocalMappingStorage } from './storage.js';
|
|
11
|
+
import { AdapterManager } from './adapter-manager.js';
|
|
12
|
+
/**
|
|
13
|
+
* 组件映射系统管理器
|
|
14
|
+
*/
|
|
15
|
+
export class ComponentMappingSystem {
|
|
16
|
+
adapterManager;
|
|
17
|
+
storage;
|
|
18
|
+
constructor(projectPath = '.') {
|
|
19
|
+
this.storage = new LocalMappingStorage(projectPath);
|
|
20
|
+
this.adapterManager = new AdapterManager();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 初始化系统
|
|
24
|
+
*/
|
|
25
|
+
async initialize() { }
|
|
26
|
+
/**
|
|
27
|
+
* 获取存储实例
|
|
28
|
+
*/
|
|
29
|
+
getStorage() {
|
|
30
|
+
return this.storage;
|
|
31
|
+
}
|
|
32
|
+
}
|