@mandujs/core 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +200 -200
- package/README.md +200 -200
- package/package.json +4 -2
- package/src/change/history.ts +145 -0
- package/src/change/index.ts +40 -0
- package/src/change/integrity.ts +81 -0
- package/src/change/snapshot.ts +233 -0
- package/src/change/transaction.ts +293 -0
- package/src/change/types.ts +102 -0
- package/src/error/classifier.ts +314 -0
- package/src/error/formatter.ts +237 -0
- package/src/error/index.ts +25 -0
- package/src/error/stack-analyzer.ts +295 -0
- package/src/error/types.ts +140 -0
- package/src/filling/context.ts +228 -219
- package/src/filling/filling.ts +256 -234
- package/src/filling/index.ts +7 -7
- package/src/generator/generate.ts +85 -3
- package/src/generator/index.ts +2 -2
- package/src/guard/auto-correct.ts +257 -203
- package/src/index.ts +2 -0
- package/src/report/index.ts +1 -1
- package/src/runtime/index.ts +3 -3
- package/src/runtime/router.ts +65 -65
- package/src/runtime/server.ts +189 -139
- package/src/runtime/ssr.ts +38 -38
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import type { GeneratedMap } from "../generator/generate";
|
|
2
|
+
import type { ManduError, RouteContext, ErrorType } from "./types";
|
|
3
|
+
import { ErrorCode, ERROR_MESSAGES, ERROR_SUMMARIES } from "./types";
|
|
4
|
+
import { StackTraceAnalyzer, type StackFrame } from "./stack-analyzer";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ValidationError 타입 체크 (filling/context.ts에서 정의)
|
|
8
|
+
*/
|
|
9
|
+
function isValidationError(error: unknown): error is { errors: unknown[] } {
|
|
10
|
+
return (
|
|
11
|
+
error !== null &&
|
|
12
|
+
typeof error === "object" &&
|
|
13
|
+
"errors" in error &&
|
|
14
|
+
Array.isArray((error as { errors: unknown[] }).errors)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 에러 분류기
|
|
20
|
+
*/
|
|
21
|
+
export class ErrorClassifier {
|
|
22
|
+
private analyzer: StackTraceAnalyzer;
|
|
23
|
+
private routeContext?: RouteContext;
|
|
24
|
+
private isDev: boolean;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
generatedMap: GeneratedMap | null = null,
|
|
28
|
+
routeContext?: RouteContext,
|
|
29
|
+
rootDir: string = process.cwd()
|
|
30
|
+
) {
|
|
31
|
+
this.analyzer = new StackTraceAnalyzer(generatedMap, rootDir);
|
|
32
|
+
this.routeContext = routeContext;
|
|
33
|
+
this.isDev = process.env.NODE_ENV !== "production";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 에러를 ManduError로 분류
|
|
38
|
+
*/
|
|
39
|
+
classify(error: unknown): ManduError {
|
|
40
|
+
// ValidationError 체크
|
|
41
|
+
if (isValidationError(error)) {
|
|
42
|
+
return this.createValidationError(error);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 일반 Error 객체
|
|
46
|
+
if (error instanceof Error) {
|
|
47
|
+
return this.classifyError(error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 그 외 (문자열, 숫자 등)
|
|
51
|
+
return this.createUnknownError(error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* ValidationError 처리
|
|
56
|
+
*/
|
|
57
|
+
private createValidationError(error: { errors: unknown[] }): ManduError {
|
|
58
|
+
const slotFile = this.findSlotFile();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
errorType: "LOGIC_ERROR",
|
|
62
|
+
code: ErrorCode.SLOT_VALIDATION_ERROR,
|
|
63
|
+
message: ERROR_MESSAGES[ErrorCode.SLOT_VALIDATION_ERROR],
|
|
64
|
+
summary: ERROR_SUMMARIES[ErrorCode.SLOT_VALIDATION_ERROR],
|
|
65
|
+
fix: {
|
|
66
|
+
file: slotFile || "spec/slots/",
|
|
67
|
+
suggestion: "요청 데이터가 스키마와 일치하는지 확인하세요",
|
|
68
|
+
},
|
|
69
|
+
route: this.routeContext,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Error 객체 분류
|
|
76
|
+
*/
|
|
77
|
+
private classifyError(error: Error): ManduError {
|
|
78
|
+
const frames = this.analyzer.parseStack(error.stack);
|
|
79
|
+
const source = this.analyzer.determineErrorSource(frames);
|
|
80
|
+
const blameFrame = this.analyzer.findBlameFrame(frames);
|
|
81
|
+
|
|
82
|
+
let errorType: ErrorType;
|
|
83
|
+
let code: ErrorCode;
|
|
84
|
+
let fixFile: string;
|
|
85
|
+
let suggestion: string;
|
|
86
|
+
|
|
87
|
+
switch (source) {
|
|
88
|
+
case "slot":
|
|
89
|
+
errorType = "LOGIC_ERROR";
|
|
90
|
+
code = ErrorCode.SLOT_RUNTIME_ERROR;
|
|
91
|
+
fixFile = blameFrame?.file || this.findSlotFile() || "spec/slots/";
|
|
92
|
+
suggestion = this.generateSlotSuggestion(error);
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case "spec":
|
|
96
|
+
errorType = "SPEC_ERROR";
|
|
97
|
+
code = ErrorCode.SPEC_VALIDATION_ERROR;
|
|
98
|
+
fixFile = blameFrame?.file || "spec/routes.manifest.json";
|
|
99
|
+
suggestion = "Spec 파일의 JSON 구문 또는 스키마를 확인하세요";
|
|
100
|
+
break;
|
|
101
|
+
|
|
102
|
+
case "generated":
|
|
103
|
+
// Generated 파일 에러 → Slot으로 매핑 시도
|
|
104
|
+
if (blameFrame) {
|
|
105
|
+
const slotLocation = this.analyzer.mapToSlotLocation(blameFrame.file, blameFrame.line);
|
|
106
|
+
if (slotLocation) {
|
|
107
|
+
errorType = "LOGIC_ERROR";
|
|
108
|
+
code = ErrorCode.SLOT_RUNTIME_ERROR;
|
|
109
|
+
fixFile = slotLocation.file;
|
|
110
|
+
suggestion = this.generateSlotSuggestion(error);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// 매핑 실패 시 프레임워크 버그로 처리
|
|
115
|
+
errorType = "FRAMEWORK_BUG";
|
|
116
|
+
code = ErrorCode.FRAMEWORK_INTERNAL;
|
|
117
|
+
fixFile = blameFrame?.file || "packages/core/";
|
|
118
|
+
suggestion = "Generated 파일에서 예기치 않은 오류 발생. 버그 리포트를 등록해주세요.";
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case "framework":
|
|
122
|
+
errorType = "FRAMEWORK_BUG";
|
|
123
|
+
code = this.determineFrameworkCode(blameFrame);
|
|
124
|
+
fixFile = blameFrame?.file || "packages/core/";
|
|
125
|
+
suggestion = "Mandu 프레임워크 내부 오류입니다. GitHub 이슈를 등록해주세요.";
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
default:
|
|
129
|
+
// Unknown → 보수적으로 LOGIC_ERROR로 분류
|
|
130
|
+
errorType = "LOGIC_ERROR";
|
|
131
|
+
code = ErrorCode.SLOT_HANDLER_ERROR;
|
|
132
|
+
fixFile = this.findSlotFile() || "spec/slots/";
|
|
133
|
+
suggestion = this.generateSlotSuggestion(error);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const manduError: ManduError = {
|
|
137
|
+
errorType,
|
|
138
|
+
code,
|
|
139
|
+
message: error.message,
|
|
140
|
+
summary: this.generateSummary(code, blameFrame),
|
|
141
|
+
fix: {
|
|
142
|
+
file: fixFile,
|
|
143
|
+
suggestion,
|
|
144
|
+
line: blameFrame?.line,
|
|
145
|
+
},
|
|
146
|
+
route: this.routeContext,
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// 개발 모드에서 디버그 정보 추가
|
|
151
|
+
if (this.isDev && error.stack) {
|
|
152
|
+
manduError.debug = {
|
|
153
|
+
stack: error.stack,
|
|
154
|
+
originalError: error.message,
|
|
155
|
+
generatedFile: this.analyzer.isGeneratedFile(blameFrame?.file || "")
|
|
156
|
+
? blameFrame?.file
|
|
157
|
+
: undefined,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return manduError;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 알 수 없는 타입의 에러 처리
|
|
166
|
+
*/
|
|
167
|
+
private createUnknownError(error: unknown): ManduError {
|
|
168
|
+
const message = typeof error === "string" ? error : String(error);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
errorType: "LOGIC_ERROR",
|
|
172
|
+
code: ErrorCode.SLOT_HANDLER_ERROR,
|
|
173
|
+
message,
|
|
174
|
+
summary: `핸들러 오류 - ${this.findSlotFile() || "slot"} 파일 확인 필요`,
|
|
175
|
+
fix: {
|
|
176
|
+
file: this.findSlotFile() || "spec/slots/",
|
|
177
|
+
suggestion: "핸들러에서 throw된 값을 확인하세요",
|
|
178
|
+
},
|
|
179
|
+
route: this.routeContext,
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 라우트 컨텍스트에서 Slot 파일 경로 찾기
|
|
186
|
+
*/
|
|
187
|
+
private findSlotFile(): string | null {
|
|
188
|
+
if (!this.routeContext?.id) return null;
|
|
189
|
+
return `spec/slots/${this.routeContext.id}.slot.ts`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Slot 에러에 대한 제안 생성
|
|
194
|
+
*/
|
|
195
|
+
private generateSlotSuggestion(error: Error): string {
|
|
196
|
+
const message = error.message.toLowerCase();
|
|
197
|
+
|
|
198
|
+
if (message.includes("undefined") || message.includes("null")) {
|
|
199
|
+
return "null/undefined 처리를 확인하세요. ctx.body() 결과나 파라미터 값이 없을 수 있습니다.";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (message.includes("is not a function")) {
|
|
203
|
+
return "호출하려는 함수가 정의되어 있는지 확인하세요.";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (message.includes("cannot read") || message.includes("cannot access")) {
|
|
207
|
+
return "객체의 속성에 접근하기 전에 객체가 존재하는지 확인하세요.";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (message.includes("import") || message.includes("module")) {
|
|
211
|
+
return "import 경로와 모듈 이름이 올바른지 확인하세요.";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return "slot 파일의 로직을 검토하세요.";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 프레임워크 에러 코드 결정
|
|
219
|
+
*/
|
|
220
|
+
private determineFrameworkCode(frame: StackFrame | null): ErrorCode {
|
|
221
|
+
if (!frame) return ErrorCode.FRAMEWORK_INTERNAL;
|
|
222
|
+
|
|
223
|
+
const file = frame.file.toLowerCase();
|
|
224
|
+
|
|
225
|
+
if (file.includes("generator") || file.includes("generate")) {
|
|
226
|
+
return ErrorCode.FRAMEWORK_GENERATOR_ERROR;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (file.includes("ssr") || file.includes("render")) {
|
|
230
|
+
return ErrorCode.FRAMEWORK_SSR_ERROR;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (file.includes("router") || file.includes("routing")) {
|
|
234
|
+
return ErrorCode.FRAMEWORK_ROUTER_ERROR;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return ErrorCode.FRAMEWORK_INTERNAL;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 요약 메시지 생성
|
|
242
|
+
*/
|
|
243
|
+
private generateSummary(code: ErrorCode, frame: StackFrame | null): string {
|
|
244
|
+
const baseSummary = ERROR_SUMMARIES[code] || "오류 발생";
|
|
245
|
+
|
|
246
|
+
if (frame?.file) {
|
|
247
|
+
const shortFile = frame.file.split("/").pop() || frame.file;
|
|
248
|
+
return `${baseSummary} (${shortFile}:${frame.line || "?"})`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return baseSummary;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* 특정 에러 타입에 대한 ManduError 생성 헬퍼
|
|
257
|
+
*/
|
|
258
|
+
export function createSpecError(
|
|
259
|
+
code: ErrorCode,
|
|
260
|
+
message: string,
|
|
261
|
+
file: string = "spec/routes.manifest.json",
|
|
262
|
+
suggestion?: string
|
|
263
|
+
): ManduError {
|
|
264
|
+
return {
|
|
265
|
+
errorType: "SPEC_ERROR",
|
|
266
|
+
code,
|
|
267
|
+
message,
|
|
268
|
+
summary: ERROR_SUMMARIES[code] || message,
|
|
269
|
+
fix: {
|
|
270
|
+
file,
|
|
271
|
+
suggestion: suggestion || ERROR_MESSAGES[code] || "Spec 파일을 확인하세요",
|
|
272
|
+
},
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function createLogicError(
|
|
278
|
+
code: ErrorCode,
|
|
279
|
+
message: string,
|
|
280
|
+
slotFile: string,
|
|
281
|
+
routeContext?: RouteContext,
|
|
282
|
+
suggestion?: string
|
|
283
|
+
): ManduError {
|
|
284
|
+
return {
|
|
285
|
+
errorType: "LOGIC_ERROR",
|
|
286
|
+
code,
|
|
287
|
+
message,
|
|
288
|
+
summary: ERROR_SUMMARIES[code] || message,
|
|
289
|
+
fix: {
|
|
290
|
+
file: slotFile,
|
|
291
|
+
suggestion: suggestion || ERROR_MESSAGES[code] || "Slot 파일을 확인하세요",
|
|
292
|
+
},
|
|
293
|
+
route: routeContext,
|
|
294
|
+
timestamp: new Date().toISOString(),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function createFrameworkBug(
|
|
299
|
+
code: ErrorCode,
|
|
300
|
+
message: string,
|
|
301
|
+
file?: string
|
|
302
|
+
): ManduError {
|
|
303
|
+
return {
|
|
304
|
+
errorType: "FRAMEWORK_BUG",
|
|
305
|
+
code,
|
|
306
|
+
message,
|
|
307
|
+
summary: ERROR_SUMMARIES[code] || message,
|
|
308
|
+
fix: {
|
|
309
|
+
file: file || "packages/core/",
|
|
310
|
+
suggestion: "Mandu 프레임워크 내부 오류입니다. GitHub 이슈를 등록해주세요.",
|
|
311
|
+
},
|
|
312
|
+
timestamp: new Date().toISOString(),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { ManduError, RouteContext } from "./types";
|
|
2
|
+
import { ErrorCode } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 포맷 옵션
|
|
6
|
+
*/
|
|
7
|
+
export interface FormatOptions {
|
|
8
|
+
/** 개발 모드 (디버그 정보 포함) */
|
|
9
|
+
isDev?: boolean;
|
|
10
|
+
/** 스택 트레이스 포함 */
|
|
11
|
+
includeStack?: boolean;
|
|
12
|
+
/** 색상 사용 (콘솔용) */
|
|
13
|
+
useColors?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ManduError를 JSON 응답용 객체로 포맷
|
|
18
|
+
*/
|
|
19
|
+
export function formatErrorResponse(error: ManduError, options: FormatOptions = {}): object {
|
|
20
|
+
const { isDev = process.env.NODE_ENV !== "production" } = options;
|
|
21
|
+
|
|
22
|
+
const response: Record<string, unknown> = {
|
|
23
|
+
errorType: error.errorType,
|
|
24
|
+
code: error.code,
|
|
25
|
+
message: error.message,
|
|
26
|
+
summary: error.summary,
|
|
27
|
+
fix: error.fix,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (error.route) {
|
|
31
|
+
response.route = error.route;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 개발 모드에서만 디버그 정보 포함
|
|
35
|
+
if (isDev && error.debug) {
|
|
36
|
+
response.debug = error.debug;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
response.timestamp = error.timestamp;
|
|
40
|
+
|
|
41
|
+
return response;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ManduError를 콘솔 출력용 문자열로 포맷
|
|
46
|
+
*/
|
|
47
|
+
export function formatErrorForConsole(error: ManduError, options: FormatOptions = {}): string {
|
|
48
|
+
const { useColors = true, includeStack = true, isDev = true } = options;
|
|
49
|
+
|
|
50
|
+
const lines: string[] = [];
|
|
51
|
+
|
|
52
|
+
// 헤더
|
|
53
|
+
const typeColor = getErrorTypeColor(error.errorType);
|
|
54
|
+
const header = useColors
|
|
55
|
+
? `${typeColor}[${error.errorType}]${RESET} ${error.code}`
|
|
56
|
+
: `[${error.errorType}] ${error.code}`;
|
|
57
|
+
lines.push(header);
|
|
58
|
+
|
|
59
|
+
// 메시지
|
|
60
|
+
lines.push(` ${error.message}`);
|
|
61
|
+
|
|
62
|
+
// 요약
|
|
63
|
+
if (useColors) {
|
|
64
|
+
lines.push(` ${CYAN}→ ${error.summary}${RESET}`);
|
|
65
|
+
} else {
|
|
66
|
+
lines.push(` → ${error.summary}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 수정 안내
|
|
70
|
+
lines.push("");
|
|
71
|
+
if (useColors) {
|
|
72
|
+
lines.push(` ${YELLOW}Fix:${RESET} ${error.fix.file}${error.fix.line ? `:${error.fix.line}` : ""}`);
|
|
73
|
+
lines.push(` ${error.fix.suggestion}`);
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(` Fix: ${error.fix.file}${error.fix.line ? `:${error.fix.line}` : ""}`);
|
|
76
|
+
lines.push(` ${error.fix.suggestion}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 라우트 컨텍스트
|
|
80
|
+
if (error.route) {
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(` Route: ${error.route.id} (${error.route.pattern})`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 디버그 정보 (개발 모드)
|
|
86
|
+
if (isDev && includeStack && error.debug?.stack) {
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push(" Stack:");
|
|
89
|
+
const stackLines = error.debug.stack.split("\n").slice(0, 10);
|
|
90
|
+
for (const stackLine of stackLines) {
|
|
91
|
+
lines.push(` ${stackLine}`);
|
|
92
|
+
}
|
|
93
|
+
if (error.debug.stack.split("\n").length > 10) {
|
|
94
|
+
lines.push(" ...(truncated)");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 404 에러 응답 생성
|
|
103
|
+
*/
|
|
104
|
+
export function createNotFoundResponse(
|
|
105
|
+
pathname: string,
|
|
106
|
+
routeContext?: RouteContext
|
|
107
|
+
): ManduError {
|
|
108
|
+
return {
|
|
109
|
+
errorType: "SPEC_ERROR",
|
|
110
|
+
code: ErrorCode.SPEC_ROUTE_NOT_FOUND,
|
|
111
|
+
message: `Route not found: ${pathname}`,
|
|
112
|
+
summary: "라우트 없음 - spec 파일에 추가 필요",
|
|
113
|
+
fix: {
|
|
114
|
+
file: "spec/routes.manifest.json",
|
|
115
|
+
suggestion: `'${pathname}' 패턴의 라우트를 추가하세요`,
|
|
116
|
+
},
|
|
117
|
+
route: routeContext,
|
|
118
|
+
timestamp: new Date().toISOString(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 핸들러 미등록 에러 응답 생성
|
|
124
|
+
*/
|
|
125
|
+
export function createHandlerNotFoundResponse(
|
|
126
|
+
routeId: string,
|
|
127
|
+
pattern: string
|
|
128
|
+
): ManduError {
|
|
129
|
+
return {
|
|
130
|
+
errorType: "FRAMEWORK_BUG",
|
|
131
|
+
code: ErrorCode.FRAMEWORK_ROUTER_ERROR,
|
|
132
|
+
message: `Handler not registered for route: ${routeId}`,
|
|
133
|
+
summary: "핸들러 미등록 - generate 재실행 필요",
|
|
134
|
+
fix: {
|
|
135
|
+
file: `apps/server/generated/routes/${routeId}.route.ts`,
|
|
136
|
+
suggestion: "bunx mandu generate를 실행하세요",
|
|
137
|
+
},
|
|
138
|
+
route: {
|
|
139
|
+
id: routeId,
|
|
140
|
+
pattern,
|
|
141
|
+
},
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 페이지 모듈 로드 실패 에러 응답 생성
|
|
148
|
+
*/
|
|
149
|
+
export function createPageLoadErrorResponse(
|
|
150
|
+
routeId: string,
|
|
151
|
+
pattern: string,
|
|
152
|
+
originalError?: Error
|
|
153
|
+
): ManduError {
|
|
154
|
+
const error: ManduError = {
|
|
155
|
+
errorType: "LOGIC_ERROR",
|
|
156
|
+
code: ErrorCode.SLOT_IMPORT_ERROR,
|
|
157
|
+
message: originalError?.message || `Failed to load page module for route: ${routeId}`,
|
|
158
|
+
summary: `페이지 모듈 로드 실패 - ${routeId}.route.tsx 확인 필요`,
|
|
159
|
+
fix: {
|
|
160
|
+
file: `apps/web/generated/routes/${routeId}.route.tsx`,
|
|
161
|
+
suggestion: "import 경로와 컴포넌트 export를 확인하세요",
|
|
162
|
+
},
|
|
163
|
+
route: {
|
|
164
|
+
id: routeId,
|
|
165
|
+
pattern,
|
|
166
|
+
kind: "page",
|
|
167
|
+
},
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (originalError?.stack && process.env.NODE_ENV !== "production") {
|
|
172
|
+
error.debug = {
|
|
173
|
+
stack: originalError.stack,
|
|
174
|
+
originalError: originalError.message,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return error;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* SSR 렌더링 에러 응답 생성
|
|
183
|
+
*/
|
|
184
|
+
export function createSSRErrorResponse(
|
|
185
|
+
routeId: string,
|
|
186
|
+
pattern: string,
|
|
187
|
+
originalError?: Error
|
|
188
|
+
): ManduError {
|
|
189
|
+
const error: ManduError = {
|
|
190
|
+
errorType: "FRAMEWORK_BUG",
|
|
191
|
+
code: ErrorCode.FRAMEWORK_SSR_ERROR,
|
|
192
|
+
message: originalError?.message || `SSR rendering failed for route: ${routeId}`,
|
|
193
|
+
summary: `SSR 렌더링 실패 - 컴포넌트 확인 필요`,
|
|
194
|
+
fix: {
|
|
195
|
+
file: `apps/web/generated/routes/${routeId}.route.tsx`,
|
|
196
|
+
suggestion: "React 컴포넌트가 서버에서 렌더링 가능한지 확인하세요 (브라우저 전용 API 사용 금지)",
|
|
197
|
+
},
|
|
198
|
+
route: {
|
|
199
|
+
id: routeId,
|
|
200
|
+
pattern,
|
|
201
|
+
kind: "page",
|
|
202
|
+
},
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (originalError?.stack && process.env.NODE_ENV !== "production") {
|
|
207
|
+
error.debug = {
|
|
208
|
+
stack: originalError.stack,
|
|
209
|
+
originalError: originalError.message,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ANSI 색상 코드
|
|
217
|
+
const RED = "\x1b[31m";
|
|
218
|
+
const YELLOW = "\x1b[33m";
|
|
219
|
+
const CYAN = "\x1b[36m";
|
|
220
|
+
const MAGENTA = "\x1b[35m";
|
|
221
|
+
const RESET = "\x1b[0m";
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 에러 타입에 따른 색상 반환
|
|
225
|
+
*/
|
|
226
|
+
function getErrorTypeColor(errorType: string): string {
|
|
227
|
+
switch (errorType) {
|
|
228
|
+
case "SPEC_ERROR":
|
|
229
|
+
return YELLOW;
|
|
230
|
+
case "LOGIC_ERROR":
|
|
231
|
+
return RED;
|
|
232
|
+
case "FRAMEWORK_BUG":
|
|
233
|
+
return MAGENTA;
|
|
234
|
+
default:
|
|
235
|
+
return CYAN;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type { ManduError, RouteContext, FixTarget, DebugInfo, ErrorType } from "./types";
|
|
3
|
+
export { ErrorCode, ERROR_MESSAGES, ERROR_SUMMARIES } from "./types";
|
|
4
|
+
|
|
5
|
+
// Stack Analyzer
|
|
6
|
+
export { StackTraceAnalyzer, type StackFrame } from "./stack-analyzer";
|
|
7
|
+
|
|
8
|
+
// Classifier
|
|
9
|
+
export {
|
|
10
|
+
ErrorClassifier,
|
|
11
|
+
createSpecError,
|
|
12
|
+
createLogicError,
|
|
13
|
+
createFrameworkBug,
|
|
14
|
+
} from "./classifier";
|
|
15
|
+
|
|
16
|
+
// Formatter
|
|
17
|
+
export {
|
|
18
|
+
formatErrorResponse,
|
|
19
|
+
formatErrorForConsole,
|
|
20
|
+
createNotFoundResponse,
|
|
21
|
+
createHandlerNotFoundResponse,
|
|
22
|
+
createPageLoadErrorResponse,
|
|
23
|
+
createSSRErrorResponse,
|
|
24
|
+
type FormatOptions,
|
|
25
|
+
} from "./formatter";
|