@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.
@@ -8,46 +8,59 @@ import { fileURLToPath, pathToFileURL as pathToFileURL4 } from "url";
8
8
  import fs from "fs";
9
9
  import path from "path";
10
10
  import { pathToFileURL } from "url";
11
- var defaultConfig = {
11
+ import { z } from "zod";
12
+ import pc from "picocolors";
13
+ var BuildConfigSchema = z.object({
14
+ target: z.string().default("es2022"),
15
+ minify: z.boolean().default(true),
16
+ sourcemap: z.boolean().default(true),
17
+ splitting: z.boolean().default(true)
18
+ }).default({});
19
+ var ServerConfigSchema = z.object({
20
+ port: z.number().min(1).max(65535).default(3e3),
21
+ host: z.string().default("localhost")
22
+ }).default({});
23
+ var SSGConfigSchema = z.object({
24
+ enabled: z.boolean().default(false),
25
+ paths: z.array(z.string()).default([])
26
+ }).default({});
27
+ var IslandsConfigSchema = z.object({
28
+ enabled: z.boolean().default(true),
29
+ directive: z.string().default("use island")
30
+ }).default({});
31
+ var RSCConfigSchema = z.object({
32
+ enabled: z.boolean().default(true)
33
+ }).default({});
34
+ var PluginSchema = z.object({
35
+ name: z.string(),
36
+ setup: z.function().optional()
37
+ }).passthrough();
38
+ var FlexiReactConfigSchema = z.object({
12
39
  // Directories
13
- pagesDir: "pages",
14
- layoutsDir: "layouts",
15
- publicDir: "public",
16
- outDir: ".flexi",
40
+ pagesDir: z.string().default("pages"),
41
+ layoutsDir: z.string().default("layouts"),
42
+ publicDir: z.string().default("public"),
43
+ outDir: z.string().default(".flexi"),
17
44
  // Build options
18
- build: {
19
- target: "es2022",
20
- minify: true,
21
- sourcemap: true,
22
- splitting: true
23
- },
45
+ build: BuildConfigSchema,
24
46
  // Server options
25
- server: {
26
- port: 3e3,
27
- host: "localhost"
28
- },
47
+ server: ServerConfigSchema,
29
48
  // SSG options
30
- ssg: {
31
- enabled: false,
32
- paths: []
33
- },
49
+ ssg: SSGConfigSchema,
34
50
  // Islands (partial hydration)
35
- islands: {
36
- enabled: true
37
- },
51
+ islands: IslandsConfigSchema,
38
52
  // RSC options
39
- rsc: {
40
- enabled: true
41
- },
53
+ rsc: RSCConfigSchema,
42
54
  // Plugins
43
- plugins: [],
55
+ plugins: z.array(PluginSchema).default([]),
44
56
  // Styles (CSS files to include)
45
- styles: [],
57
+ styles: z.array(z.string()).default([]),
46
58
  // Scripts (JS files to include)
47
- scripts: [],
59
+ scripts: z.array(z.string()).default([]),
48
60
  // Favicon path
49
- favicon: null
50
- };
61
+ favicon: z.string().nullable().default(null)
62
+ });
63
+ var defaultConfig = FlexiReactConfigSchema.parse({});
51
64
  async function loadConfig(projectRoot) {
52
65
  const configPathTs = path.join(projectRoot, "flexireact.config.ts");
53
66
  const configPathJs = path.join(projectRoot, "flexireact.config.js");
@@ -59,10 +72,22 @@ async function loadConfig(projectRoot) {
59
72
  const module = await import(`${configUrl}?t=${Date.now()}`);
60
73
  userConfig = module.default || module;
61
74
  } catch (error) {
62
- console.warn("Warning: Failed to load flexireact config:", error.message);
75
+ console.warn(pc.yellow(`\u26A0 Failed to load config: ${error.message}`));
63
76
  }
64
77
  }
65
- return deepMerge(defaultConfig, userConfig);
78
+ const merged = deepMerge(defaultConfig, userConfig);
79
+ try {
80
+ return FlexiReactConfigSchema.parse(merged);
81
+ } catch (err) {
82
+ if (err instanceof z.ZodError) {
83
+ console.error(pc.red("\u2716 Configuration validation failed:"));
84
+ for (const issue of err.issues) {
85
+ console.error(pc.dim(` - ${issue.path.join(".")}: ${issue.message}`));
86
+ }
87
+ process.exit(1);
88
+ }
89
+ throw err;
90
+ }
66
91
  }
67
92
  function deepMerge(target, source) {
68
93
  const result = { ...target };
@@ -148,7 +173,7 @@ function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
148
173
  appRoutes: [],
149
174
  // Next.js style app router routes
150
175
  flexiRoutes: []
151
- // FlexiReact v2 routes/ directory
176
+ // FlexiReact v4 routes/ directory
152
177
  };
153
178
  const routesDirPath = routesDir || path2.join(projectRoot, "routes");
