@mandujs/core 0.3.3 → 0.3.4
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/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/slot/corrector.ts +282 -0
- package/src/slot/index.ts +18 -0
- package/src/slot/validator.ts +241 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot Content Corrector
|
|
3
|
+
* 슬롯 파일의 자동 수정 가능한 문제를 해결합니다.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SlotValidationIssue } from "./validator";
|
|
7
|
+
|
|
8
|
+
export interface CorrectionResult {
|
|
9
|
+
corrected: boolean;
|
|
10
|
+
content: string;
|
|
11
|
+
appliedFixes: AppliedFix[];
|
|
12
|
+
remainingIssues: SlotValidationIssue[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AppliedFix {
|
|
16
|
+
code: string;
|
|
17
|
+
description: string;
|
|
18
|
+
before?: string;
|
|
19
|
+
after?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 금지된 import를 대체할 안전한 패턴
|
|
23
|
+
const SAFE_ALTERNATIVES: Record<string, string> = {
|
|
24
|
+
fs: "// Use Bun.file() or Bun.write() instead of fs",
|
|
25
|
+
"node:fs": "// Use Bun.file() or Bun.write() instead of fs",
|
|
26
|
+
child_process: "// Use Bun.spawn() or Bun.spawnSync() instead",
|
|
27
|
+
"node:child_process": "// Use Bun.spawn() or Bun.spawnSync() instead",
|
|
28
|
+
cluster: "// Clustering should be handled at the infrastructure level",
|
|
29
|
+
"node:cluster": "// Clustering should be handled at the infrastructure level",
|
|
30
|
+
worker_threads: "// Use Bun workers or external job queues",
|
|
31
|
+
"node:worker_threads": "// Use Bun workers or external job queues",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 슬롯 내용을 자동 수정합니다.
|
|
36
|
+
*/
|
|
37
|
+
export function correctSlotContent(
|
|
38
|
+
content: string,
|
|
39
|
+
issues: SlotValidationIssue[]
|
|
40
|
+
): CorrectionResult {
|
|
41
|
+
let correctedContent = content;
|
|
42
|
+
const appliedFixes: AppliedFix[] = [];
|
|
43
|
+
const remainingIssues: SlotValidationIssue[] = [];
|
|
44
|
+
|
|
45
|
+
for (const issue of issues) {
|
|
46
|
+
if (!issue.autoFixable) {
|
|
47
|
+
remainingIssues.push(issue);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const result = applyFix(correctedContent, issue);
|
|
52
|
+
if (result.fixed) {
|
|
53
|
+
correctedContent = result.content;
|
|
54
|
+
appliedFixes.push({
|
|
55
|
+
code: issue.code,
|
|
56
|
+
description: issue.message,
|
|
57
|
+
before: result.before,
|
|
58
|
+
after: result.after,
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
remainingIssues.push(issue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
corrected: appliedFixes.length > 0,
|
|
67
|
+
content: correctedContent,
|
|
68
|
+
appliedFixes,
|
|
69
|
+
remainingIssues,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface FixResult {
|
|
74
|
+
fixed: boolean;
|
|
75
|
+
content: string;
|
|
76
|
+
before?: string;
|
|
77
|
+
after?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function applyFix(content: string, issue: SlotValidationIssue): FixResult {
|
|
81
|
+
switch (issue.code) {
|
|
82
|
+
case "FORBIDDEN_IMPORT":
|
|
83
|
+
return fixForbiddenImport(content, issue);
|
|
84
|
+
|
|
85
|
+
case "MISSING_MANDU_IMPORT":
|
|
86
|
+
return fixMissingManduImport(content);
|
|
87
|
+
|
|
88
|
+
case "MISSING_DEFAULT_EXPORT":
|
|
89
|
+
return fixMissingDefaultExport(content);
|
|
90
|
+
|
|
91
|
+
default:
|
|
92
|
+
return { fixed: false, content };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 금지된 import를 제거하고 주석으로 대체
|
|
98
|
+
*/
|
|
99
|
+
function fixForbiddenImport(
|
|
100
|
+
content: string,
|
|
101
|
+
issue: SlotValidationIssue
|
|
102
|
+
): FixResult {
|
|
103
|
+
const lines = content.split("\n");
|
|
104
|
+
|
|
105
|
+
if (!issue.line) {
|
|
106
|
+
return { fixed: false, content };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const lineIndex = issue.line - 1;
|
|
110
|
+
const originalLine = lines[lineIndex];
|
|
111
|
+
|
|
112
|
+
// 어떤 금지된 모듈인지 찾기
|
|
113
|
+
let forbiddenModule = "";
|
|
114
|
+
for (const [module, alternative] of Object.entries(SAFE_ALTERNATIVES)) {
|
|
115
|
+
if (originalLine.includes(`'${module}'`) || originalLine.includes(`"${module}"`)) {
|
|
116
|
+
forbiddenModule = module;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!forbiddenModule) {
|
|
122
|
+
return { fixed: false, content };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// import 문을 주석으로 대체
|
|
126
|
+
const alternative = SAFE_ALTERNATIVES[forbiddenModule];
|
|
127
|
+
lines[lineIndex] = `// REMOVED: ${originalLine.trim()}\n${alternative}`;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
fixed: true,
|
|
131
|
+
content: lines.join("\n"),
|
|
132
|
+
before: originalLine,
|
|
133
|
+
after: lines[lineIndex],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Mandu import 추가
|
|
139
|
+
*/
|
|
140
|
+
function fixMissingManduImport(content: string): FixResult {
|
|
141
|
+
const manduImport = `import { Mandu } from "@mandujs/core";\n`;
|
|
142
|
+
|
|
143
|
+
// 이미 다른 import가 있는지 확인
|
|
144
|
+
const hasImports = /^import\s+/m.test(content);
|
|
145
|
+
|
|
146
|
+
let newContent: string;
|
|
147
|
+
if (hasImports) {
|
|
148
|
+
// 첫 번째 import 앞에 추가
|
|
149
|
+
newContent = content.replace(/^(import\s+)/m, `${manduImport}$1`);
|
|
150
|
+
} else {
|
|
151
|
+
// 파일 맨 앞에 추가
|
|
152
|
+
newContent = manduImport + content;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
fixed: true,
|
|
157
|
+
content: newContent,
|
|
158
|
+
before: "(없음)",
|
|
159
|
+
after: manduImport.trim(),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* default export 추가
|
|
165
|
+
*/
|
|
166
|
+
function fixMissingDefaultExport(content: string): FixResult {
|
|
167
|
+
// Mandu.filling()이 있는지 확인
|
|
168
|
+
const fillingMatch = content.match(/Mandu\s*\.\s*filling\s*\(\s*\)/);
|
|
169
|
+
|
|
170
|
+
if (!fillingMatch) {
|
|
171
|
+
// filling 패턴이 없으면 수정 불가
|
|
172
|
+
return { fixed: false, content };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// export default가 없는 Mandu.filling() 찾기
|
|
176
|
+
// 예: const handler = Mandu.filling()... -> export default Mandu.filling()...
|
|
177
|
+
const patterns = [
|
|
178
|
+
// const/let/var handler = Mandu.filling()
|
|
179
|
+
/^(\s*)(const|let|var)\s+\w+\s*=\s*(Mandu\s*\.\s*filling\s*\(\s*\))/m,
|
|
180
|
+
// 단독 Mandu.filling()
|
|
181
|
+
/^(\s*)(Mandu\s*\.\s*filling\s*\(\s*\))/m,
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
for (const pattern of patterns) {
|
|
185
|
+
if (pattern.test(content)) {
|
|
186
|
+
const newContent = content.replace(pattern, "$1export default $3");
|
|
187
|
+
return {
|
|
188
|
+
fixed: true,
|
|
189
|
+
content: newContent,
|
|
190
|
+
before: content.match(pattern)?.[0],
|
|
191
|
+
after: newContent.match(/export default Mandu\.filling\(\)/)?.[0],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 파일 끝에 export default 추가 시도
|
|
197
|
+
if (content.includes("Mandu.filling()") && !content.includes("export default")) {
|
|
198
|
+
// 마지막 세미콜론 또는 중괄호 뒤에 추가
|
|
199
|
+
const lastLine = content.trimEnd();
|
|
200
|
+
if (!lastLine.endsWith(";") && !lastLine.endsWith("}")) {
|
|
201
|
+
return { fixed: false, content };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { fixed: false, content };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 여러 번의 수정 시도 (Self-correction loop)
|
|
210
|
+
*/
|
|
211
|
+
export async function runSlotCorrection(
|
|
212
|
+
content: string,
|
|
213
|
+
validateFn: (content: string) => { valid: boolean; issues: SlotValidationIssue[] },
|
|
214
|
+
maxRetries: number = 3
|
|
215
|
+
): Promise<{
|
|
216
|
+
success: boolean;
|
|
217
|
+
finalContent: string;
|
|
218
|
+
attempts: number;
|
|
219
|
+
allFixes: AppliedFix[];
|
|
220
|
+
remainingIssues: SlotValidationIssue[];
|
|
221
|
+
}> {
|
|
222
|
+
let currentContent = content;
|
|
223
|
+
let attempts = 0;
|
|
224
|
+
const allFixes: AppliedFix[] = [];
|
|
225
|
+
|
|
226
|
+
while (attempts < maxRetries) {
|
|
227
|
+
attempts++;
|
|
228
|
+
|
|
229
|
+
// 1. 검증
|
|
230
|
+
const validation = validateFn(currentContent);
|
|
231
|
+
|
|
232
|
+
if (validation.valid) {
|
|
233
|
+
return {
|
|
234
|
+
success: true,
|
|
235
|
+
finalContent: currentContent,
|
|
236
|
+
attempts,
|
|
237
|
+
allFixes,
|
|
238
|
+
remainingIssues: [],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2. 자동 수정 가능한 문제가 있는지 확인
|
|
243
|
+
const autoFixable = validation.issues.filter((i) => i.autoFixable);
|
|
244
|
+
if (autoFixable.length === 0) {
|
|
245
|
+
// 자동 수정 불가능한 문제만 남음
|
|
246
|
+
return {
|
|
247
|
+
success: false,
|
|
248
|
+
finalContent: currentContent,
|
|
249
|
+
attempts,
|
|
250
|
+
allFixes,
|
|
251
|
+
remainingIssues: validation.issues,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 3. 수정 적용
|
|
256
|
+
const correction = correctSlotContent(currentContent, validation.issues);
|
|
257
|
+
allFixes.push(...correction.appliedFixes);
|
|
258
|
+
|
|
259
|
+
if (!correction.corrected) {
|
|
260
|
+
// 수정이 적용되지 않음
|
|
261
|
+
return {
|
|
262
|
+
success: false,
|
|
263
|
+
finalContent: currentContent,
|
|
264
|
+
attempts,
|
|
265
|
+
allFixes,
|
|
266
|
+
remainingIssues: validation.issues,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
currentContent = correction.content;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// maxRetries 도달
|
|
274
|
+
const finalValidation = validateFn(currentContent);
|
|
275
|
+
return {
|
|
276
|
+
success: finalValidation.valid,
|
|
277
|
+
finalContent: currentContent,
|
|
278
|
+
attempts,
|
|
279
|
+
allFixes,
|
|
280
|
+
remainingIssues: finalValidation.issues,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot Module
|
|
3
|
+
* 슬롯 파일 검증 및 자동 수정 기능
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
validateSlotContent,
|
|
8
|
+
summarizeValidationIssues,
|
|
9
|
+
type SlotValidationIssue,
|
|
10
|
+
type SlotValidationResult,
|
|
11
|
+
} from "./validator";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
correctSlotContent,
|
|
15
|
+
runSlotCorrection,
|
|
16
|
+
type CorrectionResult,
|
|
17
|
+
type AppliedFix,
|
|
18
|
+
} from "./corrector";
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot Content Validator
|
|
3
|
+
* 슬롯 파일 내용을 작성 전에 검증하고 문제를 식별합니다.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface SlotValidationIssue {
|
|
7
|
+
code: string;
|
|
8
|
+
severity: "error" | "warning";
|
|
9
|
+
message: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
suggestion: string;
|
|
12
|
+
autoFixable: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SlotValidationResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
issues: SlotValidationIssue[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 금지된 import 모듈들
|
|
21
|
+
const FORBIDDEN_IMPORTS = [
|
|
22
|
+
"fs",
|
|
23
|
+
"child_process",
|
|
24
|
+
"cluster",
|
|
25
|
+
"worker_threads",
|
|
26
|
+
"node:fs",
|
|
27
|
+
"node:child_process",
|
|
28
|
+
"node:cluster",
|
|
29
|
+
"node:worker_threads",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// 필수 패턴들
|
|
33
|
+
const REQUIRED_PATTERNS = {
|
|
34
|
+
manduImport: /import\s+.*\bMandu\b.*from\s+['"]@mandujs\/core['"]/,
|
|
35
|
+
fillingPattern: /Mandu\s*\.\s*filling\s*\(\s*\)/,
|
|
36
|
+
defaultExport: /export\s+default\b/,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 슬롯 내용을 검증합니다.
|
|
41
|
+
*/
|
|
42
|
+
export function validateSlotContent(content: string): SlotValidationResult {
|
|
43
|
+
const issues: SlotValidationIssue[] = [];
|
|
44
|
+
const lines = content.split("\n");
|
|
45
|
+
|
|
46
|
+
// 1. 금지된 import 검사
|
|
47
|
+
for (let i = 0; i < lines.length; i++) {
|
|
48
|
+
const line = lines[i];
|
|
49
|
+
for (const forbidden of FORBIDDEN_IMPORTS) {
|
|
50
|
+
// import 문에서 금지된 모듈 체크
|
|
51
|
+
const importPattern = new RegExp(
|
|
52
|
+
`import\\s+.*from\\s+['"]${forbidden.replace("/", "\\/")}['"]`
|
|
53
|
+
);
|
|
54
|
+
const requirePattern = new RegExp(
|
|
55
|
+
`require\\s*\\(\\s*['"]${forbidden.replace("/", "\\/")}['"]\\s*\\)`
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (importPattern.test(line) || requirePattern.test(line)) {
|
|
59
|
+
issues.push({
|
|
60
|
+
code: "FORBIDDEN_IMPORT",
|
|
61
|
+
severity: "error",
|
|
62
|
+
message: `금지된 모듈 import: '${forbidden}'`,
|
|
63
|
+
line: i + 1,
|
|
64
|
+
suggestion: `'${forbidden}' 대신 Bun의 안전한 API 또는 adapter를 사용하세요`,
|
|
65
|
+
autoFixable: true,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Mandu import 검사
|
|
72
|
+
if (!REQUIRED_PATTERNS.manduImport.test(content)) {
|
|
73
|
+
issues.push({
|
|
74
|
+
code: "MISSING_MANDU_IMPORT",
|
|
75
|
+
severity: "error",
|
|
76
|
+
message: "Mandu import가 없습니다",
|
|
77
|
+
suggestion: "import { Mandu } from '@mandujs/core' 추가 필요",
|
|
78
|
+
autoFixable: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 3. Mandu.filling() 패턴 검사
|
|
83
|
+
if (!REQUIRED_PATTERNS.fillingPattern.test(content)) {
|
|
84
|
+
issues.push({
|
|
85
|
+
code: "MISSING_FILLING_PATTERN",
|
|
86
|
+
severity: "error",
|
|
87
|
+
message: "Mandu.filling() 패턴이 없습니다",
|
|
88
|
+
suggestion: "슬롯은 Mandu.filling()으로 시작해야 합니다",
|
|
89
|
+
autoFixable: false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. default export 검사
|
|
94
|
+
if (!REQUIRED_PATTERNS.defaultExport.test(content)) {
|
|
95
|
+
issues.push({
|
|
96
|
+
code: "MISSING_DEFAULT_EXPORT",
|
|
97
|
+
severity: "error",
|
|
98
|
+
message: "default export가 없습니다",
|
|
99
|
+
suggestion: "export default Mandu.filling()... 형태로 작성하세요",
|
|
100
|
+
autoFixable: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 5. 기본 문법 검사 (간단한 체크)
|
|
105
|
+
const syntaxIssues = checkBasicSyntax(content, lines);
|
|
106
|
+
issues.push(...syntaxIssues);
|
|
107
|
+
|
|
108
|
+
// 6. HTTP 메서드 핸들러 검사
|
|
109
|
+
const methodIssues = checkHttpMethods(content);
|
|
110
|
+
issues.push(...methodIssues);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
valid: issues.filter((i) => i.severity === "error").length === 0,
|
|
114
|
+
issues,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 기본 문법 검사
|
|
120
|
+
*/
|
|
121
|
+
function checkBasicSyntax(
|
|
122
|
+
content: string,
|
|
123
|
+
lines: string[]
|
|
124
|
+
): SlotValidationIssue[] {
|
|
125
|
+
const issues: SlotValidationIssue[] = [];
|
|
126
|
+
|
|
127
|
+
// 괄호 균형 체크
|
|
128
|
+
const brackets = { "(": 0, "{": 0, "[": 0 };
|
|
129
|
+
const bracketPairs: Record<string, keyof typeof brackets> = {
|
|
130
|
+
")": "(",
|
|
131
|
+
"}": "{",
|
|
132
|
+
"]": "[",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < lines.length; i++) {
|
|
136
|
+
const line = lines[i];
|
|
137
|
+
// 문자열 내부는 스킵 (간단한 처리)
|
|
138
|
+
const withoutStrings = line
|
|
139
|
+
.replace(/"[^"]*"/g, "")
|
|
140
|
+
.replace(/'[^']*'/g, "")
|
|
141
|
+
.replace(/`[^`]*`/g, "");
|
|
142
|
+
|
|
143
|
+
for (const char of withoutStrings) {
|
|
144
|
+
if (char in brackets) {
|
|
145
|
+
brackets[char as keyof typeof brackets]++;
|
|
146
|
+
} else if (char in bracketPairs) {
|
|
147
|
+
brackets[bracketPairs[char]]--;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (brackets["("] !== 0) {
|
|
153
|
+
issues.push({
|
|
154
|
+
code: "UNBALANCED_PARENTHESES",
|
|
155
|
+
severity: "error",
|
|
156
|
+
message: `괄호 불균형: ${brackets["("] > 0 ? "닫는" : "여는"} 괄호 부족`,
|
|
157
|
+
suggestion: "괄호 쌍을 확인하세요",
|
|
158
|
+
autoFixable: false,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (brackets["{"] !== 0) {
|
|
163
|
+
issues.push({
|
|
164
|
+
code: "UNBALANCED_BRACES",
|
|
165
|
+
severity: "error",
|
|
166
|
+
message: `중괄호 불균형: ${brackets["{"] > 0 ? "닫는" : "여는"} 중괄호 부족`,
|
|
167
|
+
suggestion: "중괄호 쌍을 확인하세요",
|
|
168
|
+
autoFixable: false,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (brackets["["] !== 0) {
|
|
173
|
+
issues.push({
|
|
174
|
+
code: "UNBALANCED_BRACKETS",
|
|
175
|
+
severity: "error",
|
|
176
|
+
message: `대괄호 불균형: ${brackets["["] > 0 ? "닫는" : "여는"} 대괄호 부족`,
|
|
177
|
+
suggestion: "대괄호 쌍을 확인하세요",
|
|
178
|
+
autoFixable: false,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return issues;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* HTTP 메서드 핸들러 검사
|
|
187
|
+
*/
|
|
188
|
+
function checkHttpMethods(content: string): SlotValidationIssue[] {
|
|
189
|
+
const issues: SlotValidationIssue[] = [];
|
|
190
|
+
|
|
191
|
+
// .get(), .post() 등의 핸들러가 있는지 확인
|
|
192
|
+
const methodPattern = /\.(get|post|put|patch|delete|options|head)\s*\(/gi;
|
|
193
|
+
const hasMethod = methodPattern.test(content);
|
|
194
|
+
|
|
195
|
+
if (!hasMethod) {
|
|
196
|
+
issues.push({
|
|
197
|
+
code: "NO_HTTP_HANDLER",
|
|
198
|
+
severity: "warning",
|
|
199
|
+
message: "HTTP 메서드 핸들러가 없습니다",
|
|
200
|
+
suggestion:
|
|
201
|
+
".get(ctx => ...), .post(ctx => ...) 등의 핸들러를 추가하세요",
|
|
202
|
+
autoFixable: false,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ctx.ok(), ctx.json() 등 응답 패턴 확인
|
|
207
|
+
const responsePattern = /ctx\s*\.\s*(ok|json|created|noContent|error|html)\s*\(/;
|
|
208
|
+
if (hasMethod && !responsePattern.test(content)) {
|
|
209
|
+
issues.push({
|
|
210
|
+
code: "NO_RESPONSE_PATTERN",
|
|
211
|
+
severity: "warning",
|
|
212
|
+
message: "응답 패턴이 없습니다",
|
|
213
|
+
suggestion:
|
|
214
|
+
"핸들러에서 ctx.ok(), ctx.json() 등으로 응답을 반환하세요",
|
|
215
|
+
autoFixable: false,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return issues;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 에러 요약 생성
|
|
224
|
+
*/
|
|
225
|
+
export function summarizeValidationIssues(
|
|
226
|
+
issues: SlotValidationIssue[]
|
|
227
|
+
): string {
|
|
228
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
229
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
230
|
+
|
|
231
|
+
const parts: string[] = [];
|
|
232
|
+
|
|
233
|
+
if (errors.length > 0) {
|
|
234
|
+
parts.push(`${errors.length}개 에러`);
|
|
235
|
+
}
|
|
236
|
+
if (warnings.length > 0) {
|
|
237
|
+
parts.push(`${warnings.length}개 경고`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return parts.join(", ") || "문제 없음";
|
|
241
|
+
}
|