@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,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard Decision Manager
|
|
3
|
+
*
|
|
4
|
+
* Persists approve/reject decisions for guard violations.
|
|
5
|
+
* Stored in .mandu/guard-decisions.json.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "path";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
|
|
11
|
+
export interface GuardDecision {
|
|
12
|
+
id: string;
|
|
13
|
+
violationKey: string; // "${ruleId}::${filePath}"
|
|
14
|
+
action: "approve" | "reject";
|
|
15
|
+
ruleId: string;
|
|
16
|
+
filePath: string;
|
|
17
|
+
reason?: string;
|
|
18
|
+
decidedAt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class GuardDecisionManager {
|
|
22
|
+
private filePath: string;
|
|
23
|
+
private decisions: GuardDecision[] | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(private rootDir: string) {
|
|
26
|
+
this.filePath = path.join(rootDir, ".mandu", "guard-decisions.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async load(): Promise<GuardDecision[]> {
|
|
30
|
+
if (this.decisions !== null) return this.decisions;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const file = Bun.file(this.filePath);
|
|
34
|
+
if (await file.exists()) {
|
|
35
|
+
const text = await file.text();
|
|
36
|
+
this.decisions = JSON.parse(text);
|
|
37
|
+
return this.decisions!;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// File doesn't exist or is corrupt
|
|
41
|
+
}
|
|
42
|
+
this.decisions = [];
|
|
43
|
+
return this.decisions;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async save(
|
|
47
|
+
decision: Omit<GuardDecision, "id" | "decidedAt">,
|
|
48
|
+
): Promise<GuardDecision> {
|
|
49
|
+
const decisions = await this.load();
|
|
50
|
+
|
|
51
|
+
const full: GuardDecision = {
|
|
52
|
+
...decision,
|
|
53
|
+
id: generateId(),
|
|
54
|
+
decidedAt: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Replace existing decision for same violationKey
|
|
58
|
+
const idx = decisions.findIndex(
|
|
59
|
+
(d) => d.violationKey === full.violationKey,
|
|
60
|
+
);
|
|
61
|
+
if (idx >= 0) {
|
|
62
|
+
decisions[idx] = full;
|
|
63
|
+
} else {
|
|
64
|
+
decisions.push(full);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.decisions = decisions;
|
|
68
|
+
await this.persist();
|
|
69
|
+
return full;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async remove(id: string): Promise<boolean> {
|
|
73
|
+
const decisions = await this.load();
|
|
74
|
+
const idx = decisions.findIndex((d) => d.id === id);
|
|
75
|
+
if (idx < 0) return false;
|
|
76
|
+
|
|
77
|
+
decisions.splice(idx, 1);
|
|
78
|
+
this.decisions = decisions;
|
|
79
|
+
await this.persist();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async isApproved(ruleId: string, filePath: string): Promise<boolean> {
|
|
84
|
+
const decisions = await this.load();
|
|
85
|
+
const key = `${ruleId}::${filePath}`;
|
|
86
|
+
return decisions.some((d) => d.violationKey === key && d.action === "approve");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async persist(): Promise<void> {
|
|
90
|
+
const dir = path.dirname(this.filePath);
|
|
91
|
+
if (!fs.existsSync(dir)) {
|
|
92
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
await Bun.write(this.filePath, JSON.stringify(this.decisions, null, 2));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function generateId(): string {
|
|
99
|
+
return `gd_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
100
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routes API - Exposes RoutesManifest data to Kitchen UI.
|
|
3
|
+
*
|
|
4
|
+
* GET /__kitchen/api/routes → JSON list of all registered routes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RoutesManifest, RouteSpec } from "../../spec/schema";
|
|
8
|
+
|
|
9
|
+
export interface RouteInfo {
|
|
10
|
+
id: string;
|
|
11
|
+
pattern: string;
|
|
12
|
+
kind: "page" | "api";
|
|
13
|
+
module: string;
|
|
14
|
+
methods?: string[];
|
|
15
|
+
hasSlot: boolean;
|
|
16
|
+
hasContract: boolean;
|
|
17
|
+
hasClient: boolean;
|
|
18
|
+
hasLayout: boolean;
|
|
19
|
+
hydration?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toRouteInfo(route: RouteSpec): RouteInfo {
|
|
23
|
+
return {
|
|
24
|
+
id: route.id,
|
|
25
|
+
pattern: route.pattern,
|
|
26
|
+
kind: route.kind,
|
|
27
|
+
module: route.module,
|
|
28
|
+
methods: route.methods,
|
|
29
|
+
hasSlot: !!route.slotModule,
|
|
30
|
+
hasContract: !!route.contractModule,
|
|
31
|
+
hasClient: !!route.clientModule,
|
|
32
|
+
hasLayout: !!(route.kind === "page" && route.layoutChain?.length),
|
|
33
|
+
hydration: route.kind === "page" ? route.hydration?.strategy : undefined,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function handleRoutesRequest(manifest: RoutesManifest): Response {
|
|
38
|
+
const routes = manifest.routes.map(toRouteInfo);
|
|
39
|
+
|
|
40
|
+
const summary = {
|
|
41
|
+
total: routes.length,
|
|
42
|
+
pages: routes.filter((r) => r.kind === "page").length,
|
|
43
|
+
apis: routes.filter((r) => r.kind === "api").length,
|
|
44
|
+
withSlots: routes.filter((r) => r.hasSlot).length,
|
|
45
|
+
withContracts: routes.filter((r) => r.hasContract).length,
|
|
46
|
+
withIslands: routes.filter((r) => r.hasClient).length,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return Response.json({ routes, summary });
|
|
50
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitchen Module - Dev-only dashboard for Mandu projects.
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time MCP activity stream, route explorer,
|
|
5
|
+
* and architecture guard dashboard at /__kitchen.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { KitchenHandler, KITCHEN_PREFIX, getKitchenErrors, clearKitchenErrors } from "./kitchen-handler";
|
|
9
|
+
export type { KitchenOptions } from "./kitchen-handler";
|
|
10
|
+
export { ActivitySSEBroadcaster } from "./stream/activity-sse";
|
|
11
|
+
export { FileTailer } from "./stream/file-tailer";
|
|
12
|
+
export { GuardAPI } from "./api/guard-api";
|
|
13
|
+
export { handleRoutesRequest } from "./api/routes-api";
|
|
14
|
+
export { FileAPI } from "./api/file-api";
|
|
15
|
+
export { parseUnifiedDiff } from "./api/diff-parser";
|
|
16
|
+
export type { FileDiff, DiffHunk, DiffLine } from "./api/diff-parser";
|
|
17
|
+
export type { RecentFileChange } from "./api/file-api";
|
|
18
|
+
export { GuardDecisionManager } from "./api/guard-decisions";
|
|
19
|
+
export type { GuardDecision } from "./api/guard-decisions";
|
|
20
|
+
export { ContractPlaygroundAPI } from "./api/contract-api";
|
|
21
|
+
export type { ContractListItem } from "./api/contract-api";
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitchen HTTP Handler - Dispatches /__kitchen/* requests.
|
|
3
|
+
*
|
|
4
|
+
* Mounted inside handleRequestInternal() when isDev === true.
|
|
5
|
+
* All Kitchen routes are under /__kitchen prefix.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RoutesManifest } from "../spec/schema";
|
|
9
|
+
import type { GuardConfig } from "../guard/types";
|
|
10
|
+
import { ActivitySSEBroadcaster } from "./stream/activity-sse";
|
|
11
|
+
import { GuardAPI } from "./api/guard-api";
|
|
12
|
+
import { handleRoutesRequest } from "./api/routes-api";
|
|
13
|
+
import { FileAPI } from "./api/file-api";
|
|
14
|
+
import { GuardDecisionManager } from "./api/guard-decisions";
|
|
15
|
+
import { ContractPlaygroundAPI } from "./api/contract-api";
|
|
16
|
+
import { renderKitchenHTML } from "./kitchen-ui";
|
|
17
|
+
|
|
18
|
+
export const KITCHEN_PREFIX = "/__kitchen";
|
|
19
|
+
|
|
20
|
+
export interface KitchenOptions {
|
|
21
|
+
rootDir: string;
|
|
22
|
+
manifest: RoutesManifest;
|
|
23
|
+
guardConfig: GuardConfig | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** In-memory error store for Kitchen → MCP bridge */
|
|
27
|
+
interface KitchenError {
|
|
28
|
+
id: string;
|
|
29
|
+
type: string;
|
|
30
|
+
severity: string;
|
|
31
|
+
message: string;
|
|
32
|
+
stack?: string;
|
|
33
|
+
url?: string;
|
|
34
|
+
source?: string;
|
|
35
|
+
line?: number;
|
|
36
|
+
column?: number;
|
|
37
|
+
timestamp: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MAX_STORED_ERRORS = 50;
|
|
41
|
+
let storedErrors: KitchenError[] = [];
|
|
42
|
+
|
|
43
|
+
/** Get stored errors (used by MCP tools) */
|
|
44
|
+
export function getKitchenErrors(): KitchenError[] {
|
|
45
|
+
return storedErrors;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Clear stored errors */
|
|
49
|
+
export function clearKitchenErrors(): void {
|
|
50
|
+
storedErrors = [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class KitchenHandler {
|
|
54
|
+
private sse: ActivitySSEBroadcaster;
|
|
55
|
+
private guardAPI: GuardAPI;
|
|
56
|
+
private fileAPI: FileAPI;
|
|
57
|
+
private guardDecisions: GuardDecisionManager;
|
|
58
|
+
private contractAPI: ContractPlaygroundAPI;
|
|
59
|
+
private manifest: RoutesManifest;
|
|
60
|
+
|
|
61
|
+
constructor(private options: KitchenOptions) {
|
|
62
|
+
this.manifest = options.manifest;
|
|
63
|
+
this.sse = new ActivitySSEBroadcaster(options.rootDir);
|
|
64
|
+
this.guardAPI = new GuardAPI(options.guardConfig, options.rootDir);
|
|
65
|
+
this.fileAPI = new FileAPI(options.rootDir);
|
|
66
|
+
this.guardDecisions = new GuardDecisionManager(options.rootDir);
|
|
67
|
+
this.contractAPI = new ContractPlaygroundAPI(options.manifest, options.rootDir);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
start(): void {
|
|
71
|
+
this.sse.start();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
stop(): void {
|
|
75
|
+
this.sse.stop();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Update manifest when routes change (HMR rebuild) */
|
|
79
|
+
updateManifest(manifest: RoutesManifest): void {
|
|
80
|
+
this.manifest = manifest;
|
|
81
|
+
this.contractAPI.updateManifest(manifest);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Update guard config when mandu.config.ts changes */
|
|
85
|
+
updateGuardConfig(config: GuardConfig | null): void {
|
|
86
|
+
this.guardAPI.updateConfig(config);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Get the SSE broadcaster for external event injection */
|
|
90
|
+
get broadcaster(): ActivitySSEBroadcaster {
|
|
91
|
+
return this.sse;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Get the Guard API for pushing violation reports */
|
|
95
|
+
get guard(): GuardAPI {
|
|
96
|
+
return this.guardAPI;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Handle a /__kitchen/* request.
|
|
101
|
+
* Returns Response or null if path doesn't match.
|
|
102
|
+
*/
|
|
103
|
+
async handle(req: Request, pathname: string): Promise<Response | null> {
|
|
104
|
+
if (!pathname.startsWith(KITCHEN_PREFIX)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const sub = pathname.slice(KITCHEN_PREFIX.length) || "/";
|
|
109
|
+
|
|
110
|
+
// Kitchen dashboard UI
|
|
111
|
+
if (sub === "/" || sub === "") {
|
|
112
|
+
return new Response(renderKitchenHTML(), {
|
|
113
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// SSE activity stream
|
|
118
|
+
if (sub === "/sse/activity") {
|
|
119
|
+
return this.sse.createResponse();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Routes API
|
|
123
|
+
if (sub === "/api/routes") {
|
|
124
|
+
return handleRoutesRequest(this.manifest);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Guard API
|
|
128
|
+
if (sub === "/api/guard" && req.method === "GET") {
|
|
129
|
+
return this.guardAPI.handleGetReport();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (sub === "/api/guard/scan" && req.method === "POST") {
|
|
133
|
+
return this.guardAPI.handleScan();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Guard Decisions API
|
|
137
|
+
if (sub === "/api/guard/decisions" && req.method === "GET") {
|
|
138
|
+
const decisions = await this.guardDecisions.load();
|
|
139
|
+
return Response.json({ decisions });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (sub === "/api/guard/approve" && req.method === "POST") {
|
|
143
|
+
try {
|
|
144
|
+
const body = await req.json();
|
|
145
|
+
const decision = await this.guardDecisions.save({
|
|
146
|
+
violationKey: `${body.ruleId}::${body.filePath}`,
|
|
147
|
+
action: "approve",
|
|
148
|
+
ruleId: body.ruleId,
|
|
149
|
+
filePath: body.filePath,
|
|
150
|
+
reason: body.reason,
|
|
151
|
+
});
|
|
152
|
+
return Response.json({ decision });
|
|
153
|
+
} catch {
|
|
154
|
+
return Response.json({ error: "Invalid request body" }, { status: 400 });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (sub === "/api/guard/reject" && req.method === "POST") {
|
|
159
|
+
try {
|
|
160
|
+
const body = await req.json();
|
|
161
|
+
const decision = await this.guardDecisions.save({
|
|
162
|
+
violationKey: `${body.ruleId}::${body.filePath}`,
|
|
163
|
+
action: "reject",
|
|
164
|
+
ruleId: body.ruleId,
|
|
165
|
+
filePath: body.filePath,
|
|
166
|
+
reason: body.reason,
|
|
167
|
+
});
|
|
168
|
+
return Response.json({ decision });
|
|
169
|
+
} catch {
|
|
170
|
+
return Response.json({ error: "Invalid request body" }, { status: 400 });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (sub.startsWith("/api/guard/decisions/") && req.method === "DELETE") {
|
|
175
|
+
const id = sub.slice("/api/guard/decisions/".length);
|
|
176
|
+
const removed = await this.guardDecisions.remove(id);
|
|
177
|
+
if (!removed) {
|
|
178
|
+
return Response.json({ error: "Decision not found" }, { status: 404 });
|
|
179
|
+
}
|
|
180
|
+
return Response.json({ removed: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Error API (Kitchen → MCP bridge)
|
|
184
|
+
if (sub === "/api/errors" && req.method === "POST") {
|
|
185
|
+
try {
|
|
186
|
+
const body = await req.json() as KitchenError | KitchenError[];
|
|
187
|
+
const errors = Array.isArray(body) ? body : [body];
|
|
188
|
+
for (const error of errors) {
|
|
189
|
+
if (!error.message) continue;
|
|
190
|
+
error.timestamp = error.timestamp || Date.now();
|
|
191
|
+
error.id = error.id || `err_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
192
|
+
storedErrors.push(error);
|
|
193
|
+
if (storedErrors.length > MAX_STORED_ERRORS) {
|
|
194
|
+
storedErrors.shift();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return Response.json({ received: errors.length, total: storedErrors.length });
|
|
198
|
+
} catch {
|
|
199
|
+
return Response.json({ error: "Invalid error payload" }, { status: 400 });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (sub === "/api/errors" && req.method === "GET") {
|
|
204
|
+
return Response.json({ errors: storedErrors, count: storedErrors.length });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (sub === "/api/errors" && req.method === "DELETE") {
|
|
208
|
+
const count = storedErrors.length;
|
|
209
|
+
clearKitchenErrors();
|
|
210
|
+
return Response.json({ cleared: count });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// File API
|
|
214
|
+
if (sub === "/api/file" && req.method === "GET") {
|
|
215
|
+
return this.fileAPI.handleReadFile(new URL(req.url));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (sub === "/api/file/diff" && req.method === "GET") {
|
|
219
|
+
return this.fileAPI.handleFileDiff(new URL(req.url));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (sub === "/api/file/changes" && req.method === "GET") {
|
|
223
|
+
return this.fileAPI.handleRecentChanges();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Contract API
|
|
227
|
+
if (sub === "/api/contracts" && req.method === "GET") {
|
|
228
|
+
return this.contractAPI.handleList();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (sub === "/api/contracts/validate" && req.method === "POST") {
|
|
232
|
+
return this.contractAPI.handleValidate(req);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (sub === "/api/contracts/openapi" && req.method === "GET") {
|
|
236
|
+
return this.contractAPI.handleOpenAPI();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (sub === "/api/contracts/openapi.yaml" && req.method === "GET") {
|
|
240
|
+
return this.contractAPI.handleOpenAPIYAML();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (sub.startsWith("/api/contracts/") && req.method === "GET") {
|
|
244
|
+
const id = sub.slice("/api/contracts/".length);
|
|
245
|
+
if (id && !id.includes("/")) {
|
|
246
|
+
return this.contractAPI.handleDetail(id);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Unknown kitchen route
|
|
251
|
+
return Response.json(
|
|
252
|
+
{ error: "Not found", path: pathname },
|
|
253
|
+
{ status: 404 },
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|