metadatafy 1.0.1 → 1.0.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/dist/chunk-BT3J264A.cjs +2 -0
- package/dist/chunk-WUEHYY36.js +2 -0
- package/dist/cli.cjs +8 -1452
- package/dist/cli.js +8 -1424
- package/dist/index.cjs +1 -120
- package/dist/index.js +1 -3
- package/dist/next.cjs +3 -111
- package/dist/next.js +3 -86
- package/dist/vite.cjs +3 -91
- package/dist/vite.js +3 -66
- package/package.json +1 -1
- package/dist/chunk-ECKCIPM5.js +0 -1329
- package/dist/chunk-ECKCIPM5.js.map +0 -1
- package/dist/chunk-EDHWKZRG.cjs +0 -1384
- package/dist/chunk-EDHWKZRG.cjs.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/next.cjs.map +0 -1
- package/dist/next.js.map +0 -1
- package/dist/vite.cjs.map +0 -1
- package/dist/vite.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,1255 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import * as path2 from 'path';
|
|
4
|
-
import * as fs4 from 'fs/promises';
|
|
5
|
-
import { glob } from 'glob';
|
|
6
|
-
import ts3 from 'typescript';
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as crypto from 'crypto';
|
|
9
|
-
|
|
10
|
-
var TypeScriptParser = class {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.compilerOptions = {
|
|
13
|
-
target: ts3.ScriptTarget.ESNext,
|
|
14
|
-
module: ts3.ModuleKind.ESNext,
|
|
15
|
-
jsx: ts3.JsxEmit.React,
|
|
16
|
-
esModuleInterop: true,
|
|
17
|
-
allowSyntheticDefaultImports: true,
|
|
18
|
-
strict: false
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* 소스 코드를 AST로 파싱
|
|
23
|
-
*/
|
|
24
|
-
parse(content, filePath) {
|
|
25
|
-
return ts3.createSourceFile(
|
|
26
|
-
filePath,
|
|
27
|
-
content,
|
|
28
|
-
this.compilerOptions.target,
|
|
29
|
-
true,
|
|
30
|
-
this.getScriptKind(filePath)
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* 파일 확장자에 따른 ScriptKind 결정
|
|
35
|
-
*/
|
|
36
|
-
getScriptKind(filePath) {
|
|
37
|
-
const ext = filePath.toLowerCase();
|
|
38
|
-
if (ext.endsWith(".tsx")) return ts3.ScriptKind.TSX;
|
|
39
|
-
if (ext.endsWith(".ts")) return ts3.ScriptKind.TS;
|
|
40
|
-
if (ext.endsWith(".jsx")) return ts3.ScriptKind.JSX;
|
|
41
|
-
if (ext.endsWith(".js")) return ts3.ScriptKind.JS;
|
|
42
|
-
return ts3.ScriptKind.Unknown;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* AST 노드 순회
|
|
46
|
-
*/
|
|
47
|
-
traverse(node, visitor) {
|
|
48
|
-
const shouldContinue = visitor(node);
|
|
49
|
-
if (shouldContinue === false) return;
|
|
50
|
-
ts3.forEachChild(node, (child) => this.traverse(child, visitor));
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* 특정 조건을 만족하는 노드 수집
|
|
54
|
-
*/
|
|
55
|
-
findNodes(sourceFile, predicate) {
|
|
56
|
-
const results = [];
|
|
57
|
-
this.traverse(sourceFile, (node) => {
|
|
58
|
-
if (predicate(node)) {
|
|
59
|
-
results.push(node);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
return results;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* 노드의 텍스트 추출
|
|
66
|
-
*/
|
|
67
|
-
getNodeText(node, sourceFile) {
|
|
68
|
-
return node.getText(sourceFile);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* JSDoc 코멘트 추출
|
|
72
|
-
*/
|
|
73
|
-
getJSDocComment(node) {
|
|
74
|
-
const jsDocNodes = ts3.getJSDocCommentsAndTags(node);
|
|
75
|
-
if (jsDocNodes.length === 0) return void 0;
|
|
76
|
-
return jsDocNodes.filter(ts3.isJSDoc).map((doc) => doc.comment).filter(Boolean).join("\n");
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
new TypeScriptParser();
|
|
80
|
-
var SQLParser = class {
|
|
81
|
-
/**
|
|
82
|
-
* SQL 파일 파싱
|
|
83
|
-
*/
|
|
84
|
-
parse(content, relativePath) {
|
|
85
|
-
const tables = this.extractTables(content);
|
|
86
|
-
const tableName = tables[0]?.name || this.extractNameFromPath(relativePath);
|
|
87
|
-
return {
|
|
88
|
-
path: relativePath,
|
|
89
|
-
type: "table",
|
|
90
|
-
name: tableName,
|
|
91
|
-
imports: [],
|
|
92
|
-
exports: tables.map((t) => ({
|
|
93
|
-
name: t.name,
|
|
94
|
-
isDefault: false,
|
|
95
|
-
isTypeOnly: false,
|
|
96
|
-
kind: "variable"
|
|
97
|
-
})),
|
|
98
|
-
metadata: {
|
|
99
|
-
tableName,
|
|
100
|
-
columns: tables[0]?.columns || []
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* CREATE TABLE 문에서 테이블 정보 추출
|
|
106
|
-
*/
|
|
107
|
-
extractTables(content) {
|
|
108
|
-
const tables = [];
|
|
109
|
-
const createTableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?\s*\(([\s\S]*?)\);/gi;
|
|
110
|
-
let match;
|
|
111
|
-
while ((match = createTableRegex.exec(content)) !== null) {
|
|
112
|
-
const tableName = match[1];
|
|
113
|
-
const columnsBlock = match[2];
|
|
114
|
-
const columns = this.parseColumns(columnsBlock);
|
|
115
|
-
tables.push({ name: tableName, columns });
|
|
116
|
-
}
|
|
117
|
-
const alterTableRegex = /ALTER\s+TABLE\s+["']?(\w+)["']?\s+ADD\s+(?:COLUMN\s+)?["']?(\w+)["']?\s+(\w+)/gi;
|
|
118
|
-
while ((match = alterTableRegex.exec(content)) !== null) {
|
|
119
|
-
const tableName = match[1];
|
|
120
|
-
const columnName = match[2];
|
|
121
|
-
const columnType = match[3];
|
|
122
|
-
let table = tables.find((t) => t.name === tableName);
|
|
123
|
-
if (!table) {
|
|
124
|
-
table = { name: tableName, columns: [] };
|
|
125
|
-
tables.push(table);
|
|
126
|
-
}
|
|
127
|
-
table.columns.push({
|
|
128
|
-
name: columnName,
|
|
129
|
-
type: columnType,
|
|
130
|
-
nullable: true,
|
|
131
|
-
isPrimaryKey: false,
|
|
132
|
-
isForeignKey: false
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
return tables;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* 컬럼 정의 파싱
|
|
139
|
-
*/
|
|
140
|
-
parseColumns(columnsBlock) {
|
|
141
|
-
const columns = [];
|
|
142
|
-
const lines = columnsBlock.split(",").map((line) => line.trim()).filter(
|
|
143
|
-
(line) => line && !line.toUpperCase().startsWith("CONSTRAINT") && !line.toUpperCase().startsWith("PRIMARY KEY") && !line.toUpperCase().startsWith("FOREIGN KEY") && !line.toUpperCase().startsWith("UNIQUE") && !line.toUpperCase().startsWith("CHECK")
|
|
144
|
-
);
|
|
145
|
-
for (const line of lines) {
|
|
146
|
-
const column = this.parseColumnDefinition(line);
|
|
147
|
-
if (column) {
|
|
148
|
-
columns.push(column);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return columns;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* 단일 컬럼 정의 파싱
|
|
155
|
-
*/
|
|
156
|
-
parseColumnDefinition(line) {
|
|
157
|
-
const match = line.match(/^["']?(\w+)["']?\s+(\w+(?:\([^)]+\))?)/i);
|
|
158
|
-
if (!match) return null;
|
|
159
|
-
const [, name, type] = match;
|
|
160
|
-
const upperLine = line.toUpperCase();
|
|
161
|
-
return {
|
|
162
|
-
name,
|
|
163
|
-
type: type.toUpperCase(),
|
|
164
|
-
nullable: !upperLine.includes("NOT NULL"),
|
|
165
|
-
isPrimaryKey: upperLine.includes("PRIMARY KEY"),
|
|
166
|
-
isForeignKey: upperLine.includes("REFERENCES"),
|
|
167
|
-
references: this.extractReference(line)
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* REFERENCES 절에서 참조 정보 추출
|
|
172
|
-
*/
|
|
173
|
-
extractReference(line) {
|
|
174
|
-
const match = line.match(
|
|
175
|
-
/REFERENCES\s+["']?(\w+)["']?\s*\(["']?(\w+)["']?\)/i
|
|
176
|
-
);
|
|
177
|
-
if (!match) return void 0;
|
|
178
|
-
return {
|
|
179
|
-
table: match[1],
|
|
180
|
-
column: match[2]
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* 파일 경로에서 테이블 이름 추출
|
|
185
|
-
*/
|
|
186
|
-
extractNameFromPath(filePath) {
|
|
187
|
-
const filename = path2.basename(filePath);
|
|
188
|
-
const match = filename.match(/create_(\w+)_table/i);
|
|
189
|
-
return match ? match[1] : filename.replace(".sql", "");
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
var ImportExtractor = class {
|
|
193
|
-
/**
|
|
194
|
-
* SourceFile에서 모든 import 정보 추출
|
|
195
|
-
*/
|
|
196
|
-
extract(sourceFile) {
|
|
197
|
-
const imports = [];
|
|
198
|
-
ts3.forEachChild(sourceFile, (node) => {
|
|
199
|
-
if (ts3.isImportDeclaration(node)) {
|
|
200
|
-
const importInfo = this.parseImportDeclaration(node);
|
|
201
|
-
if (importInfo) {
|
|
202
|
-
imports.push(importInfo);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
return imports;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* ImportDeclaration 노드를 ImportInfo로 변환
|
|
210
|
-
*/
|
|
211
|
-
parseImportDeclaration(node) {
|
|
212
|
-
const moduleSpecifier = node.moduleSpecifier;
|
|
213
|
-
if (!ts3.isStringLiteral(moduleSpecifier)) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
const source = moduleSpecifier.text;
|
|
217
|
-
const specifiers = [];
|
|
218
|
-
let isDefault = false;
|
|
219
|
-
const isTypeOnly = node.importClause?.isTypeOnly || false;
|
|
220
|
-
const importClause = node.importClause;
|
|
221
|
-
if (!importClause) {
|
|
222
|
-
return {
|
|
223
|
-
source,
|
|
224
|
-
specifiers: [],
|
|
225
|
-
isDefault: false,
|
|
226
|
-
isTypeOnly: false
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (importClause.name) {
|
|
230
|
-
specifiers.push(importClause.name.text);
|
|
231
|
-
isDefault = true;
|
|
232
|
-
}
|
|
233
|
-
const namedBindings = importClause.namedBindings;
|
|
234
|
-
if (namedBindings) {
|
|
235
|
-
if (ts3.isNamespaceImport(namedBindings)) {
|
|
236
|
-
specifiers.push(`* as ${namedBindings.name.text}`);
|
|
237
|
-
} else if (ts3.isNamedImports(namedBindings)) {
|
|
238
|
-
for (const element of namedBindings.elements) {
|
|
239
|
-
const name = element.name.text;
|
|
240
|
-
const propertyName = element.propertyName?.text;
|
|
241
|
-
if (propertyName) {
|
|
242
|
-
specifiers.push(`${propertyName} as ${name}`);
|
|
243
|
-
} else {
|
|
244
|
-
specifiers.push(name);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return {
|
|
250
|
-
source,
|
|
251
|
-
specifiers,
|
|
252
|
-
isDefault,
|
|
253
|
-
isTypeOnly
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
var ExportExtractor = class {
|
|
258
|
-
/**
|
|
259
|
-
* SourceFile에서 모든 export 정보 추출
|
|
260
|
-
*/
|
|
261
|
-
extract(sourceFile) {
|
|
262
|
-
const exports$1 = [];
|
|
263
|
-
ts3.forEachChild(sourceFile, (node) => {
|
|
264
|
-
const exportInfos = this.extractFromNode(node, sourceFile);
|
|
265
|
-
exports$1.push(...exportInfos);
|
|
266
|
-
});
|
|
267
|
-
return exports$1;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* 노드에서 export 정보 추출
|
|
271
|
-
*/
|
|
272
|
-
extractFromNode(node, sourceFile) {
|
|
273
|
-
const results = [];
|
|
274
|
-
if (ts3.isFunctionDeclaration(node) && this.hasExportModifier(node)) {
|
|
275
|
-
results.push({
|
|
276
|
-
name: node.name?.text || "anonymous",
|
|
277
|
-
isDefault: this.hasDefaultModifier(node),
|
|
278
|
-
isTypeOnly: false,
|
|
279
|
-
kind: "function"
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
if (ts3.isClassDeclaration(node) && this.hasExportModifier(node)) {
|
|
283
|
-
results.push({
|
|
284
|
-
name: node.name?.text || "anonymous",
|
|
285
|
-
isDefault: this.hasDefaultModifier(node),
|
|
286
|
-
isTypeOnly: false,
|
|
287
|
-
kind: "class"
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (ts3.isVariableStatement(node) && this.hasExportModifier(node)) {
|
|
291
|
-
for (const declaration of node.declarationList.declarations) {
|
|
292
|
-
if (ts3.isIdentifier(declaration.name)) {
|
|
293
|
-
results.push({
|
|
294
|
-
name: declaration.name.text,
|
|
295
|
-
isDefault: false,
|
|
296
|
-
isTypeOnly: false,
|
|
297
|
-
kind: "variable"
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (ts3.isTypeAliasDeclaration(node) && this.hasExportModifier(node)) {
|
|
303
|
-
results.push({
|
|
304
|
-
name: node.name.text,
|
|
305
|
-
isDefault: false,
|
|
306
|
-
isTypeOnly: true,
|
|
307
|
-
kind: "type"
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
if (ts3.isInterfaceDeclaration(node) && this.hasExportModifier(node)) {
|
|
311
|
-
results.push({
|
|
312
|
-
name: node.name.text,
|
|
313
|
-
isDefault: false,
|
|
314
|
-
isTypeOnly: true,
|
|
315
|
-
kind: "interface"
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
if (ts3.isExportAssignment(node) && !node.isExportEquals) {
|
|
319
|
-
const name = this.getExportDefaultName(node, sourceFile);
|
|
320
|
-
results.push({
|
|
321
|
-
name,
|
|
322
|
-
isDefault: true,
|
|
323
|
-
isTypeOnly: false,
|
|
324
|
-
kind: "variable"
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
if (ts3.isExportDeclaration(node)) {
|
|
328
|
-
const exportClause = node.exportClause;
|
|
329
|
-
if (exportClause && ts3.isNamedExports(exportClause)) {
|
|
330
|
-
for (const element of exportClause.elements) {
|
|
331
|
-
results.push({
|
|
332
|
-
name: element.name.text,
|
|
333
|
-
isDefault: false,
|
|
334
|
-
isTypeOnly: node.isTypeOnly,
|
|
335
|
-
kind: "variable"
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
return results;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* export 키워드가 있는지 확인
|
|
344
|
-
*/
|
|
345
|
-
hasExportModifier(node) {
|
|
346
|
-
const modifiers = ts3.canHaveModifiers(node) ? ts3.getModifiers(node) : void 0;
|
|
347
|
-
return modifiers?.some((m) => m.kind === ts3.SyntaxKind.ExportKeyword) || false;
|
|
348
|
-
}
|
|
349
|
-
/**
|
|
350
|
-
* default 키워드가 있는지 확인
|
|
351
|
-
*/
|
|
352
|
-
hasDefaultModifier(node) {
|
|
353
|
-
const modifiers = ts3.canHaveModifiers(node) ? ts3.getModifiers(node) : void 0;
|
|
354
|
-
return modifiers?.some((m) => m.kind === ts3.SyntaxKind.DefaultKeyword) || false;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* export default의 이름 추출
|
|
358
|
-
*/
|
|
359
|
-
getExportDefaultName(node, _sourceFile) {
|
|
360
|
-
const expr = node.expression;
|
|
361
|
-
if (ts3.isIdentifier(expr)) {
|
|
362
|
-
return expr.text;
|
|
363
|
-
}
|
|
364
|
-
if (ts3.isArrowFunction(expr) || ts3.isFunctionExpression(expr)) {
|
|
365
|
-
return "default";
|
|
366
|
-
}
|
|
367
|
-
if (ts3.isCallExpression(expr) && ts3.isIdentifier(expr.expression)) {
|
|
368
|
-
const firstArg = expr.arguments[0];
|
|
369
|
-
if (firstArg && ts3.isIdentifier(firstArg)) {
|
|
370
|
-
return firstArg.text;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return "default";
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
var PropsExtractor = class {
|
|
377
|
-
/**
|
|
378
|
-
* SourceFile에서 Props 정보 추출
|
|
379
|
-
*/
|
|
380
|
-
extract(sourceFile) {
|
|
381
|
-
const props = [];
|
|
382
|
-
const propsType = this.findPropsType(sourceFile);
|
|
383
|
-
if (propsType) {
|
|
384
|
-
props.push(...this.extractFromTypeNode(propsType, sourceFile));
|
|
385
|
-
}
|
|
386
|
-
const componentFunction = this.findComponentFunction(sourceFile);
|
|
387
|
-
if (componentFunction) {
|
|
388
|
-
props.push(
|
|
389
|
-
...this.extractFromFunctionParams(componentFunction, sourceFile)
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
return this.deduplicateProps(props);
|
|
393
|
-
}
|
|
394
|
-
/**
|
|
395
|
-
* Props 타입 정의 찾기
|
|
396
|
-
*/
|
|
397
|
-
findPropsType(sourceFile) {
|
|
398
|
-
let propsType;
|
|
399
|
-
ts3.forEachChild(sourceFile, (node) => {
|
|
400
|
-
if (ts3.isInterfaceDeclaration(node) && node.name.text.endsWith("Props")) {
|
|
401
|
-
propsType = node;
|
|
402
|
-
}
|
|
403
|
-
if (ts3.isTypeAliasDeclaration(node) && node.name.text.endsWith("Props")) {
|
|
404
|
-
propsType = node.type;
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
return propsType;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* 컴포넌트 함수 찾기
|
|
411
|
-
*/
|
|
412
|
-
findComponentFunction(sourceFile) {
|
|
413
|
-
let component;
|
|
414
|
-
ts3.forEachChild(sourceFile, (node) => {
|
|
415
|
-
if (ts3.isFunctionDeclaration(node) && this.isExported(node)) {
|
|
416
|
-
component = node;
|
|
417
|
-
}
|
|
418
|
-
if (ts3.isVariableStatement(node) && this.isExported(node)) {
|
|
419
|
-
for (const decl of node.declarationList.declarations) {
|
|
420
|
-
if (decl.initializer && (ts3.isArrowFunction(decl.initializer) || ts3.isFunctionExpression(decl.initializer))) {
|
|
421
|
-
component = decl.initializer;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
return component;
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* 타입 노드에서 Props 추출
|
|
430
|
-
*/
|
|
431
|
-
extractFromTypeNode(typeNode, sourceFile) {
|
|
432
|
-
const props = [];
|
|
433
|
-
if (ts3.isInterfaceDeclaration(typeNode)) {
|
|
434
|
-
for (const member of typeNode.members) {
|
|
435
|
-
const propInfo = this.extractMemberProp(member, sourceFile);
|
|
436
|
-
if (propInfo) {
|
|
437
|
-
props.push(propInfo);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return props;
|
|
441
|
-
}
|
|
442
|
-
if (ts3.isTypeLiteralNode(typeNode)) {
|
|
443
|
-
for (const member of typeNode.members) {
|
|
444
|
-
const propInfo = this.extractMemberProp(member, sourceFile);
|
|
445
|
-
if (propInfo) {
|
|
446
|
-
props.push(propInfo);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
if (ts3.isIntersectionTypeNode(typeNode)) {
|
|
451
|
-
for (const type of typeNode.types) {
|
|
452
|
-
props.push(...this.extractFromTypeNode(type, sourceFile));
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return props;
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* 타입 멤버에서 PropInfo 추출
|
|
459
|
-
*/
|
|
460
|
-
extractMemberProp(member, sourceFile) {
|
|
461
|
-
if (!ts3.isPropertySignature(member)) {
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
const name = member.name;
|
|
465
|
-
if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name)) {
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
const propName = ts3.isIdentifier(name) ? name.text : name.text;
|
|
469
|
-
const required = !member.questionToken;
|
|
470
|
-
const type = member.type ? member.type.getText(sourceFile) : "any";
|
|
471
|
-
return {
|
|
472
|
-
name: propName,
|
|
473
|
-
type,
|
|
474
|
-
required
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* 함수 매개변수에서 Props 추출
|
|
479
|
-
*/
|
|
480
|
-
extractFromFunctionParams(func, sourceFile) {
|
|
481
|
-
const props = [];
|
|
482
|
-
const firstParam = func.parameters[0];
|
|
483
|
-
if (!firstParam) return props;
|
|
484
|
-
if (ts3.isObjectBindingPattern(firstParam.name)) {
|
|
485
|
-
for (const element of firstParam.name.elements) {
|
|
486
|
-
if (ts3.isBindingElement(element) && ts3.isIdentifier(element.name)) {
|
|
487
|
-
props.push({
|
|
488
|
-
name: element.name.text,
|
|
489
|
-
type: "unknown",
|
|
490
|
-
required: !element.initializer,
|
|
491
|
-
defaultValue: element.initializer?.getText(sourceFile)
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return props;
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* export 키워드가 있는지 확인
|
|
500
|
-
*/
|
|
501
|
-
isExported(node) {
|
|
502
|
-
const modifiers = ts3.canHaveModifiers(node) ? ts3.getModifiers(node) : void 0;
|
|
503
|
-
return modifiers?.some((m) => m.kind === ts3.SyntaxKind.ExportKeyword) || false;
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* 중복 Props 제거
|
|
507
|
-
*/
|
|
508
|
-
deduplicateProps(props) {
|
|
509
|
-
const seen = /* @__PURE__ */ new Set();
|
|
510
|
-
return props.filter((prop) => {
|
|
511
|
-
if (seen.has(prop.name)) return false;
|
|
512
|
-
seen.add(prop.name);
|
|
513
|
-
return true;
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
// src/utils/naming-utils.ts
|
|
519
|
-
function splitCamelCase(str) {
|
|
520
|
-
return str.replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").filter(Boolean);
|
|
521
|
-
}
|
|
522
|
-
function splitPascalCase(str) {
|
|
523
|
-
return str.replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").filter(Boolean);
|
|
524
|
-
}
|
|
525
|
-
function splitSnakeCase(str) {
|
|
526
|
-
return str.split("_").filter(Boolean);
|
|
527
|
-
}
|
|
528
|
-
function extractAcronyms(str) {
|
|
529
|
-
const acronyms = [];
|
|
530
|
-
const matches = str.match(/[A-Z]{2,}/g);
|
|
531
|
-
if (matches) {
|
|
532
|
-
acronyms.push(...matches);
|
|
533
|
-
}
|
|
534
|
-
return acronyms;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// src/utils/korean-mapper.ts
|
|
538
|
-
var KOREAN_KEYWORD_MAP = {
|
|
539
|
-
// 공통 동작
|
|
540
|
-
create: ["\uC0DD\uC131", "\uB9CC\uB4E4\uAE30", "\uCD94\uAC00"],
|
|
541
|
-
read: ["\uC77D\uAE30", "\uC870\uD68C"],
|
|
542
|
-
update: ["\uC218\uC815", "\uC5C5\uB370\uC774\uD2B8", "\uBCC0\uACBD"],
|
|
543
|
-
delete: ["\uC0AD\uC81C", "\uC81C\uAC70"],
|
|
544
|
-
list: ["\uBAA9\uB85D", "\uB9AC\uC2A4\uD2B8"],
|
|
545
|
-
search: ["\uAC80\uC0C9", "\uCC3E\uAE30"],
|
|
546
|
-
filter: ["\uD544\uD130", "\uD544\uD130\uB9C1"],
|
|
547
|
-
sort: ["\uC815\uB82C"],
|
|
548
|
-
submit: ["\uC81C\uCD9C", "\uC804\uC1A1"],
|
|
549
|
-
cancel: ["\uCDE8\uC18C"],
|
|
550
|
-
confirm: ["\uD655\uC778"],
|
|
551
|
-
save: ["\uC800\uC7A5"],
|
|
552
|
-
load: ["\uB85C\uB4DC", "\uBD88\uB7EC\uC624\uAE30"],
|
|
553
|
-
fetch: ["\uAC00\uC838\uC624\uAE30", "\uBD88\uB7EC\uC624\uAE30"],
|
|
554
|
-
// UI 요소
|
|
555
|
-
button: ["\uBC84\uD2BC"],
|
|
556
|
-
modal: ["\uBAA8\uB2EC", "\uD31D\uC5C5"],
|
|
557
|
-
dialog: ["\uB2E4\uC774\uC5BC\uB85C\uADF8", "\uB300\uD654\uC0C1\uC790"],
|
|
558
|
-
form: ["\uD3FC", "\uC591\uC2DD"],
|
|
559
|
-
input: ["\uC785\uB825", "\uC778\uD48B"],
|
|
560
|
-
select: ["\uC120\uD0DD", "\uC140\uB809\uD2B8", "\uB4DC\uB86D\uB2E4\uC6B4"],
|
|
561
|
-
checkbox: ["\uCCB4\uD06C\uBC15\uC2A4", "\uCCB4\uD06C"],
|
|
562
|
-
table: ["\uD14C\uC774\uBE14", "\uD45C"],
|
|
563
|
-
card: ["\uCE74\uB4DC"],
|
|
564
|
-
tab: ["\uD0ED"],
|
|
565
|
-
menu: ["\uBA54\uB274"],
|
|
566
|
-
header: ["\uD5E4\uB354", "\uBA38\uB9AC\uAE00"],
|
|
567
|
-
footer: ["\uD478\uD130", "\uBC14\uB2E5\uAE00"],
|
|
568
|
-
sidebar: ["\uC0AC\uC774\uB4DC\uBC14"],
|
|
569
|
-
navbar: ["\uB124\uBE44\uAC8C\uC774\uC158", "\uB124\uBE44\uBC14"],
|
|
570
|
-
// 인증/사용자
|
|
571
|
-
auth: ["\uC778\uC99D", "\uB85C\uADF8\uC778"],
|
|
572
|
-
login: ["\uB85C\uADF8\uC778"],
|
|
573
|
-
logout: ["\uB85C\uADF8\uC544\uC6C3"],
|
|
574
|
-
register: ["\uD68C\uC6D0\uAC00\uC785", "\uAC00\uC785"],
|
|
575
|
-
signup: ["\uD68C\uC6D0\uAC00\uC785", "\uAC00\uC785"],
|
|
576
|
-
user: ["\uC0AC\uC6A9\uC790", "\uC720\uC800", "\uD68C\uC6D0"],
|
|
577
|
-
profile: ["\uD504\uB85C\uD544"],
|
|
578
|
-
password: ["\uBE44\uBC00\uBC88\uD638", "\uC554\uD638"],
|
|
579
|
-
permission: ["\uAD8C\uD55C"],
|
|
580
|
-
// 비즈니스
|
|
581
|
-
attendance: ["\uCD9C\uC11D", "\uCD9C\uACB0"],
|
|
582
|
-
check: ["\uCCB4\uD06C", "\uD655\uC778"],
|
|
583
|
-
schedule: ["\uC2A4\uCF00\uC904", "\uC77C\uC815"],
|
|
584
|
-
calendar: ["\uCE98\uB9B0\uB354", "\uB2EC\uB825"],
|
|
585
|
-
notification: ["\uC54C\uB9BC", "\uD1B5\uC9C0"],
|
|
586
|
-
message: ["\uBA54\uC2DC\uC9C0", "\uC54C\uB9BC"],
|
|
587
|
-
setting: ["\uC124\uC815"],
|
|
588
|
-
settings: ["\uC124\uC815"],
|
|
589
|
-
payment: ["\uACB0\uC81C", "\uC9C0\uBD88"],
|
|
590
|
-
order: ["\uC8FC\uBB38"],
|
|
591
|
-
product: ["\uC0C1\uD488", "\uC81C\uD488"],
|
|
592
|
-
cart: ["\uC7A5\uBC14\uAD6C\uB2C8"],
|
|
593
|
-
checkout: ["\uACB0\uC81C", "\uCCB4\uD06C\uC544\uC6C3"],
|
|
594
|
-
invoice: ["\uC1A1\uC7A5", "\uCCAD\uAD6C\uC11C"],
|
|
595
|
-
report: ["\uB9AC\uD3EC\uD2B8", "\uBCF4\uACE0\uC11C"],
|
|
596
|
-
dashboard: ["\uB300\uC2DC\uBCF4\uB4DC"],
|
|
597
|
-
analytics: ["\uBD84\uC11D", "\uD1B5\uACC4"],
|
|
598
|
-
statistics: ["\uD1B5\uACC4"],
|
|
599
|
-
// 상태
|
|
600
|
-
status: ["\uC0C1\uD0DC"],
|
|
601
|
-
pending: ["\uB300\uAE30\uC911", "\uB300\uAE30"],
|
|
602
|
-
active: ["\uD65C\uC131", "\uD65C\uC131\uD654"],
|
|
603
|
-
inactive: ["\uBE44\uD65C\uC131", "\uBE44\uD65C\uC131\uD654"],
|
|
604
|
-
completed: ["\uC644\uB8CC"],
|
|
605
|
-
error: ["\uC5D0\uB7EC", "\uC624\uB958"],
|
|
606
|
-
success: ["\uC131\uACF5"],
|
|
607
|
-
loading: ["\uB85C\uB529", "\uB85C\uB4DC\uC911"],
|
|
608
|
-
// 기타
|
|
609
|
-
date: ["\uB0A0\uC9DC"],
|
|
610
|
-
time: ["\uC2DC\uAC04"],
|
|
611
|
-
image: ["\uC774\uBBF8\uC9C0", "\uC0AC\uC9C4"],
|
|
612
|
-
file: ["\uD30C\uC77C"],
|
|
613
|
-
upload: ["\uC5C5\uB85C\uB4DC", "\uC62C\uB9AC\uAE30"],
|
|
614
|
-
download: ["\uB2E4\uC6B4\uB85C\uB4DC", "\uB0B4\uB824\uBC1B\uAE30"],
|
|
615
|
-
export: ["\uB0B4\uBCF4\uB0B4\uAE30", "\uC775\uC2A4\uD3EC\uD2B8"],
|
|
616
|
-
import: ["\uAC00\uC838\uC624\uAE30", "\uC784\uD3EC\uD2B8"],
|
|
617
|
-
api: ["API", "\uC5D0\uC774\uD53C\uC544\uC774"],
|
|
618
|
-
service: ["\uC11C\uBE44\uC2A4"],
|
|
619
|
-
hook: ["\uD6C5"],
|
|
620
|
-
component: ["\uCEF4\uD3EC\uB10C\uD2B8"],
|
|
621
|
-
page: ["\uD398\uC774\uC9C0"],
|
|
622
|
-
route: ["\uB77C\uC6B0\uD2B8", "\uACBD\uB85C"]
|
|
623
|
-
};
|
|
624
|
-
function findKoreanKeywords(englishKeyword) {
|
|
625
|
-
const lower = englishKeyword.toLowerCase();
|
|
626
|
-
if (KOREAN_KEYWORD_MAP[lower]) {
|
|
627
|
-
return KOREAN_KEYWORD_MAP[lower];
|
|
628
|
-
}
|
|
629
|
-
const results = [];
|
|
630
|
-
for (const [key, values] of Object.entries(KOREAN_KEYWORD_MAP)) {
|
|
631
|
-
if (lower.includes(key) || key.includes(lower)) {
|
|
632
|
-
results.push(...values);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return [...new Set(results)];
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
// src/core/extractors/keyword-extractor.ts
|
|
639
|
-
var KeywordExtractor = class {
|
|
640
|
-
constructor(customKoreanMap) {
|
|
641
|
-
this.customKoreanMap = customKoreanMap || {};
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* 다양한 소스에서 키워드 추출
|
|
645
|
-
*/
|
|
646
|
-
extract(name, path6, exports$1 = [], props = []) {
|
|
647
|
-
const keywords = /* @__PURE__ */ new Set();
|
|
648
|
-
this.extractFromName(name, keywords);
|
|
649
|
-
this.extractFromPath(path6, keywords);
|
|
650
|
-
for (const exportName of exports$1) {
|
|
651
|
-
this.extractFromName(exportName, keywords);
|
|
652
|
-
}
|
|
653
|
-
for (const propName of props) {
|
|
654
|
-
this.extractFromName(propName, keywords);
|
|
655
|
-
}
|
|
656
|
-
const allEnglishKeywords = [...keywords];
|
|
657
|
-
for (const keyword of allEnglishKeywords) {
|
|
658
|
-
const koreanKeywords = this.findKorean(keyword);
|
|
659
|
-
koreanKeywords.forEach((k) => keywords.add(k));
|
|
660
|
-
}
|
|
661
|
-
return [...keywords].filter((k) => k.length > 1);
|
|
662
|
-
}
|
|
663
|
-
/**
|
|
664
|
-
* 이름에서 키워드 추출
|
|
665
|
-
*/
|
|
666
|
-
extractFromName(name, keywords) {
|
|
667
|
-
keywords.add(name.toLowerCase());
|
|
668
|
-
const camelParts = splitCamelCase(name);
|
|
669
|
-
camelParts.forEach((part) => keywords.add(part.toLowerCase()));
|
|
670
|
-
const pascalParts = splitPascalCase(name);
|
|
671
|
-
pascalParts.forEach((part) => keywords.add(part.toLowerCase()));
|
|
672
|
-
const snakeParts = splitSnakeCase(name);
|
|
673
|
-
snakeParts.forEach((part) => keywords.add(part.toLowerCase()));
|
|
674
|
-
const acronyms = extractAcronyms(name);
|
|
675
|
-
acronyms.forEach((acr) => keywords.add(acr.toLowerCase()));
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* 경로에서 키워드 추출
|
|
679
|
-
*/
|
|
680
|
-
extractFromPath(path6, keywords) {
|
|
681
|
-
const excludeNames = /* @__PURE__ */ new Set([
|
|
682
|
-
"src",
|
|
683
|
-
"app",
|
|
684
|
-
"components",
|
|
685
|
-
"hooks",
|
|
686
|
-
"services",
|
|
687
|
-
"lib",
|
|
688
|
-
"utils",
|
|
689
|
-
"pages",
|
|
690
|
-
"api"
|
|
691
|
-
]);
|
|
692
|
-
const segments = path6.replace(/\.[^/.]+$/, "").split("/").filter((s) => !excludeNames.has(s));
|
|
693
|
-
for (const segment of segments) {
|
|
694
|
-
this.extractFromName(segment, keywords);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* 영어 키워드에 대응하는 한글 키워드 찾기
|
|
699
|
-
*/
|
|
700
|
-
findKorean(englishKeyword) {
|
|
701
|
-
const customMatch = this.customKoreanMap[englishKeyword.toLowerCase()];
|
|
702
|
-
if (customMatch) {
|
|
703
|
-
return customMatch;
|
|
704
|
-
}
|
|
705
|
-
return findKoreanKeywords(englishKeyword);
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
var DependencyResolver = class {
|
|
709
|
-
constructor(aliasMap = {}, extensions = [".ts", ".tsx", ".js", ".jsx"]) {
|
|
710
|
-
this.aliasMap = new Map(Object.entries(aliasMap));
|
|
711
|
-
this.extensions = extensions;
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* import 경로를 실제 파일 경로로 해석
|
|
715
|
-
*/
|
|
716
|
-
resolve(importSource, importerPath, rootDir) {
|
|
717
|
-
if (this.isExternalPackage(importSource)) {
|
|
718
|
-
return null;
|
|
719
|
-
}
|
|
720
|
-
const aliasResolved = this.resolveAlias(importSource);
|
|
721
|
-
let targetPath;
|
|
722
|
-
if (aliasResolved.startsWith("./") || aliasResolved.startsWith("../")) {
|
|
723
|
-
const importerDir = path2.dirname(path2.resolve(rootDir, importerPath));
|
|
724
|
-
targetPath = path2.resolve(importerDir, aliasResolved);
|
|
725
|
-
} else if (aliasResolved !== importSource) {
|
|
726
|
-
targetPath = path2.resolve(rootDir, aliasResolved);
|
|
727
|
-
} else {
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
const resolvedPath = this.resolveWithExtensions(targetPath);
|
|
731
|
-
if (resolvedPath) {
|
|
732
|
-
return path2.relative(rootDir, resolvedPath);
|
|
733
|
-
}
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
/**
|
|
737
|
-
* 외부 패키지인지 확인
|
|
738
|
-
*/
|
|
739
|
-
isExternalPackage(source) {
|
|
740
|
-
if (source.startsWith("./") || source.startsWith("../")) {
|
|
741
|
-
return false;
|
|
742
|
-
}
|
|
743
|
-
for (const alias of this.aliasMap.keys()) {
|
|
744
|
-
if (source.startsWith(alias)) {
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return true;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* alias 해석
|
|
752
|
-
*/
|
|
753
|
-
resolveAlias(source) {
|
|
754
|
-
for (const [alias, target] of this.aliasMap.entries()) {
|
|
755
|
-
if (source === alias) {
|
|
756
|
-
return target;
|
|
757
|
-
}
|
|
758
|
-
if (source.startsWith(alias + "/")) {
|
|
759
|
-
return source.replace(alias, target);
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
return source;
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* 확장자를 추가하여 파일 경로 해석
|
|
766
|
-
*/
|
|
767
|
-
resolveWithExtensions(targetPath) {
|
|
768
|
-
if (fs.existsSync(targetPath)) {
|
|
769
|
-
try {
|
|
770
|
-
const stat = fs.statSync(targetPath);
|
|
771
|
-
if (stat.isFile()) {
|
|
772
|
-
return targetPath;
|
|
773
|
-
}
|
|
774
|
-
if (stat.isDirectory()) {
|
|
775
|
-
return this.findIndexFile(targetPath);
|
|
776
|
-
}
|
|
777
|
-
} catch {
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
for (const ext of this.extensions) {
|
|
781
|
-
const withExt = targetPath + ext;
|
|
782
|
-
if (fs.existsSync(withExt)) {
|
|
783
|
-
return withExt;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
return this.findIndexFile(targetPath);
|
|
787
|
-
}
|
|
788
|
-
/**
|
|
789
|
-
* 디렉토리에서 index 파일 찾기
|
|
790
|
-
*/
|
|
791
|
-
findIndexFile(dirPath) {
|
|
792
|
-
for (const ext of this.extensions) {
|
|
793
|
-
const indexPath = path2.join(dirPath, `index${ext}`);
|
|
794
|
-
if (fs.existsSync(indexPath)) {
|
|
795
|
-
return indexPath;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
return null;
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
// src/core/resolvers/call-graph-builder.ts
|
|
803
|
-
var CallGraphBuilder = class {
|
|
804
|
-
constructor(aliasMap) {
|
|
805
|
-
const defaultAliases = {
|
|
806
|
-
"@": "src",
|
|
807
|
-
"@/": "src/",
|
|
808
|
-
"~": "src",
|
|
809
|
-
"~/": "src/"
|
|
810
|
-
};
|
|
811
|
-
this.resolver = new DependencyResolver({
|
|
812
|
-
...defaultAliases,
|
|
813
|
-
...aliasMap
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
/**
|
|
817
|
-
* 파싱된 파일들로부터 호출 그래프 구축
|
|
818
|
-
*/
|
|
819
|
-
build(parsedFiles, rootDir) {
|
|
820
|
-
const graph = /* @__PURE__ */ new Map();
|
|
821
|
-
const filePathSet = new Set(parsedFiles.map((f) => f.path));
|
|
822
|
-
for (const file of parsedFiles) {
|
|
823
|
-
const calls = this.resolveCalls(file, rootDir, filePathSet);
|
|
824
|
-
graph.set(file.path, {
|
|
825
|
-
calls,
|
|
826
|
-
calledBy: []
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
for (const [filePath, entry] of graph.entries()) {
|
|
830
|
-
for (const calledPath of entry.calls) {
|
|
831
|
-
const calledEntry = graph.get(calledPath);
|
|
832
|
-
if (calledEntry) {
|
|
833
|
-
calledEntry.calledBy.push(filePath);
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
return graph;
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* 파일이 호출하는 다른 파일들을 해석
|
|
841
|
-
*/
|
|
842
|
-
resolveCalls(file, rootDir, validPaths) {
|
|
843
|
-
const calls = [];
|
|
844
|
-
for (const imp of file.imports) {
|
|
845
|
-
if (imp.isTypeOnly) continue;
|
|
846
|
-
const resolvedPath = this.resolver.resolve(
|
|
847
|
-
imp.source,
|
|
848
|
-
file.path,
|
|
849
|
-
rootDir
|
|
850
|
-
);
|
|
851
|
-
if (resolvedPath && validPaths.has(resolvedPath)) {
|
|
852
|
-
calls.push(resolvedPath);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return [...new Set(calls)];
|
|
856
|
-
}
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
// src/core/config.ts
|
|
860
|
-
var DEFAULT_FILE_TYPE_MAPPING = {
|
|
861
|
-
// Next.js App Router
|
|
862
|
-
"app/**/page.tsx": "route",
|
|
863
|
-
"app/**/page.ts": "route",
|
|
864
|
-
"app/**/layout.tsx": "route",
|
|
865
|
-
"app/**/layout.ts": "route",
|
|
866
|
-
"app/**/route.tsx": "api",
|
|
867
|
-
"app/**/route.ts": "api",
|
|
868
|
-
"app/api/**/*.ts": "api",
|
|
869
|
-
"app/api/**/*.tsx": "api",
|
|
870
|
-
// Pages Router (레거시)
|
|
871
|
-
"pages/**/*.tsx": "route",
|
|
872
|
-
"pages/**/*.ts": "route",
|
|
873
|
-
"pages/api/**/*.ts": "api",
|
|
874
|
-
// Components
|
|
875
|
-
"components/**/*.tsx": "component",
|
|
876
|
-
"components/**/*.ts": "component",
|
|
877
|
-
"src/components/**/*.tsx": "component",
|
|
878
|
-
"src/components/**/*.ts": "component",
|
|
879
|
-
// Hooks
|
|
880
|
-
"hooks/**/*.ts": "hook",
|
|
881
|
-
"hooks/**/*.tsx": "hook",
|
|
882
|
-
"src/hooks/**/*.ts": "hook",
|
|
883
|
-
"src/hooks/**/*.tsx": "hook",
|
|
884
|
-
// Services
|
|
885
|
-
"services/**/*.ts": "service",
|
|
886
|
-
"src/services/**/*.ts": "service",
|
|
887
|
-
// Utilities
|
|
888
|
-
"lib/**/*.ts": "utility",
|
|
889
|
-
"src/lib/**/*.ts": "utility",
|
|
890
|
-
"utils/**/*.ts": "utility",
|
|
891
|
-
"src/utils/**/*.ts": "utility",
|
|
892
|
-
// Database
|
|
893
|
-
"supabase/migrations/*.sql": "table",
|
|
894
|
-
"prisma/migrations/**/*.sql": "table"
|
|
895
|
-
};
|
|
896
|
-
var DEFAULT_INCLUDE_PATTERNS = [
|
|
897
|
-
"app/**/*.{ts,tsx}",
|
|
898
|
-
"pages/**/*.{ts,tsx}",
|
|
899
|
-
"components/**/*.{ts,tsx}",
|
|
900
|
-
"hooks/**/*.{ts,tsx}",
|
|
901
|
-
"services/**/*.ts",
|
|
902
|
-
"lib/**/*.ts",
|
|
903
|
-
"utils/**/*.ts",
|
|
904
|
-
"src/**/*.{ts,tsx}",
|
|
905
|
-
"supabase/migrations/*.sql"
|
|
906
|
-
];
|
|
907
|
-
var DEFAULT_EXCLUDE_PATTERNS = [
|
|
908
|
-
"**/node_modules/**",
|
|
909
|
-
"**/.next/**",
|
|
910
|
-
"**/dist/**",
|
|
911
|
-
"**/*.test.{ts,tsx}",
|
|
912
|
-
"**/*.spec.{ts,tsx}",
|
|
913
|
-
"**/__tests__/**",
|
|
914
|
-
"**/*.d.ts",
|
|
915
|
-
"**/coverage/**"
|
|
916
|
-
];
|
|
917
|
-
function createDefaultConfig(overrides = {}) {
|
|
918
|
-
return {
|
|
919
|
-
projectId: overrides.projectId || "default",
|
|
920
|
-
include: overrides.include || DEFAULT_INCLUDE_PATTERNS,
|
|
921
|
-
exclude: overrides.exclude || DEFAULT_EXCLUDE_PATTERNS,
|
|
922
|
-
fileTypeMapping: {
|
|
923
|
-
...DEFAULT_FILE_TYPE_MAPPING,
|
|
924
|
-
...overrides.fileTypeMapping
|
|
925
|
-
},
|
|
926
|
-
output: {
|
|
927
|
-
file: {
|
|
928
|
-
enabled: true,
|
|
929
|
-
path: "project-metadata.json",
|
|
930
|
-
...overrides.output?.file
|
|
931
|
-
},
|
|
932
|
-
api: {
|
|
933
|
-
enabled: false,
|
|
934
|
-
endpoint: "",
|
|
935
|
-
...overrides.output?.api
|
|
936
|
-
}
|
|
937
|
-
},
|
|
938
|
-
koreanKeywords: overrides.koreanKeywords,
|
|
939
|
-
mode: overrides.mode || "production",
|
|
940
|
-
verbose: overrides.verbose || false
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
function globToRegex(pattern) {
|
|
944
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\{([^}]+)\}/g, (_, group) => {
|
|
945
|
-
const alternatives = group.split(",").map((s) => s.trim());
|
|
946
|
-
return `(${alternatives.join("|")})`;
|
|
947
|
-
});
|
|
948
|
-
return new RegExp(`^${escaped}$`);
|
|
949
|
-
}
|
|
950
|
-
function generateId(projectId, filePath) {
|
|
951
|
-
const input = `${projectId}:${filePath}`;
|
|
952
|
-
return crypto.createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// src/core/analyzer.ts
|
|
956
|
-
var ProjectAnalyzer = class {
|
|
957
|
-
constructor(config) {
|
|
958
|
-
this.config = config;
|
|
959
|
-
this.tsParser = new TypeScriptParser();
|
|
960
|
-
this.sqlParser = new SQLParser();
|
|
961
|
-
this.importExtractor = new ImportExtractor();
|
|
962
|
-
this.exportExtractor = new ExportExtractor();
|
|
963
|
-
this.propsExtractor = new PropsExtractor();
|
|
964
|
-
this.keywordExtractor = new KeywordExtractor(config.koreanKeywords);
|
|
965
|
-
this.callGraphBuilder = new CallGraphBuilder();
|
|
966
|
-
}
|
|
967
|
-
/**
|
|
968
|
-
* 프로젝트 분석 실행
|
|
969
|
-
*/
|
|
970
|
-
async analyze(rootDir) {
|
|
971
|
-
const startTime = Date.now();
|
|
972
|
-
const parseErrors = [];
|
|
973
|
-
const files = await this.collectFiles(rootDir);
|
|
974
|
-
if (this.config.verbose) {
|
|
975
|
-
console.log(`[metadata-plugin] Found ${files.length} files to analyze`);
|
|
976
|
-
}
|
|
977
|
-
const parsedFiles = [];
|
|
978
|
-
for (const filePath of files) {
|
|
979
|
-
try {
|
|
980
|
-
const parsed = await this.parseFile(filePath, rootDir);
|
|
981
|
-
if (parsed) {
|
|
982
|
-
parsedFiles.push(parsed);
|
|
983
|
-
}
|
|
984
|
-
} catch (error) {
|
|
985
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
986
|
-
parseErrors.push(`${filePath}: ${errorMessage}`);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
const callGraph = this.callGraphBuilder.build(parsedFiles, rootDir);
|
|
990
|
-
const items = parsedFiles.map(
|
|
991
|
-
(parsed) => this.createIndexItem(parsed, callGraph)
|
|
992
|
-
);
|
|
993
|
-
const stats = this.calculateStats(items, parseErrors);
|
|
994
|
-
if (this.config.verbose) {
|
|
995
|
-
console.log(
|
|
996
|
-
`[metadata-plugin] Analysis completed in ${Date.now() - startTime}ms`
|
|
997
|
-
);
|
|
998
|
-
console.log(`[metadata-plugin] Processed ${items.length} items`);
|
|
999
|
-
}
|
|
1000
|
-
return {
|
|
1001
|
-
items,
|
|
1002
|
-
stats,
|
|
1003
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* 대상 파일 수집
|
|
1008
|
-
*/
|
|
1009
|
-
async collectFiles(rootDir) {
|
|
1010
|
-
const allFiles = [];
|
|
1011
|
-
for (const pattern of this.config.include) {
|
|
1012
|
-
const matches = await glob(pattern, {
|
|
1013
|
-
cwd: rootDir,
|
|
1014
|
-
ignore: this.config.exclude,
|
|
1015
|
-
absolute: true
|
|
1016
|
-
});
|
|
1017
|
-
allFiles.push(...matches);
|
|
1018
|
-
}
|
|
1019
|
-
return [...new Set(allFiles)];
|
|
1020
|
-
}
|
|
1021
|
-
/**
|
|
1022
|
-
* 단일 파일 파싱
|
|
1023
|
-
*/
|
|
1024
|
-
async parseFile(filePath, rootDir) {
|
|
1025
|
-
const relativePath = path2.relative(rootDir, filePath);
|
|
1026
|
-
const fileType = this.determineFileType(relativePath);
|
|
1027
|
-
if (!fileType) {
|
|
1028
|
-
return null;
|
|
1029
|
-
}
|
|
1030
|
-
const content = await fs4.readFile(filePath, "utf-8");
|
|
1031
|
-
const ext = path2.extname(filePath);
|
|
1032
|
-
if (ext === ".sql") {
|
|
1033
|
-
return this.sqlParser.parse(content, relativePath);
|
|
1034
|
-
}
|
|
1035
|
-
const sourceFile = this.tsParser.parse(content, filePath);
|
|
1036
|
-
const imports = this.importExtractor.extract(sourceFile);
|
|
1037
|
-
const exports$1 = this.exportExtractor.extract(sourceFile);
|
|
1038
|
-
const props = fileType === "component" ? this.propsExtractor.extract(sourceFile) : void 0;
|
|
1039
|
-
return {
|
|
1040
|
-
path: relativePath,
|
|
1041
|
-
type: fileType,
|
|
1042
|
-
name: this.extractName(relativePath, exports$1),
|
|
1043
|
-
imports,
|
|
1044
|
-
exports: exports$1,
|
|
1045
|
-
props
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
/**
|
|
1049
|
-
* 파일 타입 결정
|
|
1050
|
-
*/
|
|
1051
|
-
determineFileType(relativePath) {
|
|
1052
|
-
const sortedPatterns = Object.entries(this.config.fileTypeMapping).sort(
|
|
1053
|
-
([a], [b]) => b.length - a.length
|
|
1054
|
-
);
|
|
1055
|
-
for (const [pattern, type] of sortedPatterns) {
|
|
1056
|
-
const regex = globToRegex(pattern);
|
|
1057
|
-
if (regex.test(relativePath)) {
|
|
1058
|
-
return type;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
return null;
|
|
1062
|
-
}
|
|
1063
|
-
/**
|
|
1064
|
-
* 파일/컴포넌트 이름 추출
|
|
1065
|
-
*/
|
|
1066
|
-
extractName(relativePath, exports$1) {
|
|
1067
|
-
const defaultExport = exports$1.find((e) => e.isDefault);
|
|
1068
|
-
if (defaultExport && defaultExport.name !== "default") {
|
|
1069
|
-
return defaultExport.name;
|
|
1070
|
-
}
|
|
1071
|
-
const basename4 = path2.basename(relativePath, path2.extname(relativePath));
|
|
1072
|
-
if (["index", "page", "layout", "route"].includes(basename4)) {
|
|
1073
|
-
const dirname4 = path2.dirname(relativePath);
|
|
1074
|
-
const parentName = path2.basename(dirname4);
|
|
1075
|
-
return parentName !== "." ? parentName : basename4;
|
|
1076
|
-
}
|
|
1077
|
-
return basename4;
|
|
1078
|
-
}
|
|
1079
|
-
/**
|
|
1080
|
-
* CodeIndexItem 생성
|
|
1081
|
-
*/
|
|
1082
|
-
createIndexItem(parsed, callGraph) {
|
|
1083
|
-
const graphEntry = callGraph.get(parsed.path) || {
|
|
1084
|
-
calls: [],
|
|
1085
|
-
calledBy: []
|
|
1086
|
-
};
|
|
1087
|
-
const keywords = this.keywordExtractor.extract(
|
|
1088
|
-
parsed.name,
|
|
1089
|
-
parsed.path,
|
|
1090
|
-
parsed.exports.map((e) => e.name),
|
|
1091
|
-
parsed.props?.map((p) => p.name)
|
|
1092
|
-
);
|
|
1093
|
-
const searchText = this.buildSearchText(parsed, keywords);
|
|
1094
|
-
return {
|
|
1095
|
-
id: generateId(this.config.projectId, parsed.path),
|
|
1096
|
-
projectId: this.config.projectId,
|
|
1097
|
-
type: parsed.type,
|
|
1098
|
-
name: parsed.name,
|
|
1099
|
-
path: parsed.path,
|
|
1100
|
-
keywords,
|
|
1101
|
-
searchText,
|
|
1102
|
-
calls: graphEntry.calls,
|
|
1103
|
-
calledBy: graphEntry.calledBy,
|
|
1104
|
-
metadata: {
|
|
1105
|
-
exports: parsed.exports.map((e) => e.name),
|
|
1106
|
-
props: parsed.props?.map((p) => p.name),
|
|
1107
|
-
...parsed.metadata
|
|
1108
|
-
}
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* 검색용 텍스트 생성
|
|
1113
|
-
*/
|
|
1114
|
-
buildSearchText(parsed, keywords) {
|
|
1115
|
-
const parts = [
|
|
1116
|
-
parsed.name,
|
|
1117
|
-
parsed.path,
|
|
1118
|
-
...keywords,
|
|
1119
|
-
...parsed.exports.map((e) => e.name),
|
|
1120
|
-
...parsed.props?.map((p) => p.name) || []
|
|
1121
|
-
];
|
|
1122
|
-
return parts.join(" ").toLowerCase();
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* 분석 통계 계산
|
|
1126
|
-
*/
|
|
1127
|
-
calculateStats(items, parseErrors) {
|
|
1128
|
-
const byType = {
|
|
1129
|
-
route: 0,
|
|
1130
|
-
component: 0,
|
|
1131
|
-
hook: 0,
|
|
1132
|
-
service: 0,
|
|
1133
|
-
api: 0,
|
|
1134
|
-
table: 0,
|
|
1135
|
-
utility: 0
|
|
1136
|
-
};
|
|
1137
|
-
for (const item of items) {
|
|
1138
|
-
byType[item.type]++;
|
|
1139
|
-
}
|
|
1140
|
-
return {
|
|
1141
|
-
totalFiles: items.length,
|
|
1142
|
-
byType,
|
|
1143
|
-
parseErrors
|
|
1144
|
-
};
|
|
1145
|
-
}
|
|
1146
|
-
};
|
|
1147
|
-
var FileWriter = class {
|
|
1148
|
-
constructor(config) {
|
|
1149
|
-
this.config = config;
|
|
1150
|
-
}
|
|
1151
|
-
/**
|
|
1152
|
-
* 분석 결과를 파일로 저장
|
|
1153
|
-
*/
|
|
1154
|
-
async write(result, outputPath) {
|
|
1155
|
-
const dir = path2.dirname(outputPath);
|
|
1156
|
-
await fs4.mkdir(dir, { recursive: true });
|
|
1157
|
-
const output = this.formatOutput(result);
|
|
1158
|
-
await fs4.writeFile(outputPath, output, "utf-8");
|
|
1159
|
-
}
|
|
1160
|
-
/**
|
|
1161
|
-
* 출력 형식 생성
|
|
1162
|
-
*/
|
|
1163
|
-
formatOutput(result) {
|
|
1164
|
-
const output = {
|
|
1165
|
-
version: "1.0.0",
|
|
1166
|
-
projectId: this.config.projectId,
|
|
1167
|
-
generatedAt: result.timestamp,
|
|
1168
|
-
stats: result.stats,
|
|
1169
|
-
items: result.items
|
|
1170
|
-
};
|
|
1171
|
-
if (this.config.mode === "production") {
|
|
1172
|
-
return JSON.stringify(output);
|
|
1173
|
-
}
|
|
1174
|
-
return JSON.stringify(output, null, 2);
|
|
1175
|
-
}
|
|
1176
|
-
};
|
|
1177
|
-
|
|
1178
|
-
// src/core/output/api-sender.ts
|
|
1179
|
-
var ApiSender = class {
|
|
1180
|
-
constructor(config, options = {}) {
|
|
1181
|
-
this.config = config;
|
|
1182
|
-
this.maxRetries = options.maxRetries ?? 3;
|
|
1183
|
-
this.retryDelay = options.retryDelay ?? 1e3;
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* 분석 결과를 API로 전송
|
|
1187
|
-
*/
|
|
1188
|
-
async send(result) {
|
|
1189
|
-
const apiConfig = this.config.output.api;
|
|
1190
|
-
if (!apiConfig?.enabled || !apiConfig.endpoint) {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
const payload = {
|
|
1194
|
-
projectId: this.config.projectId,
|
|
1195
|
-
timestamp: result.timestamp,
|
|
1196
|
-
items: result.items,
|
|
1197
|
-
stats: result.stats
|
|
1198
|
-
};
|
|
1199
|
-
let lastError = null;
|
|
1200
|
-
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
1201
|
-
try {
|
|
1202
|
-
await this.sendRequest(
|
|
1203
|
-
apiConfig.endpoint,
|
|
1204
|
-
payload,
|
|
1205
|
-
apiConfig.headers
|
|
1206
|
-
);
|
|
1207
|
-
return;
|
|
1208
|
-
} catch (error) {
|
|
1209
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1210
|
-
if (this.config.verbose) {
|
|
1211
|
-
console.warn(
|
|
1212
|
-
`[metadata-plugin] API send attempt ${attempt + 1} failed:`,
|
|
1213
|
-
lastError.message
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
if (attempt < this.maxRetries - 1) {
|
|
1217
|
-
await this.delay(this.retryDelay * (attempt + 1));
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
throw new Error(
|
|
1222
|
-
`Failed to send metadata after ${this.maxRetries} attempts: ${lastError?.message}`
|
|
1223
|
-
);
|
|
1224
|
-
}
|
|
1225
|
-
/**
|
|
1226
|
-
* HTTP POST 요청
|
|
1227
|
-
*/
|
|
1228
|
-
async sendRequest(endpoint, payload, headers) {
|
|
1229
|
-
const response = await fetch(endpoint, {
|
|
1230
|
-
method: "POST",
|
|
1231
|
-
headers: {
|
|
1232
|
-
"Content-Type": "application/json",
|
|
1233
|
-
...headers
|
|
1234
|
-
},
|
|
1235
|
-
body: JSON.stringify(payload)
|
|
1236
|
-
});
|
|
1237
|
-
if (!response.ok) {
|
|
1238
|
-
const errorText = await response.text();
|
|
1239
|
-
throw new Error(`API request failed: ${response.status} ${errorText}`);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
/**
|
|
1243
|
-
* 지연 함수
|
|
1244
|
-
*/
|
|
1245
|
-
delay(ms) {
|
|
1246
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1247
|
-
}
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
// src/cli.ts
|
|
1251
|
-
var VERSION = "1.0.1";
|
|
1252
|
-
var HELP_TEXT = `
|
|
2
|
+
import {d,y,z,A as A$1}from'./chunk-WUEHYY36.js';import {parseArgs}from'util';import*as a from'path';import*as r from'fs/promises';var v="1.0.1",b=`
|
|
1253
3
|
metadatafy - \uD504\uB85C\uC81D\uD2B8 \uBA54\uD0C0\uB370\uC774\uD130 \uCD94\uCD9C \uB3C4\uAD6C
|
|
1254
4
|
|
|
1255
5
|
Usage:
|
|
@@ -1267,8 +17,7 @@ Examples:
|
|
|
1267
17
|
metadatafy analyze
|
|
1268
18
|
metadatafy analyze --project-id my-project --output ./metadata.json
|
|
1269
19
|
metadatafy init
|
|
1270
|
-
|
|
1271
|
-
var ANALYZE_HELP = `
|
|
20
|
+
`,P=`
|
|
1272
21
|
Usage: metadatafy analyze [options]
|
|
1273
22
|
|
|
1274
23
|
Options:
|
|
@@ -1277,174 +26,9 @@ Options:
|
|
|
1277
26
|
-c, --config <path> \uC124\uC815 \uD30C\uC77C \uACBD\uB85C
|
|
1278
27
|
--verbose \uC0C1\uC138 \uB85C\uADF8 \uCD9C\uB825
|
|
1279
28
|
-h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
|
|
1280
|
-
`;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
if (args[0] === "-v" || args[0] === "--version") {
|
|
1288
|
-
console.log(VERSION);
|
|
1289
|
-
process.exit(0);
|
|
1290
|
-
}
|
|
1291
|
-
const command = args[0];
|
|
1292
|
-
switch (command) {
|
|
1293
|
-
case "analyze":
|
|
1294
|
-
await runAnalyze(args.slice(1));
|
|
1295
|
-
break;
|
|
1296
|
-
case "init":
|
|
1297
|
-
await runInit();
|
|
1298
|
-
break;
|
|
1299
|
-
default:
|
|
1300
|
-
console.error(`Unknown command: ${command}`);
|
|
1301
|
-
console.log(HELP_TEXT);
|
|
1302
|
-
process.exit(1);
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
async function runAnalyze(args) {
|
|
1306
|
-
const { values } = parseArgs({
|
|
1307
|
-
args,
|
|
1308
|
-
options: {
|
|
1309
|
-
"project-id": { type: "string", short: "p" },
|
|
1310
|
-
output: { type: "string", short: "o" },
|
|
1311
|
-
config: { type: "string", short: "c" },
|
|
1312
|
-
verbose: { type: "boolean" },
|
|
1313
|
-
help: { type: "boolean", short: "h" }
|
|
1314
|
-
}
|
|
1315
|
-
});
|
|
1316
|
-
if (values.help) {
|
|
1317
|
-
console.log(ANALYZE_HELP);
|
|
1318
|
-
process.exit(0);
|
|
1319
|
-
}
|
|
1320
|
-
const rootDir = process.cwd();
|
|
1321
|
-
const projectId = values["project-id"] || path2.basename(rootDir);
|
|
1322
|
-
const outputPath = values.output || "project-metadata.json";
|
|
1323
|
-
const verbose = values.verbose || false;
|
|
1324
|
-
let configFromFile = {};
|
|
1325
|
-
if (values.config) {
|
|
1326
|
-
try {
|
|
1327
|
-
const configContent = await fs4.readFile(values.config, "utf-8");
|
|
1328
|
-
configFromFile = JSON.parse(configContent);
|
|
1329
|
-
} catch (error) {
|
|
1330
|
-
console.error(`Failed to load config file: ${values.config}`);
|
|
1331
|
-
process.exit(1);
|
|
1332
|
-
}
|
|
1333
|
-
} else {
|
|
1334
|
-
const defaultConfigPath = path2.join(rootDir, "metadata.config.json");
|
|
1335
|
-
try {
|
|
1336
|
-
const configContent = await fs4.readFile(defaultConfigPath, "utf-8");
|
|
1337
|
-
configFromFile = JSON.parse(configContent);
|
|
1338
|
-
if (verbose) {
|
|
1339
|
-
console.log(`Loaded config from ${defaultConfigPath}`);
|
|
1340
|
-
}
|
|
1341
|
-
} catch {
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
const config = createDefaultConfig({
|
|
1345
|
-
...configFromFile,
|
|
1346
|
-
projectId,
|
|
1347
|
-
verbose,
|
|
1348
|
-
output: {
|
|
1349
|
-
file: {
|
|
1350
|
-
enabled: true,
|
|
1351
|
-
path: outputPath
|
|
1352
|
-
},
|
|
1353
|
-
...configFromFile.output
|
|
1354
|
-
}
|
|
1355
|
-
});
|
|
1356
|
-
console.log(`
|
|
1357
|
-
\u{1F4E6} Analyzing project: ${projectId}`);
|
|
1358
|
-
console.log(`\u{1F4C1} Root directory: ${rootDir}
|
|
1359
|
-
`);
|
|
1360
|
-
const analyzer = new ProjectAnalyzer(config);
|
|
1361
|
-
const fileWriter = new FileWriter(config);
|
|
1362
|
-
try {
|
|
1363
|
-
const startTime = Date.now();
|
|
1364
|
-
const result = await analyzer.analyze(rootDir);
|
|
1365
|
-
const duration = Date.now() - startTime;
|
|
1366
|
-
const fullOutputPath = path2.resolve(rootDir, outputPath);
|
|
1367
|
-
await fileWriter.write(result, fullOutputPath);
|
|
1368
|
-
if (config.output.api?.enabled && config.output.api.endpoint) {
|
|
1369
|
-
const apiSender = new ApiSender(config);
|
|
1370
|
-
await apiSender.send(result);
|
|
1371
|
-
console.log(`\u2601\uFE0F Sent to API: ${config.output.api.endpoint}`);
|
|
1372
|
-
}
|
|
1373
|
-
console.log(`\u2705 Analysis completed in ${duration}ms
|
|
1374
|
-
`);
|
|
1375
|
-
console.log(`\u{1F4CA} Results:`);
|
|
1376
|
-
console.log(` Total files: ${result.stats.totalFiles}`);
|
|
1377
|
-
console.log(` - Routes: ${result.stats.byType.route}`);
|
|
1378
|
-
console.log(` - Components: ${result.stats.byType.component}`);
|
|
1379
|
-
console.log(` - Hooks: ${result.stats.byType.hook}`);
|
|
1380
|
-
console.log(` - Services: ${result.stats.byType.service}`);
|
|
1381
|
-
console.log(` - APIs: ${result.stats.byType.api}`);
|
|
1382
|
-
console.log(` - Tables: ${result.stats.byType.table}`);
|
|
1383
|
-
console.log(` - Utilities: ${result.stats.byType.utility}`);
|
|
1384
|
-
console.log(`
|
|
1385
|
-
\u{1F4C4} Output: ${fullOutputPath}`);
|
|
1386
|
-
if (result.stats.parseErrors.length > 0) {
|
|
1387
|
-
console.log(`
|
|
1388
|
-
\u26A0\uFE0F Parse errors (${result.stats.parseErrors.length}):`);
|
|
1389
|
-
result.stats.parseErrors.slice(0, 5).forEach((err) => {
|
|
1390
|
-
console.log(` - ${err}`);
|
|
1391
|
-
});
|
|
1392
|
-
if (result.stats.parseErrors.length > 5) {
|
|
1393
|
-
console.log(` ... and ${result.stats.parseErrors.length - 5} more`);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
console.log("");
|
|
1397
|
-
} catch (error) {
|
|
1398
|
-
console.error("\u274C Analysis failed:", error);
|
|
1399
|
-
process.exit(1);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
async function runInit() {
|
|
1403
|
-
const rootDir = process.cwd();
|
|
1404
|
-
const configPath = path2.join(rootDir, "metadata.config.json");
|
|
1405
|
-
try {
|
|
1406
|
-
await fs4.access(configPath);
|
|
1407
|
-
console.log(`\u26A0\uFE0F Config file already exists: ${configPath}`);
|
|
1408
|
-
process.exit(1);
|
|
1409
|
-
} catch {
|
|
1410
|
-
}
|
|
1411
|
-
const defaultConfig = {
|
|
1412
|
-
projectId: path2.basename(rootDir),
|
|
1413
|
-
include: [
|
|
1414
|
-
"app/**/*.{ts,tsx}",
|
|
1415
|
-
"pages/**/*.{ts,tsx}",
|
|
1416
|
-
"components/**/*.{ts,tsx}",
|
|
1417
|
-
"hooks/**/*.{ts,tsx}",
|
|
1418
|
-
"services/**/*.ts",
|
|
1419
|
-
"lib/**/*.ts",
|
|
1420
|
-
"src/**/*.{ts,tsx}"
|
|
1421
|
-
],
|
|
1422
|
-
exclude: [
|
|
1423
|
-
"**/node_modules/**",
|
|
1424
|
-
"**/.next/**",
|
|
1425
|
-
"**/dist/**",
|
|
1426
|
-
"**/*.test.{ts,tsx}",
|
|
1427
|
-
"**/*.spec.{ts,tsx}"
|
|
1428
|
-
],
|
|
1429
|
-
output: {
|
|
1430
|
-
file: {
|
|
1431
|
-
enabled: true,
|
|
1432
|
-
path: "project-metadata.json"
|
|
1433
|
-
},
|
|
1434
|
-
api: {
|
|
1435
|
-
enabled: false,
|
|
1436
|
-
endpoint: ""
|
|
1437
|
-
}
|
|
1438
|
-
},
|
|
1439
|
-
koreanKeywords: {},
|
|
1440
|
-
verbose: false
|
|
1441
|
-
};
|
|
1442
|
-
await fs4.writeFile(configPath, JSON.stringify(defaultConfig, null, 2));
|
|
1443
|
-
console.log(`\u2705 Created config file: ${configPath}`);
|
|
1444
|
-
}
|
|
1445
|
-
main().catch((error) => {
|
|
1446
|
-
console.error("Fatal error:", error);
|
|
1447
|
-
process.exit(1);
|
|
1448
|
-
});
|
|
1449
|
-
//# sourceMappingURL=cli.js.map
|
|
1450
|
-
//# sourceMappingURL=cli.js.map
|
|
29
|
+
`;async function E(){let e=process.argv.slice(2);(e.length===0||e[0]==="-h"||e[0]==="--help")&&(console.log(b),process.exit(0)),(e[0]==="-v"||e[0]==="--version")&&(console.log(v),process.exit(0));let t=e[0];switch(t){case "analyze":await T(e.slice(1));break;case "init":await A();break;default:console.error(`Unknown command: ${t}`),console.log(b),process.exit(1);}}async function T(e){let{values:t}=parseArgs({args:e,options:{"project-id":{type:"string",short:"p"},output:{type:"string",short:"o"},config:{type:"string",short:"c"},verbose:{type:"boolean"},help:{type:"boolean",short:"h"}}});t.help&&(console.log(P),process.exit(0));let n=process.cwd(),l=t["project-id"]||a.basename(n),p=t.output||"project-metadata.json",f=t.verbose||false,c={};if(t.config)try{let s=await r.readFile(t.config,"utf-8");c=JSON.parse(s);}catch{console.error(`Failed to load config file: ${t.config}`),process.exit(1);}else {let s=a.join(n,"metadata.config.json");try{let o=await r.readFile(s,"utf-8");c=JSON.parse(o),f&&console.log(`Loaded config from ${s}`);}catch{}}let i=d({...c,projectId:l,verbose:f,output:{file:{enabled:true,path:p},...c.output}});console.log(`
|
|
30
|
+
\u{1F4E6} Analyzing project: ${l}`),console.log(`\u{1F4C1} Root directory: ${n}
|
|
31
|
+
`);let w=new y(i),$=new z(i);try{let s=Date.now(),o=await w.analyze(n),j=Date.now()-s,g=a.resolve(n,p);await $.write(o,g),i.output.api?.enabled&&i.output.api.endpoint&&(await new A$1(i).send(o),console.log(`\u2601\uFE0F Sent to API: ${i.output.api.endpoint}`)),console.log(`\u2705 Analysis completed in ${j}ms
|
|
32
|
+
`),console.log("\u{1F4CA} Results:"),console.log(` Total files: ${o.stats.totalFiles}`),console.log(` - Routes: ${o.stats.byType.route}`),console.log(` - Components: ${o.stats.byType.component}`),console.log(` - Hooks: ${o.stats.byType.hook}`),console.log(` - Services: ${o.stats.byType.service}`),console.log(` - APIs: ${o.stats.byType.api}`),console.log(` - Tables: ${o.stats.byType.table}`),console.log(` - Utilities: ${o.stats.byType.utility}`),console.log(`
|
|
33
|
+
\u{1F4C4} Output: ${g}`),o.stats.parseErrors.length>0&&(console.log(`
|
|
34
|
+
\u26A0\uFE0F Parse errors (${o.stats.parseErrors.length}):`),o.stats.parseErrors.slice(0,5).forEach(d=>{console.log(` - ${d}`);}),o.stats.parseErrors.length>5&&console.log(` ... and ${o.stats.parseErrors.length-5} more`)),console.log("");}catch(s){console.error("\u274C Analysis failed:",s),process.exit(1);}}async function A(){let e=process.cwd(),t=a.join(e,"metadata.config.json");try{await r.access(t),console.log(`\u26A0\uFE0F Config file already exists: ${t}`),process.exit(1);}catch{}let n={projectId:a.basename(e),include:["app/**/*.{ts,tsx}","pages/**/*.{ts,tsx}","components/**/*.{ts,tsx}","hooks/**/*.{ts,tsx}","services/**/*.ts","lib/**/*.ts","src/**/*.{ts,tsx}"],exclude:["**/node_modules/**","**/.next/**","**/dist/**","**/*.test.{ts,tsx}","**/*.spec.{ts,tsx}"],output:{file:{enabled:true,path:"project-metadata.json"},api:{enabled:false,endpoint:""}},koreanKeywords:{},verbose:false};await r.writeFile(t,JSON.stringify(n,null,2)),console.log(`\u2705 Created config file: ${t}`);}E().catch(e=>{console.error("Fatal error:",e),process.exit(1);});
|