@catmint/cli 0.0.0-prealpha.1 → 0.0.0-prealpha.10

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.
@@ -2,11 +2,13 @@
2
2
  import { resolve, basename } from "node:path";
3
3
  import { networkInterfaces } from "node:os";
4
4
  import { watch } from "node:fs";
5
- import { createServer } from "vite";
5
+ import { createServer, createLogger } from "vite";
6
6
  import { loadConfig } from "catmint/config";
7
7
  import { scanRoutes } from "catmint/routing";
8
8
  import { loadEnv } from "catmint/env";
9
9
  import catmintVite from "@catmint/vite";
10
+ import pc from "picocolors";
11
+ import { detectRouteMode } from "../route-analysis.js";
10
12
  const VERSION = "0.1.0";
11
13
  function parseFlags(args) {
12
14
  const flags = {};
@@ -37,36 +39,98 @@ function getNetworkAddress() {
37
39
  return undefined;
38
40
  }
39
41
  /**
40
- * Build a formatted route table for display.
42
+ * Rendering mode symbols and colors.
43
+ */
44
+ const MODE_SYMBOL = {
45
+ static: "○",
46
+ cached: "●",
47
+ dynamic: "λ",
48
+ };
49
+ const MODE_COLOR = {
50
+ static: pc.white,
51
+ cached: pc.green,
52
+ dynamic: pc.cyan,
53
+ };
54
+ /**
55
+ * Color a HTTP method string.
56
+ */
57
+ function colorMethod(method) {
58
+ switch (method) {
59
+ case "GET":
60
+ return pc.green(method);
61
+ case "POST":
62
+ return pc.yellow(method);
63
+ case "PUT":
64
+ return pc.blue(method);
65
+ case "DELETE":
66
+ return pc.red(method);
67
+ case "PATCH":
68
+ return pc.magenta(method);
69
+ default:
70
+ return pc.white(method);
71
+ }
72
+ }
73
+ /**
74
+ * Build a formatted route table for display with rendering mode indicators.
41
75
  */
42
76
  function formatRouteTable(routes) {
43
- const lines = [];
77
+ // Build route entries with rendering mode, collecting display + plain lines
78
+ const entries = [];
44
79
  for (const route of routes) {
45
80
  const file = basename(route.filePath);
81
+ const mode = detectRouteMode(route.filePath, route.type);
82
+ const symbol = MODE_COLOR[mode](MODE_SYMBOL[mode]);
46
83
  if (route.type === "page") {
47
- lines.push(` GET ${padEnd(route.pattern, 20)} → ${file}`);
84
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd("GET", 6)}${padEnd(route.pattern, 20)} → ${file}`;
85
+ const display = ` ${symbol} ${padEnd(colorMethod("GET"), 6 + (colorMethod("GET").length - 3))}${padEnd(route.pattern, 20)} → ${file}`;
86
+ entries.push({ display, plain });
48
87
  }
49
88
  else if (route.methods && route.methods.length > 0) {
50
89
  for (const method of route.methods) {
51
90
  const m = method === "default" ? "ALL" : method;
52
- lines.push(` ${padEnd(m, 6)}${padEnd(route.pattern, 20)} → ${file}`);
91
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd(m, 6)}${padEnd(route.pattern, 20)} → ${file}`;
92
+ const display = ` ${symbol} ${padEnd(colorMethod(m), 6 + (colorMethod(m).length - m.length))}${padEnd(route.pattern, 20)} → ${file}`;
93
+ entries.push({ display, plain });
53
94
  }
54
95
  }
55
96
  else {
56
- lines.push(` ALL ${padEnd(route.pattern, 20)} → ${file}`);
97
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd("ALL", 6)}${padEnd(route.pattern, 20)} → ${file}`;
98
+ const display = ` ${symbol} ${padEnd(colorMethod("ALL"), 6 + (colorMethod("ALL").length - 3))}${padEnd(route.pattern, 20)} → ${file}`;
99
+ entries.push({ display, plain });
57
100
  }
58
101
  }
59
- if (lines.length === 0) {
60
- lines.push(" (no routes found)");
102
+ if (entries.length === 0) {
103
+ const noRoutes = " (no routes found)";
104
+ entries.push({ display: noRoutes, plain: noRoutes });
61
105
  }
62
- // Calculate box width
63
- const maxLen = Math.max(...lines.map((l) => l.length), 20);
106
+ // Calculate box width based on plain-text (no ANSI) widths
107
+ const maxLen = Math.max(...entries.map((e) => e.plain.length), 20);
64
108
  const boxWidth = maxLen + 4;
65
109
  const top = ` ┌${"─".repeat(boxWidth)}┐`;
66
110
  const bottom = ` └${"─".repeat(boxWidth)}┘`;
67
- const header = ` │ Routes:${" ".repeat(boxWidth - 10)}│`;
68
- const boxedLines = lines.map((l) => ` │${l}${" ".repeat(boxWidth - l.length)}│`);
69
- return [top, header, ...boxedLines, bottom].join("\n");
111
+ const header = ` │ Routes:${" ".repeat(boxWidth - 10)} │`;
112
+ const boxedLines = entries.map((e) => {
113
+ const pad = boxWidth - e.plain.length;
114
+ return ` │${e.display}${" ".repeat(pad)}│`;
115
+ });
116
+ // Route count summary
117
+ const pageCount = routes.filter((r) => r.type === "page").length;
118
+ const endpointCount = routes.filter((r) => r.type === "endpoint").length;
119
+ const parts = [];
120
+ if (pageCount > 0)
121
+ parts.push(`${pageCount} page${pageCount !== 1 ? "s" : ""}`);
122
+ if (endpointCount > 0)
123
+ parts.push(`${endpointCount} endpoint${endpointCount !== 1 ? "s" : ""}`);
124
+ const countLine = parts.length > 0
125
+ ? ` ${pc.dim(`${routes.length} route${routes.length !== 1 ? "s" : ""} (${parts.join(", ")})`)}`
126
+ : "";
127
+ // Legend
128
+ const legend = ` ${MODE_COLOR.static(MODE_SYMBOL.static)} Static ${MODE_COLOR.cached(MODE_SYMBOL.cached)} Cached ${MODE_COLOR.dynamic(MODE_SYMBOL.dynamic)} Dynamic`;
129
+ const result = [top, header, ...boxedLines, bottom];
130
+ if (countLine)
131
+ result.push(countLine);
132
+ result.push(legend);
133
+ return result.join("\n");
70
134
  }
71
135
  function padEnd(str, len) {
72
136
  return str.length >= len ? str + " " : str + " ".repeat(len - str.length + 1);
@@ -102,23 +166,27 @@ export async function dev(args) {
102
166
  catch {
103
167
  routes = [];
104
168
  }
105
- // Display startup info
169
+ // Display startup header (before Vite starts, so user sees immediate feedback)
106
170
  console.log();
107
171
  console.log(` catmint v${VERSION} dev server`);
108
172
  console.log();
109
173
  console.log(formatRouteTable(routes));
110
174
  console.log();
111
- console.log(` Local: http://${host}:${port}`);
112
- const networkAddr = getNetworkAddress();
113
- if (networkAddr) {
114
- console.log(` Network: http://${networkAddr}:${port}`);
115
- }
116
- console.log();
175
+ // Create a custom logger that suppresses Vite's port-in-use message
176
+ // (we display our own, more informative version after server.listen())
177
+ const logger = createLogger(undefined, { allowClearScreen: false });
178
+ const originalInfo = logger.info.bind(logger);
179
+ logger.info = (msg, options) => {
180
+ if (msg.includes("Port") && msg.includes("is in use"))
181
+ return;
182
+ originalInfo(msg, options);
183
+ };
117
184
  // Create Vite dev server with Catmint plugin
118
185
  // Merge user's vite.plugins with catmintVite() instead of overwriting
119
186
  const { plugins: userPlugins, ...viteRest } = config.vite;
120
187
  const server = await createServer({
121
188
  root: rootDir,
189
+ customLogger: logger,
122
190
  server: {
123
191
  port,
124
192
  host,
@@ -138,6 +206,20 @@ export async function dev(args) {
138
206
  ...viteRest,
139
207
  });
140
208
  await server.listen();
209
+ // Display the actual URLs after Vite has resolved the port
210
+ // (the port may differ from the requested one if it was already in use)
211
+ const addr = server.httpServer?.address();
212
+ const resolvedPort = typeof addr === "object" && addr ? addr.port : port;
213
+ if (resolvedPort !== port) {
214
+ console.log(pc.yellow(` Port ${port} is in use, using ${resolvedPort} instead`));
215
+ console.log();
216
+ }
217
+ console.log(` Local: http://${host}:${resolvedPort}`);
218
+ const networkAddr = getNetworkAddress();
219
+ if (networkAddr) {
220
+ console.log(` Network: http://${networkAddr}:${resolvedPort}`);
221
+ }
222
+ console.log();
141
223
  // Watch app/ directory for route changes
142
224
  try {
143
225
  const watcher = watch(appDir, { recursive: true }, async (eventType, filename) => {
@@ -145,10 +227,8 @@ export async function dev(args) {
145
227
  return;
146
228
  const base = basename(filename);
147
229
  if (base === "page.tsx" ||
148
- base === "page.jsx" ||
149
230
  base === "page.mdx" ||
150
- base === "endpoint.ts" ||
151
- base === "endpoint.js") {
231
+ base === "endpoint.ts") {
152
232
  try {
153
233
  const updatedRoutes = await scanRoutes(appDir);
154
234
  console.log();
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAoJA;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4azD"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AA4SA;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2mBzD"}
@@ -14,6 +14,7 @@ import { networkInterfaces } from "node:os";
14
14
  import { pathToFileURL } from "node:url";
15
15
  import { errorPageHtml } from "@catmint/vite";
16
16
  import { shouldAutoPreflight, buildPreflightHeaders } from "../cors-utils.js";
17
+ import pc from "picocolors";
17
18
  function parseFlags(args) {
18
19
  const flags = {};
19
20
  for (let i = 0; i < args.length; i++) {
@@ -25,6 +26,119 @@ function parseFlags(args) {
25
26
  }
26
27
  return flags;
27
28
  }
29
+ /**
30
+ * Rendering mode symbols and colors.
31
+ */
32
+ const MODE_SYMBOL = {
33
+ static: "○",
34
+ cached: "●",
35
+ dynamic: "λ",
36
+ };
37
+ const MODE_COLOR = {
38
+ static: pc.white,
39
+ cached: pc.green,
40
+ dynamic: pc.cyan,
41
+ };
42
+ /**
43
+ * Color a HTTP method string.
44
+ */
45
+ function colorMethod(method) {
46
+ switch (method) {
47
+ case "GET":
48
+ return pc.green(method);
49
+ case "POST":
50
+ return pc.yellow(method);
51
+ case "PUT":
52
+ return pc.blue(method);
53
+ case "DELETE":
54
+ return pc.red(method);
55
+ case "PATCH":
56
+ return pc.magenta(method);
57
+ default:
58
+ return pc.white(method);
59
+ }
60
+ }
61
+ function padEnd(str, len) {
62
+ return str.length >= len ? str + " " : str + " ".repeat(len - str.length + 1);
63
+ }
64
+ /**
65
+ * Derive the rendering mode from a manifest route entry.
66
+ *
67
+ * - Has explicit `renderMode` → use it directly
68
+ * - Has `cache` options → `"cached"` (ISR)
69
+ * - Endpoint → `"dynamic"`
70
+ * - Page without cache → `"dynamic"`
71
+ */
72
+ function manifestRouteMode(route) {
73
+ if (route.renderMode)
74
+ return route.renderMode;
75
+ if (route.type === "endpoint")
76
+ return "dynamic";
77
+ if (route.cache)
78
+ return "cached";
79
+ return "dynamic";
80
+ }
81
+ /**
82
+ * Build a formatted route table from manifest data for production display.
83
+ */
84
+ function formatManifestRouteTable(manifest) {
85
+ const allRoutes = [...manifest.routes, ...manifest.endpoints];
86
+ const entries = [];
87
+ for (const route of allRoutes) {
88
+ const source = route.source.split("/").pop() ?? route.source;
89
+ const mode = manifestRouteMode(route);
90
+ const symbol = MODE_COLOR[mode](MODE_SYMBOL[mode]);
91
+ if (route.type === "page") {
92
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd("GET", 6)}${padEnd(route.path, 20)} → ${source}`;
93
+ const display = ` ${symbol} ${padEnd(colorMethod("GET"), 6 + (colorMethod("GET").length - 3))}${padEnd(route.path, 20)} → ${source}`;
94
+ entries.push({ display, plain });
95
+ }
96
+ else if (route.methods && route.methods.length > 0) {
97
+ for (const method of route.methods) {
98
+ const m = method === "default" ? "ALL" : method;
99
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd(m, 6)}${padEnd(route.path, 20)} → ${source}`;
100
+ const display = ` ${symbol} ${padEnd(colorMethod(m), 6 + (colorMethod(m).length - m.length))}${padEnd(route.path, 20)} → ${source}`;
101
+ entries.push({ display, plain });
102
+ }
103
+ }
104
+ else {
105
+ const plain = ` ${MODE_SYMBOL[mode]} ${padEnd("ALL", 6)}${padEnd(route.path, 20)} → ${source}`;
106
+ const display = ` ${symbol} ${padEnd(colorMethod("ALL"), 6 + (colorMethod("ALL").length - 3))}${padEnd(route.path, 20)} → ${source}`;
107
+ entries.push({ display, plain });
108
+ }
109
+ }
110
+ if (entries.length === 0) {
111
+ const noRoutes = " (no routes found)";
112
+ entries.push({ display: noRoutes, plain: noRoutes });
113
+ }
114
+ const maxLen = Math.max(...entries.map((e) => e.plain.length), 20);
115
+ const boxWidth = maxLen + 4;
116
+ const top = ` ┌${"─".repeat(boxWidth)}┐`;
117
+ const bottom = ` └${"─".repeat(boxWidth)}┘`;
118
+ const header = ` │ Routes:${" ".repeat(boxWidth - 10)} │`;
119
+ const boxedLines = entries.map((e) => {
120
+ const pad = boxWidth - e.plain.length;
121
+ return ` │${e.display}${" ".repeat(pad)}│`;
122
+ });
123
+ // Route count summary
124
+ const pageCount = manifest.routes.length;
125
+ const endpointCount = manifest.endpoints.length;
126
+ const totalCount = pageCount + endpointCount;
127
+ const parts = [];
128
+ if (pageCount > 0)
129
+ parts.push(`${pageCount} page${pageCount !== 1 ? "s" : ""}`);
130
+ if (endpointCount > 0)
131
+ parts.push(`${endpointCount} endpoint${endpointCount !== 1 ? "s" : ""}`);
132
+ const countLine = parts.length > 0
133
+ ? ` ${pc.dim(`${totalCount} route${totalCount !== 1 ? "s" : ""} (${parts.join(", ")})`)}`
134
+ : "";
135
+ const legend = ` ${MODE_COLOR.static(MODE_SYMBOL.static)} Static ${MODE_COLOR.cached(MODE_SYMBOL.cached)} Cached ${MODE_COLOR.dynamic(MODE_SYMBOL.dynamic)} Dynamic`;
136
+ const result = [top, header, ...boxedLines, bottom];
137
+ if (countLine)
138
+ result.push(countLine);
139
+ result.push(legend);
140
+ return result.join("\n");
141
+ }
28
142
  /**
29
143
  * Common MIME types for static file serving.
30
144
  */
@@ -123,6 +237,32 @@ async function pipeWebStreamToResponse(webStream, res) {
123
237
  res.end();
124
238
  }
125
239
  }
240
+ /**
241
+ * Convert a Node.js IncomingMessage to a Web Request object.
242
+ * Used for middleware execution which operates on Web standards.
243
+ */
244
+ function nodeReqToWebRequest(req) {
245
+ const protocol = "http";
246
+ const host = req.headers.host ?? "localhost";
247
+ const url = new URL(req.url ?? "/", `${protocol}://${host}`);
248
+ const headers = new Headers();
249
+ for (const [key, value] of Object.entries(req.headers)) {
250
+ if (value) {
251
+ if (Array.isArray(value)) {
252
+ for (const v of value) {
253
+ headers.append(key, v);
254
+ }
255
+ }
256
+ else {
257
+ headers.set(key, value);
258
+ }
259
+ }
260
+ }
261
+ return new Request(url.href, {
262
+ method: req.method ?? "GET",
263
+ headers,
264
+ });
265
+ }
126
266
  /**
127
267
  * Start the Catmint production server.
128
268
  *
@@ -133,7 +273,7 @@ export async function start(args) {
133
273
  const flags = parseFlags(args);
134
274
  const rootDir = process.cwd();
135
275
  // Read port/host from manifest config first, then CLI flags override
136
- let port = 3000;
276
+ let port = 6468;
137
277
  let host = "0.0.0.0";
138
278
  // We'll read these from the manifest after loading it, but CLI flags take precedence
139
279
  const portFlag = typeof flags.port === "string" ? parseInt(flags.port, 10) : undefined;
@@ -158,13 +298,15 @@ export async function start(args) {
158
298
  console.error(" Error: Failed to parse app_manifest.json:", err);
159
299
  process.exit(1);
160
300
  }
161
- // Apply port/host from manifest config, then override with CLI flags
301
+ // Apply port/host/maxBodySize from manifest config, then override with CLI flags
162
302
  if (manifest.config?.server?.port) {
163
303
  port = manifest.config.server.port;
164
304
  }
165
305
  if (manifest.config?.server?.host) {
166
306
  host = manifest.config.server.host;
167
307
  }
308
+ const maxBodySize = manifest.config?.server?.maxBodySize ?? 1_048_576;
309
+ const headerPreset = manifest.config?.security?.headerPreset ?? "baseline";
168
310
  if (portFlag !== undefined) {
169
311
  port = portFlag;
170
312
  }
@@ -175,6 +317,7 @@ export async function start(args) {
175
317
  const rscDir = join(distDir, "rsc");
176
318
  const ssrDir = join(distDir, "ssr");
177
319
  const staticDir = join(distDir, "static");
320
+ const prerenderedDir = join(distDir, "prerendered");
178
321
  // 3. Load RSC entry — renders pages to RSC flight streams
179
322
  let rscEntry;
180
323
  const rscEntryPath = join(rscDir, "index.js");
@@ -248,6 +391,12 @@ export async function start(args) {
248
391
  }
249
392
  // 5. Create HTTP server
250
393
  const server = createServer(async (req, res) => {
394
+ // Baseline security headers (configurable via security.headerPreset)
395
+ if (headerPreset === "baseline") {
396
+ res.setHeader("X-Content-Type-Options", "nosniff");
397
+ res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
398
+ res.setHeader("X-Frame-Options", "SAMEORIGIN");
399
+ }
251
400
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
252
401
  const urlPath = url.pathname;
253
402
  // Client assets (dist/client/) have hashed filenames and never conflict
@@ -255,14 +404,125 @@ export async function start(args) {
255
404
  if (tryServeStatic(req, res, urlPath, clientDir, true)) {
256
405
  return;
257
406
  }
407
+ // Pre-rendered static pages WITHOUT middleware: serve directly from
408
+ // dist/static/ as a fast path — no middleware execution needed.
409
+ if (req.method === "GET") {
410
+ let staticHtmlPath = urlPath;
411
+ if (!extname(staticHtmlPath)) {
412
+ staticHtmlPath = staticHtmlPath.endsWith("/")
413
+ ? staticHtmlPath + "index.html"
414
+ : staticHtmlPath + ".html";
415
+ }
416
+ if (tryServeStatic(req, res, staticHtmlPath, staticDir, false)) {
417
+ return;
418
+ }
419
+ }
420
+ // --- Middleware execution ---
421
+ // For all remaining routes (server functions, endpoints, prerendered
422
+ // pages with middleware, RSC-rendered pages), execute the middleware
423
+ // chain first. If middleware short-circuits, return its response.
424
+ // For RSC flight requests, run middleware against the TARGET page path
425
+ // (from ?path= query param), not the literal /__catmint/rsc pathname.
426
+ let middlewareHeaders;
427
+ if (ssrEntry?.executeMiddleware) {
428
+ try {
429
+ let middlewarePath = urlPath;
430
+ if (urlPath === "/__catmint/rsc") {
431
+ const parsedUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
432
+ const rscTargetPath = parsedUrl.searchParams.get("path");
433
+ if (rscTargetPath)
434
+ middlewarePath = rscTargetPath;
435
+ }
436
+ const webRequest = nodeReqToWebRequest(req);
437
+ const mwResult = await ssrEntry.executeMiddleware(middlewarePath, webRequest);
438
+ if (mwResult.shortCircuit) {
439
+ // Middleware short-circuited — return its response directly
440
+ res.statusCode = mwResult.shortCircuit.status;
441
+ mwResult.shortCircuit.headers.forEach((value, key) => {
442
+ res.setHeader(key, value);
443
+ });
444
+ const body = await mwResult.shortCircuit.text();
445
+ res.end(body);
446
+ return;
447
+ }
448
+ // Middleware passed — capture headers to merge into final response
449
+ if (mwResult.headers &&
450
+ typeof mwResult.headers.forEach === "function") {
451
+ middlewareHeaders = mwResult.headers;
452
+ }
453
+ }
454
+ catch (err) {
455
+ console.error(" Middleware error:", err);
456
+ if (!res.headersSent) {
457
+ res.statusCode = 500;
458
+ res.end("Internal Server Error");
459
+ }
460
+ return;
461
+ }
462
+ }
463
+ /**
464
+ * Helper: merge middleware headers into the response before sending.
465
+ */
466
+ function applyMiddlewareHeaders() {
467
+ if (middlewareHeaders) {
468
+ middlewareHeaders.forEach((value, key) => {
469
+ res.setHeader(key, value);
470
+ });
471
+ }
472
+ }
258
473
  // Server function RPC handling (/__catmint/fn/<basePath>/<hash>)
474
+ // NOTE: No built-in rate limiting is applied here. For production
475
+ // deployments, configure rate limiting at the edge/WAF/reverse-proxy
476
+ // layer or use a framework middleware hook.
259
477
  const SERVER_FN_PREFIX = "/__catmint/fn/";
260
478
  if (urlPath.startsWith(SERVER_FN_PREFIX) && ssrEntry?.handleServerFn) {
261
479
  try {
262
- // Read request body
480
+ const reqMethod = (req.method ?? "GET").toUpperCase();
481
+ // Enforce POST method for server function calls
482
+ if (reqMethod !== "POST") {
483
+ res.statusCode = 405;
484
+ res.setHeader("Content-Type", "application/json");
485
+ res.end(JSON.stringify({
486
+ error: `Method ${reqMethod} not allowed, expected POST`,
487
+ }));
488
+ return;
489
+ }
490
+ // Enforce Content-Type: application/json
491
+ const ct = (req.headers["content-type"] ?? "").toLowerCase();
492
+ if (ct && !ct.startsWith("application/json")) {
493
+ res.statusCode = 415;
494
+ res.setHeader("Content-Type", "application/json");
495
+ res.end(JSON.stringify({
496
+ error: "Unsupported Content-Type, expected application/json",
497
+ }));
498
+ return;
499
+ }
500
+ // Validate Origin header to mitigate cross-site invocation
501
+ const origin = req.headers["origin"];
502
+ if (origin) {
503
+ const expectedHost = req.headers.host ?? "localhost";
504
+ if (origin !== `http://${expectedHost}` &&
505
+ origin !== `https://${expectedHost}`) {
506
+ res.statusCode = 403;
507
+ res.setHeader("Content-Type", "application/json");
508
+ res.end(JSON.stringify({ error: "Origin not allowed" }));
509
+ return;
510
+ }
511
+ }
512
+ // Read request body with size limit
513
+ const MAX_BODY_SIZE = maxBodySize;
263
514
  const chunks = [];
515
+ let totalLength = 0;
264
516
  await new Promise((resolve, reject) => {
265
- req.on("data", (chunk) => chunks.push(chunk));
517
+ req.on("data", (chunk) => {
518
+ totalLength += chunk.length;
519
+ if (totalLength > MAX_BODY_SIZE) {
520
+ req.destroy();
521
+ reject(new Error("Request body too large"));
522
+ return;
523
+ }
524
+ chunks.push(chunk);
525
+ });
266
526
  req.on("end", () => resolve());
267
527
  req.on("error", reject);
268
528
  });
@@ -280,6 +540,7 @@ export async function start(args) {
280
540
  }
281
541
  const result = await ssrEntry.handleServerFn(urlPath, body);
282
542
  if (result) {
543
+ applyMiddlewareHeaders();
283
544
  res.statusCode = 200;
284
545
  res.setHeader("Content-Type", "application/json");
285
546
  res.end(JSON.stringify(result.result));
@@ -321,8 +582,17 @@ export async function start(args) {
321
582
  // Attach body for methods that have one
322
583
  if (method !== "GET" && method !== "HEAD") {
323
584
  const chunks = [];
585
+ let totalLength = 0;
324
586
  await new Promise((resolve, reject) => {
325
- req.on("data", (chunk) => chunks.push(chunk));
587
+ req.on("data", (chunk) => {
588
+ totalLength += chunk.length;
589
+ if (totalLength > maxBodySize) {
590
+ req.destroy();
591
+ reject(new Error("Request body too large"));
592
+ return;
593
+ }
594
+ chunks.push(chunk);
595
+ });
326
596
  req.on("end", () => resolve());
327
597
  req.on("error", reject);
328
598
  });
@@ -337,6 +607,8 @@ export async function start(args) {
337
607
  result.response.headers.forEach((value, key) => {
338
608
  res.setHeader(key, value);
339
609
  });
610
+ // Merge middleware headers
611
+ applyMiddlewareHeaders();
340
612
  // Stream the response body
341
613
  if (result.response.body) {
342
614
  await pipeWebStreamToResponse(result.response.body, res);
@@ -378,6 +650,31 @@ export async function start(args) {
378
650
  return;
379
651
  }
380
652
  }
653
+ // Pre-rendered pages WITH middleware: serve from dist/prerendered/
654
+ // These are static routes that have middleware ancestors — their HTML
655
+ // was pre-rendered but they must go through the middleware pipeline.
656
+ if (req.method === "GET" && existsSync(prerenderedDir)) {
657
+ let prerenderedHtmlPath = urlPath;
658
+ if (!extname(prerenderedHtmlPath)) {
659
+ prerenderedHtmlPath = prerenderedHtmlPath.endsWith("/")
660
+ ? prerenderedHtmlPath + "index.html"
661
+ : prerenderedHtmlPath + ".html";
662
+ }
663
+ // Serve the prerendered HTML with middleware headers applied
664
+ const safePath = prerenderedHtmlPath
665
+ .replace(/\.\./g, "")
666
+ .replace(/\/+/g, "/");
667
+ const filePath = join(prerenderedDir, safePath);
668
+ if (filePath.startsWith(prerenderedDir) &&
669
+ existsSync(filePath) &&
670
+ statSync(filePath).isFile()) {
671
+ applyMiddlewareHeaders();
672
+ res.statusCode = 200;
673
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
674
+ createReadStream(filePath).pipe(res);
675
+ return;
676
+ }
677
+ }
381
678
  // RSC → SSR: render the page using the two-phase pipeline
382
679
  // 1. RSC entry renders the page to a flight stream
383
680
  // 2. SSR entry converts flight stream to HTML with embedded RSC payload
@@ -389,6 +686,7 @@ export async function start(args) {
389
686
  const result = await rscEntry.render(urlPath);
390
687
  if (result) {
391
688
  const htmlStream = await ssrEntry.renderToHtml(result.stream, result.headConfig);
689
+ applyMiddlewareHeaders();
392
690
  res.statusCode = 200;
393
691
  res.setHeader("Content-Type", "text/html; charset=utf-8");
394
692
  await pipeWebStreamToResponse(htmlStream, res);
@@ -418,7 +716,8 @@ export async function start(args) {
418
716
  console.log(" catmint production server");
419
717
  console.log();
420
718
  console.log(` Build ID: ${manifest.buildId}`);
421
- console.log(` Routes: ${manifest.routes.length} pages, ${manifest.endpoints.length} endpoints`);
719
+ console.log();
720
+ console.log(formatManifestRouteTable(manifest));
422
721
  if (hasRscPipeline) {
423
722
  console.log(` Mode: RSC`);
424
723
  }