@mandujs/core 0.19.0 → 0.19.2
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 +0 -14
- package/package.json +4 -1
- package/src/brain/architecture/analyzer.ts +4 -4
- package/src/brain/doctor/analyzer.ts +18 -14
- package/src/bundler/build.test.ts +127 -0
- package/src/bundler/build.ts +291 -113
- package/src/bundler/css.ts +20 -5
- package/src/bundler/dev.ts +55 -2
- package/src/bundler/prerender.ts +195 -0
- package/src/change/snapshot.ts +4 -23
- package/src/change/types.ts +2 -3
- package/src/client/Form.tsx +105 -0
- package/src/client/__tests__/use-sse.test.ts +153 -0
- package/src/client/hooks.ts +105 -6
- package/src/client/index.ts +35 -6
- package/src/client/router.ts +670 -433
- package/src/client/rpc.ts +140 -0
- package/src/client/runtime.ts +24 -21
- package/src/client/use-fetch.ts +239 -0
- package/src/client/use-head.ts +197 -0
- package/src/client/use-sse.ts +378 -0
- package/src/components/Image.tsx +162 -0
- package/src/config/mandu.ts +5 -0
- package/src/config/validate.ts +34 -0
- package/src/content/index.ts +5 -1
- package/src/devtools/client/catchers/error-catcher.ts +17 -0
- package/src/devtools/client/catchers/network-proxy.ts +390 -367
- package/src/devtools/client/components/kitchen-root.tsx +479 -467
- package/src/devtools/client/components/panel/diff-viewer.tsx +219 -0
- package/src/devtools/client/components/panel/guard-panel.tsx +374 -244
- package/src/devtools/client/components/panel/index.ts +45 -32
- package/src/devtools/client/components/panel/panel-container.tsx +332 -312
- package/src/devtools/client/components/panel/preview-panel.tsx +188 -0
- package/src/devtools/client/state-manager.ts +535 -478
- package/src/devtools/design-tokens.ts +265 -264
- package/src/devtools/types.ts +345 -319
- package/src/filling/filling.ts +336 -14
- package/src/filling/index.ts +5 -1
- package/src/filling/session.ts +216 -0
- package/src/filling/ws.ts +78 -0
- package/src/generator/generate.ts +2 -2
- package/src/guard/auto-correct.ts +0 -29
- package/src/guard/check.ts +14 -31
- package/src/guard/presets/index.ts +296 -294
- package/src/guard/rules.ts +15 -19
- package/src/guard/validator.ts +834 -834
- package/src/index.ts +5 -1
- package/src/island/index.ts +373 -304
- package/src/kitchen/api/contract-api.ts +225 -0
- package/src/kitchen/api/diff-parser.ts +108 -0
- package/src/kitchen/api/file-api.ts +273 -0
- package/src/kitchen/api/guard-api.ts +83 -0
- package/src/kitchen/api/guard-decisions.ts +100 -0
- package/src/kitchen/api/routes-api.ts +50 -0
- package/src/kitchen/index.ts +21 -0
- package/src/kitchen/kitchen-handler.ts +256 -0
- package/src/kitchen/kitchen-ui.ts +1732 -0
- package/src/kitchen/stream/activity-sse.ts +145 -0
- package/src/kitchen/stream/file-tailer.ts +99 -0
- package/src/middleware/compress.ts +62 -0
- package/src/middleware/cors.ts +47 -0
- package/src/middleware/index.ts +10 -0
- package/src/middleware/jwt.ts +134 -0
- package/src/middleware/logger.ts +58 -0
- package/src/middleware/timeout.ts +55 -0
- package/src/paths.ts +0 -4
- package/src/plugins/hooks.ts +64 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/types.ts +5 -0
- package/src/report/build.ts +0 -6
- package/src/resource/__tests__/backward-compat.test.ts +0 -1
- package/src/router/fs-patterns.ts +11 -1
- package/src/router/fs-routes.ts +78 -14
- package/src/router/fs-scanner.ts +2 -2
- package/src/router/fs-types.ts +2 -1
- package/src/runtime/adapter-bun.ts +62 -0
- package/src/runtime/adapter.ts +47 -0
- package/src/runtime/cache.ts +310 -0
- package/src/runtime/handler.ts +65 -0
- package/src/runtime/image-handler.ts +195 -0
- package/src/runtime/index.ts +12 -0
- package/src/runtime/middleware.ts +263 -0
- package/src/runtime/server.ts +662 -83
- package/src/runtime/ssr.ts +55 -29
- package/src/runtime/streaming-ssr.ts +106 -82
- package/src/spec/index.ts +0 -1
- package/src/spec/schema.ts +1 -0
- package/src/testing/index.ts +144 -0
- package/src/watcher/watcher.ts +27 -1
- package/src/spec/lock.ts +0 -56
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Global Middleware
|
|
3
|
+
* 라우트 매칭 전에 실행되는 글로벌 미들웨어 시스템
|
|
4
|
+
*
|
|
5
|
+
* 프로젝트 루트의 middleware.ts 파일에서 자동 로드
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CookieManager } from "../filling/context";
|
|
9
|
+
|
|
10
|
+
// ========== Types ==========
|
|
11
|
+
|
|
12
|
+
export interface MiddlewareContext {
|
|
13
|
+
/** 원본 Request */
|
|
14
|
+
request: Request;
|
|
15
|
+
/** 파싱된 URL */
|
|
16
|
+
url: URL;
|
|
17
|
+
/** Cookie 매니저 */
|
|
18
|
+
cookies: CookieManager;
|
|
19
|
+
/** matcher에서 추출된 파라미터 */
|
|
20
|
+
params: Record<string, string>;
|
|
21
|
+
|
|
22
|
+
/** 리다이렉트 응답 생성 */
|
|
23
|
+
redirect(url: string, status?: 301 | 302 | 307 | 308): Response;
|
|
24
|
+
/** JSON 응답 생성 */
|
|
25
|
+
json(data: unknown, status?: number): Response;
|
|
26
|
+
/** 내부 라우트 재작성 (URL 변경 없이 다른 라우트 처리) */
|
|
27
|
+
rewrite(url: string): Request;
|
|
28
|
+
|
|
29
|
+
/** 다음 핸들러에 데이터 전달 */
|
|
30
|
+
set(key: string, value: unknown): void;
|
|
31
|
+
/** 전달된 데이터 읽기 */
|
|
32
|
+
get<T>(key: string): T | undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type MiddlewareNext = () => Promise<Response>;
|
|
36
|
+
|
|
37
|
+
export type MiddlewareFn = (
|
|
38
|
+
ctx: MiddlewareContext,
|
|
39
|
+
next: MiddlewareNext
|
|
40
|
+
) => Response | Promise<Response>;
|
|
41
|
+
|
|
42
|
+
export interface MiddlewareConfig {
|
|
43
|
+
/** 미들웨어를 적용할 경로 패턴 */
|
|
44
|
+
matcher?: string[];
|
|
45
|
+
/** 제외할 경로 패턴 */
|
|
46
|
+
exclude?: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ========== Implementation ==========
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* MiddlewareContext 생성
|
|
53
|
+
*/
|
|
54
|
+
interface MiddlewareMatchResult {
|
|
55
|
+
matched: boolean;
|
|
56
|
+
params: Record<string, string>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface InternalMiddlewareContext extends MiddlewareContext {
|
|
60
|
+
getRewrittenRequest(): Request | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createMiddlewareContext(
|
|
64
|
+
request: Request,
|
|
65
|
+
params: Record<string, string> = {}
|
|
66
|
+
): InternalMiddlewareContext {
|
|
67
|
+
const url = new URL(request.url);
|
|
68
|
+
const cookies = new CookieManager(request);
|
|
69
|
+
const store = new Map<string, unknown>();
|
|
70
|
+
let rewrittenRequest: Request | null = null;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
request,
|
|
74
|
+
url,
|
|
75
|
+
cookies,
|
|
76
|
+
params,
|
|
77
|
+
|
|
78
|
+
redirect(target: string, status: 301 | 302 | 307 | 308 = 302): Response {
|
|
79
|
+
return Response.redirect(new URL(target, url.origin).href, status);
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
json(data: unknown, status: number = 200): Response {
|
|
83
|
+
return Response.json(data, { status });
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
rewrite(target: string): Request {
|
|
87
|
+
const rewriteUrl = new URL(target, url.origin);
|
|
88
|
+
rewrittenRequest = new Request(rewriteUrl.href, {
|
|
89
|
+
method: request.method,
|
|
90
|
+
headers: request.headers,
|
|
91
|
+
body: request.clone().body, // 원본 request body 소비 방지
|
|
92
|
+
});
|
|
93
|
+
return rewrittenRequest;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
set(key: string, value: unknown): void {
|
|
97
|
+
store.set(key, value);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
get<T>(key: string): T | undefined {
|
|
101
|
+
return store.get(key) as T | undefined;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
getRewrittenRequest(): Request | null {
|
|
105
|
+
return rewrittenRequest;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 경로가 matcher 패턴과 일치하는지 확인
|
|
112
|
+
* :path* → 와일드카드 매칭
|
|
113
|
+
*/
|
|
114
|
+
export function matchesMiddlewarePath(
|
|
115
|
+
pathname: string,
|
|
116
|
+
config: MiddlewareConfig | null
|
|
117
|
+
): boolean {
|
|
118
|
+
return getMiddlewareMatch(pathname, config).matched;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getMiddlewareMatch(
|
|
122
|
+
pathname: string,
|
|
123
|
+
config: MiddlewareConfig | null
|
|
124
|
+
): MiddlewareMatchResult {
|
|
125
|
+
// config 없으면 모든 경로에 적용
|
|
126
|
+
if (!config) return { matched: true, params: {} };
|
|
127
|
+
|
|
128
|
+
// exclude 패턴 먼저 확인
|
|
129
|
+
if (config.exclude) {
|
|
130
|
+
for (const pattern of config.exclude) {
|
|
131
|
+
if (matchPattern(pathname, pattern)) return { matched: false, params: {} };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// matcher가 없으면 모든 경로에 적용
|
|
136
|
+
if (!config.matcher || config.matcher.length === 0) return { matched: true, params: {} };
|
|
137
|
+
|
|
138
|
+
// matcher 패턴 중 하나라도 일치하면 적용
|
|
139
|
+
for (const pattern of config.matcher) {
|
|
140
|
+
const params = matchPatternWithParams(pathname, pattern);
|
|
141
|
+
if (params) {
|
|
142
|
+
return { matched: true, params };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { matched: false, params: {} };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 단순 경로 패턴 매칭
|
|
151
|
+
* - /api/* → /api/ 하위 모든 경로
|
|
152
|
+
* - /dashboard/:path* → /dashboard/ 하위 모든 경로
|
|
153
|
+
* - /about → 정확히 /about
|
|
154
|
+
*/
|
|
155
|
+
function matchPattern(pathname: string, pattern: string): boolean {
|
|
156
|
+
return matchPatternWithParams(pathname, pattern) !== null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function matchPatternWithParams(pathname: string, pattern: string): Record<string, string> | null {
|
|
160
|
+
// 와일드카드 패턴: /api/* → /api, /api/anything 모두 매칭
|
|
161
|
+
if (pattern.endsWith("*") || pattern.endsWith(":path*")) {
|
|
162
|
+
const prefix = pattern.replace(/[:*]path\*$/, "").replace(/\*$/, "");
|
|
163
|
+
// prefix 자체 또는 prefix 하위 경로 매칭
|
|
164
|
+
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
165
|
+
// 정확히 prefix이거나, prefix/ 로 시작하는 경우만 매칭 (/api/* → /apikeys 매칭 방지)
|
|
166
|
+
if (pathname === normalizedPrefix || pathname.startsWith(normalizedPrefix + "/")) {
|
|
167
|
+
const wildcard = pathname.slice(normalizedPrefix.length).replace(/^\/+/, "");
|
|
168
|
+
if (pattern.endsWith(":path*")) {
|
|
169
|
+
return { path: wildcard };
|
|
170
|
+
}
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const pathSegments = pathname.split("/").filter(Boolean);
|
|
177
|
+
const patternSegments = pattern.split("/").filter(Boolean);
|
|
178
|
+
if (pathSegments.length !== patternSegments.length) return null;
|
|
179
|
+
|
|
180
|
+
const params: Record<string, string> = {};
|
|
181
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
182
|
+
const patternSegment = patternSegments[i];
|
|
183
|
+
const pathSegment = pathSegments[i];
|
|
184
|
+
|
|
185
|
+
if (patternSegment.startsWith(":")) {
|
|
186
|
+
params[patternSegment.slice(1)] = pathSegment;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (patternSegment !== pathSegment) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return params;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* middleware.ts 파일에서 미들웨어 로드 (async)
|
|
200
|
+
*/
|
|
201
|
+
export async function loadMiddleware(
|
|
202
|
+
rootDir: string
|
|
203
|
+
): Promise<{ fn: MiddlewareFn; config: MiddlewareConfig | null } | null> {
|
|
204
|
+
const possiblePaths = [
|
|
205
|
+
`${rootDir}/middleware.ts`,
|
|
206
|
+
`${rootDir}/middleware.js`,
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
for (const mwPath of possiblePaths) {
|
|
210
|
+
try {
|
|
211
|
+
const file = Bun.file(mwPath);
|
|
212
|
+
if (await file.exists()) {
|
|
213
|
+
const mod = await import(mwPath);
|
|
214
|
+
return validateMiddlewareModule(mod);
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.warn(`[Mandu] middleware.ts 로드 실패:`, error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* middleware.ts 동기 로드 (서버 시작 시 사용 — 첫 요청부터 미들웨어 보장)
|
|
226
|
+
*/
|
|
227
|
+
export function loadMiddlewareSync(
|
|
228
|
+
rootDir: string
|
|
229
|
+
): { fn: MiddlewareFn; config: MiddlewareConfig | null } | null {
|
|
230
|
+
const fs = require("fs") as typeof import("fs");
|
|
231
|
+
const possiblePaths = [
|
|
232
|
+
`${rootDir}/middleware.ts`,
|
|
233
|
+
`${rootDir}/middleware.js`,
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
for (const mwPath of possiblePaths) {
|
|
237
|
+
try {
|
|
238
|
+
if (fs.existsSync(mwPath)) {
|
|
239
|
+
// Bun에서 require()는 .ts도 동기 로드 가능
|
|
240
|
+
const mod = require(mwPath);
|
|
241
|
+
return validateMiddlewareModule(mod);
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.warn(`[Mandu] middleware.ts 로드 실패:`, error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function validateMiddlewareModule(
|
|
252
|
+
mod: Record<string, unknown>
|
|
253
|
+
): { fn: MiddlewareFn; config: MiddlewareConfig | null } | null {
|
|
254
|
+
const fn = mod.default as MiddlewareFn;
|
|
255
|
+
const config = (mod.config as MiddlewareConfig) ?? null;
|
|
256
|
+
|
|
257
|
+
if (typeof fn !== "function") {
|
|
258
|
+
console.warn(`[Mandu] middleware.ts의 default export가 함수가 아닙니다.`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return { fn, config };
|
|
263
|
+
}
|