154
179
  if (fs3.existsSync(routesDirPath)) {
@@ -499,32 +524,72 @@ async function renderPage(options) {
499
524
  favicon = null,
500
525
  isSSG = false,
501
526
  route = "/",
502
- needsHydration = false
527
+ needsHydration = false,
528
+ componentPath = ""
503
529
  } = options;
504
530
  const renderStart = Date.now();
505
531
  try {
506
- let element = React.createElement(Component, props);
507
- if (error) {
508
- element = React.createElement(ErrorBoundaryWrapper, {
509
- fallback: error,
510
- children: element
511
- });
512
- }
513
- if (loading) {
514
- element = React.createElement(React.Suspense, {
515
- fallback: React.createElement(loading),
516
- children: element
517
- });
518
- }
519
- for (const layout of [...layouts].reverse()) {
520
- if (layout.Component) {
521
- const LayoutComponent = layout.Component;
522
- element = React.createElement(LayoutComponent, {
523
- ...layout.props
524
- }, element);
532
+ let element;
533
+ let content;
534
+ let isClientOnly = false;
535
+ try {
536
+ element = React.createElement(Component, props);
537
+ if (error) {
538
+ element = React.createElement(ErrorBoundaryWrapper, {
539
+ fallback: error,
540
+ children: element
541
+ });
542
+ }
543
+ if (loading) {
544
+ element = React.createElement(React.Suspense, {
545
+ fallback: React.createElement(loading),
546
+ children: element
547
+ });
548
+ }
549
+ for (const layout of [...layouts].reverse()) {
550
+ if (layout.Component) {
551
+ const LayoutComponent = layout.Component;
552
+ element = React.createElement(LayoutComponent, {
553
+ ...layout.props
554
+ }, element);
555
+ }
556
+ }
557
+ content = renderToString(element);
558
+ } catch (renderErr) {
559
+ 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");
560
+ if (isHookError || needsHydration) {
561
+ isClientOnly = true;
562
+ const placeholderHtml = `
563
+ <div id="flexi-root" data-client-component="true" data-component-path="${escapeHtml(componentPath)}">
564
+ <div class="flexi-loading" style="display:flex;align-items:center;justify-content:center;min-height:200px;color:#666;">
565
+ <div style="text-align:center;">
566
+ <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>
567
+ <div>Loading...</div>
568
+ </div>
569
+ </div>
570
+ <style>@keyframes spin{to{transform:rotate(360deg)}}</style>
571
+ </div>
572
+ `;
573
+ let wrappedContent = placeholderHtml;
574
+ for (const layout of [...layouts].reverse()) {
575
+ if (layout.Component) {
576
+ try {
577
+ const layoutElement = React.createElement(layout.Component, {
578
+ ...layout.props,
579
+ children: React.createElement("div", {
580
+ dangerouslySetInnerHTML: { __html: wrappedContent }
581
+ })
582
+ });
583
+ wrappedContent = renderToString(layoutElement);
584
+ } catch {
585
+ }
586
+ }
587
+ }
588
+ content = wrappedContent;
589
+ } else {
590
+ throw renderErr;
525
591
  }
526
592
  }
527
- const content = renderToString(element);
528
593
  const renderTime = Date.now() - renderStart;
529
594
  const islandScripts = generateIslandScripts(islands);
530
595
  return buildHtmlDocument({
@@ -538,7 +603,8 @@ async function renderPage(options) {
538
603
  isSSG,
539
604
  renderTime,
540
605
  route,
541
- isClientComponent: needsHydration
606
+ isClientComponent: needsHydration || isClientOnly,
607
+ componentPath
542
608
  });
543
609
  } catch (err) {
544
610
  console.error("Render Error:", err);
@@ -589,7 +655,7 @@ function generateDevToolbar(options = {}) {
589
655
  const timeColor = renderTime < 50 ? "#00FF9C" : renderTime < 200 ? "#fbbf24" : "#ef4444";
590
656
  const timeLabel = renderTime < 50 ? "Fast" : renderTime < 200 ? "OK" : "Slow";
591
657
  return `
592
- <!-- FlexiReact v2 Dev Toolbar -->
658
+ <!-- FlexiReact v4.1.0 Dev Toolbar -->
593
659
  <div id="flexi-dev-toolbar" class="flexi-dev-collapsed">
594
660
  <style>
595
661
  #flexi-dev-toolbar {
@@ -897,7 +963,7 @@ function generateDevToolbar(options = {}) {
897
963
  <div class="flexi-dev-header-logo">F</div>
898
964
  <div class="flexi-dev-header-info">
899
965
  <div class="flexi-dev-header-title">FlexiReact</div>
900
- <div class="flexi-dev-header-subtitle">v2.0.0 \u2022 Development</div>
966
+ <div class="flexi-dev-header-subtitle">v4.1.0 \u2022 Development</div>
901
967
  </div>
902
968
  <button class="flexi-dev-close" onclick="this.closest('#flexi-dev-toolbar').classList.remove('flexi-dev-open')">\u2715</button>
903
969
  </div>
@@ -950,9 +1016,9 @@ function generateDevToolbar(options = {}) {
950
1016
  </div>
951
1017
 
952
1018
  <script>
953
- // FlexiReact v2 DevTools
1019
+ // FlexiReact v4 DevTools
954
1020
  window.__FLEXI_DEV__ = {
955
- version: '2.0.0',
1021
+ version: '4.1.0',
956
1022
  renderTime: ${renderTime},
957
1023
  pageType: '${pageType}',
958
1024
  route: '${route}',
@@ -987,7 +1053,7 @@ function generateDevToolbar(options = {}) {
987
1053
 
988
1054
  // Console branding
989
1055
  console.log(
990
- '%c \u26A1 FlexiReact v2 %c ${pageType} %c ${renderTime}ms ',
1056
+ '%c \u26A1 FlexiReact v4.1.0 %c ${pageType} %c ${renderTime}ms ',
991
1057
  'background: #00FF9C; color: #000; font-weight: bold; padding: 2px 6px; border-radius: 4px 0 0 4px;',
992
1058
  'background: #1e1e1e; color: #fafafa; padding: 2px 6px;',
993
1059
  'background: ${timeColor}20; color: ${timeColor}; padding: 2px 6px; border-radius: 0 4px 4px 0;'
@@ -1088,7 +1154,7 @@ function renderError(statusCode, message, stack = null) {
1088
1154
  401: { title: "Unauthorized", icon: "key", color: "#8b5cf6", desc: "Please log in to access this page." }
1089
1155
  };
1090
1156
  const errorInfo = errorMessages[statusCode] || { title: "Error", icon: "alert", color: "#ef4444", desc: message };
1091
- const errorFramesHtml = showStack && errorDetails?.frames?.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
1157
+ const errorFramesHtml = showStack && errorDetails?.frames?.length && errorDetails.frames.length > 0 ? errorDetails.frames.slice(0, 5).map((frame, i) => `
1092
1158
  <div class="error-frame ${i === 0 ? "error-frame-first" : ""}">
1093
1159
  <div class="error-frame-fn">${escapeHtml(frame.fn)}</div>
1094
1160
  <div class="error-frame-loc">${escapeHtml(frame.file)}:${frame.line}:${frame.col}</div>
@@ -1388,7 +1454,7 @@ function renderError(statusCode, message, stack = null) {
1388
1454
  ${isDev ? `
1389
1455
  <div class="dev-badge">
1390
1456
  <div class="dev-badge-dot"></div>
1391
- FlexiReact v2
1457
+ FlexiReact v4.1.0
1392
1458
  </div>
1393
1459
  ` : ""}
1394
1460
  </body>
@@ -1826,10 +1892,7 @@ var colors = {
1826
1892
  reset: "\x1B[0m",
1827
1893
  bold: "\x1B[1m",
1828
1894
  dim: "\x1B[2m",
1829
- italic: "\x1B[3m",
1830
- underline: "\x1B[4m",
1831
1895
  // Text colors
1832
- black: "\x1B[30m",
1833
1896
  red: "\x1B[31m",
1834
1897
  green: "\x1B[32m",
1835
1898
  yellow: "\x1B[33m",
@@ -1837,22 +1900,7 @@ var colors = {
1837
1900
  magenta: "\x1B[35m",
1838
1901
  cyan: "\x1B[36m",
1839
1902
  white: "\x1B[37m",
1840
- gray: "\x1B[90m",
1841
- // Bright colors
1842
- brightRed: "\x1B[91m",
1843
- brightGreen: "\x1B[92m",
1844
- brightYellow: "\x1B[93m",
1845
- brightBlue: "\x1B[94m",
1846
- brightMagenta: "\x1B[95m",
1847
- brightCyan: "\x1B[96m",
1848
- brightWhite: "\x1B[97m",
1849
- // Background colors
1850
- bgRed: "\x1B[41m",
1851
- bgGreen: "\x1B[42m",
1852
- bgYellow: "\x1B[43m",
1853
- bgBlue: "\x1B[44m",
1854
- bgMagenta: "\x1B[45m",
1855
- bgCyan: "\x1B[46m"
1903
+ gray: "\x1B[90m"
1856
1904
  };
1857
1905
  var c = colors;
1858
1906
  function getStatusColor(status) {
@@ -1863,16 +1911,7 @@ function getStatusColor(status) {
1863
1911
  return c.white;
1864
1912
  }
1865
1913
  function getMethodColor(method) {
1866
- const methodColors = {
1867
- GET: c.brightGreen,
1868
- POST: c.brightBlue,
1869
- PUT: c.brightYellow,
1870
- PATCH: c.brightMagenta,
1871
- DELETE: c.brightRed,
1872
- OPTIONS: c.gray,
1873
- HEAD: c.gray
1874
- };
1875
- return methodColors[method] || c.white;
1914
+ return c.white;
1876
1915
  }
1877
1916
  function formatTime(ms) {
1878
1917
  if (ms < 1) return `${c.gray}<1ms${c.reset}`;
@@ -1881,137 +1920,107 @@ function formatTime(ms) {
1881
1920
  return `${c.red}${ms}ms${c.reset}`;
1882
1921
  }
1883
1922
  var LOGO = `
1884
- ${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}
1885
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
1886
- ${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}
1887
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
1888
- ${c.green} \u2502${c.reset} ${c.dim}The Modern React Framework${c.reset} ${c.green}\u2502${c.reset}
1889
- ${c.green} \u2502${c.reset} ${c.green}\u2502${c.reset}
1890
- ${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}
1923
+ ${c.white}\u25B2${c.reset} ${c.bold}FlexiReact${c.reset} ${c.dim}4.1.0${c.reset}
1891
1924
  `;
1892
- var MINI_LOGO = `${c.brightGreen}\u26A1${c.reset} ${c.bold}FlexiReact${c.reset}`;
1893
- var READY_MSG = ` ${c.green}\u25B2${c.reset} ${c.bold}Ready${c.reset} in`;
1894
1925
  var logger = {
1895
1926
  // Show startup logo
1896
1927
  logo() {
1897
1928
  console.log(LOGO);
1929
+ 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}`);
1930
+ console.log("");
1898
1931
  },
1899
- // Server started - Next.js style
1932
+ // Server started - Minimalist style
1900
1933
  serverStart(config, startTime = Date.now()) {
1901
- const { port, host, mode, pagesDir, islands, rsc } = config;
1934
+ const { port, host, mode, pagesDir } = config;
1902
1935
  const elapsed = Date.now() - startTime;
1936
+ console.log(` ${c.green}\u2714${c.reset} ${c.bold}Ready${c.reset} in ${elapsed}ms`);
1903
1937
  console.log("");
1904
- console.log(` ${c.green}\u25B2${c.reset} ${c.bold}Ready${c.reset} in ${c.cyan}${elapsed}ms${c.reset}`);
1905
- console.log("");
1906
- console.log(` ${c.dim}\u250C${c.reset} ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
1907
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Environment:${c.reset} ${mode === "development" ? `${c.yellow}development${c.reset}` : `${c.green}production${c.reset}`}`);
1908
- if (islands) {
1909
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}Islands:${c.reset} ${c.green}enabled${c.reset}`);
1910
- }
1911
- if (rsc) {
1912
- console.log(` ${c.dim}\u251C${c.reset} ${c.bold}RSC:${c.reset} ${c.green}enabled${c.reset}`);
1913
- }
1914
- console.log(` ${c.dim}\u2514${c.reset} ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
1938
+ console.log(` ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
1939
+ console.log(` ${c.bold}Mode:${c.reset} ${mode === "development" ? c.yellow : c.green}${mode}${c.reset}`);
1940
+ console.log(` ${c.bold}Pages:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
1915
1941
  console.log("");
1916
1942
  },
1917
- // HTTP request log - Compact single line like Next.js
1943
+ // HTTP request log - Compact single line
1918
1944
  request(method, path8, status, time, extra = {}) {
1919
1945
  const methodColor = getMethodColor(method);
1920
1946
  const statusColor = getStatusColor(status);
1921
1947
  const timeStr = formatTime(time);
1922
- let badge = "";
1923
- if (extra.type === "static" || extra.type === "ssg") {
1924
- badge = `${c.dim}\u25CB${c.reset}`;
1925
- } else if (extra.type === "dynamic" || extra.type === "ssr") {
1926
- badge = `${c.magenta}\u0192${c.reset}`;
1948
+ let badge = `${c.dim}\u25CB${c.reset}`;
1949
+ if (extra.type === "dynamic" || extra.type === "ssr") {
1950
+ badge = `${c.white}\u0192${c.reset}`;
1927
1951
  } else if (extra.type === "api") {
1928
- badge = `${c.blue}\u03BB${c.reset}`;
1929
- } else if (extra.type === "asset") {
1930
- badge = `${c.dim}\u25E6${c.reset}`;
1931
- } else {
1932
- badge = `${c.magenta}\u0192${c.reset}`;
1952
+ badge = `${c.cyan}\u03BB${c.reset}`;
1933
1953
  }
1934
1954
  const statusStr = `${statusColor}${status}${c.reset}`;
1935
1955
  const methodStr = `${methodColor}${method}${c.reset}`;
1936
- console.log(` ${badge} ${methodStr} ${path8} ${statusStr} ${c.dim}in${c.reset} ${timeStr}`);
1956
+ console.log(` ${badge} ${methodStr} ${path8} ${statusStr} ${c.dim}${timeStr}${c.reset}`);
1937
1957
  },
1938
1958
  // Info message
1939
1959
  info(msg) {
1940
- console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
1960
+ console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
1941
1961
  },
1942
1962
  // Success message
1943
1963
  success(msg) {
1944
- console.log(` ${c.green}\u2713${c.reset} ${msg}`);
1964
+ console.log(` ${c.green}\u2714${c.reset} ${msg}`);
1945
1965
  },
1946
1966
  // Warning message
1947
1967
  warn(msg) {
1948
- console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
1968
+ console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
1949
1969
  },
1950
1970
  // Error message
1951
1971
  error(msg, err = null) {
1952
- console.log(` ${c.red}\u2717${c.reset} ${c.red}${msg}${c.reset}`);
1972
+ console.log(` ${c.red}\u2716${c.reset} ${c.red}${msg}${c.reset}`);
1953
1973
  if (err && err.stack) {
1974
+ console.log("");
1954
1975
  const stack = err.stack.split("\n").slice(1, 4).join("\n");
1955
1976
  console.log(`${c.dim}${stack}${c.reset}`);
1977
+ console.log("");
1956
1978
  }
1957
1979
  },
1958
1980
  // Compilation message
1959
1981
  compile(file, time) {
1960
- console.log(` ${c.magenta}\u25C9${c.reset} Compiled ${c.cyan}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
1982
+ console.log(` ${c.white}\u25CF${c.reset} Compiling ${c.dim}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
1961
1983
  },
1962
1984
  // Hot reload
1963
1985
  hmr(file) {
1964
- console.log(` ${c.yellow}\u21BB${c.reset} HMR update: ${c.cyan}${file}${c.reset}`);
1986
+ console.log(` ${c.green}\u21BB${c.reset} Fast Refresh ${c.dim}${file}${c.reset}`);
1965
1987
  },
1966
1988
  // Plugin loaded
1967
1989
  plugin(name) {
1968
- console.log(` ${c.blue}\u2B21${c.reset} Plugin: ${c.cyan}${name}${c.reset}`);
1990
+ console.log(` ${c.cyan}\u25C6${c.reset} Plugin ${c.dim}${name}${c.reset}`);
1969
1991
  },
1970
1992
  // Route info
1971
1993
  route(path8, type) {
1972
- const typeColors = {
1973
- static: c.green,
1974
- dynamic: c.yellow,
1975
- api: c.blue
1976
- };
1977
- const color = typeColors[type] || c.white;
1978
- console.log(` ${c.dim}\u251C\u2500${c.reset} ${path8} ${color}[${type}]${c.reset}`);
1994
+ const typeLabel = type === "api" ? "\u03BB" : type === "dynamic" ? "\u0192" : "\u25CB";
1995
+ const color = type === "api" ? c.cyan : type === "dynamic" ? c.white : c.dim;
1996
+ console.log(` ${color}${typeLabel}${c.reset} ${path8}`);
1979
1997
  },
1980
1998
  // Divider
1981
1999
  divider() {
1982
- 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}`);
2000
+ 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}`);
1983
2001
  },
1984
2002
  // Blank line
1985
2003
  blank() {
1986
2004
  console.log("");
1987
2005
  },
1988
- // Port in use error with solution
2006
+ // Port in use error
1989
2007
  portInUse(port) {
1990
- console.log(`
1991
- ${c.red} \u2717 Port ${port} is already in use${c.reset}
1992
-
1993
- ${c.dim}Try one of these solutions:${c.reset}
1994
-
1995
- ${c.yellow}1.${c.reset} Kill the process using the port:
1996
- ${c.cyan}npx kill-port ${port}${c.reset}
1997
-
1998
- ${c.yellow}2.${c.reset} Use a different port in ${c.cyan}flexireact.config.js${c.reset}:
1999
- ${c.dim}server: { port: 3001 }${c.reset}
2000
-
2001
- ${c.yellow}3.${c.reset} Set PORT environment variable:
2002
- ${c.cyan}PORT=3001 npm run dev${c.reset}
2003
- `);
2008
+ this.error(`Port ${port} is already in use.`);
2009
+ this.blank();
2010
+ console.log(` ${c.dim}Try:${c.reset}`);
2011
+ console.log(` 1. Kill the process on port ${port}`);
2012
+ console.log(` 2. Use a different port via PORT env var`);
2013
+ this.blank();
2004
2014
  },
2005
2015
  // Build info
2006
2016
  build(stats) {
2007
- console.log(`
2008
- ${c.green}\u2713${c.reset} Build complete!
2009
-
2010
- ${c.dim}\u251C\u2500${c.reset} Pages: ${c.cyan}${stats.pages}${c.reset}
2011
- ${c.dim}\u251C\u2500${c.reset} API: ${c.cyan}${stats.api}${c.reset}
2012
- ${c.dim}\u251C\u2500${c.reset} Assets: ${c.cyan}${stats.assets}${c.reset}
2013
- ${c.dim}\u2514\u2500${c.reset} Time: ${c.green}${stats.time}ms${c.reset}
2014
- `);
2017
+ this.blank();
2018
+ console.log(` ${c.green}\u2714${c.reset} Build completed`);
2019
+ this.blank();
2020
+ console.log(` ${c.dim}Route${c.reset} ${c.dim}Size${c.reset}`);
2021
+ 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}`);
2022
+ console.log(` ${c.dim}Total time:${c.reset} ${c.white}${stats.time}ms${c.reset}`);
2023
+ this.blank();
2015
2024
  }
2016
2025
  };
2017
2026
 
@@ -2302,22 +2311,69 @@ async function executeAction(actionId, args, context) {
2302
2311
  globalThis.__FLEXI_ACTION_CONTEXT__ = null;
2303
2312
  }
2304
2313
  }
2314
+ var ALLOWED_SERIALIZED_TYPES = /* @__PURE__ */ new Set(["FormData", "Date", "File"]);
2315
+ var MAX_PAYLOAD_DEPTH = 10;
2316
+ var MAX_STRING_LENGTH = 1e6;
2317
+ function validateInput(obj, depth = 0) {
2318
+ if (depth > MAX_PAYLOAD_DEPTH) {
2319
+ throw new Error("Payload too deeply nested");
2320
+ }
2321
+ if (obj === null || obj === void 0) return true;
2322
+ if (typeof obj === "string") {
2323
+ if (obj.length > MAX_STRING_LENGTH) {
2324
+ throw new Error("String value too long");
2325
+ }
2326
+ return true;
2327
+ }
2328
+ if (typeof obj !== "object") return true;
2329
+ if ("__proto__" in obj || "constructor" in obj || "prototype" in obj) {
2330
+ throw new Error("Invalid payload: prototype pollution attempt detected");
2331
+ }
2332
+ if ("$$type" in obj) {
2333
+ if (!ALLOWED_SERIALIZED_TYPES.has(obj.$$type)) {
2334
+ throw new Error(`Invalid serialized type: ${obj.$$type}`);
2335
+ }
2336
+ }
2337
+ if (Array.isArray(obj)) {
2338
+ for (const item of obj) {
2339
+ validateInput(item, depth + 1);
2340
+ }
2341
+ } else {
2342
+ for (const value of Object.values(obj)) {
2343
+ validateInput(value, depth + 1);
2344
+ }
2345
+ }
2346
+ return true;
2347
+ }
2305
2348
  function deserializeArgs(args) {
2349
+ validateInput(args);
2306
2350
  return args.map((arg) => {
2307
2351
  if (arg && typeof arg === "object") {
2308
2352
  if (arg.$$type === "FormData") {
2309
2353
  const formData = new FormData();
2310
- for (const [key, value] of Object.entries(arg.data)) {
2354
+ for (const [key, value] of Object.entries(arg.data || {})) {
2355
+ if (typeof key !== "string" || key.startsWith("__")) continue;
2311
2356
  if (Array.isArray(value)) {
2312
- value.forEach((v) => formData.append(key, v));
2313
- } else {
2314
- formData.append(key, value);
2357
+ value.forEach((v) => {
2358
+ if (typeof v === "string" || typeof v === "number") {
2359
+ formData.append(key, String(v));
2360
+ }
2361
+ });
2362
+ } else if (typeof value === "string" || typeof value === "number") {
2363
+ formData.append(key, String(value));
2315
2364
  }
2316
2365
  }
2317
2366
  return formData;
2318
2367
  }
2319
2368
  if (arg.$$type === "Date") {
2320
- return new Date(arg.value);
2369
+ const date = new Date(arg.value);
2370
+ if (isNaN(date.getTime())) {
2371
+ throw new Error("Invalid date value");
2372
+ }
2373
+ return date;
2374
+ }
2375
+ if (arg.$$type === "File") {
2376
+ return { name: String(arg.name || ""), type: String(arg.type || ""), size: Number(arg.size || 0) };
2321
2377
  }
2322
2378
  }
2323
2379
  return arg;
@@ -2591,7 +2647,6 @@ async function createServer(options = {}) {
2591
2647
  const serverStartTime = Date.now();
2592
2648
  const projectRoot = options.projectRoot || process.cwd();
2593
2649
  const isDev = options.mode === "development";
2594
- logger.logo();
2595
2650
  const rawConfig = await loadConfig(projectRoot);
2596
2651
  const config = resolvePaths(rawConfig, projectRoot);
2597
2652
  await loadPlugins(projectRoot, config);
@@ -2602,7 +2657,7 @@ async function createServer(options = {}) {
2602
2657
  const loadModule = createModuleLoader(isDev);
2603
2658
  const server = http.createServer(async (req, res) => {
2604
2659
  const startTime = Date.now();
2605
- const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
2660
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
2606
2661
  const pathname = url.pathname;
2607
2662
  try {
2608
2663
  await pluginManager.runHook(PluginHooks.REQUEST, req, res);
@@ -2610,7 +2665,7 @@ async function createServer(options = {}) {
2610
2665
  if (!middlewareResult.continue) {
2611
2666
  return;
2612
2667
  }
2613
- const effectivePath = middlewareResult.rewritten ? new URL(req.url, `http://${req.headers.host}`).pathname : pathname;
2668
+ const effectivePath = middlewareResult.rewritten ? new URL(req.url || "/", `http://${req.headers.host}`).pathname : pathname;
2614
2669
  if (await serveStaticFile(res, config.publicDir, effectivePath)) {
2615
2670
  return;
2616
2671
  }
@@ -2624,6 +2679,26 @@ async function createServer(options = {}) {
2624
2679
  const componentName = effectivePath.slice(18).replace(".js", "");
2625
2680
  return await serveClientComponent(res, config.pagesDir, componentName);
2626
2681
  }
2682
+ if (effectivePath.startsWith("/_flexi/client/")) {
2683
+ const componentName = effectivePath.slice(15).replace(".js", "");
2684
+ const searchDirs = [
2685
+ config.pagesDir,
2686
+ path7.join(process.cwd(), "app", "components"),
2687
+ path7.join(process.cwd(), "routes"),
2688
+ path7.join(process.cwd(), "routes", "(public)")
2689
+ ];
2690
+ for (const dir of searchDirs) {
2691
+ try {
2692
+ await serveClientComponent(res, dir, componentName);
2693
+ return;
2694
+ } catch {
2695
+ continue;
2696
+ }
2697
+ }
2698
+ res.writeHead(404, { "Content-Type": "text/plain" });
2699
+ res.end("Component not found: " + componentName);
2700
+ return;
2701
+ }
2627
2702
  if (effectivePath === "/_flexi/action" && req.method === "POST") {
2628
2703
  return await handleServerAction(req, res);
2629
2704
  }
@@ -2674,7 +2749,7 @@ async function createServer(options = {}) {
2674
2749
  const duration = Date.now() - startTime;
2675
2750
  if (isDev) {
2676
2751
  const routeType = pathname.startsWith("/api/") ? "api" : pathname.startsWith("/_flexi/") ? "asset" : pathname.match(/\.(js|css|png|jpg|svg|ico)$/) ? "asset" : "dynamic";
2677
- logger.request(req.method, pathname, res.statusCode, duration, { type: routeType });
2752
+ logger.request(req.method || "GET", pathname, res.statusCode, duration, { type: routeType });
2678
2753
  }
2679
2754
  await pluginManager.runHook(PluginHooks.RESPONSE, req, res, duration);
2680
2755
  }
@@ -2735,9 +2810,9 @@ async function serveStaticFile(res, baseDir, pathname) {
2735
2810
  async function handleApiRoute(req, res, route, loadModule) {
2736
2811
  try {
2737
2812
  const module = await loadModule(route.filePath);
2738
- const method = req.method.toLowerCase();
2813
+ const method = (req.method || "GET").toLowerCase();
2739
2814
  const body = await parseBody(req);
2740
- const url = new URL(req.url, `http://${req.headers.host}`);
2815
+ const url = new URL(req.url || "/", `http://${req.headers.host}`);
2741
2816
  const query = Object.fromEntries(url.searchParams);
2742
2817
  const enhancedReq = {
2743
2818
  ...req,
@@ -2763,11 +2838,28 @@ async function handleApiRoute(req, res, route, loadModule) {
2763
2838
  }
2764
2839
  async function handleServerAction(req, res) {
2765
2840
  try {
2841
+ const contentType = req.headers["content-type"];
2842
+ if (!contentType?.includes("application/json")) {
2843
+ res.writeHead(400, { "Content-Type": "application/json" });
2844
+ res.end(JSON.stringify({ success: false, error: "Invalid content type" }));
2845
+ return;
2846
+ }
2847
+ const contentLength = parseInt(req.headers["content-length"] || "0", 10);
2848
+ if (contentLength > 10 * 1024 * 1024) {
2849
+ res.writeHead(413, { "Content-Type": "application/json" });
2850
+ res.end(JSON.stringify({ success: false, error: "Payload too large" }));
2851
+ return;
2852
+ }
2766
2853
  const body = await parseBody(req);
2767
2854
  const { actionId, args } = body;
2768
- if (!actionId) {
2855
+ if (!actionId || typeof actionId !== "string" || !/^[a-zA-Z0-9_]+$/.test(actionId)) {
2769
2856
  res.writeHead(400, { "Content-Type": "application/json" });
2770
- res.end(JSON.stringify({ success: false, error: "Missing actionId" }));
2857
+ res.end(JSON.stringify({ success: false, error: "Invalid actionId format" }));
2858
+ return;
2859
+ }
2860
+ if (args !== void 0 && !Array.isArray(args)) {
2861
+ res.writeHead(400, { "Content-Type": "application/json" });
2862
+ res.end(JSON.stringify({ success: false, error: "Invalid args format" }));
2771
2863
  return;
2772
2864
  }
2773
2865
  const deserializedArgs = deserializeArgs(args || []);
@@ -2784,10 +2876,11 @@ async function handleServerAction(req, res) {
2784
2876
  res.end(JSON.stringify(result));
2785
2877
  } catch (error) {
2786
2878
  console.error("Server Action Error:", error);
2879
+ const isDev = process.env.NODE_ENV !== "production";
2787
2880
  res.writeHead(500, { "Content-Type": "application/json" });
2788
2881
  res.end(JSON.stringify({
2789
2882
  success: false,
2790
- error: error.message || "Action execution failed"
2883
+ error: isDev ? error.message : "Action execution failed"
2791
2884
  }));
2792
2885
  }
2793
2886
  }
@@ -3007,6 +3100,8 @@ async function serveClientComponent(res, pagesDir, componentName) {
3007
3100
  const useCallback = window.useCallback;
3008
3101
  const useMemo = window.useMemo;
3009
3102
  const useRef = window.useRef;
3103
+ const useContext = window.useContext;
3104
+ const useReducer = window.useReducer;
3010
3105
  `
3011
3106
  });
3012
3107
  let code = result.code;
@@ -3027,41 +3122,53 @@ async function serveClientComponent(res, pagesDir, componentName) {
3027
3122
  function generateClientHydrationScript(componentPath, props) {
3028
3123
  const ext = path7.extname(componentPath);
3029
3124
  const componentName = path7.basename(componentPath, ext);
3125
+ const propsJson = JSON.stringify(props);
3030
3126
  return `
3031
3127
  <script type="module">
3032
- // FlexiReact Client Hydration
3128
+ // FlexiReact Client Hydration v4.1
3129
+ import React, { useState, useEffect, useCallback, useMemo, useRef, useContext, useReducer } from 'https://esm.sh/react@19.0.0';
3130
+ import { createRoot, hydrateRoot } from 'https://esm.sh/react-dom@19.0.0/client';
3131
+
3132
+ // Make React hooks available globally
3133
+ window.React = React;
3134
+ window.useState = useState;
3135
+ window.useEffect = useEffect;
3136
+ window.useCallback = useCallback;
3137
+ window.useMemo = useMemo;
3138
+ window.useRef = useRef;
3139
+ window.useContext = useContext;
3140
+ window.useReducer = useReducer;
3141
+
3033
3142
  (async function() {
3034
3143
  try {
3035
- const React = await import('https://esm.sh/react@18.3.1');
3036
- const ReactDOM = await import('https://esm.sh/react-dom@18.3.1/client');
3144
+ // Fetch and execute the component
3145
+ const response = await fetch('/_flexi/client/${componentName}.js');
3146
+ if (!response.ok) throw new Error('Failed to load component: ' + response.status);
3037
3147
 
3038
- // Make React available globally for the component
3039
- window.React = React.default || React;
3040
- window.useState = React.useState;
3041
- window.useEffect = React.useEffect;
3042
- window.useCallback = React.useCallback;
3043
- window.useMemo = React.useMemo;
3044
- window.useRef = React.useRef;
3045
-
3046
- // Fetch the component code
3047
- const response = await fetch('/_flexi/component/${componentName}.js');
3048
3148
  const code = await response.text();
3049
-
3050
- // Create and import the module
3051
3149
  const blob = new Blob([code], { type: 'application/javascript' });
3052
3150
  const moduleUrl = URL.createObjectURL(blob);
3053
3151
  const module = await import(moduleUrl);
3054
3152
 
3055
3153
  const Component = module.default;
3056
- const props = ${JSON.stringify(props)};
3154
+ if (!Component) throw new Error('No default export found');
3057
3155
 
3058
- // Hydrate the root
3059
- const root = document.getElementById('root');
3060
- ReactDOM.hydrateRoot(root, window.React.createElement(Component, props));
3156
+ const props = ${propsJson};
3157
+ const container = document.getElementById('flexi-root') || document.getElementById('root');
3061
3158
 
3062
- console.log('\u26A1 FlexiReact: Component hydrated successfully');
3159
+ if (container) {
3160
+ // Use createRoot for client components (not hydrate since we rendered placeholder)
3161
+ const root = createRoot(container);
3162
+ root.render(React.createElement(Component, props));
3163
+ console.log('%c\u26A1 FlexiReact', 'color: #00FF9C; font-weight: bold', 'Component mounted');
3164
+ }
3063
3165
  } catch (error) {
3064
- console.error('\u26A1 FlexiReact: Hydration failed', error);
3166
+ console.error('%c\u26A1 FlexiReact', 'color: #ef4444; font-weight: bold', 'Hydration failed:', error);
3167
+ // Show error in UI
3168
+ const container = document.getElementById('flexi-root');
3169
+ if (container) {
3170
+ container.innerHTML = '<div style="color:#ef4444;padding:20px;text-align:center;">Failed to load component: ' + error.message + '</div>';
3171
+ }
3065
3172
  }
3066
3173
  })();
3067
3174
  </script>`;