@flexireact/core 4.0.0 → 4.1.1

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,46 +2,59 @@
2
2
  import fs from "fs";
3
3
  import path from "path";
4
4
  import { pathToFileURL } from "url";
5
- var defaultConfig = {
5
+ import { z } from "zod";
6
+ import pc from "picocolors";
7
+ var BuildConfigSchema = z.object({
8
+ target: z.string().default("es2022"),
9
+ minify: z.boolean().default(true),
10
+ sourcemap: z.boolean().default(true),
11
+ splitting: z.boolean().default(true)
12
+ }).default({});
13
+ var ServerConfigSchema = z.object({
14
+ port: z.number().min(1).max(65535).default(3e3),
15
+ host: z.string().default("localhost")
16
+ }).default({});
17
+ var SSGConfigSchema = z.object({
18
+ enabled: z.boolean().default(false),
19
+ paths: z.array(z.string()).default([])
20
+ }).default({});
21
+ var IslandsConfigSchema = z.object({
22
+ enabled: z.boolean().default(true),
23
+ directive: z.string().default("use island")
24
+ }).default({});
25
+ var RSCConfigSchema = z.object({
26
+ enabled: z.boolean().default(true)
27
+ }).default({});
28
+ var PluginSchema = z.object({
29
+ name: z.string(),
30
+ setup: z.function().optional()
31
+ }).passthrough();
32
+ var FlexiReactConfigSchema = z.object({
6
33
  // Directories
7
- pagesDir: "pages",
8
- layoutsDir: "layouts",
9
- publicDir: "public",
10
- outDir: ".flexi",
34
+ pagesDir: z.string().default("pages"),
35
+ layoutsDir: z.string().default("layouts"),
36
+ publicDir: z.string().default("public"),
37
+ outDir: z.string().default(".flexi"),
11
38
  // Build options
12
- build: {
13
- target: "es2022",
14
- minify: true,
15
- sourcemap: true,
16
- splitting: true
17
- },
39
+ build: BuildConfigSchema,
18
40
  // Server options
19
- server: {
20
- port: 3e3,
21
- host: "localhost"
22
- },
41
+ server: ServerConfigSchema,
23
42
  // SSG options
24
- ssg: {
25
- enabled: false,
26
- paths: []
27
- },
43
+ ssg: SSGConfigSchema,
28
44
  // Islands (partial hydration)
29
- islands: {
30
- enabled: true
31
- },
45
+ islands: IslandsConfigSchema,
32
46
  // RSC options
33
- rsc: {
34
- enabled: true
35
- },
47
+ rsc: RSCConfigSchema,
36
48
  // Plugins
37
- plugins: [],
49
+ plugins: z.array(PluginSchema).default([]),
38
50
  // Styles (CSS files to include)
39
- styles: [],
51
+ styles: z.array(z.string()).default([]),
40
52
  // Scripts (JS files to include)
41
- scripts: [],
53
+ scripts: z.array(z.string()).default([]),
42
54
  // Favicon path
43
- favicon: null
44
- };
55
+ favicon: z.string().nullable().default(null)
56
+ });
57
+ var defaultConfig = FlexiReactConfigSchema.parse({});
45
58
  async function loadConfig(projectRoot) {
46
59
  const configPathTs = path.join(projectRoot, "flexireact.config.ts");
47
60
  const configPathJs = path.join(projectRoot, "flexireact.config.js");
@@ -53,10 +66,22 @@ async function loadConfig(projectRoot) {
53
66
  const module = await import(`${configUrl}?t=${Date.now()}`);
54
67
  userConfig = module.default || module;
55
68
  } catch (error) {
56
- console.warn("Warning: Failed to load flexireact config:", error.message);
69
+ console.warn(pc.yellow(`\u26A0 Failed to load config: ${error.message}`));
70
+ }
71
+ }
72
+ const merged = deepMerge(defaultConfig, userConfig);
73
+ try {
74
+ return FlexiReactConfigSchema.parse(merged);
75
+ } catch (err) {
76
+ if (err instanceof z.ZodError) {
77
+ console.error(pc.red("\u2716 Configuration validation failed:"));
78
+ for (const issue of err.issues) {
79
+ console.error(pc.dim(` - ${issue.path.join(".")}: ${issue.message}`));
80
+ }
81
+ process.exit(1);
57
82
  }
83
+ throw err;
58
84
  }
59
- return deepMerge(defaultConfig, userConfig);
60
85
  }
61
86
  function deepMerge(target, source) {
62
87
  const result = { ...target };
@@ -259,7 +284,7 @@ function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
259
284
  appRoutes: [],
260
285
  // Next.js style app router routes
261
286
  flexiRoutes: []
262
- // FlexiReact v2 routes/ directory
287
+ // FlexiReact v4 routes/ directory
263
288
  };
264
289
  const routesDirPath = routesDir || path3.join(projectRoot, "routes");
