@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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu WebSocket Handler
|
|
3
|
+
* filling.ws({ open, message, close }) 패턴
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ========== Types ==========
|
|
7
|
+
|
|
8
|
+
export interface ManduWebSocket {
|
|
9
|
+
/** 고유 연결 ID */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** 연결에 첨부된 데이터 */
|
|
12
|
+
readonly data: Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
/** 메시지 전송 */
|
|
15
|
+
send(data: string | ArrayBuffer | Uint8Array): void;
|
|
16
|
+
/** 토픽 구독 (pub/sub) */
|
|
17
|
+
subscribe(topic: string): void;
|
|
18
|
+
/** 토픽 구독 해제 */
|
|
19
|
+
unsubscribe(topic: string): void;
|
|
20
|
+
/** 토픽에 브로드캐스트 (자신 제외) */
|
|
21
|
+
publish(topic: string, data: string | ArrayBuffer | Uint8Array): void;
|
|
22
|
+
/** 연결 종료 */
|
|
23
|
+
close(code?: number, reason?: string): void;
|
|
24
|
+
/** JSON 전송 헬퍼 */
|
|
25
|
+
sendJSON(data: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WSHandlers {
|
|
29
|
+
/** 연결 시 */
|
|
30
|
+
open?(ws: ManduWebSocket): void;
|
|
31
|
+
/** 메시지 수신 시 */
|
|
32
|
+
message?(ws: ManduWebSocket, message: string | ArrayBuffer): void;
|
|
33
|
+
/** 연결 종료 시 */
|
|
34
|
+
close?(ws: ManduWebSocket, code: number, reason: string): void;
|
|
35
|
+
/** 백프레셔 해소 시 */
|
|
36
|
+
drain?(ws: ManduWebSocket): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WSUpgradeData {
|
|
40
|
+
routeId: string;
|
|
41
|
+
params: Record<string, string>;
|
|
42
|
+
id: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ========== Implementation ==========
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Bun WebSocket을 ManduWebSocket으로 래핑
|
|
49
|
+
*/
|
|
50
|
+
export function wrapBunWebSocket(
|
|
51
|
+
bunWs: { send: Function; subscribe: Function; unsubscribe: Function; publish: Function; close: Function; data: unknown }
|
|
52
|
+
): ManduWebSocket {
|
|
53
|
+
const wsData = bunWs.data as WSUpgradeData;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
get id() { return wsData.id; },
|
|
57
|
+
get data() { return wsData as unknown as Record<string, unknown>; },
|
|
58
|
+
|
|
59
|
+
send(data: string | ArrayBuffer | Uint8Array) {
|
|
60
|
+
bunWs.send(data);
|
|
61
|
+
},
|
|
62
|
+
subscribe(topic: string) {
|
|
63
|
+
bunWs.subscribe(topic);
|
|
64
|
+
},
|
|
65
|
+
unsubscribe(topic: string) {
|
|
66
|
+
bunWs.unsubscribe(topic);
|
|
67
|
+
},
|
|
68
|
+
publish(topic: string, data: string | ArrayBuffer | Uint8Array) {
|
|
69
|
+
bunWs.publish(topic, data);
|
|
70
|
+
},
|
|
71
|
+
close(code?: number, reason?: string) {
|
|
72
|
+
bunWs.close(code, reason);
|
|
73
|
+
},
|
|
74
|
+
sendJSON(data: unknown) {
|
|
75
|
+
bunWs.send(JSON.stringify(data));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RoutesManifest, RouteSpec } from "../spec/schema";
|
|
2
2
|
import { generateApiHandler, generatePageComponent, generateSlotLogic } from "./templates";
|
|
3
3
|
import { generateContractTypeGlue, generateContractTemplate, generateContractTypesIndex } from "./contract-glue";
|
|
4
|
-
import {
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
5
|
import { getWatcher } from "../watcher/watcher";
|
|
6
6
|
import { resolveGeneratedPaths, GENERATED_RELATIVE_PATHS } from "../paths";
|
|
7
7
|
import path from "path";
|
|
@@ -158,7 +158,7 @@ export async function generateRoutes(
|
|
|
158
158
|
generatedAt: new Date().toISOString(),
|
|
159
159
|
specSource: {
|
|
160
160
|
path: ".mandu/routes.manifest.json",
|
|
161
|
-
hash:
|
|
161
|
+
hash: createHash("sha256").update(JSON.stringify(manifest)).digest("hex"),
|
|
162
162
|
},
|
|
163
163
|
files: {},
|
|
164
164
|
frameworkPaths: [
|
|
@@ -2,7 +2,6 @@ import type { RoutesManifest } from "../spec/schema";
|
|
|
2
2
|
import type { GuardViolation } from "./rules";
|
|
3
3
|
import { GUARD_RULES } from "./rules";
|
|
4
4
|
import { runGuardCheck } from "./check";
|
|
5
|
-
import { writeLock } from "../spec/lock";
|
|
6
5
|
import { generateRoutes } from "../generator/generate";
|
|
7
6
|
import { beginChange, commitChange, rollbackChange } from "../change";
|
|
8
7
|
import path from "path";
|
|
@@ -27,7 +26,6 @@ export interface AutoCorrectResult {
|
|
|
27
26
|
|
|
28
27
|
// 자동 수정 가능한 규칙들
|
|
29
28
|
const AUTO_CORRECTABLE_RULES = new Set([
|
|
30
|
-
GUARD_RULES.SPEC_HASH_MISMATCH.id,
|
|
31
29
|
GUARD_RULES.GENERATED_MANUAL_EDIT.id,
|
|
32
30
|
GUARD_RULES.SLOT_NOT_FOUND.id,
|
|
33
31
|
]);
|
|
@@ -148,9 +146,6 @@ async function correctViolation(
|
|
|
148
146
|
rootDir: string
|
|
149
147
|
): Promise<AutoCorrectStep> {
|
|
150
148
|
switch (violation.ruleId) {
|
|
151
|
-
case GUARD_RULES.SPEC_HASH_MISMATCH.id:
|
|
152
|
-
return await correctSpecHashMismatch(manifest, rootDir);
|
|
153
|
-
|
|
154
149
|
case GUARD_RULES.GENERATED_MANUAL_EDIT.id:
|
|
155
150
|
return await correctGeneratedManualEdit(manifest, rootDir);
|
|
156
151
|
|
|
@@ -167,30 +162,6 @@ async function correctViolation(
|
|
|
167
162
|
}
|
|
168
163
|
}
|
|
169
164
|
|
|
170
|
-
async function correctSpecHashMismatch(
|
|
171
|
-
manifest: RoutesManifest,
|
|
172
|
-
rootDir: string
|
|
173
|
-
): Promise<AutoCorrectStep> {
|
|
174
|
-
try {
|
|
175
|
-
const lockPath = path.join(rootDir, ".mandu/spec.lock.json");
|
|
176
|
-
await writeLock(lockPath, manifest);
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
ruleId: GUARD_RULES.SPEC_HASH_MISMATCH.id,
|
|
180
|
-
action: "spec-upsert",
|
|
181
|
-
success: true,
|
|
182
|
-
message: "spec.lock.json 업데이트 완료",
|
|
183
|
-
};
|
|
184
|
-
} catch (error) {
|
|
185
|
-
return {
|
|
186
|
-
ruleId: GUARD_RULES.SPEC_HASH_MISMATCH.id,
|
|
187
|
-
action: "spec-upsert",
|
|
188
|
-
success: false,
|
|
189
|
-
message: `spec.lock.json 업데이트 실패: ${error instanceof Error ? error.message : String(error)}`,
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
165
|
async function correctGeneratedManualEdit(
|
|
195
166
|
manifest: RoutesManifest,
|
|
196
167
|
rootDir: string
|
package/src/guard/check.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { GUARD_RULES, FORBIDDEN_IMPORTS, type GuardViolation } from "./rules";
|
|
2
|
-
import { verifyLock, computeHash } from "../spec/lock";
|
|
3
2
|
import { runContractGuardCheck } from "./contract-guard";
|
|
4
3
|
import { validateSlotContent } from "../slot/validator";
|
|
5
4
|
import type { RoutesManifest } from "../spec/schema";
|
|
@@ -59,27 +58,6 @@ async function readFileContent(filePath: string): Promise<string | null> {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
// Rule 1: Spec hash mismatch
|
|
63
|
-
export async function checkSpecHashMismatch(
|
|
64
|
-
manifest: RoutesManifest,
|
|
65
|
-
lockPath: string
|
|
66
|
-
): Promise<GuardViolation | null> {
|
|
67
|
-
const result = await verifyLock(lockPath, manifest);
|
|
68
|
-
|
|
69
|
-
if (!result.valid) {
|
|
70
|
-
return {
|
|
71
|
-
ruleId: GUARD_RULES.SPEC_HASH_MISMATCH.id,
|
|
72
|
-
file: lockPath,
|
|
73
|
-
message: result.lockHash
|
|
74
|
-
? `Spec 해시 불일치: lock(${result.lockHash.slice(0, 8)}...) != current(${result.currentHash.slice(0, 8)}...)`
|
|
75
|
-
: "spec.lock.json 파일이 없습니다",
|
|
76
|
-
suggestion: "bunx mandu spec-upsert를 실행하여 변경사항을 반영하세요",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
61
|
// Rule 2: Generated file manual edit detection
|
|
84
62
|
export async function checkGeneratedManualEdit(
|
|
85
63
|
rootDir: string,
|
|
@@ -247,16 +225,25 @@ export async function checkIslandFirstIntegrity(
|
|
|
247
225
|
continue;
|
|
248
226
|
}
|
|
249
227
|
|
|
250
|
-
// 2.
|
|
251
|
-
|
|
228
|
+
// 2. Island-First integrity: verify that a .island.tsx or .client.tsx file
|
|
229
|
+
// exists alongside the page's componentModule. The page does NOT need to
|
|
230
|
+
// directly import the island - the framework auto-links them via
|
|
231
|
+
// data-island attributes and the manifest's clientModule field.
|
|
232
|
+
if (route.componentModule && route.clientModule) {
|
|
252
233
|
const componentPath = path.join(rootDir, route.componentModule);
|
|
253
234
|
const content = await readFileContent(componentPath);
|
|
254
|
-
if
|
|
235
|
+
// Check if the island file actually exists on disk
|
|
236
|
+
const clientPath = path.join(rootDir, route.clientModule);
|
|
237
|
+
const clientExists = await fileExists(clientPath);
|
|
238
|
+
if (content && !clientExists && !content.includes("data-island") && !content.includes("data-mandu-island")) {
|
|
255
239
|
violations.push({
|
|
256
240
|
ruleId: "ISLAND_FIRST_INTEGRITY",
|
|
257
241
|
file: route.componentModule,
|
|
258
|
-
message: `
|
|
259
|
-
suggestion:
|
|
242
|
+
message: `No island file found for page route (routeId: ${route.id}). The clientModule '${route.clientModule}' does not exist.`,
|
|
243
|
+
suggestion:
|
|
244
|
+
"Create a .island.tsx file in the same app/ directory as page.tsx. " +
|
|
245
|
+
"The page should reference islands via data-island attributes, NOT by directly importing or re-exporting the island. " +
|
|
246
|
+
"Importing island() return values into page.tsx causes a runtime crash because they are config objects, not React components.",
|
|
260
247
|
});
|
|
261
248
|
}
|
|
262
249
|
}
|
|
@@ -370,20 +357,17 @@ export async function runGuardCheck(
|
|
|
370
357
|
): Promise<GuardCheckResult> {
|
|
371
358
|
const config = await loadManduConfig(rootDir);
|
|
372
359
|
|
|
373
|
-
const lockPath = path.join(rootDir, ".mandu/spec.lock.json");
|
|
374
360
|
const mapPath = path.join(rootDir, ".mandu/generated/generated.map.json");
|
|
375
361
|
|
|
376
362
|
// ============================================
|
|
377
363
|
// Phase 1: 독립적인 검사 병렬 실행
|
|
378
364
|
// ============================================
|
|
379
365
|
const [
|
|
380
|
-
hashViolation,
|
|
381
366
|
importViolations,
|
|
382
367
|
slotViolations,
|
|
383
368
|
specDirViolations,
|
|
384
369
|
islandViolations,
|
|
385
370
|
] = await Promise.all([
|
|
386
|
-
checkSpecHashMismatch(manifest, lockPath),
|
|
387
371
|
checkInvalidGeneratedImport(rootDir),
|
|
388
372
|
checkSlotFileExists(manifest, rootDir),
|
|
389
373
|
checkSpecDirNaming(rootDir),
|
|
@@ -391,7 +375,6 @@ export async function runGuardCheck(
|
|
|
391
375
|
]);
|
|
392
376
|
|
|
393
377
|
const violations: GuardViolation[] = [];
|
|
394
|
-
if (hashViolation) violations.push(hashViolation);
|
|
395
378
|
violations.push(...importViolations);
|
|
396
379
|
violations.push(...slotViolations);
|
|
397
380
|
violations.push(...specDirViolations);
|