@flexireact/core 3.0.3 → 4.0.0

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.
@@ -0,0 +1,3095 @@
1
+ // core/server/index.ts
2
+ import http from "http";
3
+ import fs8 from "fs";
4
+ import path7 from "path";
5
+ import { fileURLToPath, pathToFileURL as pathToFileURL4 } from "url";
6
+
7
+ // core/config.ts
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { pathToFileURL } from "url";
11
+ var defaultConfig = {
12
+ // Directories
13
+ pagesDir: "pages",
14
+ layoutsDir: "layouts",
15
+ publicDir: "public",
16
+ outDir: ".flexi",
17
+ // Build options
18
+ build: {
19
+ target: "es2022",
20
+ minify: true,
21
+ sourcemap: true,
22
+ splitting: true
23
+ },
24
+ // Server options
25
+ server: {
26
+ port: 3e3,
27
+ host: "localhost"
28
+ },
29
+ // SSG options
30
+ ssg: {
31
+ enabled: false,
32
+ paths: []
33
+ },
34
+ // Islands (partial hydration)
35
+ islands: {
36
+ enabled: true
37
+ },
38
+ // RSC options
39
+ rsc: {
40
+ enabled: true
41
+ },
42
+ // Plugins
43
+ plugins: [],
44
+ // Styles (CSS files to include)
45
+ styles: [],
46
+ // Scripts (JS files to include)
47
+ scripts: [],
48
+ // Favicon path
49
+ favicon: null
50
+ };
51
+ async function loadConfig(projectRoot) {
52
+ const configPathTs = path.join(projectRoot, "flexireact.config.ts");
53
+ const configPathJs = path.join(projectRoot, "flexireact.config.js");
54
+ const configPath = fs.existsSync(configPathTs) ? configPathTs : configPathJs;
55
+ let userConfig = {};
56
+ if (fs.existsSync(configPath)) {
57
+ try {
58
+ const configUrl = pathToFileURL(configPath).href;
59
+ const module = await import(`${configUrl}?t=${Date.now()}`);
60
+ userConfig = module.default || module;
61
+ } catch (error) {
62
+ console.warn("Warning: Failed to load flexireact config:", error.message);
63
+ }
64
+ }
65
+ return deepMerge(defaultConfig, userConfig);
66
+ }
67
+ function deepMerge(target, source) {
68
+ const result = { ...target };
69
+ for (const key in source) {
70
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
71
+ result[key] = deepMerge(target[key] || {}, source[key]);
72
+ } else {
73
+ result[key] = source[key];
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+ function resolvePaths(config, projectRoot) {
79
+ return {
80
+ ...config,
81
+ pagesDir: path.resolve(projectRoot, config.pagesDir),
82
+ layoutsDir: path.resolve(projectRoot, config.layoutsDir),
83
+ publicDir: path.resolve(projectRoot, config.publicDir),
84
+ outDir: path.resolve(projectRoot, config.outDir)
85
+ };
86
+ }
87
+
88
+ // core/router/index.ts
89
+ import fs3 from "fs";
90
+ import path2 from "path";
91
+
92
+ // core/utils.ts
93
+ import fs2 from "fs";
94
+ function escapeHtml(str) {
95
+ const htmlEntities = {
96
+ "&": "&",
97
+ "<": "&lt;",
98
+ ">": "&gt;",
99
+ '"': "&quot;",
100
+ "'": "&#39;"
101
+ };
102
+ return String(str).replace(/[&<>"']/g, (char) => htmlEntities[char]);
103
+ }
104
+ function isServerComponent(filePath) {
105
+ try {
106
+ const content = fs2.readFileSync(filePath, "utf-8");
107
+ const firstLine = content.split("\n")[0].trim();
108
+ return firstLine === "'use server'" || firstLine === '"use server"';
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+ function isClientComponent(filePath) {
114
+ try {
115
+ const content = fs2.readFileSync(filePath, "utf-8");
116
+ const firstLine = content.split("\n")[0].trim();
117
+ return firstLine === "'use client'" || firstLine === '"use client"';
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+ function isIsland(filePath) {
123
+ try {
124
+ const content = fs2.readFileSync(filePath, "utf-8");
125
+ const firstLine = content.split("\n")[0].trim();
126
+ return firstLine === "'use island'" || firstLine === '"use island"';
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ // core/router/index.ts
133
+ var RouteType = {
134
+ PAGE: "page",
135
+ API: "api",
136
+ LAYOUT: "layout",
137
+ LOADING: "loading",
138
+ ERROR: "error",
139
+ NOT_FOUND: "not-found"
140
+ };
141
+ function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
142
+ const projectRoot = path2.dirname(pagesDir);
143
+ const routes = {
144
+ pages: [],
145
+ api: [],
146
+ layouts: /* @__PURE__ */ new Map(),
147
+ tree: {},
148
+ appRoutes: [],
149
+ // Next.js style app router routes
150
+ flexiRoutes: []
151
+ // FlexiReact v2 routes/ directory
152
+ };
153
+ const routesDirPath = routesDir || path2.join(projectRoot, "routes");
154
+ if (fs3.existsSync(routesDirPath)) {
155
+ scanRoutesDirectory(routesDirPath, routesDirPath, routes);
156
+ }
157
+ const appDirPath = appDir || path2.join(projectRoot, "app");
158
+ if (fs3.existsSync(appDirPath)) {
159
+ scanAppDirectory(appDirPath, appDirPath, routes);
160
+ }
161
+ if (fs3.existsSync(pagesDir)) {
162
+ scanDirectory(pagesDir, pagesDir, routes);
163
+ }
164
+ if (fs3.existsSync(layoutsDir)) {
165
+ scanLayouts(layoutsDir, routes.layouts);
166
+ }
167
+ const rootLayoutPath = path2.join(appDirPath, "layout.tsx");
168
+ const rootLayoutPathJs = path2.join(appDirPath, "layout.jsx");
169
+ if (fs3.existsSync(rootLayoutPath)) {
170
+ routes.rootLayout = rootLayoutPath;
171
+ } else if (fs3.existsSync(rootLayoutPathJs)) {
172
+ routes.rootLayout = rootLayoutPathJs;
173
+ }
174
+ routes.tree = buildTree([...routes.flexiRoutes, ...routes.appRoutes, ...routes.pages]);
175
+ return routes;
176
+ }
177
+ function scanRoutesDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
178
+ const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
179
+ let layoutFile = null;
180
+ let loadingFile = null;
181
+ let errorFile = null;
182
+ let middlewareFile = null;
183
+ for (const entry of entries) {
184
+ if (entry.isFile()) {
185
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
186
+ const fullPath = path2.join(currentDir, entry.name);
187
+ const ext = path2.extname(entry.name);
188
+ if (name === "layout") layoutFile = fullPath;
189
+ if (name === "loading") loadingFile = fullPath;
190
+ if (name === "error") errorFile = fullPath;
191
+ if (name === "_middleware" || name === "middleware") middlewareFile = fullPath;
192
+ if (["layout", "loading", "error", "not-found", "_middleware", "middleware"].includes(name)) continue;
193
+ if (![".tsx", ".jsx", ".ts", ".js"].includes(ext)) continue;
194
+ const relativePath = path2.relative(baseDir, currentDir);
195
+ const isApiRoute = relativePath.startsWith("api") || relativePath.startsWith("api/");
196
+ if (isApiRoute && [".ts", ".js"].includes(ext)) {
197
+ const apiPath = "/" + [...parentSegments, name === "index" ? "" : name].filter(Boolean).join("/");
198
+ routes.api.push({
199
+ type: RouteType.API,
200
+ path: apiPath.replace(/\/+/g, "/") || "/",
201
+ filePath: fullPath,
202
+ pattern: createRoutePattern(apiPath),
203
+ segments: [...parentSegments, name === "index" ? "" : name].filter(Boolean)
204
+ });
205
+ continue;
206
+ }
207
+ if ([".tsx", ".jsx"].includes(ext)) {
208
+ let routePath;
209
+ if (name === "home" && parentSegments.length === 0) {
210
+ routePath = "/";
211
+ } else if (name === "index") {
212
+ routePath = "/" + parentSegments.join("/") || "/";
213
+ } else if (name.startsWith("[") && name.endsWith("]")) {
214
+ const paramName = name.slice(1, -1);
215
+ if (paramName.startsWith("...")) {
216
+ routePath = "/" + [...parentSegments, "*" + paramName.slice(3)].join("/");
217
+ } else {
218
+ routePath = "/" + [...parentSegments, ":" + paramName].join("/");
219
+ }
220
+ } else {
221
+ routePath = "/" + [...parentSegments, name].join("/");
222
+ }
223
+ routes.flexiRoutes.push({
224
+ type: RouteType.PAGE,
225
+ path: routePath.replace(/\/+/g, "/"),
226
+ filePath: fullPath,
227
+ pattern: createRoutePattern(routePath),
228
+ segments: routePath.split("/").filter(Boolean),
229
+ layout: layoutFile || parentLayout,
230
+ loading: loadingFile,
231
+ error: errorFile,
232
+ middleware: middlewareFile || parentMiddleware,
233
+ isFlexiRouter: true,
234
+ isServerComponent: isServerComponent(fullPath),
235
+ isClientComponent: isClientComponent(fullPath),
236
+ isIsland: isIsland(fullPath)
237
+ });
238
+ }
239
+ }
240
+ }
241
+ for (const entry of entries) {
242
+ if (entry.isDirectory()) {
243
+ const fullPath = path2.join(currentDir, entry.name);
244
+ const dirName = entry.name;
245
+ if (dirName.startsWith("_") || dirName.startsWith(".")) continue;
246
+ const isGroup = dirName.startsWith("(") && dirName.endsWith(")");
247
+ let segmentName = dirName;
248
+ if (dirName.startsWith("[") && dirName.endsWith("]")) {
249
+ const paramName = dirName.slice(1, -1);
250
+ if (paramName.startsWith("...")) {
251
+ segmentName = "*" + paramName.slice(3);
252
+ } else {
253
+ segmentName = ":" + paramName;
254
+ }
255
+ }
256
+ const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
257
+ const newLayout = layoutFile || parentLayout;
258
+ const newMiddleware = middlewareFile || parentMiddleware;
259
+ scanRoutesDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
260
+ }
261
+ }
262
+ }
263
+ function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
264
+ const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
265
+ const specialFiles = {
266
+ page: null,
267
+ layout: null,
268
+ loading: null,
269
+ error: null,
270
+ notFound: null,
271
+ template: null,
272
+ middleware: null
273
+ };
274
+ for (const entry of entries) {
275
+ if (entry.isFile()) {
276
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
277
+ const fullPath = path2.join(currentDir, entry.name);
278
+ if (name === "page") specialFiles.page = fullPath;
279
+ if (name === "layout") specialFiles.layout = fullPath;
280
+ if (name === "loading") specialFiles.loading = fullPath;
281
+ if (name === "error") specialFiles.error = fullPath;
282
+ if (name === "not-found") specialFiles.notFound = fullPath;
283
+ if (name === "template") specialFiles.template = fullPath;
284
+ if (name === "middleware" || name === "_middleware") specialFiles.middleware = fullPath;
285
+ }
286
+ }
287
+ if (specialFiles.page) {
288
+ const routePath = "/" + parentSegments.join("/") || "/";
289
+ routes.appRoutes.push({
290
+ type: RouteType.PAGE,
291
+ path: routePath.replace(/\/+/g, "/"),
292
+ filePath: specialFiles.page,
293
+ pattern: createRoutePattern(routePath),
294
+ segments: parentSegments,
295
+ layout: specialFiles.layout || parentLayout,
296
+ loading: specialFiles.loading,
297
+ error: specialFiles.error,
298
+ notFound: specialFiles.notFound,
299
+ template: specialFiles.template,
300
+ middleware: specialFiles.middleware || parentMiddleware,
301
+ isAppRouter: true,
302
+ isServerComponent: isServerComponent(specialFiles.page),
303
+ isClientComponent: isClientComponent(specialFiles.page),
304
+ isIsland: isIsland(specialFiles.page)
305
+ });
306
+ }
307
+ for (const entry of entries) {
308
+ if (entry.isDirectory()) {
309
+ const fullPath = path2.join(currentDir, entry.name);
310
+ const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
311
+ let segmentName = entry.name;
312
+ if (entry.name.startsWith("[") && entry.name.endsWith("]")) {
313
+ segmentName = ":" + entry.name.slice(1, -1);
314
+ if (entry.name.startsWith("[...")) {
315
+ segmentName = "*" + entry.name.slice(4, -1);
316
+ }
317
+ if (entry.name.startsWith("[[...")) {
318
+ segmentName = "*" + entry.name.slice(5, -2);
319
+ }
320
+ }
321
+ const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
322
+ const newLayout = specialFiles.layout || parentLayout;
323
+ const newMiddleware = specialFiles.middleware || parentMiddleware;
324
+ scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
325
+ }
326
+ }
327
+ }
328
+ function scanDirectory(baseDir, currentDir, routes, parentSegments = []) {
329
+ const entries = fs3.readdirSync(currentDir, { withFileTypes: true });
330
+ const specialFiles = {
331
+ layout: null,
332
+ loading: null,
333
+ error: null,
334
+ notFound: null
335
+ };
336
+ for (const entry of entries) {
337
+ if (entry.isFile()) {
338
+ const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
339
+ const fullPath = path2.join(currentDir, entry.name);
340
+ if (name === "layout") specialFiles.layout = fullPath;
341
+ if (name === "loading") specialFiles.loading = fullPath;
342
+ if (name === "error") specialFiles.error = fullPath;
343
+ if (name === "not-found" || name === "404") specialFiles.notFound = fullPath;
344
+ }
345
+ }
346
+ for (const entry of entries) {
347
+ const fullPath = path2.join(currentDir, entry.name);
348
+ const relativePath = path2.relative(baseDir, fullPath);
349
+ if (entry.isDirectory()) {
350
+ const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
351
+ const newSegments = isGroup ? parentSegments : [...parentSegments, entry.name];
352
+ scanDirectory(baseDir, fullPath, routes, newSegments);
353
+ } else if (entry.isFile()) {
354
+ const ext = path2.extname(entry.name);
355
+ const baseName = path2.basename(entry.name, ext);
356
+ if (["layout", "loading", "error", "not-found", "404"].includes(baseName)) {
357
+ continue;
358
+ }
359
+ if ([".jsx", ".js", ".tsx", ".ts"].includes(ext)) {
360
+ const isApi = relativePath.startsWith("api" + path2.sep) || relativePath.startsWith("api/");
361
+ if (isApi && [".js", ".ts"].includes(ext)) {
362
+ routes.api.push(createRoute(fullPath, baseDir, specialFiles, RouteType.API));
363
+ } else if (!isApi && [".jsx", ".tsx"].includes(ext)) {
364
+ routes.pages.push(createRoute(fullPath, baseDir, specialFiles, RouteType.PAGE));
365
+ }
366
+ }
367
+ }
368
+ }
369
+ }
370
+ function createRoute(filePath, baseDir, specialFiles, type) {
371
+ const relativePath = path2.relative(baseDir, filePath);
372
+ const routePath = filePathToRoute(relativePath);
373
+ return {
374
+ type,
375
+ path: routePath,
376
+ filePath,
377
+ pattern: createRoutePattern(routePath),
378
+ segments: routePath.split("/").filter(Boolean),
379
+ layout: specialFiles.layout,
380
+ loading: specialFiles.loading,
381
+ error: specialFiles.error,
382
+ notFound: specialFiles.notFound,
383
+ isServerComponent: isServerComponent(filePath),
384
+ isClientComponent: isClientComponent(filePath),
385
+ isIsland: isIsland(filePath)
386
+ };
387
+ }
388
+ function scanLayouts(layoutsDir, layoutsMap) {
389
+ const entries = fs3.readdirSync(layoutsDir, { withFileTypes: true });
390
+ for (const entry of entries) {
391
+ if (entry.isFile() && /\.(jsx|tsx)$/.test(entry.name)) {
392
+ const name = entry.name.replace(/\.(jsx|tsx)$/, "");
393
+ layoutsMap.set(name, path2.join(layoutsDir, entry.name));
394
+ }
395
+ }
396
+ }
397
+ function filePathToRoute(filePath) {
398
+ let route = filePath.replace(/\\/g, "/");
399
+ route = route.replace(/\.(jsx|js|tsx|ts)$/, "");
400
+ route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
401
+ route = route.replace(/\[([^\]]+)\]/g, ":$1");
402
+ if (route.endsWith("/index")) {
403
+ route = route.slice(0, -6) || "/";
404
+ } else if (route === "index") {
405
+ route = "/";
406
+ }
407
+ route = route.replace(/\/?\([^)]+\)\/?/g, "/");
408
+ if (!route.startsWith("/")) {
409
+ route = "/" + route;
410
+ }
411
+ route = route.replace(/\/+/g, "/");
412
+ return route;
413
+ }
414
+ function createRoutePattern(routePath) {
415
+ let pattern = routePath.replace(/\*[^/]*/g, "(.*)").replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
416
+ return new RegExp(`^${pattern}$`);
417
+ }
418
+ function buildTree(routes) {
419
+ const tree = { children: {}, routes: [] };
420
+ for (const route of routes) {
421
+ let current = tree;
422
+ for (const segment of route.segments) {
423
+ if (!current.children[segment]) {
424
+ current.children[segment] = { children: {}, routes: [] };
425
+ }
426
+ current = current.children[segment];
427
+ }
428
+ current.routes.push(route);
429
+ }
430
+ return tree;
431
+ }
432
+ function matchRoute(urlPath, routes) {
433
+ const normalizedPath = urlPath === "" ? "/" : urlPath.split("?")[0];
434
+ for (const route of routes) {
435
+ const match = normalizedPath.match(route.pattern);
436
+ if (match) {
437
+ const params = extractParams(route.path, match);
438
+ return { ...route, params };
439
+ }
440
+ }
441
+ return null;
442
+ }
443
+ function extractParams(routePath, match) {
444
+ const params = {};
445
+ const paramNames = [];
446
+ const paramRegex = /:([^/]+)|\*([^/]*)/g;
447
+ let paramMatch;
448
+ while ((paramMatch = paramRegex.exec(routePath)) !== null) {
449
+ paramNames.push(paramMatch[1] || paramMatch[2] || "splat");
450
+ }
451
+ paramNames.forEach((name, index) => {
452
+ params[name] = match[index + 1];
453
+ });
454
+ return params;
455
+ }
456
+ function findRouteLayouts(route, layoutsMap) {
457
+ const layouts = [];
458
+ let currentPath = "";
459
+ for (const segment of route.segments) {
460
+ currentPath += "/" + segment;
461
+ const layoutName = segment;
462
+ if (layoutsMap.has(layoutName)) {
463
+ layouts.push({
464
+ name: layoutName,
465
+ filePath: layoutsMap.get(layoutName)
466
+ });
467
+ }
468
+ }
469
+ if (route.layout) {
470
+ layouts.push({
471
+ name: "route",
472
+ filePath: route.layout
473
+ });
474
+ }
475
+ if (layoutsMap.has("root")) {
476
+ layouts.unshift({
477
+ name: "root",
478
+ filePath: layoutsMap.get("root")
479
+ });
480
+ }
481
+ return layouts;
482
+ }
483
+
484
+ // core/render/index.ts
485
+ import React from "react";
486
+ import { renderToString, renderToPipeableStream } from "react-dom/server";
487
+ async function renderPage(options) {
488
+ const {
489
+ Component,
490
+ props = {},
491
+ layouts = [],
492
+ loading = null,
493
+ error = null,
494
+ islands = [],
495
+ title = "FlexiReact App",
496
+ meta = {},
497
+ scripts = [],
498
+ styles = [],
499
+ favicon = null,
500
+ isSSG = false,
501
+ route = "/",
502
+ needsHydration = false
503
+ } = options;
504
+ const renderStart = Date.now();
505
+ 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);
525
+ }
526
+ }
527
+ const content = renderToString(element);
528
+ const renderTime = Date.now() - renderStart;
529
+ const islandScripts = generateIslandScripts(islands);
530
+ return buildHtmlDocument({
531
+ content,
532
+ title,
533
+ meta,
534
+ scripts: [...scripts, ...islandScripts],
535
+ styles,
536
+ favicon,
537
+ props,
538
+ isSSG,
539
+ renderTime,
540
+ route,
541
+ isClientComponent: needsHydration
542
+ });
543
+ } catch (err) {
544
+ console.error("Render Error:", err);
545
+ throw err;
546
+ }
547
+ }
548
+ var ErrorBoundaryWrapper = class extends React.Component {
549
+ constructor(props) {
550
+ super(props);
551
+ this.state = { hasError: false, error: null };
552
+ }
553
+ static getDerivedStateFromError(error) {
554
+ return { hasError: true, error };
555
+ }
556
+ render() {
557
+ if (this.state.hasError) {
558
+ const FallbackComponent = this.props.fallback;
559
+ return React.createElement(FallbackComponent, { error: this.state.error });
560
+ }
561
+ return this.props.children;
562
+ }
563
+ };
564
+ function generateIslandScripts(islands) {
565
+ if (!islands.length) return [];
566
+ const scripts = [];
567
+ for (const island of islands) {
568
+ scripts.push({
569
+ type: "module",
570
+ content: `
571
+ import { hydrateIsland } from '/_flexi/client.js';
572
+ import ${island.name} from '${island.clientPath}';
573
+ hydrateIsland('${island.id}', ${island.name}, ${JSON.stringify(island.props)});
574
+ `
575
+ });
576
+ }
577
+ return scripts;
578
+ }
579
+ function generateDevToolbar(options = {}) {
580
+ const {
581
+ renderTime = 0,
582
+ pageType = "SSR",
583
+ route = "/",
584
+ hasError = false,
585
+ isHydrated = false,
586
+ errorMessage = null,
587
+ componentName = null
588
+ } = options;
589
+ const timeColor = renderTime < 50 ? "#00FF9C" : renderTime < 200 ? "#fbbf24" : "#ef4444";
590
+ const timeLabel = renderTime < 50 ? "Fast" : renderTime < 200 ? "OK" : "Slow";
591
+ return `
592
+ <!-- FlexiReact v2 Dev Toolbar -->
593
+ <div id="flexi-dev-toolbar" class="flexi-dev-collapsed">
594
+ <style>
595
+ #flexi-dev-toolbar {
596
+ position: fixed;
597
+ bottom: 16px;
598
+ left: 16px;
599
+ z-index: 99999;
600
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
601
+ font-size: 13px;
602
+ }
603
+
604
+ /* Main Button */
605
+ .flexi-dev-trigger {
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 8px;
609
+ padding: 8px 12px;
610
+ background: rgba(10, 10, 10, 0.95);
611
+ backdrop-filter: blur(20px);
612
+ border: 1px solid rgba(0, 255, 156, 0.2);
613
+ border-radius: 10px;
614
+ color: #fafafa;
615
+ cursor: pointer;
616
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
617
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
618
+ }
619
+
620
+ .flexi-dev-trigger:hover {
621
+ border-color: rgba(0, 255, 156, 0.5);
622
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.6), 0 0 20px rgba(0, 255, 156, 0.15);
623
+ transform: translateY(-2px);
624
+ }
625
+
626
+ .flexi-dev-trigger.has-error {
627
+ border-color: rgba(239, 68, 68, 0.5);
628
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), 0 0 15px rgba(239, 68, 68, 0.2);
629
+ }
630
+
631
+ .flexi-dev-logo {
632
+ width: 20px;
633
+ height: 20px;
634
+ background: linear-gradient(135deg, #00FF9C, #00D68F);
635
+ border-radius: 5px;
636
+ display: flex;
637
+ align-items: center;
638
+ justify-content: center;
639
+ font-weight: 800;
640
+ font-size: 11px;
641
+ color: #000;
642
+ }
643
+
644
+ .flexi-dev-trigger.has-error .flexi-dev-logo {
645
+ background: linear-gradient(135deg, #ef4444, #dc2626);
646
+ }
647
+
648
+ .flexi-dev-indicator {
649
+ display: flex;
650
+ align-items: center;
651
+ gap: 6px;
652
+ }
653
+
654
+ .flexi-dev-dot {
655
+ width: 6px;
656
+ height: 6px;
657
+ border-radius: 50%;
658
+ background: #00FF9C;
659
+ box-shadow: 0 0 8px rgba(0, 255, 156, 0.6);
660
+ }
661
+
662
+ .flexi-dev-dot.error {
663
+ background: #ef4444;
664
+ box-shadow: 0 0 8px rgba(239, 68, 68, 0.6);
665
+ animation: errorPulse 1s infinite;
666
+ }
667
+
668
+ @keyframes errorPulse {
669
+ 0%, 100% { opacity: 1; }
670
+ 50% { opacity: 0.4; }
671
+ }
672
+
673
+ .flexi-dev-time {
674
+ font-size: 11px;
675
+ font-weight: 600;
676
+ color: ${timeColor};
677
+ font-variant-numeric: tabular-nums;
678
+ }
679
+
680
+ /* Panel */
681
+ .flexi-dev-panel {
682
+ position: absolute;
683
+ bottom: calc(100% + 8px);
684
+ left: 0;
685
+ min-width: 340px;
686
+ background: rgba(10, 10, 10, 0.98);
687
+ backdrop-filter: blur(20px);
688
+ border: 1px solid rgba(255, 255, 255, 0.08);
689
+ border-radius: 16px;
690
+ opacity: 0;
691
+ visibility: hidden;
692
+ transform: translateY(8px) scale(0.96);
693
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
694
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
695
+ overflow: hidden;
696
+ }
697
+
698
+ #flexi-dev-toolbar.flexi-dev-open .flexi-dev-panel {
699
+ opacity: 1;
700
+ visibility: visible;
701
+ transform: translateY(0) scale(1);
702
+ }
703
+
704
+ /* Header */
705
+ .flexi-dev-header {
706
+ display: flex;
707
+ align-items: center;
708
+ gap: 10px;
709
+ padding: 14px 16px;
710
+ background: linear-gradient(135deg, rgba(0, 255, 156, 0.08), rgba(0, 214, 143, 0.04));
711
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
712
+ }
713
+
714
+ .flexi-dev-header-logo {
715
+ width: 26px;
716
+ height: 26px;
717
+ background: linear-gradient(135deg, #00FF9C, #00D68F);
718
+ border-radius: 7px;
719
+ display: flex;
720
+ align-items: center;
721
+ justify-content: center;
722
+ font-weight: 800;
723
+ font-size: 13px;
724
+ color: #000;
725
+ }
726
+
727
+ .flexi-dev-header-info {
728
+ flex: 1;
729
+ }
730
+
731
+ .flexi-dev-header-title {
732
+ font-weight: 700;
733
+ font-size: 14px;
734
+ color: #fafafa;
735
+ }
736
+
737
+ .flexi-dev-header-subtitle {
738
+ font-size: 11px;
739
+ color: #52525b;
740
+ margin-top: 1px;
741
+ }
742
+
743
+ .flexi-dev-close {
744
+ width: 24px;
745
+ height: 24px;
746
+ display: flex;
747
+ align-items: center;
748
+ justify-content: center;
749
+ background: rgba(255, 255, 255, 0.05);
750
+ border: none;
751
+ border-radius: 6px;
752
+ color: #71717a;
753
+ cursor: pointer;
754
+ transition: all 0.15s;
755
+ }
756
+
757
+ .flexi-dev-close:hover {
758
+ background: rgba(255, 255, 255, 0.1);
759
+ color: #fafafa;
760
+ }
761
+
762
+ /* Content */
763
+ .flexi-dev-content {
764
+ padding: 12px 16px;
765
+ }
766
+
767
+ .flexi-dev-section {
768
+ margin-bottom: 12px;
769
+ }
770
+
771
+ .flexi-dev-section:last-child {
772
+ margin-bottom: 0;
773
+ }
774
+
775
+ .flexi-dev-section-title {
776
+ font-size: 10px;
777
+ font-weight: 600;
778
+ color: #52525b;
779
+ text-transform: uppercase;
780
+ letter-spacing: 0.5px;
781
+ margin-bottom: 8px;
782
+ }
783
+
784
+ .flexi-dev-grid {
785
+ display: grid;
786
+ grid-template-columns: 1fr 1fr;
787
+ gap: 8px;
788
+ }
789
+
790
+ .flexi-dev-stat {
791
+ background: rgba(255, 255, 255, 0.03);
792
+ border: 1px solid rgba(255, 255, 255, 0.05);
793
+ border-radius: 10px;
794
+ padding: 10px 12px;
795
+ }
796
+
797
+ .flexi-dev-stat-label {
798
+ font-size: 10px;
799
+ color: #52525b;
800
+ margin-bottom: 4px;
801
+ }
802
+
803
+ .flexi-dev-stat-value {
804
+ font-size: 14px;
805
+ font-weight: 600;
806
+ color: #fafafa;
807
+ }
808
+
809
+ .flexi-dev-stat-value.success { color: #00FF9C; }
810
+ .flexi-dev-stat-value.warning { color: #fbbf24; }
811
+ .flexi-dev-stat-value.error { color: #ef4444; }
812
+
813
+ /* Badges */
814
+ .flexi-dev-badge {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ padding: 3px 8px;
818
+ border-radius: 5px;
819
+ font-size: 10px;
820
+ font-weight: 700;
821
+ letter-spacing: 0.3px;
822
+ }
823
+
824
+ .flexi-dev-badge.ssr { background: rgba(251, 191, 36, 0.15); color: #fbbf24; }
825
+ .flexi-dev-badge.ssg { background: rgba(0, 255, 156, 0.15); color: #00FF9C; }
826
+ .flexi-dev-badge.csr { background: rgba(6, 182, 212, 0.15); color: #06b6d4; }
827
+ .flexi-dev-badge.isr { background: rgba(139, 92, 246, 0.15); color: #a78bfa; }
828
+
829
+ /* Error Display */
830
+ .flexi-dev-error {
831
+ background: rgba(239, 68, 68, 0.1);
832
+ border: 1px solid rgba(239, 68, 68, 0.2);
833
+ border-radius: 10px;
834
+ padding: 12px;
835
+ margin-top: 8px;
836
+ }
837
+
838
+ .flexi-dev-error-title {
839
+ display: flex;
840
+ align-items: center;
841
+ gap: 6px;
842
+ font-size: 11px;
843
+ font-weight: 600;
844
+ color: #ef4444;
845
+ margin-bottom: 6px;
846
+ }
847
+
848
+ .flexi-dev-error-message {
849
+ font-size: 12px;
850
+ color: #fca5a5;
851
+ font-family: 'SF Mono', 'Fira Code', monospace;
852
+ word-break: break-word;
853
+ line-height: 1.5;
854
+ }
855
+
856
+ /* Footer */
857
+ .flexi-dev-footer {
858
+ display: flex;
859
+ gap: 6px;
860
+ padding: 12px 16px;
861
+ background: rgba(0, 0, 0, 0.3);
862
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
863
+ }
864
+
865
+ .flexi-dev-action {
866
+ flex: 1;
867
+ padding: 8px;
868
+ background: rgba(255, 255, 255, 0.03);
869
+ border: 1px solid rgba(255, 255, 255, 0.06);
870
+ border-radius: 8px;
871
+ color: #71717a;
872
+ font-size: 11px;
873
+ font-weight: 500;
874
+ text-decoration: none;
875
+ text-align: center;
876
+ cursor: pointer;
877
+ transition: all 0.15s;
878
+ }
879
+
880
+ .flexi-dev-action:hover {
881
+ background: rgba(0, 255, 156, 0.1);
882
+ border-color: rgba(0, 255, 156, 0.2);
883
+ color: #00FF9C;
884
+ }
885
+ </style>
886
+
887
+ <button class="flexi-dev-trigger ${hasError ? "has-error" : ""}" onclick="this.parentElement.classList.toggle('flexi-dev-open')">
888
+ <div class="flexi-dev-logo">F</div>
889
+ <div class="flexi-dev-indicator">
890
+ <div class="flexi-dev-dot ${hasError ? "error" : ""}"></div>
891
+ <span class="flexi-dev-time">${renderTime}ms</span>
892
+ </div>
893
+ </button>
894
+
895
+ <div class="flexi-dev-panel">
896
+ <div class="flexi-dev-header">
897
+ <div class="flexi-dev-header-logo">F</div>
898
+ <div class="flexi-dev-header-info">
899
+ <div class="flexi-dev-header-title">FlexiReact</div>
900
+ <div class="flexi-dev-header-subtitle">v2.0.0 \u2022 Development</div>
901
+ </div>
902
+ <button class="flexi-dev-close" onclick="this.closest('#flexi-dev-toolbar').classList.remove('flexi-dev-open')">\u2715</button>
903
+ </div>
904
+
905
+ <div class="flexi-dev-content">
906
+ <div class="flexi-dev-section">
907
+ <div class="flexi-dev-section-title">Page Info</div>
908
+ <div class="flexi-dev-grid">
909
+ <div class="flexi-dev-stat">
910
+ <div class="flexi-dev-stat-label">Route</div>
911
+ <div class="flexi-dev-stat-value">${route}</div>
912
+ </div>
913
+ <div class="flexi-dev-stat">
914
+ <div class="flexi-dev-stat-label">Type</div>
915
+ <div class="flexi-dev-stat-value"><span class="flexi-dev-badge ${pageType.toLowerCase()}">${pageType}</span></div>
916
+ </div>
917
+ </div>
918
+ </div>
919
+
920
+ <div class="flexi-dev-section">
921
+ <div class="flexi-dev-section-title">Performance</div>
922
+ <div class="flexi-dev-grid">
923
+ <div class="flexi-dev-stat">
924
+ <div class="flexi-dev-stat-label">Render Time</div>
925
+ <div class="flexi-dev-stat-value ${renderTime < 50 ? "success" : renderTime < 200 ? "warning" : "error"}">${renderTime}ms <small style="color:#52525b">${timeLabel}</small></div>
926
+ </div>
927
+ <div class="flexi-dev-stat">
928
+ <div class="flexi-dev-stat-label">Hydration</div>
929
+ <div class="flexi-dev-stat-value" id="flexi-hydration-status">${isHydrated ? "\u2713 Client" : "\u25CB Server"}</div>
930
+ </div>
931
+ </div>
932
+ </div>
933
+
934
+ ${hasError && errorMessage ? `
935
+ <div class="flexi-dev-error">
936
+ <div class="flexi-dev-error-title">
937
+ <span>\u26A0</span> Runtime Error
938
+ </div>
939
+ <div class="flexi-dev-error-message">${errorMessage}</div>
940
+ </div>
941
+ ` : ""}
942
+ </div>
943
+
944
+ <div class="flexi-dev-footer">
945
+ <a href="/_flexi/routes" class="flexi-dev-action">Routes</a>
946
+ <button class="flexi-dev-action" onclick="location.reload()">Refresh</button>
947
+ <a href="https://github.com/flexireact/flexireact" target="_blank" class="flexi-dev-action">Docs \u2197</a>
948
+ </div>
949
+ </div>
950
+ </div>
951
+
952
+ <script>
953
+ // FlexiReact v2 DevTools
954
+ window.__FLEXI_DEV__ = {
955
+ version: '2.0.0',
956
+ renderTime: ${renderTime},
957
+ pageType: '${pageType}',
958
+ route: '${route}',
959
+ hydrated: false,
960
+ errors: []
961
+ };
962
+
963
+ // Track hydration
964
+ const root = document.getElementById('root');
965
+ if (root) {
966
+ const observer = new MutationObserver(() => {
967
+ if (root.children.length > 0) {
968
+ window.__FLEXI_DEV__.hydrated = true;
969
+ const el = document.getElementById('flexi-hydration-status');
970
+ if (el) el.textContent = '\u2713 Client';
971
+ observer.disconnect();
972
+ }
973
+ });
974
+ observer.observe(root, { childList: true, subtree: true });
975
+ }
976
+
977
+ // Capture runtime errors
978
+ window.addEventListener('error', (e) => {
979
+ window.__FLEXI_DEV__.errors.push({
980
+ message: e.message,
981
+ file: e.filename,
982
+ line: e.lineno,
983
+ col: e.colno
984
+ });
985
+ console.error('%c[FlexiReact Error]', 'color: #ef4444; font-weight: bold;', e.message);
986
+ });
987
+
988
+ // Console branding
989
+ console.log(
990
+ '%c \u26A1 FlexiReact v2 %c ${pageType} %c ${renderTime}ms ',
991
+ 'background: #00FF9C; color: #000; font-weight: bold; padding: 2px 6px; border-radius: 4px 0 0 4px;',
992
+ 'background: #1e1e1e; color: #fafafa; padding: 2px 6px;',
993
+ 'background: ${timeColor}20; color: ${timeColor}; padding: 2px 6px; border-radius: 0 4px 4px 0;'
994
+ );
995
+ </script>
996
+ `;
997
+ }
998
+ function buildHtmlDocument(options) {
999
+ const {
1000
+ content,
1001
+ title,
1002
+ meta = {},
1003
+ scripts = [],
1004
+ styles = [],
1005
+ props = {},
1006
+ isSSG = false,
1007
+ renderTime = 0,
1008
+ route = "/",
1009
+ isClientComponent: isClientComponent2 = false,
1010
+ favicon = null
1011
+ } = options;
1012
+ const metaTags = Object.entries(meta).map(([name, content2]) => {
1013
+ if (name.startsWith("og:")) {
1014
+ return `<meta property="${escapeHtml(name)}" content="${escapeHtml(content2)}">`;
1015
+ }
1016
+ return `<meta name="${escapeHtml(name)}" content="${escapeHtml(content2)}">`;
1017
+ }).join("\n ");
1018
+ const styleTags = styles.map((style) => {
1019
+ if (typeof style === "string") {
1020
+ return `<link rel="stylesheet" href="${escapeHtml(style)}">`;
1021
+ }
1022
+ return `<style>${style.content}</style>`;
1023
+ }).join("\n ");
1024
+ const scriptTags = scripts.map((script) => {
1025
+ if (typeof script === "string") {
1026
+ return `<script src="${escapeHtml(script)}"></script>`;
1027
+ }
1028
+ const type = script.type ? ` type="${script.type}"` : "";
1029
+ if (script.src) {
1030
+ return `<script${type} src="${escapeHtml(script.src)}"></script>`;
1031
+ }
1032
+ return `<script${type}>${script.content}</script>`;
1033
+ }).join("\n ");
1034
+ const faviconSvg = encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#00FF9C"/><text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" fill="#000" font-family="system-ui" font-weight="bold" font-size="16">F</text></svg>`);
1035
+ const isDev = process.env.NODE_ENV !== "production";
1036
+ const pageType = isSSG ? "SSG" : isClientComponent2 ? "CSR" : "SSR";
1037
+ const devToolbar = isDev ? generateDevToolbar({
1038
+ renderTime,
1039
+ pageType,
1040
+ route,
1041
+ isHydrated: isClientComponent2
1042
+ }) : "";
1043
+ const faviconLink = favicon ? `<link rel="icon" href="${escapeHtml(favicon)}">` : `<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,${faviconSvg}">`;
1044
+ return `<!DOCTYPE html>
1045
+ <html lang="en" class="dark">
1046
+ <head>
1047
+ <meta charset="UTF-8">
1048
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1049
+ <title>${escapeHtml(title)}</title>
1050
+ ${faviconLink}
1051
+ ${metaTags}
1052
+ <style>
1053
+ :root { --flexi-bg: #0f172a; --flexi-fg: #f8fafc; }
1054
+ html, body { background-color: #0f172a; color: #f8fafc; min-height: 100vh; margin: 0; }
1055
+ </style>
1056
+ ${styleTags}
1057
+ <script>
1058
+ (function() {
1059
+ var theme = localStorage.getItem('theme');
1060
+ if (theme === 'light') {
1061
+ document.documentElement.classList.remove('dark');
1062
+ document.documentElement.style.backgroundColor = '#ffffff';
1063
+ document.documentElement.style.color = '#0f172a';
1064
+ document.body.style.backgroundColor = '#ffffff';
1065
+ document.body.style.color = '#0f172a';
1066
+ }
1067
+ })();
1068
+ </script>
1069
+ </head>
1070
+ <body>
1071
+ <div id="root">${content}</div>
1072
+ <script>
1073
+ window.__FLEXI_DATA__ = ${JSON.stringify({ props, isSSG })};
1074
+ </script>
1075
+ ${scriptTags}
1076
+ ${devToolbar}
1077
+ </body>
1078
+ </html>`;
1079
+ }
1080
+ function renderError(statusCode, message, stack = null) {
1081
+ const showStack = process.env.NODE_ENV !== "production" && stack;
1082
+ const isDev = process.env.NODE_ENV !== "production";
1083
+ const errorDetails = parseErrorStack(stack);
1084
+ const errorMessages = {
1085
+ 404: { title: "Page Not Found", icon: "search", color: "#00FF9C", desc: "The page you're looking for doesn't exist or has been moved." },
1086
+ 500: { title: "Server Error", icon: "alert", color: "#ef4444", desc: "Something went wrong on our end." },
1087
+ 403: { title: "Forbidden", icon: "lock", color: "#f59e0b", desc: "You don't have permission to access this resource." },
1088
+ 401: { title: "Unauthorized", icon: "key", color: "#8b5cf6", desc: "Please log in to access this page." }
1089
+ };
1090
+ 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) => `
1092
+ <div class="error-frame ${i === 0 ? "error-frame-first" : ""}">
1093
+ <div class="error-frame-fn">${escapeHtml(frame.fn)}</div>
1094
+ <div class="error-frame-loc">${escapeHtml(frame.file)}:${frame.line}:${frame.col}</div>
1095
+ </div>
1096
+ `).join("") : "";
1097
+ return `<!DOCTYPE html>
1098
+ <html lang="en">
1099
+ <head>
1100
+ <meta charset="UTF-8">
1101
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1102
+ <title>${statusCode} - ${errorInfo.title} | FlexiReact</title>
1103
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%2300FF9C'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='central' text-anchor='middle' fill='%23000' font-family='system-ui' font-weight='bold' font-size='16'%3EF%3C/text%3E%3C/svg%3E">
1104
+ <style>
1105
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1106
+
1107
+ body {
1108
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1109
+ min-height: 100vh;
1110
+ background: #0a0a0a;
1111
+ color: #fafafa;
1112
+ display: flex;
1113
+ align-items: center;
1114
+ justify-content: center;
1115
+ overflow-x: hidden;
1116
+ }
1117
+
1118
+ /* Animated background */
1119
+ .bg-pattern {
1120
+ position: fixed;
1121
+ inset: 0;
1122
+ background-image:
1123
+ radial-gradient(circle at 25% 25%, rgba(0, 255, 156, 0.03) 0%, transparent 50%),
1124
+ radial-gradient(circle at 75% 75%, rgba(99, 102, 241, 0.03) 0%, transparent 50%);
1125
+ pointer-events: none;
1126
+ }
1127
+
1128
+ .bg-grid {
1129
+ position: fixed;
1130
+ inset: 0;
1131
+ background-image:
1132
+ linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
1133
+ linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
1134
+ background-size: 64px 64px;
1135
+ pointer-events: none;
1136
+ }
1137
+
1138
+ .container {
1139
+ position: relative;
1140
+ width: 100%;
1141
+ max-width: ${showStack ? "800px" : "500px"};
1142
+ padding: 2rem;
1143
+ animation: fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1);
1144
+ }
1145
+
1146
+ @keyframes fadeIn {
1147
+ from { opacity: 0; transform: translateY(30px); }
1148
+ to { opacity: 1; transform: translateY(0); }
1149
+ }
1150
+
1151
+ /* Error Card */
1152
+ .error-card {
1153
+ background: linear-gradient(145deg, rgba(23, 23, 23, 0.9), rgba(10, 10, 10, 0.95));
1154
+ border: 1px solid rgba(255, 255, 255, 0.08);
1155
+ border-radius: 24px;
1156
+ padding: 3rem;
1157
+ text-align: center;
1158
+ backdrop-filter: blur(20px);
1159
+ box-shadow:
1160
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
1161
+ 0 20px 50px -20px rgba(0, 0, 0, 0.5),
1162
+ 0 0 100px -50px ${errorInfo.color}40;
1163
+ }
1164
+
1165
+ /* Logo */
1166
+ .logo {
1167
+ width: 56px;
1168
+ height: 56px;
1169
+ background: linear-gradient(135deg, #00FF9C, #00D68F);
1170
+ border-radius: 14px;
1171
+ display: flex;
1172
+ align-items: center;
1173
+ justify-content: center;
1174
+ margin: 0 auto 2rem;
1175
+ font-weight: 800;
1176
+ font-size: 24px;
1177
+ color: #000;
1178
+ box-shadow: 0 0 30px rgba(0, 255, 156, 0.3);
1179
+ }
1180
+
1181
+ /* Error Code */
1182
+ .error-code {
1183
+ font-size: 7rem;
1184
+ font-weight: 800;
1185
+ line-height: 1;
1186
+ background: linear-gradient(135deg, ${errorInfo.color}, ${errorInfo.color}99);
1187
+ -webkit-background-clip: text;
1188
+ -webkit-text-fill-color: transparent;
1189
+ background-clip: text;
1190
+ margin-bottom: 0.5rem;
1191
+ letter-spacing: -4px;
1192
+ }
1193
+
1194
+ .error-title {
1195
+ font-size: 1.5rem;
1196
+ font-weight: 600;
1197
+ color: #fafafa;
1198
+ margin-bottom: 0.75rem;
1199
+ }
1200
+
1201
+ .error-desc {
1202
+ font-size: 1rem;
1203
+ color: #71717a;
1204
+ line-height: 1.6;
1205
+ margin-bottom: 2rem;
1206
+ }
1207
+
1208
+ /* Buttons */
1209
+ .buttons {
1210
+ display: flex;
1211
+ gap: 12px;
1212
+ justify-content: center;
1213
+ flex-wrap: wrap;
1214
+ }
1215
+
1216
+ .btn {
1217
+ display: inline-flex;
1218
+ align-items: center;
1219
+ gap: 8px;
1220
+ padding: 12px 24px;
1221
+ border-radius: 12px;
1222
+ font-weight: 600;
1223
+ font-size: 14px;
1224
+ text-decoration: none;
1225
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
1226
+ border: none;
1227
+ cursor: pointer;
1228
+ }
1229
+
1230
+ .btn-primary {
1231
+ background: #00FF9C;
1232
+ color: #000;
1233
+ box-shadow: 0 0 20px rgba(0, 255, 156, 0.3);
1234
+ }
1235
+
1236
+ .btn-primary:hover {
1237
+ transform: translateY(-2px);
1238
+ box-shadow: 0 0 30px rgba(0, 255, 156, 0.5);
1239
+ }
1240
+
1241
+ .btn-secondary {
1242
+ background: rgba(255, 255, 255, 0.06);
1243
+ color: #a1a1aa;
1244
+ border: 1px solid rgba(255, 255, 255, 0.1);
1245
+ }
1246
+
1247
+ .btn-secondary:hover {
1248
+ background: rgba(255, 255, 255, 0.1);
1249
+ color: #fafafa;
1250
+ border-color: rgba(255, 255, 255, 0.2);
1251
+ }
1252
+
1253
+ /* Error Stack (Dev Mode) */
1254
+ .error-stack {
1255
+ margin-top: 2rem;
1256
+ text-align: left;
1257
+ }
1258
+
1259
+ .error-stack-header {
1260
+ display: flex;
1261
+ align-items: center;
1262
+ gap: 8px;
1263
+ margin-bottom: 12px;
1264
+ font-size: 12px;
1265
+ font-weight: 600;
1266
+ color: #ef4444;
1267
+ text-transform: uppercase;
1268
+ letter-spacing: 0.5px;
1269
+ }
1270
+
1271
+ .error-stack-header::before {
1272
+ content: '';
1273
+ width: 8px;
1274
+ height: 8px;
1275
+ background: #ef4444;
1276
+ border-radius: 50%;
1277
+ animation: pulse 2s infinite;
1278
+ }
1279
+
1280
+ @keyframes pulse {
1281
+ 0%, 100% { opacity: 1; }
1282
+ 50% { opacity: 0.5; }
1283
+ }
1284
+
1285
+ .error-message {
1286
+ background: rgba(239, 68, 68, 0.1);
1287
+ border: 1px solid rgba(239, 68, 68, 0.2);
1288
+ border-radius: 12px;
1289
+ padding: 16px;
1290
+ margin-bottom: 12px;
1291
+ font-family: 'SF Mono', 'Fira Code', monospace;
1292
+ font-size: 13px;
1293
+ color: #fca5a5;
1294
+ word-break: break-word;
1295
+ }
1296
+
1297
+ .error-frames {
1298
+ background: rgba(0, 0, 0, 0.4);
1299
+ border: 1px solid rgba(255, 255, 255, 0.05);
1300
+ border-radius: 12px;
1301
+ overflow: hidden;
1302
+ }
1303
+
1304
+ .error-frame {
1305
+ padding: 12px 16px;
1306
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
1307
+ font-family: 'SF Mono', 'Fira Code', monospace;
1308
+ font-size: 12px;
1309
+ }
1310
+
1311
+ .error-frame:last-child {
1312
+ border-bottom: none;
1313
+ }
1314
+
1315
+ .error-frame-first {
1316
+ background: rgba(239, 68, 68, 0.05);
1317
+ }
1318
+
1319
+ .error-frame-fn {
1320
+ color: #fafafa;
1321
+ font-weight: 500;
1322
+ margin-bottom: 4px;
1323
+ }
1324
+
1325
+ .error-frame-loc {
1326
+ color: #52525b;
1327
+ font-size: 11px;
1328
+ }
1329
+
1330
+ /* Dev Badge */
1331
+ .dev-badge {
1332
+ position: fixed;
1333
+ bottom: 20px;
1334
+ right: 20px;
1335
+ display: flex;
1336
+ align-items: center;
1337
+ gap: 8px;
1338
+ padding: 8px 14px;
1339
+ background: rgba(0, 0, 0, 0.8);
1340
+ border: 1px solid rgba(255, 255, 255, 0.1);
1341
+ border-radius: 10px;
1342
+ font-size: 12px;
1343
+ font-weight: 500;
1344
+ color: #71717a;
1345
+ backdrop-filter: blur(10px);
1346
+ }
1347
+
1348
+ .dev-badge-dot {
1349
+ width: 8px;
1350
+ height: 8px;
1351
+ background: #00FF9C;
1352
+ border-radius: 50%;
1353
+ box-shadow: 0 0 10px rgba(0, 255, 156, 0.5);
1354
+ }
1355
+ </style>
1356
+ </head>
1357
+ <body>
1358
+ <div class="bg-pattern"></div>
1359
+ <div class="bg-grid"></div>
1360
+
1361
+ <div class="container">
1362
+ <div class="error-card">
1363
+ <div class="logo">F</div>
1364
+
1365
+ <div class="error-code">${statusCode}</div>
1366
+ <h1 class="error-title">${errorInfo.title}</h1>
1367
+ <p class="error-desc">${errorInfo.desc}</p>
1368
+
1369
+ <div class="buttons">
1370
+ <a href="/" class="btn btn-primary">
1371
+ \u2190 Back to Home
1372
+ </a>
1373
+ <a href="javascript:history.back()" class="btn btn-secondary">
1374
+ Go Back
1375
+ </a>
1376
+ </div>
1377
+
1378
+ ${showStack ? `
1379
+ <div class="error-stack">
1380
+ <div class="error-stack-header">Error Details</div>
1381
+ <div class="error-message">${escapeHtml(message)}</div>
1382
+ ${errorFramesHtml ? `<div class="error-frames">${errorFramesHtml}</div>` : ""}
1383
+ </div>
1384
+ ` : ""}
1385
+ </div>
1386
+ </div>
1387
+
1388
+ ${isDev ? `
1389
+ <div class="dev-badge">
1390
+ <div class="dev-badge-dot"></div>
1391
+ FlexiReact v2
1392
+ </div>
1393
+ ` : ""}
1394
+ </body>
1395
+ </html>`;
1396
+ }
1397
+ function parseErrorStack(stack) {
1398
+ if (!stack) return null;
1399
+ const lines = stack.split("\n");
1400
+ const parsed = {
1401
+ message: lines[0] || "",
1402
+ frames: []
1403
+ };
1404
+ for (let i = 1; i < lines.length; i++) {
1405
+ const line = lines[i].trim();
1406
+ const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/) || line.match(/at\s+(.+?):(\d+):(\d+)/);
1407
+ if (match) {
1408
+ parsed.frames.push({
1409
+ fn: match[1] || "anonymous",
1410
+ file: match[2] || match[1],
1411
+ line: match[3] || match[2],
1412
+ col: match[4] || match[3]
1413
+ });
1414
+ }
1415
+ }
1416
+ return parsed;
1417
+ }
1418
+
1419
+ // core/middleware/index.ts
1420
+ import fs4 from "fs";
1421
+ import path3 from "path";
1422
+ import { pathToFileURL as pathToFileURL2 } from "url";
1423
+ var MiddlewareRequest = class {
1424
+ raw;
1425
+ method;
1426
+ url;
1427
+ headers;
1428
+ pathname;
1429
+ searchParams;
1430
+ query;
1431
+ cookies;
1432
+ constructor(req) {
1433
+ this.raw = req;
1434
+ this.method = req.method;
1435
+ this.url = req.url;
1436
+ this.headers = new Map(Object.entries(req.headers || {}));
1437
+ const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`);
1438
+ this.pathname = parsedUrl.pathname;
1439
+ this.searchParams = parsedUrl.searchParams;
1440
+ this.query = Object.fromEntries(parsedUrl.searchParams);
1441
+ this.cookies = this._parseCookies(req.headers.cookie || "");
1442
+ }
1443
+ _parseCookies(cookieHeader) {
1444
+ const cookies2 = /* @__PURE__ */ new Map();
1445
+ if (!cookieHeader) return cookies2;
1446
+ cookieHeader.split(";").forEach((cookie) => {
1447
+ const [name, ...rest] = cookie.split("=");
1448
+ if (name) {
1449
+ cookies2.set(name.trim(), rest.join("=").trim());
1450
+ }
1451
+ });
1452
+ return cookies2;
1453
+ }
1454
+ /**
1455
+ * Get a header value
1456
+ */
1457
+ header(name) {
1458
+ return this.headers.get(name.toLowerCase());
1459
+ }
1460
+ /**
1461
+ * Get a cookie value
1462
+ */
1463
+ cookie(name) {
1464
+ return this.cookies.get(name);
1465
+ }
1466
+ /**
1467
+ * Check if request matches a path pattern
1468
+ */
1469
+ matches(pattern) {
1470
+ return matchPath(this.pathname, pattern);
1471
+ }
1472
+ };
1473
+ async function loadMiddleware(projectRoot) {
1474
+ const middlewarePath = path3.join(projectRoot, "middleware.js");
1475
+ if (!fs4.existsSync(middlewarePath)) {
1476
+ return null;
1477
+ }
1478
+ try {
1479
+ const url = pathToFileURL2(middlewarePath).href;
1480
+ const module = await import(`${url}?t=${Date.now()}`);
1481
+ return {
1482
+ handler: module.default,
1483
+ config: module.config || {}
1484
+ };
1485
+ } catch (error) {
1486
+ console.error("Failed to load middleware:", error);
1487
+ return null;
1488
+ }
1489
+ }
1490
+ async function runMiddleware(req, res, middleware) {
1491
+ if (!middleware) {
1492
+ return { continue: true };
1493
+ }
1494
+ const { handler, config } = middleware;
1495
+ const request = new MiddlewareRequest(req);
1496
+ if (config.matcher) {
1497
+ const patterns = Array.isArray(config.matcher) ? config.matcher : [config.matcher];
1498
+ const matches = patterns.some((pattern) => matchPath(request.pathname, pattern));
1499
+ if (!matches) {
1500
+ return { continue: true };
1501
+ }
1502
+ }
1503
+ try {
1504
+ const response = await handler(request);
1505
+ if (!response || response.type === "next") {
1506
+ if (response?.headers) {
1507
+ for (const [key, value] of response.headers) {
1508
+ res.setHeader(key, value);
1509
+ }
1510
+ }
1511
+ return { continue: true };
1512
+ }
1513
+ if (response.type === "redirect") {
1514
+ res.writeHead(response.status, { Location: response.url });
1515
+ res.end();
1516
+ return { continue: false };
1517
+ }
1518
+ if (response.type === "rewrite") {
1519
+ req.url = response.url;
1520
+ return { continue: true, rewritten: true };
1521
+ }
1522
+ if (response.type === "response") {
1523
+ for (const [key, value] of response.headers) {
1524
+ res.setHeader(key, value);
1525
+ }
1526
+ res.writeHead(response.status);
1527
+ res.end(response.body);
1528
+ return { continue: false };
1529
+ }
1530
+ return { continue: true };
1531
+ } catch (error) {
1532
+ console.error("Middleware error:", error);
1533
+ return { continue: true, error };
1534
+ }
1535
+ }
1536
+ function matchPath(pathname, pattern) {
1537
+ let regex = pattern.replace(/\*/g, ".*").replace(/:path\*/g, ".*").replace(/:(\w+)/g, "[^/]+");
1538
+ regex = `^${regex}$`;
1539
+ return new RegExp(regex).test(pathname);
1540
+ }
1541
+
1542
+ // core/plugins/index.ts
1543
+ import fs5 from "fs";
1544
+ import path4 from "path";
1545
+ import { pathToFileURL as pathToFileURL3 } from "url";
1546
+ var PluginHooks = {
1547
+ // Server lifecycle
1548
+ SERVER_START: "onServerStart",
1549
+ SERVER_STOP: "onServerStop",
1550
+ // Request lifecycle
1551
+ REQUEST: "onRequest",
1552
+ RESPONSE: "onResponse",
1553
+ // Render lifecycle
1554
+ BEFORE_RENDER: "onBeforeRender",
1555
+ AFTER_RENDER: "onAfterRender",
1556
+ // Build lifecycle
1557
+ BUILD_START: "onBuildStart",
1558
+ BUILD_END: "onBuildEnd",
1559
+ // Route lifecycle
1560
+ ROUTES_LOADED: "onRoutesLoaded",
1561
+ // Config
1562
+ CONFIG: "onConfig",
1563
+ ESBUILD_CONFIG: "esbuildConfig"
1564
+ };
1565
+ var PluginManager = class {
1566
+ plugins;
1567
+ hooks;
1568
+ constructor() {
1569
+ this.plugins = [];
1570
+ this.hooks = /* @__PURE__ */ new Map();
1571
+ for (const hook of Object.values(PluginHooks)) {
1572
+ this.hooks.set(hook, []);
1573
+ }
1574
+ }
1575
+ /**
1576
+ * Registers a plugin
1577
+ */
1578
+ register(plugin) {
1579
+ if (!plugin.name) {
1580
+ throw new Error("Plugin must have a name");
1581
+ }
1582
+ if (this.plugins.find((p) => p.name === plugin.name)) {
1583
+ console.warn(`Plugin "${plugin.name}" is already registered`);
1584
+ return;
1585
+ }
1586
+ this.plugins.push(plugin);
1587
+ for (const [hookName, handlers] of this.hooks) {
1588
+ if (typeof plugin[hookName] === "function") {
1589
+ handlers.push({
1590
+ plugin: plugin.name,
1591
+ handler: plugin[hookName].bind(plugin)
1592
+ });
1593
+ }
1594
+ }
1595
+ console.log(` \u2713 Plugin loaded: ${plugin.name}`);
1596
+ }
1597
+ /**
1598
+ * Unregisters a plugin
1599
+ */
1600
+ unregister(pluginName) {
1601
+ const index = this.plugins.findIndex((p) => p.name === pluginName);
1602
+ if (index === -1) return;
1603
+ this.plugins.splice(index, 1);
1604
+ for (const handlers of this.hooks.values()) {
1605
+ const hookIndex = handlers.findIndex((h) => h.plugin === pluginName);
1606
+ if (hookIndex !== -1) {
1607
+ handlers.splice(hookIndex, 1);
1608
+ }
1609
+ }
1610
+ }
1611
+ /**
1612
+ * Runs a hook with all registered handlers
1613
+ */
1614
+ async runHook(hookName, ...args) {
1615
+ const handlers = this.hooks.get(hookName) || [];
1616
+ const results = [];
1617
+ for (const { plugin, handler } of handlers) {
1618
+ try {
1619
+ const result = await handler(...args);
1620
+ results.push({ plugin, result });
1621
+ } catch (error) {
1622
+ console.error(`Plugin "${plugin}" error in ${hookName}:`, error);
1623
+ results.push({ plugin, error });
1624
+ }
1625
+ }
1626
+ return results;
1627
+ }
1628
+ /**
1629
+ * Runs a hook that can modify a value (waterfall)
1630
+ */
1631
+ async runWaterfallHook(hookName, initialValue, ...args) {
1632
+ const handlers = this.hooks.get(hookName) || [];
1633
+ let value = initialValue;
1634
+ for (const { plugin, handler } of handlers) {
1635
+ try {
1636
+ const result = await handler(value, ...args);
1637
+ if (result !== void 0) {
1638
+ value = result;
1639
+ }
1640
+ } catch (error) {
1641
+ console.error(`Plugin "${plugin}" error in ${hookName}:`, error);
1642
+ }
1643
+ }
1644
+ return value;
1645
+ }
1646
+ /**
1647
+ * Checks if any plugin handles a hook
1648
+ */
1649
+ hasHook(hookName) {
1650
+ const handlers = this.hooks.get(hookName) || [];
1651
+ return handlers.length > 0;
1652
+ }
1653
+ /**
1654
+ * Gets all registered plugins
1655
+ */
1656
+ getPlugins() {
1657
+ return [...this.plugins];
1658
+ }
1659
+ };
1660
+ var pluginManager = new PluginManager();
1661
+ async function loadPlugins(projectRoot, config) {
1662
+ console.log("\n\u{1F4E6} Loading plugins...\n");
1663
+ const pluginPath = path4.join(projectRoot, "flexireact.plugin.js");
1664
+ if (fs5.existsSync(pluginPath)) {
1665
+ try {
1666
+ const url = pathToFileURL3(pluginPath).href;
1667
+ const module = await import(`${url}?t=${Date.now()}`);
1668
+ const plugin = module.default;
1669
+ if (plugin) {
1670
+ pluginManager.register(plugin);
1671
+ }
1672
+ } catch (error) {
1673
+ console.error("Failed to load flexireact.plugin.js:", error);
1674
+ }
1675
+ }
1676
+ if (config.plugins && Array.isArray(config.plugins)) {
1677
+ for (const pluginConfig of config.plugins) {
1678
+ try {
1679
+ if (typeof pluginConfig === "string") {
1680
+ const module = await import(pluginConfig);
1681
+ pluginManager.register(module.default);
1682
+ } else if (typeof pluginConfig === "object") {
1683
+ pluginManager.register(pluginConfig);
1684
+ } else if (typeof pluginConfig === "function") {
1685
+ pluginManager.register(pluginConfig());
1686
+ }
1687
+ } catch (error) {
1688
+ console.error(`Failed to load plugin:`, error);
1689
+ }
1690
+ }
1691
+ }
1692
+ console.log(`
1693
+ Total plugins: ${pluginManager.getPlugins().length}
1694
+ `);
1695
+ return pluginManager;
1696
+ }
1697
+
1698
+ // core/islands/index.ts
1699
+ import React2 from "react";
1700
+ import { renderToString as renderToString2 } from "react-dom/server";
1701
+ var islandRegistry = /* @__PURE__ */ new Map();
1702
+ function getRegisteredIslands() {
1703
+ const islands = Array.from(islandRegistry.values());
1704
+ islandRegistry.clear();
1705
+ return islands;
1706
+ }
1707
+ var LoadStrategy = {
1708
+ // Hydrate immediately when page loads
1709
+ IMMEDIATE: "immediate",
1710
+ // Hydrate when island becomes visible
1711
+ VISIBLE: "visible",
1712
+ // Hydrate when user interacts with the page
1713
+ IDLE: "idle",
1714
+ // Hydrate on specific media query
1715
+ MEDIA: "media"
1716
+ };
1717
+ function generateAdvancedHydrationScript(islands) {
1718
+ if (!islands.length) return "";
1719
+ const islandData = islands.map((island) => ({
1720
+ id: island.id,
1721
+ name: island.name,
1722
+ path: island.clientPath,
1723
+ props: island.props,
1724
+ strategy: island.strategy || LoadStrategy.IMMEDIATE,
1725
+ media: island.media
1726
+ }));
1727
+ return `
1728
+ <script type="module">
1729
+ const islands = ${JSON.stringify(islandData)};
1730
+
1731
+ async function hydrateIsland(island) {
1732
+ const element = document.querySelector(\`[data-island="\${island.id}"]\`);
1733
+ if (!element || element.hasAttribute('data-hydrated')) return;
1734
+
1735
+ try {
1736
+ const { hydrateRoot } = await import('/_flexi/react-dom-client.js');
1737
+ const React = await import('/_flexi/react.js');
1738
+ const module = await import(island.path);
1739
+ const Component = module.default;
1740
+
1741
+ hydrateRoot(element, React.createElement(Component, island.props));
1742
+ element.setAttribute('data-hydrated', 'true');
1743
+ } catch (error) {
1744
+ console.error(\`Failed to hydrate island \${island.name}:\`, error);
1745
+ }
1746
+ }
1747
+
1748
+ // Intersection Observer for visible strategy
1749
+ const visibleObserver = new IntersectionObserver((entries) => {
1750
+ entries.forEach(entry => {
1751
+ if (entry.isIntersecting) {
1752
+ const id = entry.target.getAttribute('data-island');
1753
+ const island = islands.find(i => i.id === id);
1754
+ if (island) {
1755
+ hydrateIsland(island);
1756
+ visibleObserver.unobserve(entry.target);
1757
+ }
1758
+ }
1759
+ });
1760
+ }, { rootMargin: '50px' });
1761
+
1762
+ // Process islands based on strategy
1763
+ function processIslands() {
1764
+ for (const island of islands) {
1765
+ const element = document.querySelector(\`[data-island="\${island.id}"]\`);
1766
+ if (!element) continue;
1767
+
1768
+ switch (island.strategy) {
1769
+ case 'immediate':
1770
+ hydrateIsland(island);
1771
+ break;
1772
+ case 'visible':
1773
+ visibleObserver.observe(element);
1774
+ break;
1775
+ case 'idle':
1776
+ requestIdleCallback(() => hydrateIsland(island));
1777
+ break;
1778
+ case 'media':
1779
+ if (island.media && window.matchMedia(island.media).matches) {
1780
+ hydrateIsland(island);
1781
+ }
1782
+ break;
1783
+ }
1784
+ }
1785
+ }
1786
+
1787
+ if (document.readyState === 'loading') {
1788
+ document.addEventListener('DOMContentLoaded', processIslands);
1789
+ } else {
1790
+ processIslands();
1791
+ }
1792
+ </script>`;
1793
+ }
1794
+
1795
+ // core/context.ts
1796
+ import React3 from "react";
1797
+ var RequestContext = React3.createContext(null);
1798
+ var RouteContext = React3.createContext(null);
1799
+ var LayoutContext = React3.createContext(null);
1800
+ function createRequestContext(req, res, params = {}, query = {}) {
1801
+ return {
1802
+ req,
1803
+ res,
1804
+ params,
1805
+ query,
1806
+ url: req.url,
1807
+ method: req.method,
1808
+ headers: req.headers,
1809
+ cookies: parseCookies(req.headers.cookie || "")
1810
+ };
1811
+ }
1812
+ function parseCookies(cookieHeader) {
1813
+ const cookies2 = {};
1814
+ if (!cookieHeader) return cookies2;
1815
+ cookieHeader.split(";").forEach((cookie) => {
1816
+ const [name, ...rest] = cookie.split("=");
1817
+ if (name) {
1818
+ cookies2[name.trim()] = rest.join("=").trim();
1819
+ }
1820
+ });
1821
+ return cookies2;
1822
+ }
1823
+
1824
+ // core/logger.ts
1825
+ var colors = {
1826
+ reset: "\x1B[0m",
1827
+ bold: "\x1B[1m",
1828
+ dim: "\x1B[2m",
1829
+ italic: "\x1B[3m",
1830
+ underline: "\x1B[4m",
1831
+ // Text colors
1832
+ black: "\x1B[30m",
1833
+ red: "\x1B[31m",
1834
+ green: "\x1B[32m",
1835
+ yellow: "\x1B[33m",
1836
+ blue: "\x1B[34m",
1837
+ magenta: "\x1B[35m",
1838
+ cyan: "\x1B[36m",
1839
+ 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"
1856
+ };
1857
+ var c = colors;
1858
+ function getStatusColor(status) {
1859
+ if (status >= 500) return c.red;
1860
+ if (status >= 400) return c.yellow;
1861
+ if (status >= 300) return c.cyan;
1862
+ if (status >= 200) return c.green;
1863
+ return c.white;
1864
+ }
1865
+ 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;
1876
+ }
1877
+ function formatTime(ms) {
1878
+ if (ms < 1) return `${c.gray}<1ms${c.reset}`;
1879
+ if (ms < 100) return `${c.green}${ms}ms${c.reset}`;
1880
+ if (ms < 500) return `${c.yellow}${ms}ms${c.reset}`;
1881
+ return `${c.red}${ms}ms${c.reset}`;
1882
+ }
1883
+ 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}
1891
+ `;
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
+ var logger = {
1895
+ // Show startup logo
1896
+ logo() {
1897
+ console.log(LOGO);
1898
+ },
1899
+ // Server started - Next.js style
1900
+ serverStart(config, startTime = Date.now()) {
1901
+ const { port, host, mode, pagesDir, islands, rsc } = config;
1902
+ const elapsed = Date.now() - startTime;
1903
+ 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}`);
1915
+ console.log("");
1916
+ },
1917
+ // HTTP request log - Compact single line like Next.js
1918
+ request(method, path8, status, time, extra = {}) {
1919
+ const methodColor = getMethodColor(method);
1920
+ const statusColor = getStatusColor(status);
1921
+ 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}`;
1927
+ } 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}`;
1933
+ }
1934
+ const statusStr = `${statusColor}${status}${c.reset}`;
1935
+ const methodStr = `${methodColor}${method}${c.reset}`;
1936
+ console.log(` ${badge} ${methodStr} ${path8} ${statusStr} ${c.dim}in${c.reset} ${timeStr}`);
1937
+ },
1938
+ // Info message
1939
+ info(msg) {
1940
+ console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
1941
+ },
1942
+ // Success message
1943
+ success(msg) {
1944
+ console.log(` ${c.green}\u2713${c.reset} ${msg}`);
1945
+ },
1946
+ // Warning message
1947
+ warn(msg) {
1948
+ console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
1949
+ },
1950
+ // Error message
1951
+ error(msg, err = null) {
1952
+ console.log(` ${c.red}\u2717${c.reset} ${c.red}${msg}${c.reset}`);
1953
+ if (err && err.stack) {
1954
+ const stack = err.stack.split("\n").slice(1, 4).join("\n");
1955
+ console.log(`${c.dim}${stack}${c.reset}`);
1956
+ }
1957
+ },
1958
+ // Compilation message
1959
+ compile(file, time) {
1960
+ console.log(` ${c.magenta}\u25C9${c.reset} Compiled ${c.cyan}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
1961
+ },
1962
+ // Hot reload
1963
+ hmr(file) {
1964
+ console.log(` ${c.yellow}\u21BB${c.reset} HMR update: ${c.cyan}${file}${c.reset}`);
1965
+ },
1966
+ // Plugin loaded
1967
+ plugin(name) {
1968
+ console.log(` ${c.blue}\u2B21${c.reset} Plugin: ${c.cyan}${name}${c.reset}`);
1969
+ },
1970
+ // Route info
1971
+ 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}`);
1979
+ },
1980
+ // Divider
1981
+ 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}`);
1983
+ },
1984
+ // Blank line
1985
+ blank() {
1986
+ console.log("");
1987
+ },
1988
+ // Port in use error with solution
1989
+ 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
+ `);
2004
+ },
2005
+ // Build info
2006
+ 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
+ `);
2015
+ }
2016
+ };
2017
+
2018
+ // core/helpers.ts
2019
+ var RedirectError = class extends Error {
2020
+ url;
2021
+ statusCode;
2022
+ type = "redirect";
2023
+ constructor(url, statusCode = 307) {
2024
+ super(`Redirect to ${url}`);
2025
+ this.name = "RedirectError";
2026
+ this.url = url;
2027
+ this.statusCode = statusCode;
2028
+ }
2029
+ };
2030
+ var NotFoundError = class extends Error {
2031
+ type = "notFound";
2032
+ constructor(message = "Page not found") {
2033
+ super(message);
2034
+ this.name = "NotFoundError";
2035
+ }
2036
+ };
2037
+ function redirect(url, type = "replace") {
2038
+ const statusCode = type === "permanent" ? 308 : 307;
2039
+ throw new RedirectError(url, statusCode);
2040
+ }
2041
+ function notFound(message) {
2042
+ throw new NotFoundError(message);
2043
+ }
2044
+ var cookies = {
2045
+ /**
2046
+ * Parse cookies from a cookie header string
2047
+ */
2048
+ parse(cookieHeader) {
2049
+ const cookies2 = {};
2050
+ if (!cookieHeader) return cookies2;
2051
+ cookieHeader.split(";").forEach((cookie) => {
2052
+ const [name, ...rest] = cookie.split("=");
2053
+ if (name) {
2054
+ const value = rest.join("=");
2055
+ cookies2[name.trim()] = decodeURIComponent(value.trim());
2056
+ }
2057
+ });
2058
+ return cookies2;
2059
+ },
2060
+ /**
2061
+ * Get a cookie value from request headers
2062
+ */
2063
+ get(request, name) {
2064
+ const cookieHeader = request.headers.get("cookie") || "";
2065
+ const parsed = this.parse(cookieHeader);
2066
+ return parsed[name];
2067
+ },
2068
+ /**
2069
+ * Get all cookies from request
2070
+ */
2071
+ getAll(request) {
2072
+ const cookieHeader = request.headers.get("cookie") || "";
2073
+ return this.parse(cookieHeader);
2074
+ },
2075
+ /**
2076
+ * Serialize a cookie for Set-Cookie header
2077
+ */
2078
+ serialize(name, value, options = {}) {
2079
+ let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
2080
+ if (options.maxAge !== void 0) {
2081
+ cookie += `; Max-Age=${options.maxAge}`;
2082
+ }
2083
+ if (options.expires) {
2084
+ cookie += `; Expires=${options.expires.toUTCString()}`;
2085
+ }
2086
+ if (options.path) {
2087
+ cookie += `; Path=${options.path}`;
2088
+ }
2089
+ if (options.domain) {
2090
+ cookie += `; Domain=${options.domain}`;
2091
+ }
2092
+ if (options.secure) {
2093
+ cookie += "; Secure";
2094
+ }
2095
+ if (options.httpOnly) {
2096
+ cookie += "; HttpOnly";
2097
+ }
2098
+ if (options.sameSite) {
2099
+ cookie += `; SameSite=${options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1)}`;
2100
+ }
2101
+ return cookie;
2102
+ },
2103
+ /**
2104
+ * Create a Set-Cookie header value
2105
+ */
2106
+ set(name, value, options = {}) {
2107
+ return this.serialize(name, value, {
2108
+ path: "/",
2109
+ httpOnly: true,
2110
+ secure: process.env.NODE_ENV === "production",
2111
+ sameSite: "lax",
2112
+ ...options
2113
+ });
2114
+ },
2115
+ /**
2116
+ * Create a cookie deletion header
2117
+ */
2118
+ delete(name, options = {}) {
2119
+ return this.serialize(name, "", {
2120
+ ...options,
2121
+ path: "/",
2122
+ maxAge: 0
2123
+ });
2124
+ }
2125
+ };
2126
+ var headers = {
2127
+ /**
2128
+ * Create a new Headers object with common defaults
2129
+ */
2130
+ create(init) {
2131
+ return new Headers(init);
2132
+ },
2133
+ /**
2134
+ * Get a header value from request
2135
+ */
2136
+ get(request, name) {
2137
+ return request.headers.get(name);
2138
+ },
2139
+ /**
2140
+ * Get all headers as an object
2141
+ */
2142
+ getAll(request) {
2143
+ const result = {};
2144
+ request.headers.forEach((value, key) => {
2145
+ result[key] = value;
2146
+ });
2147
+ return result;
2148
+ },
2149
+ /**
2150
+ * Check if request has a specific header
2151
+ */
2152
+ has(request, name) {
2153
+ return request.headers.has(name);
2154
+ },
2155
+ /**
2156
+ * Get content type from request
2157
+ */
2158
+ contentType(request) {
2159
+ return request.headers.get("content-type");
2160
+ },
2161
+ /**
2162
+ * Check if request accepts JSON
2163
+ */
2164
+ acceptsJson(request) {
2165
+ const accept = request.headers.get("accept") || "";
2166
+ return accept.includes("application/json") || accept.includes("*/*");
2167
+ },
2168
+ /**
2169
+ * Check if request is AJAX/fetch
2170
+ */
2171
+ isAjax(request) {
2172
+ return request.headers.get("x-requested-with") === "XMLHttpRequest" || this.acceptsJson(request);
2173
+ },
2174
+ /**
2175
+ * Get authorization header
2176
+ */
2177
+ authorization(request) {
2178
+ const auth = request.headers.get("authorization");
2179
+ if (!auth) return null;
2180
+ const [type, ...rest] = auth.split(" ");
2181
+ return {
2182
+ type: type.toLowerCase(),
2183
+ credentials: rest.join(" ")
2184
+ };
2185
+ },
2186
+ /**
2187
+ * Get bearer token from authorization header
2188
+ */
2189
+ bearerToken(request) {
2190
+ const auth = this.authorization(request);
2191
+ if (auth?.type === "bearer") {
2192
+ return auth.credentials;
2193
+ }
2194
+ return null;
2195
+ },
2196
+ /**
2197
+ * Common security headers
2198
+ */
2199
+ security() {
2200
+ return {
2201
+ "X-Content-Type-Options": "nosniff",
2202
+ "X-Frame-Options": "DENY",
2203
+ "X-XSS-Protection": "1; mode=block",
2204
+ "Referrer-Policy": "strict-origin-when-cross-origin",
2205
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=()"
2206
+ };
2207
+ },
2208
+ /**
2209
+ * CORS headers
2210
+ */
2211
+ cors(options = {}) {
2212
+ const {
2213
+ origin = "*",
2214
+ methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
2215
+ headers: allowHeaders = ["Content-Type", "Authorization"],
2216
+ credentials = false,
2217
+ maxAge = 86400
2218
+ } = options;
2219
+ const corsHeaders = {
2220
+ "Access-Control-Allow-Origin": origin,
2221
+ "Access-Control-Allow-Methods": methods.join(", "),
2222
+ "Access-Control-Allow-Headers": allowHeaders.join(", "),
2223
+ "Access-Control-Max-Age": String(maxAge)
2224
+ };
2225
+ if (credentials) {
2226
+ corsHeaders["Access-Control-Allow-Credentials"] = "true";
2227
+ }
2228
+ return corsHeaders;
2229
+ },
2230
+ /**
2231
+ * Cache control headers
2232
+ */
2233
+ cache(options = {}) {
2234
+ if (options.noStore) {
2235
+ return { "Cache-Control": "no-store, no-cache, must-revalidate" };
2236
+ }
2237
+ const directives = [];
2238
+ if (options.private) {
2239
+ directives.push("private");
2240
+ } else {
2241
+ directives.push("public");
2242
+ }
2243
+ if (options.maxAge !== void 0) {
2244
+ directives.push(`max-age=${options.maxAge}`);
2245
+ }
2246
+ if (options.sMaxAge !== void 0) {
2247
+ directives.push(`s-maxage=${options.sMaxAge}`);
2248
+ }
2249
+ if (options.staleWhileRevalidate !== void 0) {
2250
+ directives.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
2251
+ }
2252
+ return { "Cache-Control": directives.join(", ") };
2253
+ }
2254
+ };
2255
+
2256
+ // core/actions/index.ts
2257
+ import { useActionState, useOptimistic } from "react";
2258
+ import { useFormStatus } from "react-dom";
2259
+ import { useActionState as useActionStateReact } from "react";
2260
+ globalThis.__FLEXI_ACTIONS__ = globalThis.__FLEXI_ACTIONS__ || {};
2261
+ globalThis.__FLEXI_ACTION_CONTEXT__ = null;
2262
+ async function executeAction(actionId, args, context) {
2263
+ const action = globalThis.__FLEXI_ACTIONS__[actionId];
2264
+ if (!action) {
2265
+ return {
2266
+ success: false,
2267
+ error: `Server action not found: ${actionId}`
2268
+ };
2269
+ }
2270
+ const actionContext = {
2271
+ request: context?.request || new Request("http://localhost"),
2272
+ cookies,
2273
+ headers,
2274
+ redirect,
2275
+ notFound
2276
+ };
2277
+ globalThis.__FLEXI_ACTION_CONTEXT__ = actionContext;
2278
+ try {
2279
+ const result = await action(...args);
2280
+ return {
2281
+ success: true,
2282
+ data: result
2283
+ };
2284
+ } catch (error) {
2285
+ if (error instanceof RedirectError) {
2286
+ return {
2287
+ success: true,
2288
+ redirect: error.url
2289
+ };
2290
+ }
2291
+ if (error instanceof NotFoundError) {
2292
+ return {
2293
+ success: false,
2294
+ error: "Not found"
2295
+ };
2296
+ }
2297
+ return {
2298
+ success: false,
2299
+ error: error.message || "Action failed"
2300
+ };
2301
+ } finally {
2302
+ globalThis.__FLEXI_ACTION_CONTEXT__ = null;
2303
+ }
2304
+ }
2305
+ function deserializeArgs(args) {
2306
+ return args.map((arg) => {
2307
+ if (arg && typeof arg === "object") {
2308
+ if (arg.$$type === "FormData") {
2309
+ const formData = new FormData();
2310
+ for (const [key, value] of Object.entries(arg.data)) {
2311
+ if (Array.isArray(value)) {
2312
+ value.forEach((v) => formData.append(key, v));
2313
+ } else {
2314
+ formData.append(key, value);
2315
+ }
2316
+ }
2317
+ return formData;
2318
+ }
2319
+ if (arg.$$type === "Date") {
2320
+ return new Date(arg.value);
2321
+ }
2322
+ }
2323
+ return arg;
2324
+ });
2325
+ }
2326
+
2327
+ // core/image/index.ts
2328
+ import React4 from "react";
2329
+ import path5 from "path";
2330
+ import fs6 from "fs";
2331
+ import crypto from "crypto";
2332
+ var defaultImageConfig = {
2333
+ domains: [],
2334
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
2335
+ imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
2336
+ formats: ["webp", "avif"],
2337
+ minimumCacheTTL: 60 * 60 * 24 * 30,
2338
+ // 30 days
2339
+ dangerouslyAllowSVG: false,
2340
+ quality: 75,
2341
+ cacheDir: ".flexi/image-cache"
2342
+ };
2343
+ function generateSrcSet(src, widths, quality = 75) {
2344
+ return widths.map((w) => `/_flexi/image?url=${encodeURIComponent(src)}&w=${w}&q=${quality} ${w}w`).join(", ");
2345
+ }
2346
+ function generateSizes(sizes) {
2347
+ if (sizes) return sizes;
2348
+ return "100vw";
2349
+ }
2350
+ async function handleImageOptimization(req, res, config = {}) {
2351
+ const fullConfig = { ...defaultImageConfig, ...config };
2352
+ const url = new URL(req.url, `http://${req.headers.host}`);
2353
+ const imageUrl = url.searchParams.get("url");
2354
+ const width = parseInt(url.searchParams.get("w") || "0", 10);
2355
+ const quality = parseInt(url.searchParams.get("q") || String(fullConfig.quality), 10);
2356
+ const format = url.searchParams.get("f");
2357
+ if (!imageUrl) {
2358
+ res.writeHead(400, { "Content-Type": "text/plain" });
2359
+ res.end("Missing url parameter");
2360
+ return;
2361
+ }
2362
+ try {
2363
+ let imageBuffer;
2364
+ let contentType;
2365
+ if (imageUrl.startsWith("http")) {
2366
+ const response = await fetch(imageUrl);
2367
+ if (!response.ok) throw new Error("Failed to fetch image");
2368
+ imageBuffer = Buffer.from(await response.arrayBuffer());
2369
+ contentType = response.headers.get("content-type") || "image/jpeg";
2370
+ } else {
2371
+ const imagePath = path5.join(process.cwd(), "public", imageUrl);
2372
+ if (!fs6.existsSync(imagePath)) {
2373
+ res.writeHead(404, { "Content-Type": "text/plain" });
2374
+ res.end("Image not found");
2375
+ return;
2376
+ }
2377
+ imageBuffer = fs6.readFileSync(imagePath);
2378
+ contentType = getContentType(imagePath);
2379
+ }
2380
+ const cacheKey = crypto.createHash("md5").update(`${imageUrl}-${width}-${quality}-${format}`).digest("hex");
2381
+ const cacheDir = path5.join(process.cwd(), fullConfig.cacheDir);
2382
+ const cachePath = path5.join(cacheDir, `${cacheKey}.${format || "webp"}`);
2383
+ if (fs6.existsSync(cachePath)) {
2384
+ const cachedImage = fs6.readFileSync(cachePath);
2385
+ res.writeHead(200, {
2386
+ "Content-Type": `image/${format || "webp"}`,
2387
+ "Cache-Control": `public, max-age=${fullConfig.minimumCacheTTL}`,
2388
+ "X-Flexi-Image-Cache": "HIT"
2389
+ });
2390
+ res.end(cachedImage);
2391
+ return;
2392
+ }
2393
+ res.writeHead(200, {
2394
+ "Content-Type": contentType,
2395
+ "Cache-Control": `public, max-age=${fullConfig.minimumCacheTTL}`,
2396
+ "X-Flexi-Image-Cache": "MISS"
2397
+ });
2398
+ res.end(imageBuffer);
2399
+ } catch (error) {
2400
+ console.error("Image optimization error:", error);
2401
+ res.writeHead(500, { "Content-Type": "text/plain" });
2402
+ res.end("Image optimization failed");
2403
+ }
2404
+ }
2405
+ function getContentType(filePath) {
2406
+ const ext = path5.extname(filePath).toLowerCase();
2407
+ const types = {
2408
+ ".jpg": "image/jpeg",
2409
+ ".jpeg": "image/jpeg",
2410
+ ".png": "image/png",
2411
+ ".gif": "image/gif",
2412
+ ".webp": "image/webp",
2413
+ ".avif": "image/avif",
2414
+ ".svg": "image/svg+xml",
2415
+ ".ico": "image/x-icon"
2416
+ };
2417
+ return types[ext] || "application/octet-stream";
2418
+ }
2419
+ function createImageComponent(config = {}) {
2420
+ const fullConfig = { ...defaultImageConfig, ...config };
2421
+ return function Image2(props) {
2422
+ const {
2423
+ src,
2424
+ alt,
2425
+ width,
2426
+ height,
2427
+ fill = false,
2428
+ sizes,
2429
+ quality = fullConfig.quality,
2430
+ priority = false,
2431
+ placeholder = "empty",
2432
+ blurDataURL,
2433
+ loading,
2434
+ className = "",
2435
+ style = {},
2436
+ unoptimized = false,
2437
+ ...rest
2438
+ } = props;
2439
+ const loadingAttr = priority ? "eager" : loading || "lazy";
2440
+ const optimizedSrc = unoptimized ? src : `/_flexi/image?url=${encodeURIComponent(src)}&w=${width || 1920}&q=${quality}`;
2441
+ const allSizes = [...fullConfig.imageSizes, ...fullConfig.deviceSizes].sort((a, b) => a - b);
2442
+ const relevantSizes = width ? allSizes.filter((s) => s <= width * 2) : allSizes;
2443
+ const srcSet = unoptimized ? void 0 : generateSrcSet(src, relevantSizes, quality);
2444
+ const imgStyle = {
2445
+ ...style,
2446
+ ...fill ? {
2447
+ position: "absolute",
2448
+ top: 0,
2449
+ left: 0,
2450
+ width: "100%",
2451
+ height: "100%",
2452
+ objectFit: "cover"
2453
+ } : {}
2454
+ };
2455
+ const wrapperStyle = fill ? {
2456
+ position: "relative",
2457
+ width: "100%",
2458
+ height: "100%"
2459
+ } : {};
2460
+ const placeholderStyle = placeholder === "blur" ? {
2461
+ backgroundImage: `url(${blurDataURL || generateBlurPlaceholderSync()})`,
2462
+ backgroundSize: "cover",
2463
+ backgroundPosition: "center",
2464
+ filter: "blur(20px)",
2465
+ transform: "scale(1.1)"
2466
+ } : {};
2467
+ const imgElement = React4.createElement("img", {
2468
+ src: optimizedSrc,
2469
+ alt,
2470
+ width: fill ? void 0 : width,
2471
+ height: fill ? void 0 : height,
2472
+ loading: loadingAttr,
2473
+ decoding: "async",
2474
+ srcSet,
2475
+ sizes: generateSizes(sizes),
2476
+ className: `flexi-image ${className}`.trim(),
2477
+ style: imgStyle,
2478
+ fetchPriority: priority ? "high" : void 0,
2479
+ ...rest
2480
+ });
2481
+ if (fill || placeholder === "blur") {
2482
+ return React4.createElement("div", {
2483
+ className: "flexi-image-wrapper",
2484
+ style: { ...wrapperStyle, ...placeholderStyle }
2485
+ }, imgElement);
2486
+ }
2487
+ return imgElement;
2488
+ };
2489
+ }
2490
+ function generateBlurPlaceholderSync() {
2491
+ return `data:image/svg+xml;base64,${Buffer.from(
2492
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 5">
2493
+ <filter id="b" color-interpolation-filters="sRGB">
2494
+ <feGaussianBlur stdDeviation="1"/>
2495
+ </filter>
2496
+ <rect width="100%" height="100%" fill="#1a1a1a"/>
2497
+ </svg>`
2498
+ ).toString("base64")}`;
2499
+ }
2500
+ var Image = createImageComponent();
2501
+
2502
+ // core/font/index.ts
2503
+ import fs7 from "fs";
2504
+ import path6 from "path";
2505
+ import crypto2 from "crypto";
2506
+ var GOOGLE_FONTS_API = "https://fonts.googleapis.com/css2";
2507
+ async function handleFontRequest(req, res) {
2508
+ const url = new URL(req.url, `http://${req.headers.host}`);
2509
+ const pathParts = url.pathname.split("/");
2510
+ const fontFamily = decodeURIComponent(pathParts[pathParts.length - 1] || "");
2511
+ const weight = url.searchParams.get("weight") || "400";
2512
+ const style = url.searchParams.get("style") || "normal";
2513
+ if (!fontFamily) {
2514
+ res.writeHead(400, { "Content-Type": "text/plain" });
2515
+ res.end("Missing font family");
2516
+ return;
2517
+ }
2518
+ try {
2519
+ const cacheDir = path6.join(process.cwd(), ".flexi", "font-cache");
2520
+ const cacheKey = crypto2.createHash("md5").update(`${fontFamily}-${weight}-${style}`).digest("hex");
2521
+ const cachePath = path6.join(cacheDir, `${cacheKey}.woff2`);
2522
+ if (fs7.existsSync(cachePath)) {
2523
+ const fontData = fs7.readFileSync(cachePath);
2524
+ res.writeHead(200, {
2525
+ "Content-Type": "font/woff2",
2526
+ "Cache-Control": "public, max-age=31536000, immutable",
2527
+ "X-Flexi-Font-Cache": "HIT"
2528
+ });
2529
+ res.end(fontData);
2530
+ return;
2531
+ }
2532
+ const googleUrl = `${GOOGLE_FONTS_API}?family=${encodeURIComponent(fontFamily)}:wght@${weight}&display=swap`;
2533
+ const cssResponse = await fetch(googleUrl, {
2534
+ headers: {
2535
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
2536
+ }
2537
+ });
2538
+ if (!cssResponse.ok) {
2539
+ throw new Error("Failed to fetch font CSS");
2540
+ }
2541
+ const css = await cssResponse.text();
2542
+ const woff2Match = css.match(/url\((https:\/\/fonts\.gstatic\.com[^)]+\.woff2)\)/);
2543
+ if (!woff2Match) {
2544
+ throw new Error("Could not find woff2 URL");
2545
+ }
2546
+ const fontResponse = await fetch(woff2Match[1]);
2547
+ if (!fontResponse.ok) {
2548
+ throw new Error("Failed to fetch font file");
2549
+ }
2550
+ const fontBuffer = Buffer.from(await fontResponse.arrayBuffer());
2551
+ if (!fs7.existsSync(cacheDir)) {
2552
+ fs7.mkdirSync(cacheDir, { recursive: true });
2553
+ }
2554
+ fs7.writeFileSync(cachePath, fontBuffer);
2555
+ res.writeHead(200, {
2556
+ "Content-Type": "font/woff2",
2557
+ "Cache-Control": "public, max-age=31536000, immutable",
2558
+ "X-Flexi-Font-Cache": "MISS"
2559
+ });
2560
+ res.end(fontBuffer);
2561
+ } catch (error) {
2562
+ console.error("Font loading error:", error);
2563
+ res.writeHead(500, { "Content-Type": "text/plain" });
2564
+ res.end("Font loading failed");
2565
+ }
2566
+ }
2567
+
2568
+ // core/server/index.ts
2569
+ var __filename2 = fileURLToPath(import.meta.url);
2570
+ var __dirname2 = path7.dirname(__filename2);
2571
+ var MIME_TYPES = {
2572
+ ".html": "text/html",
2573
+ ".css": "text/css",
2574
+ ".js": "application/javascript",
2575
+ ".mjs": "application/javascript",
2576
+ ".json": "application/json",
2577
+ ".png": "image/png",
2578
+ ".jpg": "image/jpeg",
2579
+ ".jpeg": "image/jpeg",
2580
+ ".gif": "image/gif",
2581
+ ".svg": "image/svg+xml",
2582
+ ".ico": "image/x-icon",
2583
+ ".woff": "font/woff",
2584
+ ".woff2": "font/woff2",
2585
+ ".ttf": "font/ttf",
2586
+ ".webp": "image/webp",
2587
+ ".mp4": "video/mp4",
2588
+ ".webm": "video/webm"
2589
+ };
2590
+ async function createServer(options = {}) {
2591
+ const serverStartTime = Date.now();
2592
+ const projectRoot = options.projectRoot || process.cwd();
2593
+ const isDev = options.mode === "development";
2594
+ logger.logo();
2595
+ const rawConfig = await loadConfig(projectRoot);
2596
+ const config = resolvePaths(rawConfig, projectRoot);
2597
+ await loadPlugins(projectRoot, config);
2598
+ await pluginManager.runHook(PluginHooks.CONFIG, config);
2599
+ const middleware = await loadMiddleware(projectRoot);
2600
+ let routes = buildRouteTree(config.pagesDir, config.layoutsDir);
2601
+ await pluginManager.runHook(PluginHooks.ROUTES_LOADED, routes);
2602
+ const loadModule = createModuleLoader(isDev);
2603
+ const server = http.createServer(async (req, res) => {
2604
+ const startTime = Date.now();
2605
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
2606
+ const pathname = url.pathname;
2607
+ try {
2608
+ await pluginManager.runHook(PluginHooks.REQUEST, req, res);
2609
+ const middlewareResult = await runMiddleware(req, res, middleware);
2610
+ if (!middlewareResult.continue) {
2611
+ return;
2612
+ }
2613
+ const effectivePath = middlewareResult.rewritten ? new URL(req.url, `http://${req.headers.host}`).pathname : pathname;
2614
+ if (await serveStaticFile(res, config.publicDir, effectivePath)) {
2615
+ return;
2616
+ }
2617
+ if (!isDev && effectivePath.startsWith("/_flexi/")) {
2618
+ const assetPath = path7.join(config.outDir, "client", effectivePath.slice(8));
2619
+ if (await serveStaticFile(res, path7.dirname(assetPath), path7.basename(assetPath))) {
2620
+ return;
2621
+ }
2622
+ }
2623
+ if (effectivePath.startsWith("/_flexi/component/")) {
2624
+ const componentName = effectivePath.slice(18).replace(".js", "");
2625
+ return await serveClientComponent(res, config.pagesDir, componentName);
2626
+ }
2627
+ if (effectivePath === "/_flexi/action" && req.method === "POST") {
2628
+ return await handleServerAction(req, res);
2629
+ }
2630
+ if (effectivePath.startsWith("/_flexi/image")) {
2631
+ return await handleImageOptimization(req, res, config.images || {});
2632
+ }
2633
+ if (effectivePath.startsWith("/_flexi/font")) {
2634
+ return await handleFontRequest(req, res);
2635
+ }
2636
+ if (isDev) {
2637
+ routes = buildRouteTree(config.pagesDir, config.layoutsDir);
2638
+ }
2639
+ const apiRoute = matchRoute(effectivePath, routes.api);
2640
+ if (apiRoute) {
2641
+ return await handleApiRoute(req, res, apiRoute, loadModule);
2642
+ }
2643
+ const flexiRoute = matchRoute(effectivePath, routes.flexiRoutes || []);
2644
+ if (flexiRoute) {
2645
+ return await handlePageRoute(req, res, flexiRoute, routes, config, loadModule, url);
2646
+ }
2647
+ const appRoute = matchRoute(effectivePath, routes.appRoutes || []);
2648
+ if (appRoute) {
2649
+ return await handlePageRoute(req, res, appRoute, routes, config, loadModule, url);
2650
+ }
2651
+ const pageRoute = matchRoute(effectivePath, routes.pages);
2652
+ if (pageRoute) {
2653
+ return await handlePageRoute(req, res, pageRoute, routes, config, loadModule, url);
2654
+ }
2655
+ res.writeHead(404, { "Content-Type": "text/html" });
2656
+ res.end(renderError(404, "Page not found"));
2657
+ } catch (error) {
2658
+ if (error instanceof RedirectError) {
2659
+ res.writeHead(error.statusCode, { "Location": error.url });
2660
+ res.end();
2661
+ return;
2662
+ }
2663
+ if (error instanceof NotFoundError) {
2664
+ res.writeHead(404, { "Content-Type": "text/html" });
2665
+ res.end(renderError(404, error.message));
2666
+ return;
2667
+ }
2668
+ console.error("Server Error:", error);
2669
+ if (!res.headersSent) {
2670
+ res.writeHead(500, { "Content-Type": "text/html" });
2671
+ res.end(renderError(500, error.message, isDev ? error.stack : null));
2672
+ }
2673
+ } finally {
2674
+ const duration = Date.now() - startTime;
2675
+ if (isDev) {
2676
+ 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 });
2678
+ }
2679
+ await pluginManager.runHook(PluginHooks.RESPONSE, req, res, duration);
2680
+ }
2681
+ });
2682
+ const port = process.env.PORT || options.port || config.server.port;
2683
+ const host = options.host || config.server.host;
2684
+ return new Promise((resolve, reject) => {
2685
+ server.on("error", (err) => {
2686
+ if (err.code === "EADDRINUSE") {
2687
+ logger.portInUse(port);
2688
+ process.exit(1);
2689
+ } else {
2690
+ logger.error("Server error", err);
2691
+ reject(err);
2692
+ }
2693
+ });
2694
+ server.listen(port, host, async () => {
2695
+ logger.serverStart({
2696
+ port,
2697
+ host,
2698
+ mode: isDev ? "development" : "production",
2699
+ pagesDir: config.pagesDir,
2700
+ islands: config.islands?.enabled,
2701
+ rsc: config.rsc?.enabled
2702
+ }, serverStartTime);
2703
+ await pluginManager.runHook(PluginHooks.SERVER_START, server);
2704
+ resolve(server);
2705
+ });
2706
+ });
2707
+ }
2708
+ function createModuleLoader(isDev) {
2709
+ return async (filePath) => {
2710
+ const url = pathToFileURL4(filePath).href;
2711
+ const cacheBuster = isDev ? `?t=${Date.now()}` : "";
2712
+ return import(`${url}${cacheBuster}`);
2713
+ };
2714
+ }
2715
+ async function serveStaticFile(res, baseDir, pathname) {
2716
+ const safePath = path7.normalize(pathname).replace(/^(\.\.[\/\\])+/, "");
2717
+ const filePath = path7.join(baseDir, safePath);
2718
+ if (!filePath.startsWith(baseDir) || !fs8.existsSync(filePath)) {
2719
+ return false;
2720
+ }
2721
+ const stat = fs8.statSync(filePath);
2722
+ if (!stat.isFile()) {
2723
+ return false;
2724
+ }
2725
+ const ext = path7.extname(filePath).toLowerCase();
2726
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
2727
+ res.writeHead(200, {
2728
+ "Content-Type": contentType,
2729
+ "Content-Length": stat.size,
2730
+ "Cache-Control": "public, max-age=31536000"
2731
+ });
2732
+ fs8.createReadStream(filePath).pipe(res);
2733
+ return true;
2734
+ }
2735
+ async function handleApiRoute(req, res, route, loadModule) {
2736
+ try {
2737
+ const module = await loadModule(route.filePath);
2738
+ const method = req.method.toLowerCase();
2739
+ const body = await parseBody(req);
2740
+ const url = new URL(req.url, `http://${req.headers.host}`);
2741
+ const query = Object.fromEntries(url.searchParams);
2742
+ const enhancedReq = {
2743
+ ...req,
2744
+ body,
2745
+ query,
2746
+ params: route.params,
2747
+ method: req.method
2748
+ };
2749
+ const enhancedRes = createApiResponse(res);
2750
+ const handler = module[method] || module[method.toUpperCase()] || module.default;
2751
+ if (!handler) {
2752
+ enhancedRes.status(405).json({ error: "Method not allowed" });
2753
+ return;
2754
+ }
2755
+ await handler(enhancedReq, enhancedRes);
2756
+ } catch (error) {
2757
+ console.error("API Error:", error);
2758
+ if (!res.headersSent) {
2759
+ res.writeHead(500, { "Content-Type": "application/json" });
2760
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
2761
+ }
2762
+ }
2763
+ }
2764
+ async function handleServerAction(req, res) {
2765
+ try {
2766
+ const body = await parseBody(req);
2767
+ const { actionId, args } = body;
2768
+ if (!actionId) {
2769
+ res.writeHead(400, { "Content-Type": "application/json" });
2770
+ res.end(JSON.stringify({ success: false, error: "Missing actionId" }));
2771
+ return;
2772
+ }
2773
+ const deserializedArgs = deserializeArgs(args || []);
2774
+ const result = await executeAction(actionId, deserializedArgs, {
2775
+ request: new Request(`http://${req.headers.host}${req.url}`, {
2776
+ method: req.method,
2777
+ headers: req.headers
2778
+ })
2779
+ });
2780
+ res.writeHead(200, {
2781
+ "Content-Type": "application/json",
2782
+ "X-Flexi-Action": actionId
2783
+ });
2784
+ res.end(JSON.stringify(result));
2785
+ } catch (error) {
2786
+ console.error("Server Action Error:", error);
2787
+ res.writeHead(500, { "Content-Type": "application/json" });
2788
+ res.end(JSON.stringify({
2789
+ success: false,
2790
+ error: error.message || "Action execution failed"
2791
+ }));
2792
+ }
2793
+ }
2794
+ function createApiResponse(res) {
2795
+ return {
2796
+ _res: res,
2797
+ _status: 200,
2798
+ _headers: {},
2799
+ status(code) {
2800
+ this._status = code;
2801
+ return this;
2802
+ },
2803
+ setHeader(name, value) {
2804
+ this._headers[name] = value;
2805
+ return this;
2806
+ },
2807
+ json(data) {
2808
+ this._headers["Content-Type"] = "application/json";
2809
+ this._send(JSON.stringify(data));
2810
+ },
2811
+ send(data) {
2812
+ if (typeof data === "object") {
2813
+ this.json(data);
2814
+ } else {
2815
+ this._headers["Content-Type"] = this._headers["Content-Type"] || "text/plain";
2816
+ this._send(String(data));
2817
+ }
2818
+ },
2819
+ html(data) {
2820
+ this._headers["Content-Type"] = "text/html";
2821
+ this._send(data);
2822
+ },
2823
+ redirect(url, status = 302) {
2824
+ this._status = status;
2825
+ this._headers["Location"] = url;
2826
+ this._send("");
2827
+ },
2828
+ _send(body) {
2829
+ if (!this._res.headersSent) {
2830
+ this._res.writeHead(this._status, this._headers);
2831
+ this._res.end(body);
2832
+ }
2833
+ }
2834
+ };
2835
+ }
2836
+ async function handlePageRoute(req, res, route, routes, config, loadModule, url) {
2837
+ try {
2838
+ if (route.middleware) {
2839
+ try {
2840
+ const middlewareModule = await loadModule(route.middleware);
2841
+ const middlewareFn = middlewareModule.default || middlewareModule.middleware;
2842
+ if (typeof middlewareFn === "function") {
2843
+ const result = await middlewareFn(req, res, { route, params: route.params });
2844
+ if (result?.redirect) {
2845
+ res.writeHead(result.statusCode || 307, { "Location": result.redirect });
2846
+ res.end();
2847
+ return;
2848
+ }
2849
+ if (result?.rewrite) {
2850
+ req.url = result.rewrite;
2851
+ }
2852
+ if (result === false || result?.stop) {
2853
+ return;
2854
+ }
2855
+ }
2856
+ } catch (middlewareError) {
2857
+ console.error("Route middleware error:", middlewareError.message);
2858
+ }
2859
+ }
2860
+ const pageModule = await loadModule(route.filePath);
2861
+ const Component = pageModule.default;
2862
+ if (!Component) {
2863
+ throw new Error(`No default export in ${route.filePath}`);
2864
+ }
2865
+ const query = Object.fromEntries(url.searchParams);
2866
+ const context = createRequestContext(req, res, route.params, query);
2867
+ let props = { params: route.params, query };
2868
+ if (pageModule.getServerSideProps) {
2869
+ const result = await pageModule.getServerSideProps({
2870
+ params: route.params,
2871
+ query,
2872
+ req,
2873
+ res
2874
+ });
2875
+ if (result.redirect) {
2876
+ res.writeHead(result.redirect.statusCode || 302, {
2877
+ Location: result.redirect.destination
2878
+ });
2879
+ res.end();
2880
+ return;
2881
+ }
2882
+ if (result.notFound) {
2883
+ res.writeHead(404, { "Content-Type": "text/html" });
2884
+ res.end(renderError(404, "Page not found"));
2885
+ return;
2886
+ }
2887
+ props = { ...props, ...result.props };
2888
+ }
2889
+ if (pageModule.getStaticProps) {
2890
+ const result = await pageModule.getStaticProps({ params: route.params });
2891
+ if (result.notFound) {
2892
+ res.writeHead(404, { "Content-Type": "text/html" });
2893
+ res.end(renderError(404, "Page not found"));
2894
+ return;
2895
+ }
2896
+ props = { ...props, ...result.props };
2897
+ }
2898
+ const layouts = [];
2899
+ try {
2900
+ const layoutConfigs = findRouteLayouts(route, routes.layouts);
2901
+ for (const layoutConfig of layoutConfigs) {
2902
+ if (layoutConfig.filePath) {
2903
+ const layoutModule = await loadModule(layoutConfig.filePath);
2904
+ if (layoutModule.default) {
2905
+ layouts.push({
2906
+ Component: layoutModule.default,
2907
+ props: {}
2908
+ });
2909
+ }
2910
+ }
2911
+ }
2912
+ } catch (layoutError) {
2913
+ console.warn("Layout loading skipped:", layoutError.message);
2914
+ }
2915
+ let LoadingComponent = null;
2916
+ if (route.loading) {
2917
+ const loadingModule = await loadModule(route.loading);
2918
+ LoadingComponent = loadingModule.default;
2919
+ }
2920
+ let ErrorComponent = null;
2921
+ if (route.error) {
2922
+ const errorModule = await loadModule(route.error);
2923
+ ErrorComponent = errorModule.default;
2924
+ }
2925
+ props = await pluginManager.runWaterfallHook(
2926
+ PluginHooks.BEFORE_RENDER,
2927
+ props,
2928
+ { route, Component }
2929
+ );
2930
+ const isClientComponent2 = route.isClientComponent || pageModule.__isClient || typeof pageModule.default === "function" && pageModule.default.toString().includes("useState");
2931
+ let html = await renderPage({
2932
+ Component,
2933
+ props,
2934
+ layouts,
2935
+ loading: LoadingComponent,
2936
+ error: ErrorComponent,
2937
+ islands: getRegisteredIslands(),
2938
+ title: pageModule.title || pageModule.metadata?.title || "FlexiReact App",
2939
+ meta: pageModule.metadata || {},
2940
+ styles: config.styles || [],
2941
+ scripts: config.scripts || [],
2942
+ favicon: config.favicon || null,
2943
+ needsHydration: isClientComponent2,
2944
+ componentPath: route.filePath,
2945
+ route: route.path || url.pathname,
2946
+ isSSG: !!pageModule.getStaticProps
2947
+ });
2948
+ const islands = getRegisteredIslands();
2949
+ if (islands.length > 0 && config.islands.enabled) {
2950
+ const hydrationScript = generateAdvancedHydrationScript(islands);
2951
+ html = html.replace("</body>", `${hydrationScript}</body>`);
2952
+ }
2953
+ if (isClientComponent2) {
2954
+ const hydrationScript = generateClientHydrationScript(route.filePath, props);
2955
+ html = html.replace("</body>", `${hydrationScript}</body>`);
2956
+ }
2957
+ html = await pluginManager.runWaterfallHook(
2958
+ PluginHooks.AFTER_RENDER,
2959
+ html,
2960
+ { route, Component, props }
2961
+ );
2962
+ res.writeHead(200, { "Content-Type": "text/html" });
2963
+ res.end(html);
2964
+ } catch (error) {
2965
+ console.error("Page Render Error:", error);
2966
+ throw error;
2967
+ }
2968
+ }
2969
+ async function serveClientComponent(res, pagesDir, componentName) {
2970
+ const { transformSync } = await import("esbuild");
2971
+ const cleanName = componentName.replace(/\.(tsx|jsx|ts|js)\.js$/, "").replace(/\.js$/, "");
2972
+ const possiblePaths = [
2973
+ path7.join(pagesDir, `${cleanName}.tsx`),
2974
+ path7.join(pagesDir, `${cleanName}.ts`),
2975
+ path7.join(pagesDir, `${cleanName}.jsx`),
2976
+ path7.join(pagesDir, `${cleanName}.js`)
2977
+ ];
2978
+ let componentPath = null;
2979
+ for (const p of possiblePaths) {
2980
+ if (fs8.existsSync(p)) {
2981
+ componentPath = p;
2982
+ break;
2983
+ }
2984
+ }
2985
+ if (!componentPath) {
2986
+ res.writeHead(404, { "Content-Type": "text/plain" });
2987
+ res.end(`Component not found: ${cleanName}`);
2988
+ return;
2989
+ }
2990
+ const ext = path7.extname(componentPath);
2991
+ const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : "jsx";
2992
+ try {
2993
+ let source = fs8.readFileSync(componentPath, "utf-8");
2994
+ source = source.replace(/^['"]use (client|server|island)['"];?\s*/m, "");
2995
+ const result = transformSync(source, {
2996
+ loader,
2997
+ format: "esm",
2998
+ jsx: "transform",
2999
+ jsxFactory: "React.createElement",
3000
+ jsxFragment: "React.Fragment",
3001
+ target: "es2020",
3002
+ // Replace React imports with global
3003
+ banner: `
3004
+ const React = window.React;
3005
+ const useState = window.useState;
3006
+ const useEffect = window.useEffect;
3007
+ const useCallback = window.useCallback;
3008
+ const useMemo = window.useMemo;
3009
+ const useRef = window.useRef;
3010
+ `
3011
+ });
3012
+ let code = result.code;
3013
+ code = code.replace(/import\s+React\s+from\s+['"]react['"];?\s*/g, "");
3014
+ code = code.replace(/import\s+\{[^}]+\}\s+from\s+['"]react['"];?\s*/g, "");
3015
+ code = code.replace(/import\s+React\s*,\s*\{[^}]+\}\s+from\s+['"]react['"];?\s*/g, "");
3016
+ res.writeHead(200, {
3017
+ "Content-Type": "application/javascript",
3018
+ "Cache-Control": "no-cache"
3019
+ });
3020
+ res.end(code);
3021
+ } catch (error) {
3022
+ console.error("Error serving client component:", error);
3023
+ res.writeHead(500, { "Content-Type": "text/plain" });
3024
+ res.end("Error compiling component");
3025
+ }
3026
+ }
3027
+ function generateClientHydrationScript(componentPath, props) {
3028
+ const ext = path7.extname(componentPath);
3029
+ const componentName = path7.basename(componentPath, ext);
3030
+ return `
3031
+ <script type="module">
3032
+ // FlexiReact Client Hydration
3033
+ (async function() {
3034
+ 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');
3037
+
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
+ const code = await response.text();
3049
+
3050
+ // Create and import the module
3051
+ const blob = new Blob([code], { type: 'application/javascript' });
3052
+ const moduleUrl = URL.createObjectURL(blob);
3053
+ const module = await import(moduleUrl);
3054
+
3055
+ const Component = module.default;
3056
+ const props = ${JSON.stringify(props)};
3057
+
3058
+ // Hydrate the root
3059
+ const root = document.getElementById('root');
3060
+ ReactDOM.hydrateRoot(root, window.React.createElement(Component, props));
3061
+
3062
+ console.log('\u26A1 FlexiReact: Component hydrated successfully');
3063
+ } catch (error) {
3064
+ console.error('\u26A1 FlexiReact: Hydration failed', error);
3065
+ }
3066
+ })();
3067
+ </script>`;
3068
+ }
3069
+ async function parseBody(req) {
3070
+ return new Promise((resolve) => {
3071
+ const contentType = req.headers["content-type"] || "";
3072
+ let body = "";
3073
+ req.on("data", (chunk) => {
3074
+ body += chunk.toString();
3075
+ });
3076
+ req.on("end", () => {
3077
+ try {
3078
+ if (contentType.includes("application/json") && body) {
3079
+ resolve(JSON.parse(body));
3080
+ } else if (contentType.includes("application/x-www-form-urlencoded") && body) {
3081
+ resolve(Object.fromEntries(new URLSearchParams(body)));
3082
+ } else {
3083
+ resolve(body || null);
3084
+ }
3085
+ } catch {
3086
+ resolve(body);
3087
+ }
3088
+ });
3089
+ req.on("error", () => resolve(null));
3090
+ });
3091
+ }
3092
+
3093
+ // core/start-dev.ts
3094
+ createServer({ mode: "development" });
3095
+ //# sourceMappingURL=start-dev.js.map