265
290
  if (fs3.existsSync(routesDirPath)) {
@@ -610,32 +635,72 @@ async function renderPage(options) {
610
635
  favicon = null,
611
636
  isSSG = false,
612
637
  route = "/",
613
- needsHydration = false
638
+ needsHydration = false,
639
+ componentPath = ""
614
640
  } = options;
615
641
  const renderStart = Date.now();
616
642
  try {
617
- let element = React2.createElement(Component, props);
618
- if (error) {
619
- element = React2.createElement(ErrorBoundaryWrapper, {
620
- fallback: error,
621
- children: element
622
- });
623
- }
624
- if (loading) {
625
- element = React2.createElement(React2.Suspense, {
626
- fallback: React2.createElement(loading),
627
- children: element
628
- });
629
- }
630
- for (const layout of [...layouts].reverse()) {
631
- if (layout.Component) {
632
- const LayoutComponent = layout.Component;
633
- element = React2.createElement(LayoutComponent, {
634
- ...layout.props
635
- }, element);
643
+ let element;
644
+ let content;
645
+ let isClientOnly = false;
646
+ try {
647
+ element = React2.createElement(Component, props);
648
+ if (error) {
649
+ element = React2.createElement(ErrorBoundaryWrapper, {
650
+ fallback: error,
651
+ children: element
652
+ });
653
+ }
654
+ if (loading) {
655
+ element = React2.createElement(React2.Suspense, {
656
+ fallback: React2.createElement(loading),
657
+ children: element
658
+ });
659
+ }
660
+ for (const layout of [...layouts].reverse()) {
661
+ if (layout.Component) {
662
+ const LayoutComponent = layout.Component;
663
+ element = React2.createElement(LayoutComponent, {
664
+ ...layout.props
665
+ }, element);
666
+ }
667
+ }
668
+ content = renderToString(element);
669
+ } catch (renderErr) {
670
+ const isHookError = renderErr.message?.includes("useState") || renderErr.message?.includes("useEffect") || renderErr.message?.includes("useContext") || renderErr.message?.includes("useReducer") || renderErr.message?.includes("useRef") || renderErr.message?.includes("Invalid hook call");
671
+ if (isHookError || needsHydration) {
672
+ isClientOnly = true;
673
+ const placeholderHtml = `
674
+ <div id="flexi-root" data-client-component="true" data-component-path="${escapeHtml(componentPath)}">
675
+ <div class="flexi-loading" style="display:flex;align-items:center;justify-content:center;min-height:200px;color:#666;">
676
+ <div style="text-align:center;">
677
+ <div style="width:40px;height:40px;border:3px solid #e0e0e0;border-top-color:#3b82f6;border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 12px;"></div>
678
+ <div>Loading...</div>
679
+ </div>
680
+ </div>
681
+ <style>@keyframes spin{to{transform:rotate(360deg)}}</style>
682
+ </div>
683
+ `;
684
+ let wrappedContent = placeholderHtml;
685
+ for (const layout of [...layouts].reverse()) {
686
+ if (layout.Component) {
687
+ try {
688
+ const layoutElement = React2.createElement(layout.Component, {
689
+ ...layout.props,
690
+ children: React2.createElement("div", {
691
+ dangerouslySetInnerHTML: { __html: wrappedContent }
692
+ })
693
+ });
694
+ wrappedContent = renderToString(layoutElement);
695
+ } catch {
696
+ }
697
+ }
698
+ }
699
+ content = wrappedContent;
700
+ } else {
701
+ throw renderErr;
636
702
  }
637
703
  }
638
- const content = renderToString(element);
639
704
  const renderTime = Date.now() - renderStart;
640
705
  const islandScripts = generateIslandScripts(islands);
641
706
  return buildHtmlDocument({
@@ -649,7 +714,8 @@ async function renderPage(options) {
649
714
  isSSG,
650
715
  renderTime,
651
716
  route,
652
- isClientComponent: needsHydration
717
+ isClientComponent: needsHydration || isClientOnly,
718
+ componentPath
653
719
  });
654
720
  } catch (err) {
655
721
  console.error("Render Error:", err);
@@ -807,7 +873,7 @@ function generateDevToolbar(options = {}) {
807
873
  const timeColor = renderTime < 50 ? "#00FF9C" : renderTime < 200 ? "#fbbf24" : "#ef4444";
808
874
  const timeLabel = renderTime < 50 ? "Fast" : renderTime < 200 ? "OK" : "Slow";
809
875
  return `
810
- <!-- FlexiReact v2 Dev Toolbar -->
876
+ <!-- FlexiReact v4.1.0 Dev Toolbar -->
811
877
  <div id="flexi-dev-toolbar" class="flexi-dev-collapsed">
812
878
  <style>
813
879
  #flexi-dev-toolbar {
@@ -1115,7 +1181,7 @@ function generateDevToolbar(options = {}) {
1115
1181
  <div class="flexi-dev-header-logo">F</div>
1116
1182
  <div class="flexi-dev-header-info">
1117
1183
  <div class="flexi-dev-header-title">FlexiReact</div>
1118
- <div class="flexi-dev-header-subtitle">v2.0.0 \u2022 Development</div>
1184
+ <div class="flexi-dev-header-subtitle">v4.1.0 \u2022 Development</div>
1119
1185
  </div>
1120
1186
  <button class="flexi-dev-close" onclick="this.closest('#flexi-dev-toolbar').classList.remove('flexi-dev-open')">\u2715</button>
1121
1187
  </div>
@@ -1168,9 +1234,9 @@ function generateDevToolbar(options = {}) {
1168
1234
  </div>
1169
1235
 
1170
1236
  <script>
1171
- // FlexiReact v2 DevTools
1237
+ // FlexiReact v4 DevTools
1172
1238
  window.__FLEXI_DEV__ = {
1173
- version: '2.0.0',
1239
+ version: '4.1.0',
1174
1240
  renderTime: ${renderTime},
1175
1241
  pageType: '${pageType}',
1176
1242
  route: '${route}',
@@ -1205,7 +1271,7 @@ function generateDevToolbar(options = {}) {
1205
1271
 
1206
1272
  // Console branding
1207
1273
  console.log(
1208
- '%c \u26A1 FlexiReact v2 %c ${pageType} %c ${renderTime}ms ',
1274
+ '%c \u26A1 FlexiReact v4.1.0 %c ${pageType} %c ${renderTime}ms ',
1209
1275
  'background: #00FF9C; color: #000; font-weight: bold; padding: 2px 6px; border-radius: 4px 0 0 4px;',
1210
1276
  'background: #1e1e1e; color: #fafafa; padding: 2px 6px;',
1211
1277
  'background: ${timeColor}20; color: ${timeColor}; padding: 2px 6px; border-radius: 0 4px 4px 0;'
@@ -1306,7 +1372,7 @@ function renderError(statusCode, message, stack = null) {
1306
1372
  401: { title: "Unauthorized", icon: "key", color: "#8b5cf6", desc: "Please log in to access this page." }
1307
1373
  };
1308
1374
  const errorInfo = errorMessages[statusCode] || { title: "Error", icon: "alert", color: "#ef4444", desc: message };
1309
- const errorFramesHtml = showStack && errorDetails?.frames?.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
1375
+ const errorFramesHtml = showStack && errorDetails?.frames?.length && errorDetails.frames.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
1310
1376
  <div class="error-frame ${i === 0 ? "error-frame-first" : ""}">
1311
1377
  <div class="error-frame-fn">${escapeHtml(frame.fn)}</div>
1312
1378
  <div class="error-frame-loc">${escapeHtml(frame.file)}:${frame.line}:${frame.col}</div>
@@ -1606,7 +1672,7 @@ function renderError(statusCode, message, stack = null) {
1606
1672
  ${isDev ? `
1607
1673
  <div class="dev-badge">
1608
1674
  <div class="dev-badge-dot"></div>
1609
- FlexiReact v2
1675
+ FlexiReact v4.1.0
1610
1676
  </div>
1611
1677
  ` : ""}
1612
1678
  </body>
@@ -2229,12 +2295,12 @@ function Island({ component: Component, props = {}, name, clientPath }) {
2229
2295
  clientPath,
2230
2296
  props
2231
2297
  });
2232
- const content = renderToString2(React3.createElement(Component, props));
2298
+ const placeholder = `<div class="island-loading" data-island-placeholder="${name}">Loading...</div>`;
2233
2299
  return React3.createElement("div", {
2234
2300
  "data-island": islandId,
2235
2301
  "data-island-name": name,
2236
2302
  "data-island-props": JSON.stringify(props),
2237
- dangerouslySetInnerHTML: { __html: content }
2303
+ dangerouslySetInnerHTML: { __html: placeholder }
2238
2304
  });
2239
2305
  }
2240
2306
  function getRegisteredIslands() {
@@ -2425,10 +2491,7 @@ var colors = {
2425
2491
  reset: "\x1B[0m",
2426
2492
  bold: "\x1B[1m",
2427
2493
  dim: "\x1B[2m",
2428
- italic: "\x1B[3m",
2429
- underline: "\x1B[4m",
2430
2494
  // Text colors
2431
- black: "\x1B[30m",
2432
2495
  red: "\x1B[31m",
2433
2496
  green: "\x1B[32m",
2434
2497
  yellow: "\x1B[33m",
@@ -2436,22 +2499,7 @@ var colors = {
2436
2499
  magenta: "\x1B[35m",
2437
2500
  cyan: "\x1B[36m",
2438
2501
  white: "\x1B[37m",
2439
- gray: "\x1B[90m",
2440
- // Bright colors
2441
- brightRed: "\x1B[91m",
2442
- brightGreen: "\x1B[92m",
2443
- brightYellow: "\x1B[93m",
2444
- brightBlue: "\x1B[94m",
2445
- brightMagenta: "\x1B[95m",
2446
- brightCyan: "\x1B[96m",
2447
- brightWhite: "\x1B[97m",
2448
- // Background colors
2449
- bgRed: "\x1B[41m",
2450
- bgGreen: "\x1B[42m",
2451
- bgYellow: "\x1B[43m",
2452
- bgBlue: "\x1B[44m",
2453
- bgMagenta: "\x1B[45m",
2454
- bgCyan: "\x1B[46m"
2502
+ gray: "\x1B[90m"
2455
2503
  };
2456
2504
  var c = colors;
2457
2505
  function getStatusColor(status) {
@@ -2462,16 +2510,7 @@ function getStatusColor(status) {
2462
2510
  return c.white;
2463
2511
  }
2464
2512
  function getMethodColor(method) {
2465
- const methodColors = {
2466
- GET: c.brightGreen,
2467
- POST: c.brightBlue,
2468
- PUT: c.brightYellow,
2469
- PATCH: c.brightMagenta,
2470
- DELETE: c.brightRed,
2471
- OPTIONS: c.gray,
2472
- HEAD: c.gray
2473
- };
2474
- return methodColors[method] || c.white;
2513
+ return c.white;
2475
2514
  }
2476
2515
  function formatTime2(ms) {
2477
2516
  if (ms < 1) return `${c.gray}<1ms${c.reset}`;
@@ -2480,137 +2519,107 @@ function formatTime2(ms) {
2480
2519
  return `${c.red}${ms}ms${c.reset}`;
2481
2520
  }
2482
2521
  var LOGO = `
2483
- ${c.green} \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${c.reset}
2484
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
2485
- ${c.green} \u2502${c.reset} ${c.brightGreen}\u26A1${c.reset} ${c.bold}${c.white}F L E X I R E A C T${c.reset} ${c.dim}v1.0.0${c.reset} ${c.green}\u2502${c.reset}
2486
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
2487
- ${c.green} \u2502${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}\u2502${c.reset}
2488
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
2489
- ${c.green} \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${c.reset}
2522
+ ${c.white}\u25B2${c.reset} ${c.bold}FlexiReact${c.reset} ${c.dim}4.1.0${c.reset}
2490
2523
  `;
2491
- var MINI_LOGO = `${c.brightGreen}\u26A1${c.reset} ${c.bold}FlexiReact${c.reset}`;
2492
- var READY_MSG = ` ${c.green}\u25B2${c.reset} ${c.bold}Ready${c.reset} in`;
2493
2524
  var logger = {
2494
2525
  // Show startup logo
2495
2526
  logo() {
2496
2527
  console.log(LOGO);
2528
+ console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
2529
+ console.log("");
2497
2530
  },
2498
- // Server started - Next.js style
2531
+ // Server started - Minimalist style
2499
2532
  serverStart(config, startTime = Date.now()) {
2500
- const { port, host, mode, pagesDir, islands, rsc } = config;
2533
+ const { port, host, mode, pagesDir } = config;
2501
2534
  const elapsed = Date.now() - startTime;
2535
+ console.log(` ${c.green}\u2714${c.reset} ${c.bold}Ready${c.reset} in ${elapsed}ms`);
2502
2536
  console.log("");
2503
- console.log(` ${c.green}\u25B2${c.reset} ${c.bold}Ready${c.reset} in ${c.cyan}${elapsed}ms${c.reset}`);
2504
- console.log("");
2505
- console.log(` ${c.dim}\u250C${c.reset} ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
2506
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Environment:${c.reset} ${mode === "development" ? `${c.yellow}development${c.reset}` : `${c.green}production${c.reset}`}`);
2507
- if (islands) {
2508
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Islands:${c.reset} ${c.green}enabled${c.reset}`);
2509
- }
2510
- if (rsc) {
2511
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}RSC:${c.reset} ${c.green}enabled${c.reset}`);
2512
- }
2513
- console.log(` ${c.dim}\u2514${c.reset} ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
2537
+ console.log(` ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
2538
+ console.log(` ${c.bold}Mode:${c.reset} ${mode === "development" ? c.yellow : c.green}${mode}${c.reset}`);
2539
+ console.log(` ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
2514
2540
  console.log("");
2515
2541
  },
2516
- // HTTP request log - Compact single line like Next.js
2542
+ // HTTP request log - Compact single line
2517
2543
  request(method, path11, status, time, extra = {}) {
2518
2544
  const methodColor = getMethodColor(method);
2519
2545
  const statusColor = getStatusColor(status);
2520
2546
  const timeStr = formatTime2(time);
2521
- let badge = "";
2522
- if (extra.type === "static" || extra.type === "ssg") {
2523
- badge = `${c.dim}\u25CB${c.reset}`;
2524
- } else if (extra.type === "dynamic" || extra.type === "ssr") {
2525
- badge = `${c.magenta}\u0192${c.reset}`;
2547
+ let badge = `${c.dim}\u25CB${c.reset}`;
2548
+ if (extra.type === "dynamic" || extra.type === "ssr") {
2549
+ badge = `${c.white}\u0192${c.reset}`;
2526
2550
  } else if (extra.type === "api") {
2527
- badge = `${c.blue}\u03BB${c.reset}`;
2528
- } else if (extra.type === "asset") {
2529
- badge = `${c.dim}\u25E6${c.reset}`;
2530
- } else {
2531
- badge = `${c.magenta}\u0192${c.reset}`;
2551
+ badge = `${c.cyan}\u03BB${c.reset}`;
2532
2552
  }
2533
2553
  const statusStr = `${statusColor}${status}${c.reset}`;
2534
2554
  const methodStr = `${methodColor}${method}${c.reset}`;
2535
- console.log(` ${badge} ${methodStr} ${path11} ${statusStr} ${c.dim}in${c.reset} ${timeStr}`);
2555
+ console.log(` ${badge} ${methodStr} ${path11} ${statusStr} ${c.dim}${timeStr}${c.reset}`);
2536
2556
  },
2537
2557
  // Info message
2538
2558
  info(msg) {
2539
- console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
2559
+ console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
2540
2560
  },
2541
2561
  // Success message
2542
2562
  success(msg) {
2543
- console.log(` ${c.green}\u2713${c.reset} ${msg}`);
2563
+ console.log(` ${c.green}\u2714${c.reset} ${msg}`);
2544
2564
  },
2545
2565
  // Warning message
2546
2566
  warn(msg) {
2547
- console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
2567
+ console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
2548
2568
  },
2549
2569
  // Error message
2550
2570
  error(msg, err = null) {
2551
- console.log(` ${c.red}\u2717${c.reset} ${c.red}${msg}${c.reset}`);
2571
+ console.log(` ${c.red}\u2716${c.reset} ${c.red}${msg}${c.reset}`);
2552
2572
  if (err && err.stack) {
2573
+ console.log("");
2553
2574
  const stack = err.stack.split("\n").slice(1, 4).join("\n");
2554
2575
  console.log(`${c.dim}${stack}${c.reset}`);
2576
+ console.log("");
2555
2577
  }
2556
2578
  },
2557
2579
  // Compilation message
2558
2580
  compile(file, time) {
2559
- console.log(` ${c.magenta}\u25C9${c.reset} Compiled ${c.cyan}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
2581
+ console.log(` ${c.white}\u25CF${c.reset} Compiling ${c.dim}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
2560
2582
  },
2561
2583
  // Hot reload
2562
2584
  hmr(file) {
2563
- console.log(` ${c.yellow}\u21BB${c.reset} HMR update: ${c.cyan}${file}${c.reset}`);
2585
+ console.log(` ${c.green}\u21BB${c.reset} Fast Refresh ${c.dim}${file}${c.reset}`);
2564
2586
  },
2565
2587
  // Plugin loaded
2566
2588
  plugin(name) {
2567
- console.log(` ${c.blue}\u2B21${c.reset} Plugin: ${c.cyan}${name}${c.reset}`);
2589
+ console.log(` ${c.cyan}\u25C6${c.reset} Plugin ${c.dim}${name}${c.reset}`);
2568
2590
  },
2569
2591
  // Route info
2570
2592
  route(path11, type) {
2571
- const typeColors = {
2572
- static: c.green,
2573
- dynamic: c.yellow,
2574
- api: c.blue
2575
- };
2576
- const color = typeColors[type] || c.white;
2577
- console.log(` ${c.dim}\u251C\u2500${c.reset} ${path11} ${color}[${type}]${c.reset}`);
2593
+ const typeLabel = type === "api" ? "\u03BB" : type === "dynamic" ? "\u0192" : "\u25CB";
2594
+ const color = type === "api" ? c.cyan : type === "dynamic" ? c.white : c.dim;
2595
+ console.log(` ${color}${typeLabel}${c.reset} ${path11}`);
2578
2596
  },
2579
2597
  // Divider
2580
2598
  divider() {
2581
- console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
2599
+ console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
2582
2600
  },
2583
2601
  // Blank line
2584
2602
  blank() {
2585
2603
  console.log("");
2586
2604
  },
2587
- // Port in use error with solution
2605
+ // Port in use error
2588
2606
  portInUse(port) {
2589
- console.log(`
2590
- ${c.red} \u2717 Port ${port} is already in use${c.reset}
2591
-
2592
- ${c.dim}Try one of these solutions:${c.reset}
2593
-
2594
- ${c.yellow}1.${c.reset} Kill the process using the port:
2595
- ${c.cyan}npx kill-port ${port}${c.reset}
2596
-
2597
- ${c.yellow}2.${c.reset} Use a different port in ${c.cyan}flexireact.config.js${c.reset}:
2598
- ${c.dim}server: { port: 3001 }${c.reset}
2599
-
2600
- ${c.yellow}3.${c.reset} Set PORT environment variable:
2601
- ${c.cyan}PORT=3001 npm run dev${c.reset}
2602
- `);
2607
+ this.error(`Port ${port} is already in use.`);
2608
+ this.blank();
2609
+ console.log(` ${c.dim}Try:${c.reset}`);
2610
+ console.log(` 1. Kill the process on port ${port}`);
2611
+ console.log(` 2. Use a different port via PORT env var`);
2612
+ this.blank();
2603
2613
  },
2604
2614
  // Build info
2605
2615
  build(stats) {
2606
- console.log(`
2607
- ${c.green}\u2713${c.reset} Build complete!
2608
-
2609
- ${c.dim}\u251C\u2500${c.reset} Pages: ${c.cyan}${stats.pages}${c.reset}
2610
- ${c.dim}\u251C\u2500${c.reset} API: ${c.cyan}${stats.api}${c.reset}
2611
- ${c.dim}\u251C\u2500${c.reset} Assets: ${c.cyan}${stats.assets}${c.reset}
2612
- ${c.dim}\u2514\u2500${c.reset} Time: ${c.green}${stats.time}ms${c.reset}
2613
- `);
2616
+ this.blank();
2617
+ console.log(` ${c.green}\u2714${c.reset} Build completed`);
2618
+ this.blank();
2619
+ console.log(` ${c.dim}Route${c.reset} ${c.dim}Size${c.reset}`);
2620
+ console.log(` ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
2621
+ console.log(` ${c.dim}Total time:${c.reset} ${c.white}${stats.time}ms${c.reset}`);
2622
+ this.blank();
2614
2623
  }
2615
2624
  };
2616
2625
 
@@ -3038,22 +3047,69 @@ function serializeArgs(args) {
3038
3047
  return arg;
3039
3048
  });
3040
3049
  }
3050
+ var ALLOWED_SERIALIZED_TYPES = /* @__PURE__ */ new Set(["FormData", "Date", "File"]);
3051
+ var MAX_PAYLOAD_DEPTH = 10;
3052
+ var MAX_STRING_LENGTH = 1e6;
3053
+ function validateInput(obj, depth = 0) {
3054
+ if (depth > MAX_PAYLOAD_DEPTH) {
3055
+ throw new Error("Payload too deeply nested");
3056
+ }
3057
+ if (obj === null || obj === void 0) return true;
3058
+ if (typeof obj === "string") {
3059
+ if (obj.length > MAX_STRING_LENGTH) {
3060
+ throw new Error("String value too long");
3061
+ }
3062
+ return true;
3063
+ }
3064
+ if (typeof obj !== "object") return true;
3065
+ if ("__proto__" in obj || "constructor" in obj || "prototype" in obj) {
3066
+ throw new Error("Invalid payload: prototype pollution attempt detected");
3067
+ }
3068
+ if ("$$type" in obj) {
3069
+ if (!ALLOWED_SERIALIZED_TYPES.has(obj.$$type)) {
3070
+ throw new Error(`Invalid serialized type: ${obj.$$type}`);
3071
+ }
3072
+ }
3073
+ if (Array.isArray(obj)) {
3074
+ for (const item of obj) {
3075
+ validateInput(item, depth + 1);
3076
+ }
3077
+ } else {
3078
+ for (const value of Object.values(obj)) {
3079
+ validateInput(value, depth + 1);
3080
+ }
3081
+ }
3082
+ return true;
3083
+ }
3041
3084
  function deserializeArgs(args) {
3085
+ validateInput(args);
3042
3086
  return args.map((arg) => {
3043
3087
  if (arg && typeof arg === "object") {
3044
3088
  if (arg.$$type === "FormData") {
3045
3089
  const formData = new FormData();
3046
- for (const [key, value] of Object.entries(arg.data)) {
3090
+ for (const [key, value] of Object.entries(arg.data || {})) {
3091
+ if (typeof key !== "string" || key.startsWith("__")) continue;
3047
3092
  if (Array.isArray(value)) {
3048
- value.forEach((v) => formData.append(key, v));
3049
- } else {
3050
- formData.append(key, value);
3093
+ value.forEach((v) => {
3094
+ if (typeof v === "string" || typeof v === "number") {
3095
+ formData.append(key, String(v));
3096
+ }
3097
+ });
3098
+ } else if (typeof value === "string" || typeof value === "number") {
3099
+ formData.append(key, String(value));
3051
3100
  }
3052
3101
  }
3053
3102
  return formData;
3054
3103
  }
3055
3104
  if (arg.$$type === "Date") {
3056
- return new Date(arg.value);
3105
+ const date = new Date(arg.value);
3106
+ if (isNaN(date.getTime())) {
3107
+ throw new Error("Invalid date value");
3108
+ }
3109
+ return date;
3110
+ }
3111
+ if (arg.$$type === "File") {
3112
+ return { name: String(arg.name || ""), type: String(arg.type || ""), size: Number(arg.size || 0) };
3057
3113
  }
3058
3114
  }
3059
3115
  return arg;
@@ -3586,7 +3642,6 @@ async function createServer(options = {}) {
3586
3642
  const serverStartTime = Date.now();
3587
3643
  const projectRoot = options.projectRoot || process.cwd();
3588
3644
  const isDev = options.mode === "development";
3589
- logger.logo();
3590
3645
  const rawConfig = await loadConfig(projectRoot);
3591
3646
  const config = resolvePaths(rawConfig, projectRoot);
3592
3647
  await loadPlugins(projectRoot, config);
@@ -3597,7 +3652,7 @@ async function createServer(options = {}) {
3597
3652
  const loadModule = createModuleLoader(isDev);
3598
3653
  const server = http.createServer(async (req, res) => {
3599
3654
  const startTime = Date.now();
3600
- const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
3655
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
3601
3656
  const pathname = url.pathname;
3602
3657
  try {
3603
3658
  await pluginManager.runHook(PluginHooks.REQUEST, req, res);
@@ -3605,7 +3660,7 @@ async function createServer(options = {}) {
3605
3660
  if (!middlewareResult.continue) {
3606
3661
  return;
3607
3662
  }
3608
- const effectivePath = middlewareResult.rewritten ? new URL(req.url, `http://${req.headers.host}`).pathname : pathname;
3663
+ const effectivePath = middlewareResult.rewritten ? new URL(req.url || "/", `http://${req.headers.host}`).pathname : pathname;
3609
3664
  if (await serveStaticFile(res, config.publicDir, effectivePath)) {
3610
3665
  return;
3611
3666
  }
@@ -3619,6 +3674,26 @@ async function createServer(options = {}) {
3619
3674
  const componentName = effectivePath.slice(18).replace(".js", "");
3620
3675
  return await serveClientComponent(res, config.pagesDir, componentName);
3621
3676
  }
3677
+ if (effectivePath.startsWith("/_flexi/client/")) {
3678
+ const componentName = effectivePath.slice(15).replace(".js", "");
3679
+ const searchDirs = [
3680
+ config.pagesDir,
3681
+ path8.join(process.cwd(), "app", "components"),
3682
+ path8.join(process.cwd(), "routes"),
3683
+ path8.join(process.cwd(), "routes", "(public)")
3684
+ ];
3685
+ for (const dir of searchDirs) {
3686
+ try {
3687
+ await serveClientComponent(res, dir, componentName);
3688
+ return;
3689
+ } catch {
3690
+ continue;
3691
+ }
3692
+ }
3693
+ res.writeHead(404, { "Content-Type": "text/plain" });
3694
+ res.end("Component not found: " + componentName);
3695
+ return;
3696
+ }
3622
3697
  if (effectivePath === "/_flexi/action" && req.method === "POST") {
3623
3698
  return await handleServerAction(req, res);
3624
3699
  }
@@ -3669,7 +3744,7 @@ async function createServer(options = {}) {
3669
3744
  const duration = Date.now() - startTime;
3670
3745
  if (isDev) {
3671
3746
  const routeType = pathname.startsWith("/api/") ? "api" : pathname.startsWith("/_flexi/") ? "asset" : pathname.match(/\.(js|css|png|jpg|svg|ico)$/) ? "asset" : "dynamic";
3672
- logger.request(req.method, pathname, res.statusCode, duration, { type: routeType });
3747
+ logger.request(req.method || "GET", pathname, res.statusCode, duration, { type: routeType });
3673
3748
  }
3674
3749
  await pluginManager.runHook(PluginHooks.RESPONSE, req, res, duration);
3675
3750
  }
@@ -3730,9 +3805,9 @@ async function serveStaticFile(res, baseDir, pathname) {
3730
3805
  async function handleApiRoute(req, res, route, loadModule) {
3731
3806
  try {
3732
3807
  const module = await loadModule(route.filePath);
3733
- const method = req.method.toLowerCase();
3808
+ const method = (req.method || "GET").toLowerCase();
3734
3809
  const body = await parseBody(req);
3735
- const url = new URL(req.url, `http://${req.headers.host}`);
3810
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
3736
3811
  const query = Object.fromEntries(url.searchParams);
3737
3812
  const enhancedReq = {
3738
3813
  ...req,
@@ -3758,11 +3833,28 @@ async function handleApiRoute(req, res, route, loadModule) {
3758
3833
  }
3759
3834
  async function handleServerAction(req, res) {
3760
3835
  try {
3836
+ const contentType = req.headers["content-type"];
3837
+ if (!contentType?.includes("application/json")) {
3838
+ res.writeHead(400, { "Content-Type": "application/json" });
3839
+ res.end(JSON.stringify({ success: false, error: "Invalid content type" }));
3840
+ return;
3841
+ }
3842
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
3843
+ if (contentLength > 10 * 1024 * 1024) {
3844
+ res.writeHead(413, { "Content-Type": "application/json" });
3845
+ res.end(JSON.stringify({ success: false, error: "Payload too large" }));
3846
+ return;
3847
+ }
3761
3848
  const body = await parseBody(req);
3762
3849
  const { actionId, args } = body;
3763
- if (!actionId) {
3850
+ if (!actionId || typeof actionId !== "string" || !/^[a-zA-Z0-9_]+$/.test(actionId)) {
3764
3851
  res.writeHead(400, { "Content-Type": "application/json" });
3765
- res.end(JSON.stringify({ success: false, error: "Missing actionId" }));
3852
+ res.end(JSON.stringify({ success: false, error: "Invalid actionId format" }));
3853
+ return;
3854
+ }
3855
+ if (args !== void 0 && !Array.isArray(args)) {
3856
+ res.writeHead(400, { "Content-Type": "application/json" });
3857
+ res.end(JSON.stringify({ success: false, error: "Invalid args format" }));
3766
3858
  return;
3767
3859
  }
3768
3860
  const deserializedArgs = deserializeArgs(args || []);
@@ -3779,10 +3871,11 @@ async function handleServerAction(req, res) {
3779
3871
  res.end(JSON.stringify(result));
3780
3872
  } catch (error) {
3781
3873
  console.error("Server Action Error:", error);
3874
+ const isDev = process.env.NODE_ENV !== "production";
3782
3875
  res.writeHead(500, { "Content-Type": "application/json" });
3783
3876
  res.end(JSON.stringify({
3784
3877
  success: false,
3785
- error: error.message || "Action execution failed"
3878
+ error: isDev ? error.message : "Action execution failed"
3786
3879
  }));
3787
3880
  }
3788
3881
  }
@@ -4002,6 +4095,8 @@ async function serveClientComponent(res, pagesDir, componentName) {
4002
4095
  const useCallback = window.useCallback;
4003
4096
  const useMemo = window.useMemo;
4004
4097
  const useRef = window.useRef;
4098
+ const useContext = window.useContext;
4099
+ const useReducer = window.useReducer;
4005
4100
  `
4006
4101
  });
4007
4102
  let code = result.code;
@@ -4022,41 +4117,53 @@ async function serveClientComponent(res, pagesDir, componentName) {
4022
4117
  function generateClientHydrationScript(componentPath, props) {
4023
4118
  const ext = path8.extname(componentPath);
4024
4119
  const componentName = path8.basename(componentPath, ext);
4120
+ const propsJson = JSON.stringify(props);
4025
4121
  return `
4026
4122
  <script type="module">
4027
- // FlexiReact Client Hydration
4123
+ // FlexiReact Client Hydration v4.1
4124
+ import React, { useState, useEffect, useCallback, useMemo, useRef, useContext, useReducer } from 'https://esm.sh/react@19.0.0';
4125
+ import { createRoot, hydrateRoot } from 'https://esm.sh/react-dom@19.0.0/client';
4126
+
4127
+ // Make React hooks available globally
4128
+ window.React = React;
4129
+ window.useState = useState;
4130
+ window.useEffect = useEffect;
4131
+ window.useCallback = useCallback;
4132
+ window.useMemo = useMemo;
4133
+ window.useRef = useRef;
4134
+ window.useContext = useContext;
4135
+ window.useReducer = useReducer;
4136
+
4028
4137
  (async function() {
4029
4138
  try {
4030
- const React = await import('https://esm.sh/react@18.3.1');
4031
- const ReactDOM = await import('https://esm.sh/react-dom@18.3.1/client');
4139
+ // Fetch and execute the component
4140
+ const response = await fetch('/_flexi/client/${componentName}.js');
4141
+ if (!response.ok) throw new Error('Failed to load component: ' + response.status);
4032
4142
 
4033
- // Make React available globally for the component
4034
- window.React = React.default || React;
4035
- window.useState = React.useState;
4036
- window.useEffect = React.useEffect;
4037
- window.useCallback = React.useCallback;
4038
- window.useMemo = React.useMemo;
4039
- window.useRef = React.useRef;
4040
-
4041
- // Fetch the component code
4042
- const response = await fetch('/_flexi/component/${componentName}.js');
4043
4143
  const code = await response.text();
4044
-
4045
- // Create and import the module
4046
4144
  const blob = new Blob([code], { type: 'application/javascript' });
4047
4145
  const moduleUrl = URL.createObjectURL(blob);
4048
4146
  const module = await import(moduleUrl);
4049
4147
 
4050
4148
  const Component = module.default;
4051
- const props = ${JSON.stringify(props)};
4149
+ if (!Component) throw new Error('No default export found');
4052
4150
 
4053
- // Hydrate the root
4054
- const root = document.getElementById('root');
4055
- ReactDOM.hydrateRoot(root, window.React.createElement(Component, props));
4151
+ const props = ${propsJson};
4152
+ const container = document.getElementById('flexi-root') || document.getElementById('root');
4056
4153
 
4057
- console.log('\u26A1 FlexiReact: Component hydrated successfully');
4154
+ if (container) {
4155
+ // Use createRoot for client components (not hydrate since we rendered placeholder)
4156
+ const root = createRoot(container);
4157
+ root.render(React.createElement(Component, props));
4158
+ console.log('%c\u26A1 FlexiReact', 'color: #00FF9C; font-weight: bold', 'Component mounted');
4159
+ }
4058
4160
  } catch (error) {
4059
- console.error('\u26A1 FlexiReact: Hydration failed', error);
4161
+ console.error('%c\u26A1 FlexiReact', 'color: #ef4444; font-weight: bold', 'Hydration failed:', error);
4162
+ // Show error in UI
4163
+ const container = document.getElementById('flexi-root');
4164
+ if (container) {
4165
+ container.innerHTML = '<div style="color:#ef4444;padding:20px;text-align:center;">Failed to load component: ' + error.message + '</div>';
4166
+ }
4060
4167
  }
4061
4168
  })();
4062
4169
  </script>`;
@@ -4341,7 +4448,7 @@ async function copyPublicAssets(publicDir, outDir) {
4341
4448
  function generateManifest(options) {
4342
4449
  const { routes, clientResult, serverResult, config } = options;
4343
4450
  return {
4344
- version: "2.0.0",
4451
+ version: "4.1.0",
4345
4452
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4346
4453
  routes: {
4347
4454
  pages: routes.pages.map((r) => ({
@@ -6302,7 +6409,7 @@ function formatBytes2(bytes) {
6302
6409
  }
6303
6410
 
6304
6411
  // core/index.ts
6305
- var VERSION = "3.1.0";
6412
+ var VERSION = "4.0.0";
6306
6413
  var core_default = {
6307
6414
  VERSION,
6308
6415
  createServer