olovaplugin 1.0.5 → 1.0.7

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,1540 @@
1
+ // router/config.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var colors = {
5
+ reset: "\x1B[0m",
6
+ bold: "\x1B[1m",
7
+ dim: "\x1B[2m",
8
+ cyan: "\x1B[36m",
9
+ green: "\x1B[32m",
10
+ white: "\x1B[37m"
11
+ };
12
+ function configPlugin() {
13
+ const olovaDir = path.resolve(".olova");
14
+ return {
15
+ name: "olova-config",
16
+ config(_config, { command }) {
17
+ if (!fs.existsSync(olovaDir)) {
18
+ fs.mkdirSync(olovaDir, { recursive: true });
19
+ }
20
+ if (process.env.IS_SSG_BUILD) {
21
+ return {
22
+ appType: "custom",
23
+ cacheDir: "./.olova/cache"
24
+ };
25
+ }
26
+ if (command === "serve") {
27
+ console.log("");
28
+ console.log(` ${colors.bold}${colors.cyan}\u25B2 Olova${colors.reset} ${colors.dim}v1.0.0${colors.reset}`);
29
+ console.log("");
30
+ }
31
+ return {
32
+ appType: "custom",
33
+ cacheDir: "./.olova/cache",
34
+ build: {
35
+ outDir: "./.olova/dist",
36
+ sourcemap: false,
37
+ chunkSizeWarningLimit: 500,
38
+ rollupOptions: {
39
+ onwarn(warning, warn) {
40
+ if (warning.code === "PLUGIN_WARNING" && warning.message?.includes("dynamic import will not move")) {
41
+ return;
42
+ }
43
+ if (warning.message?.includes("Module level directives") && warning.message?.includes("static")) {
44
+ return;
45
+ }
46
+ if (warning.message?.includes("sourcemap") && warning.message?.includes("Can't resolve original location")) {
47
+ return;
48
+ }
49
+ warn(warning);
50
+ },
51
+ output: {
52
+ chunkFileNames: "pro_olova_static/chunks/[name]-[hash].js",
53
+ entryFileNames: "pro_olova_static/olova-[hash].js",
54
+ assetFileNames: "pro_olova_static/[name]-[hash][extname]",
55
+ manualChunks(id) {
56
+ if (id.includes("node_modules")) {
57
+ if (id.includes("react-dom")) {
58
+ return "vendor-react-dom";
59
+ }
60
+ if (id.includes("react")) {
61
+ return "vendor-react";
62
+ }
63
+ return "vendor";
64
+ }
65
+ }
66
+ }
67
+ }
68
+ },
69
+ preview: {
70
+ port: 4173,
71
+ strictPort: false
72
+ }
73
+ };
74
+ },
75
+ configureServer(server) {
76
+ server.httpServer?.once("listening", () => {
77
+ const address = server.httpServer?.address();
78
+ const port = typeof address === "object" && address ? address.port : 5173;
79
+ setTimeout(() => {
80
+ console.log(` ${colors.green}\u2713${colors.reset} Ready in ${colors.dim}${Math.round(performance.now())}ms${colors.reset}`);
81
+ console.log("");
82
+ console.log(` ${colors.dim}\u279C${colors.reset} ${colors.bold}Local:${colors.reset} ${colors.cyan}http://localhost:${port}/${colors.reset}`);
83
+ console.log("");
84
+ }, 100);
85
+ });
86
+ }
87
+ };
88
+ }
89
+
90
+ // router/router.ts
91
+ import fs2 from "fs";
92
+ import path2 from "path";
93
+ function routerPlugin() {
94
+ const virtualModuleId = "olova/routes";
95
+ const resolvedVirtualModuleId = "\0" + virtualModuleId;
96
+ let server = null;
97
+ const invalidateRoutes = () => {
98
+ if (server) {
99
+ const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
100
+ if (mod) {
101
+ server.moduleGraph.invalidateModule(mod);
102
+ server.ws.send({
103
+ type: "full-reload",
104
+ path: "*"
105
+ });
106
+ }
107
+ }
108
+ };
109
+ return {
110
+ name: "olova-router",
111
+ // Configure dev server to watch for file changes
112
+ configureServer(devServer) {
113
+ server = devServer;
114
+ const srcDir = path2.resolve(__dirname, "..", "src");
115
+ const watcher = devServer.watcher;
116
+ watcher.on("add", (filePath) => {
117
+ if (filePath.startsWith(srcDir) && /\.(tsx|jsx|html|md)$/.test(filePath)) {
118
+ console.log("\x1B[36m[olova]\x1B[0m New route detected:", path2.relative(srcDir, filePath));
119
+ invalidateRoutes();
120
+ }
121
+ });
122
+ watcher.on("unlink", (filePath) => {
123
+ if (filePath.startsWith(srcDir) && /\.(tsx|jsx|html|md)$/.test(filePath)) {
124
+ console.log("\x1B[36m[olova]\x1B[0m Route removed:", path2.relative(srcDir, filePath));
125
+ invalidateRoutes();
126
+ }
127
+ });
128
+ watcher.on("addDir", (dirPath) => {
129
+ if (dirPath.startsWith(srcDir) && dirPath !== srcDir) {
130
+ console.log("\x1B[36m[olova]\x1B[0m New route folder detected:", path2.relative(srcDir, dirPath));
131
+ }
132
+ });
133
+ watcher.on("unlinkDir", (dirPath) => {
134
+ if (dirPath.startsWith(srcDir) && dirPath !== srcDir) {
135
+ console.log("\x1B[36m[olova]\x1B[0m Route folder removed:", path2.relative(srcDir, dirPath));
136
+ invalidateRoutes();
137
+ }
138
+ });
139
+ },
140
+ resolveId(id) {
141
+ if (id === virtualModuleId) {
142
+ return resolvedVirtualModuleId;
143
+ }
144
+ },
145
+ // Mark the virtual module as having side effects for proper HMR
146
+ handleHotUpdate({ file, server: devServer }) {
147
+ const srcDir = path2.resolve(__dirname, "..", "src");
148
+ if (file.startsWith(srcDir) && /\.(tsx|jsx)$/.test(file)) {
149
+ const mod = devServer.moduleGraph.getModuleById(resolvedVirtualModuleId);
150
+ if (mod) {
151
+ devServer.moduleGraph.invalidateModule(mod);
152
+ }
153
+ }
154
+ },
155
+ load(id) {
156
+ if (id === resolvedVirtualModuleId) {
157
+ const srcDir = path2.resolve(__dirname, "..", "src");
158
+ const getRoutes = (dir, baseRoute = "", baseImportPath = "") => {
159
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
160
+ let routes = [];
161
+ for (const entry of entries) {
162
+ const entryName = entry.name;
163
+ const fullPath = path2.join(dir, entryName);
164
+ if (entry.isDirectory()) {
165
+ const isRouteGroup = /^\(.+\)$/.test(entryName);
166
+ if (isRouteGroup) {
167
+ routes = routes.concat(getRoutes(
168
+ fullPath,
169
+ baseRoute,
170
+ // URL path stays the same (group is ignored)
171
+ `${baseImportPath}/${entryName}`
172
+ // Import path includes the folder
173
+ ));
174
+ } else {
175
+ routes = routes.concat(getRoutes(
176
+ fullPath,
177
+ `${baseRoute}/${entryName}`,
178
+ `${baseImportPath}/${entryName}`
179
+ ));
180
+ }
181
+ } else {
182
+ const ext = path2.extname(entryName);
183
+ const supportedExts = [".tsx", ".jsx", ".html", ".md"];
184
+ if (supportedExts.includes(ext)) {
185
+ const nameNoExt = entryName.replace(/\.(tsx|jsx|html|md)$/, "");
186
+ let routePath = baseRoute;
187
+ if (nameNoExt !== "index" && nameNoExt !== "App") {
188
+ routePath = `${baseRoute}/${nameNoExt}`;
189
+ }
190
+ if (routePath === "") routePath = "/";
191
+ const normalizedRoutePath = routePath.replace(/\$(.+?)(?=\/|$)/g, ":$1");
192
+ const importPath = `/src${baseImportPath}/${entryName}`;
193
+ if (ext === ".html") {
194
+ routes.push(` "${normalizedRoutePath}": () => import("${importPath}?raw").then(m => ({ default: () => { const div = document.createElement('div'); div.innerHTML = m.default; return div.innerHTML; }, __isHtml: true, __isStatic: true, __rawHtml: m.default })),`);
195
+ } else if (ext === ".md") {
196
+ routes.push(` "${normalizedRoutePath}": () => import("${importPath}?raw").then(m => ({ default: m.default, __isMd: true, __isStatic: true })),`);
197
+ } else {
198
+ const fileContent = fs2.readFileSync(fullPath, "utf-8");
199
+ const firstLine = fileContent.trim().split("\n")[0].trim();
200
+ const hasStaticDirective = firstLine === '"static"' || firstLine === "'static'";
201
+ if (hasStaticDirective) {
202
+ routes.push(` "${normalizedRoutePath}": () => import("${importPath}").then(m => Object.assign({}, m, { __isStatic: true })),`);
203
+ } else {
204
+ routes.push(` "${normalizedRoutePath}": () => import("${importPath}"),`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ return routes;
211
+ };
212
+ let routeLines = [];
213
+ if (fs2.existsSync(srcDir)) {
214
+ routeLines = getRoutes(srcDir);
215
+ }
216
+ return `export const routes = {
217
+ ${routeLines.join("\n")}
218
+ };`;
219
+ }
220
+ }
221
+ };
222
+ }
223
+
224
+ // router/framework.ts
225
+ import { transformWithEsbuild } from "vite";
226
+ function frameworkPlugin() {
227
+ const virtualClientEntry = "olova/client";
228
+ const resolvedVirtualClientEntry = "\0" + virtualClientEntry;
229
+ const virtualServerEntry = "olova/server";
230
+ const resolvedVirtualServerEntry = "\0" + virtualServerEntry;
231
+ return {
232
+ name: "olova-framework",
233
+ resolveId(id) {
234
+ if (id === virtualClientEntry || id === "/olova/client" || id === "olova/client.tsx") return resolvedVirtualClientEntry;
235
+ if (id === virtualServerEntry || id === "/olova/server" || id === "olova/server.tsx") return resolvedVirtualServerEntry;
236
+ },
237
+ async load(id) {
238
+ if (id === resolvedVirtualClientEntry) {
239
+ const code = `
240
+ import React from 'react';
241
+ import { hydrateRoot, createRoot } from 'react-dom/client';
242
+ import Layout, { metadata as defaultMetadata } from '/src/root.tsx';
243
+ import { Router, loadRoute } from '/route.tsx';
244
+
245
+ // Helper to generate SEO meta tags
246
+ function generateSeoTags(metadata) {
247
+ const meta = { ...defaultMetadata, ...metadata };
248
+ const tags = [];
249
+
250
+ // Title
251
+ if (meta.title) {
252
+ document.title = meta.title;
253
+ }
254
+
255
+ // Description
256
+ if (meta.description) {
257
+ let tag = document.querySelector('meta[name="description"]');
258
+ if (!tag) {
259
+ tag = document.createElement('meta');
260
+ tag.setAttribute('name', 'description');
261
+ document.head.appendChild(tag);
262
+ }
263
+ tag.setAttribute('content', meta.description);
264
+ }
265
+
266
+ // Keywords
267
+ if (meta.keywords) {
268
+ const content = Array.isArray(meta.keywords) ? meta.keywords.join(', ') : meta.keywords;
269
+ let tag = document.querySelector('meta[name="keywords"]');
270
+ if (!tag) {
271
+ tag = document.createElement('meta');
272
+ tag.setAttribute('name', 'keywords');
273
+ document.head.appendChild(tag);
274
+ }
275
+ tag.setAttribute('content', content);
276
+ }
277
+
278
+ // Open Graph
279
+ if (meta.openGraph) {
280
+ const og = meta.openGraph;
281
+ const ogTags = [
282
+ ['og:title', og.title || meta.title],
283
+ ['og:description', og.description],
284
+ ['og:url', og.url],
285
+ ['og:site_name', og.siteName],
286
+ ['og:type', og.type],
287
+ ];
288
+ ogTags.forEach(([prop, content]) => {
289
+ if (content) {
290
+ let tag = document.querySelector(\`meta[property="\${prop}"]\`);
291
+ if (!tag) {
292
+ tag = document.createElement('meta');
293
+ tag.setAttribute('property', prop);
294
+ document.head.appendChild(tag);
295
+ }
296
+ tag.setAttribute('content', content);
297
+ }
298
+ });
299
+ }
300
+
301
+ // Twitter Card
302
+ if (meta.twitter) {
303
+ const tw = meta.twitter;
304
+ const twTags = [
305
+ ['twitter:card', tw.card || 'summary'],
306
+ ['twitter:site', tw.site],
307
+ ['twitter:creator', tw.creator],
308
+ ['twitter:title', tw.title || meta.title],
309
+ ['twitter:description', tw.description],
310
+ ];
311
+ twTags.forEach(([name, content]) => {
312
+ if (content) {
313
+ let tag = document.querySelector(\`meta[name="\${name}"]\`);
314
+ if (!tag) {
315
+ tag = document.createElement('meta');
316
+ tag.setAttribute('name', name);
317
+ document.head.appendChild(tag);
318
+ }
319
+ tag.setAttribute('content', content);
320
+ }
321
+ });
322
+ }
323
+ }
324
+
325
+ const path = window.location.pathname;
326
+ loadRoute(path).then((result) => {
327
+ const Component = result ? result.module.default : () => <div>404 Not Found</div>;
328
+ // @ts-ignore
329
+ const serverData = window.__OLOVA_DATA__ || {};
330
+ const params = serverData.params || (result ? result.params : {});
331
+ const metadata = serverData.metadata || (result ? result.metadata : undefined);
332
+
333
+ // Framework handles SEO automatically (like Next.js)
334
+ generateSeoTags(metadata);
335
+
336
+ const rootElement = document.getElementById('root');
337
+ const app = (
338
+ <React.StrictMode>
339
+ <Layout>
340
+ <Router url={path} initialComponent={Component} initialParams={params} onRouteChange={(newMetadata) => generateSeoTags(newMetadata)} />
341
+ </Layout>
342
+ </React.StrictMode>
343
+ );
344
+
345
+ if (rootElement && rootElement.innerHTML.trim() !== "") {
346
+ console.log("[Olova] Hydrating pre-rendered content");
347
+ hydrateRoot(document, app);
348
+ } else {
349
+ console.log("[Olova] Rendering client-side (no SSR content found)");
350
+ createRoot(document).render(app);
351
+ }
352
+ });`;
353
+ const result = await transformWithEsbuild(code, "olova-client.tsx", {
354
+ loader: "tsx",
355
+ jsx: "automatic"
356
+ });
357
+ return result.code;
358
+ }
359
+ if (id === resolvedVirtualServerEntry) {
360
+ const code = `
361
+ import React from 'react';
362
+ import { renderToString } from 'react-dom/server';
363
+ import Layout, { metadata as defaultMetadata } from '/src/root.tsx';
364
+ import { Router, loadRoute } from '/route.tsx';
365
+
366
+ // Generate SEO head content
367
+ function generateSeoHead(metadata) {
368
+ const meta = { ...defaultMetadata, ...metadata };
369
+ let head = '';
370
+
371
+ // Title
372
+ if (meta.title) {
373
+ head += \`<title>\${meta.title}</title>\\n\`;
374
+ }
375
+
376
+ // Basic meta
377
+ if (meta.description) {
378
+ head += \`<meta name="description" content="\${meta.description}" />\\n\`;
379
+ }
380
+ if (meta.keywords) {
381
+ const content = Array.isArray(meta.keywords) ? meta.keywords.join(', ') : meta.keywords;
382
+ head += \`<meta name="keywords" content="\${content}" />\\n\`;
383
+ }
384
+ if (meta.robots) {
385
+ head += \`<meta name="robots" content="\${meta.robots}" />\\n\`;
386
+ }
387
+ if (meta.canonical) {
388
+ head += \`<link rel="canonical" href="\${meta.canonical}" />\\n\`;
389
+ }
390
+
391
+ // Open Graph
392
+ if (meta.openGraph) {
393
+ const og = meta.openGraph;
394
+ head += \`<meta property="og:title" content="\${og.title || meta.title}" />\\n\`;
395
+ if (og.description) head += \`<meta property="og:description" content="\${og.description}" />\\n\`;
396
+ if (og.url) head += \`<meta property="og:url" content="\${og.url}" />\\n\`;
397
+ if (og.siteName) head += \`<meta property="og:site_name" content="\${og.siteName}" />\\n\`;
398
+ if (og.type) head += \`<meta property="og:type" content="\${og.type}" />\\n\`;
399
+ if (og.images) {
400
+ og.images.forEach(img => {
401
+ head += \`<meta property="og:image" content="\${img.url}" />\\n\`;
402
+ });
403
+ }
404
+ }
405
+
406
+ // Twitter Card
407
+ if (meta.twitter) {
408
+ const tw = meta.twitter;
409
+ head += \`<meta name="twitter:card" content="\${tw.card || 'summary'}" />\\n\`;
410
+ if (tw.site) head += \`<meta name="twitter:site" content="\${tw.site}" />\\n\`;
411
+ if (tw.creator) head += \`<meta name="twitter:creator" content="\${tw.creator}" />\\n\`;
412
+ head += \`<meta name="twitter:title" content="\${tw.title || meta.title}" />\\n\`;
413
+ if (tw.description) head += \`<meta name="twitter:description" content="\${tw.description}" />\\n\`;
414
+ if (tw.images) {
415
+ tw.images.forEach(img => {
416
+ head += \`<meta name="twitter:image" content="\${img}" />\\n\`;
417
+ });
418
+ }
419
+ }
420
+
421
+ return head;
422
+ }
423
+
424
+ export async function render(url) {
425
+ const result = await loadRoute(url);
426
+ const Component = result ? result.module.default : () => <div>404 Not Found</div>;
427
+ const params = result ? result.params : {};
428
+ const metadata = result ? result.metadata : undefined;
429
+
430
+ // Generate the SEO head content
431
+ const seoHead = generateSeoHead(metadata);
432
+
433
+ let html = renderToString(
434
+ <Layout>
435
+ <Router url={url} initialComponent={Component} initialParams={params} />
436
+ </Layout>
437
+ );
438
+
439
+ // Inject SEO tags into head (framework handles this automatically)
440
+ if (html.includes('</head>')) {
441
+ html = html.replace('</head>', seoHead + '</head>');
442
+ }
443
+
444
+ return { html, hydrationData: { params, metadata } };
445
+ }
446
+
447
+ // Render empty shell from Layout (for client-only pages)
448
+ export function renderShell() {
449
+ const seoHead = generateSeoHead({});
450
+ let html = renderToString(<Layout>{null}</Layout>);
451
+ if (html.includes('</head>')) {
452
+ html = html.replace('</head>', seoHead + '</head>');
453
+ }
454
+ return html;
455
+ }
456
+
457
+ // Render shell WITH metadata for client-only pages that have metadata exports
458
+ // This enables SEO pre-rendering even for pages without "static" directive
459
+ export function renderShellWithMetadata(metadata) {
460
+ const seoHead = generateSeoHead(metadata);
461
+ let html = renderToString(<Layout>{null}</Layout>);
462
+ if (html.includes('</head>')) {
463
+ html = html.replace('</head>', seoHead + '</head>');
464
+ }
465
+ return html;
466
+ }
467
+
468
+ // Re-export loadRoute so it can be used during SSG to extract metadata from any route
469
+ export { loadRoute };`;
470
+ const result = await transformWithEsbuild(code, "olova-server.tsx", {
471
+ loader: "tsx",
472
+ jsx: "automatic"
473
+ });
474
+ return result.code;
475
+ }
476
+ }
477
+ };
478
+ }
479
+
480
+ // router/virtual-html.ts
481
+ import fs3 from "fs";
482
+ import path3 from "path";
483
+
484
+ // router/hydration.ts
485
+ function generateOlovaHydration(data, buildId) {
486
+ const meta = data.metadata || {};
487
+ const scripts = [];
488
+ scripts.push(`<script>self.__olova_f=self.__olova_f||[];function _oF(d){self.__olova_f.push(d)}</script>`);
489
+ const routeChunk = {
490
+ type: "R",
491
+ data: {
492
+ path: data.route,
493
+ params: data.params || {},
494
+ pattern: data.route === "/" ? "/" : data.route.replace(/\/[^/]+$/, "/:slug"),
495
+ isStatic: data.isStatic ?? true,
496
+ buildId
497
+ }
498
+ };
499
+ scripts.push(`<script>_oF([0,"${routeChunk.type}",${JSON.stringify(routeChunk.data)}])</script>`);
500
+ const metadataChunk = {
501
+ type: "M",
502
+ data: {
503
+ title: meta.title || "Olova App",
504
+ description: meta.description || "",
505
+ keywords: Array.isArray(meta.keywords) ? meta.keywords : [],
506
+ robots: meta.robots || "index, follow",
507
+ canonical: meta.canonical || null,
508
+ og: {
509
+ type: "website",
510
+ locale: "en_US",
511
+ ...meta.openGraph || {}
512
+ },
513
+ twitter: {
514
+ card: "summary_large_image",
515
+ ...meta.twitter || {}
516
+ }
517
+ }
518
+ };
519
+ scripts.push(`<script>_oF([1,"${metadataChunk.type}",${JSON.stringify(metadataChunk.data)}])</script>`);
520
+ const pageName = data.route === "/" ? "HomePage" : data.route.slice(1).split("/").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("") + "Page";
521
+ const treeChunk = {
522
+ type: "T",
523
+ data: {
524
+ layout: "RootLayout",
525
+ page: pageName,
526
+ template: null,
527
+ loading: null,
528
+ error: null,
529
+ notFound: null
530
+ }
531
+ };
532
+ scripts.push(`<script>_oF([2,"${treeChunk.type}",${JSON.stringify(treeChunk.data)}])</script>`);
533
+ const structuredData = {
534
+ type: "D",
535
+ data: [
536
+ {
537
+ "@context": "https://schema.org",
538
+ "@type": "WebPage",
539
+ name: meta.title || "Olova App",
540
+ description: meta.description || "",
541
+ url: data.route
542
+ },
543
+ {
544
+ "@context": "https://schema.org",
545
+ "@type": "WebApplication",
546
+ name: "Olova",
547
+ applicationCategory: "WebApplication",
548
+ operatingSystem: "Any"
549
+ }
550
+ ]
551
+ };
552
+ scripts.push(`<script>_oF([3,"${structuredData.type}",${JSON.stringify(structuredData.data)}])</script>`);
553
+ const assetsChunk = {
554
+ type: "A",
555
+ data: {
556
+ chunks: data.chunks || [],
557
+ styles: [],
558
+ // Prefetch hints for next likely navigations
559
+ prefetch: (data.chunks || []).slice(0, 5)
560
+ }
561
+ };
562
+ scripts.push(`<script>_oF([4,"${assetsChunk.type}",${JSON.stringify(assetsChunk.data)}])</script>`);
563
+ const hintsChunk = {
564
+ type: "H",
565
+ data: {
566
+ dnsPrefetch: ["fonts.googleapis.com", "fonts.gstatic.com"],
567
+ preconnect: ["https://fonts.googleapis.com", "https://fonts.gstatic.com"],
568
+ modulePreload: (data.chunks || []).slice(0, 3)
569
+ }
570
+ };
571
+ scripts.push(`<script>_oF([5,"${hintsChunk.type}",${JSON.stringify(hintsChunk.data)}])</script>`);
572
+ const stateChunk = {
573
+ type: "S",
574
+ data: {
575
+ hydrated: false,
576
+ streaming: false,
577
+ ready: true,
578
+ timestamp: Date.now(),
579
+ version: "1.0.0",
580
+ buildId
581
+ }
582
+ };
583
+ scripts.push(`<script>_oF([6,"${stateChunk.type}",${JSON.stringify(stateChunk.data)}])</script>`);
584
+ scripts.push(`<script>_oF([7,"E",null])</script>`);
585
+ const globalPayload = {
586
+ $route: routeChunk.data,
587
+ $meta: metadataChunk.data,
588
+ $tree: treeChunk.data,
589
+ $schema: structuredData.data,
590
+ $assets: assetsChunk.data,
591
+ $hints: hintsChunk.data,
592
+ $state: stateChunk.data,
593
+ $build: {
594
+ id: buildId,
595
+ version: "1.0.0",
596
+ timestamp: Date.now(),
597
+ env: "production"
598
+ }
599
+ };
600
+ scripts.push(`<script>$OLOVA=${JSON.stringify(globalPayload)}</script>`);
601
+ return scripts.join("");
602
+ }
603
+ function generateJsonLd(data) {
604
+ const meta = data.metadata || {};
605
+ const jsonLd = {
606
+ "@context": "https://schema.org",
607
+ "@graph": [
608
+ {
609
+ "@type": "WebPage",
610
+ "@id": data.route,
611
+ name: meta.title || "Olova App",
612
+ description: meta.description || "",
613
+ url: data.route,
614
+ isPartOf: {
615
+ "@type": "WebSite",
616
+ name: "Olova App"
617
+ }
618
+ },
619
+ {
620
+ "@type": "BreadcrumbList",
621
+ itemListElement: data.route.split("/").filter(Boolean).map((segment, index, arr) => ({
622
+ "@type": "ListItem",
623
+ position: index + 1,
624
+ name: segment.charAt(0).toUpperCase() + segment.slice(1),
625
+ item: "/" + arr.slice(0, index + 1).join("/")
626
+ }))
627
+ }
628
+ ]
629
+ };
630
+ return `<script type="application/ld+json">${JSON.stringify(jsonLd)}</script>`;
631
+ }
632
+ function parseFlightData() {
633
+ if (typeof globalThis === "undefined" || typeof globalThis.document === "undefined") {
634
+ return null;
635
+ }
636
+ const flightArray = globalThis.__olova_f;
637
+ if (!flightArray) return null;
638
+ const result = {};
639
+ const typeMap = {
640
+ "M": "$meta",
641
+ "T": "$tree",
642
+ "R": "$route",
643
+ "P": "$params",
644
+ "A": "$assets",
645
+ "S": "$state",
646
+ "D": "$schema",
647
+ "H": "$hints",
648
+ "E": "$end"
649
+ };
650
+ for (const chunk of flightArray) {
651
+ if (Array.isArray(chunk) && chunk.length >= 3) {
652
+ const [_index, type, data] = chunk;
653
+ const key = typeMap[type];
654
+ if (key && key !== "$end") {
655
+ result[key] = data;
656
+ }
657
+ }
658
+ }
659
+ return result;
660
+ }
661
+
662
+ // router/virtual-html.ts
663
+ function virtualHtmlPlugin() {
664
+ const htmlTemplate = `<!DOCTYPE html>
665
+ <html lang="en">
666
+ <head>
667
+ <meta charset="UTF-8" />
668
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
669
+ <title>My Framework</title>
670
+ </head>
671
+ <body>
672
+ <div id="root"></div>
673
+ <script type="module">
674
+ import '/olova/client';
675
+ </script>
676
+ </body>
677
+ </html>`;
678
+ return {
679
+ name: "olova-virtual-html",
680
+ enforce: "pre",
681
+ // Handle virtual index.html resolution for build
682
+ resolveId(id) {
683
+ if (id === "index.html" || id === "virtual:index.html" || id === "olova.html") {
684
+ return "olova.html";
685
+ }
686
+ },
687
+ load(id) {
688
+ if (id === "olova.html" || id === "virtual:index.html") {
689
+ return htmlTemplate;
690
+ }
691
+ },
692
+ // Serve HTML for all routes in dev mode with SSR (like Next.js)
693
+ configureServer(server) {
694
+ const isStaticRoute = (routePath) => {
695
+ const srcDir = path3.resolve("src");
696
+ let filePath = routePath === "/" ? "index" : routePath.slice(1);
697
+ const staticPaths = [
698
+ path3.join(srcDir, filePath, "index.html"),
699
+ path3.join(srcDir, filePath, "index.md"),
700
+ path3.join(srcDir, filePath + ".html"),
701
+ path3.join(srcDir, filePath + ".md")
702
+ ];
703
+ for (const p of staticPaths) {
704
+ if (fs3.existsSync(p)) {
705
+ return true;
706
+ }
707
+ }
708
+ const possiblePaths = [
709
+ path3.join(srcDir, filePath, "index.tsx"),
710
+ path3.join(srcDir, filePath, "index.jsx"),
711
+ path3.join(srcDir, filePath + ".tsx"),
712
+ path3.join(srcDir, filePath + ".jsx"),
713
+ path3.join(srcDir, "App.tsx"),
714
+ path3.join(srcDir, "App.jsx")
715
+ ];
716
+ for (const p of possiblePaths) {
717
+ if (fs3.existsSync(p)) {
718
+ const content = fs3.readFileSync(p, "utf-8");
719
+ const firstLine = content.trim().split("\n")[0].trim();
720
+ return firstLine === '"static"' || firstLine === "'static'";
721
+ }
722
+ }
723
+ return false;
724
+ };
725
+ return () => {
726
+ server.middlewares.use(async (req, res, next) => {
727
+ const url = req.url || "/";
728
+ const accept = req.headers.accept || "";
729
+ if (!accept.includes("text/html")) {
730
+ return next();
731
+ }
732
+ if (url.startsWith("/@") || url.startsWith("/__") || url.startsWith("/node_modules")) {
733
+ return next();
734
+ }
735
+ if (/\.\w+$/.test(url) && !url.endsWith(".html")) {
736
+ return next();
737
+ }
738
+ const routePath = url.split("?")[0];
739
+ const shouldSSR = isStaticRoute(routePath);
740
+ try {
741
+ if (shouldSSR) {
742
+ const { render } = await server.ssrLoadModule("olova/server");
743
+ const { html: ssrHtml, hydrationData } = await render(routePath);
744
+ let fullHtml = ssrHtml;
745
+ const hydrationScript = `<script>window.__OLOVA_DATA__ = ${JSON.stringify(hydrationData)};</script>`;
746
+ fullHtml = fullHtml.replace("</body>", `${hydrationScript}
747
+ </body>`);
748
+ const clientScript = `<script type="module" src="/olova/client"></script>`;
749
+ fullHtml = fullHtml.replace("</body>", `${clientScript}
750
+ </body>`);
751
+ const devBuildId = "dev-" + Date.now().toString(36);
752
+ const flightScripts = generateOlovaHydration({
753
+ route: routePath,
754
+ metadata: hydrationData.metadata,
755
+ params: hydrationData.params,
756
+ chunks: []
757
+ }, devBuildId);
758
+ fullHtml = fullHtml.replace("</body>", `${flightScripts}</body>`);
759
+ const jsonLdScript = generateJsonLd({ route: routePath, metadata: hydrationData.metadata });
760
+ fullHtml = fullHtml.replace("</head>", `${jsonLdScript}</head>`);
761
+ const html = await server.transformIndexHtml(url, fullHtml);
762
+ res.setHeader("Content-Type", "text/html");
763
+ res.statusCode = 200;
764
+ res.end(html);
765
+ } else {
766
+ const { renderShellWithMetadata } = await server.ssrLoadModule("olova/server");
767
+ const { loadRoute: clientLoadRoute } = await server.ssrLoadModule("/route.tsx");
768
+ let pageMetadata = {};
769
+ try {
770
+ const result = await clientLoadRoute(routePath);
771
+ if (result && result.metadata) {
772
+ pageMetadata = result.metadata;
773
+ }
774
+ } catch (e) {
775
+ }
776
+ let fullHtml = renderShellWithMetadata(pageMetadata);
777
+ const clientScript = `<script type="module" src="/olova/client"></script>`;
778
+ fullHtml = fullHtml.replace("</body>", `${clientScript}
779
+ </body>`);
780
+ const devBuildId = "dev-" + Date.now().toString(36);
781
+ const flightScripts = generateOlovaHydration({
782
+ route: routePath,
783
+ metadata: pageMetadata,
784
+ chunks: []
785
+ }, devBuildId);
786
+ fullHtml = fullHtml.replace("</body>", `${flightScripts}</body>`);
787
+ const jsonLdScript = generateJsonLd({ route: routePath, metadata: pageMetadata });
788
+ fullHtml = fullHtml.replace("</head>", `${jsonLdScript}</head>`);
789
+ const html = await server.transformIndexHtml(url, fullHtml);
790
+ res.setHeader("Content-Type", "text/html");
791
+ res.statusCode = 200;
792
+ res.end(html);
793
+ }
794
+ } catch (e) {
795
+ console.error("[Olova] Error:", e);
796
+ try {
797
+ const html = await server.transformIndexHtml(url, htmlTemplate);
798
+ res.setHeader("Content-Type", "text/html");
799
+ res.statusCode = 200;
800
+ res.end(html);
801
+ } catch (e2) {
802
+ next(e2);
803
+ }
804
+ }
805
+ });
806
+ };
807
+ }
808
+ };
809
+ }
810
+
811
+ // router/ssg.ts
812
+ import { build } from "vite";
813
+ import fs4 from "fs";
814
+ import path4 from "path";
815
+ import { pathToFileURL } from "url";
816
+
817
+ // router/utils.ts
818
+ function minifyHtml(html) {
819
+ return html.replace(/<!--(?!\[if).*?-->/gs, "").replace(/\s+/g, " ").replace(/> </g, "><").replace(/\s*(<[^>]+>)\s*/g, "$1").trim();
820
+ }
821
+ function generateBuildId() {
822
+ return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
823
+ }
824
+ function generateResourceHints() {
825
+ return `
826
+ <link rel="dns-prefetch" href="//fonts.googleapis.com">
827
+ <link rel="dns-prefetch" href="//fonts.gstatic.com">
828
+ <link rel="dns-prefetch" href="//cdn.jsdelivr.net">
829
+ <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
830
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
831
+ `.trim().replace(/\n\s+/g, "");
832
+ }
833
+ function generateCriticalCSS() {
834
+ return `<style id="olova-critical">
835
+ *{box-sizing:border-box;margin:0;padding:0}
836
+ html{-webkit-text-size-adjust:100%;line-height:1.5}
837
+ body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
838
+ img,video{max-width:100%;height:auto}
839
+ [hidden]{display:none!important}
840
+ </style>`.replace(/\n\s*/g, "");
841
+ }
842
+ function generateServiceWorkerScript() {
843
+ return `<script>if('serviceWorker'in navigator){window.addEventListener('load',()=>{navigator.serviceWorker.register('/sw.js').catch(()=>{})})}</script>`;
844
+ }
845
+ function generateServiceWorkerContent(buildId, assets) {
846
+ return `// Olova Service Worker v${buildId}
847
+ const CACHE_NAME = 'olova-cache-${buildId}';
848
+ const ASSETS = ${JSON.stringify(assets)};
849
+
850
+ self.addEventListener('install', (e) => {
851
+ e.waitUntil(caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS)));
852
+ self.skipWaiting();
853
+ });
854
+
855
+ self.addEventListener('activate', (e) => {
856
+ e.waitUntil(caches.keys().then(keys => Promise.all(
857
+ keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k))
858
+ )));
859
+ });
860
+
861
+ self.addEventListener('fetch', (e) => {
862
+ e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
863
+ });
864
+ `;
865
+ }
866
+ function generatePerformanceMeta() {
867
+ return `<meta http-equiv="x-dns-prefetch-control" content="on"><meta name="color-scheme" content="light dark">`;
868
+ }
869
+
870
+ // router/ssg.ts
871
+ var colors2 = {
872
+ reset: "\x1B[0m",
873
+ bold: "\x1B[1m",
874
+ dim: "\x1B[2m",
875
+ green: "\x1B[32m",
876
+ cyan: "\x1B[36m",
877
+ yellow: "\x1B[33m",
878
+ magenta: "\x1B[35m",
879
+ blue: "\x1B[34m",
880
+ red: "\x1B[31m",
881
+ white: "\x1B[37m",
882
+ gray: "\x1B[90m",
883
+ bgGreen: "\x1B[42m",
884
+ bgCyan: "\x1B[46m",
885
+ bgMagenta: "\x1B[45m"
886
+ };
887
+ var symbols = {
888
+ success: "\u2713",
889
+ arrow: "\u2192",
890
+ bullet: "\u25CB",
891
+ filled: "\u25CF",
892
+ lambda: "\u03BB",
893
+ static: "\u25CB",
894
+ ssr: "\u25CF",
895
+ info: "\u2139"
896
+ };
897
+ function formatTime(ms) {
898
+ if (ms < 1) return "<1ms";
899
+ if (ms < 10) return `${ms.toFixed(1)}ms`;
900
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
901
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
902
+ const mins = Math.floor(ms / 6e4);
903
+ const secs = Math.round(ms % 6e4 / 1e3);
904
+ return `${mins}m ${secs}s`;
905
+ }
906
+ function formatBytes(bytes) {
907
+ if (bytes < 1024) return `${bytes} B`;
908
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
909
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
910
+ }
911
+ var logger = {
912
+ header: (text) => {
913
+ console.log(`
914
+ ${colors2.bold}${colors2.white}${text}${colors2.reset}`);
915
+ },
916
+ info: (text) => {
917
+ console.log(`${colors2.cyan}${symbols.info}${colors2.reset} ${text}`);
918
+ },
919
+ success: (text) => {
920
+ console.log(`${colors2.green}${symbols.success}${colors2.reset} ${text}`);
921
+ },
922
+ route: (route, type, size, time) => {
923
+ const symbol = type === "static" ? symbols.static : symbols.ssr;
924
+ const color = type === "static" ? colors2.white : colors2.magenta;
925
+ const sizeStr = size ? ` ${colors2.dim}${formatBytes(size)}${colors2.reset}` : "";
926
+ const timeStr = time ? ` ${colors2.gray}(${formatTime(time)})${colors2.reset}` : "";
927
+ console.log(`${color}${symbol}${colors2.reset} ${route}${sizeStr}${timeStr}`);
928
+ },
929
+ step: (step, time) => {
930
+ const timeStr = time ? ` ${colors2.gray}${formatTime(time)}${colors2.reset}` : "";
931
+ console.log(` ${colors2.dim}${symbols.arrow}${colors2.reset} ${step}${timeStr}`);
932
+ },
933
+ buildComplete: (totalRoutes, staticRoutes, totalTime) => {
934
+ console.log("");
935
+ console.log(`${colors2.bold}${colors2.green}${symbols.success} Build completed!${colors2.reset}`);
936
+ console.log("");
937
+ console.log(` ${colors2.dim}Routes:${colors2.reset} ${totalRoutes} total`);
938
+ console.log(` ${colors2.dim}Static:${colors2.reset} ${staticRoutes} prerendered`);
939
+ console.log(` ${colors2.dim}Time:${colors2.reset} ${formatTime(totalTime)}`);
940
+ console.log("");
941
+ },
942
+ legend: () => {
943
+ console.log(`${colors2.dim}${symbols.static} Static${colors2.reset} ${colors2.dim}${symbols.ssr} SSR${colors2.reset} ${colors2.dim}\u03BB ISR${colors2.reset}`);
944
+ },
945
+ banner: (buildId) => {
946
+ console.log("");
947
+ console.log(`${colors2.bold}${colors2.cyan} \u25B2 Olova${colors2.reset} ${colors2.dim}Static Export${colors2.reset}`);
948
+ console.log(`${colors2.dim} Build ID: ${buildId}${colors2.reset}`);
949
+ console.log("");
950
+ },
951
+ error: (text) => {
952
+ console.log(`${colors2.red}\u2717${colors2.reset} ${colors2.red}${text}${colors2.reset}`);
953
+ },
954
+ warn: (text) => {
955
+ console.log(`${colors2.yellow}\u26A0${colors2.reset} ${colors2.yellow}${text}${colors2.reset}`);
956
+ }
957
+ };
958
+ function ssgPlugin() {
959
+ return {
960
+ name: "olova-ssg",
961
+ apply: "build",
962
+ config() {
963
+ if (process.env.IS_SSG_BUILD) {
964
+ return {};
965
+ }
966
+ return {
967
+ build: {
968
+ manifest: true,
969
+ rollupOptions: {
970
+ input: "index.html"
971
+ }
972
+ }
973
+ };
974
+ },
975
+ async closeBundle() {
976
+ if (process.env.IS_SSG_BUILD) return;
977
+ process.env.IS_SSG_BUILD = "true";
978
+ const totalStartTime = performance.now();
979
+ const buildId = generateBuildId();
980
+ logger.banner(buildId);
981
+ logger.header("Generating static pages...");
982
+ const serverBuildStart = performance.now();
983
+ try {
984
+ await build({
985
+ configFile: "./vite.config.ts",
986
+ build: {
987
+ ssr: true,
988
+ outDir: ".olova/server",
989
+ rollupOptions: {
990
+ input: "olova/server"
991
+ },
992
+ emptyOutDir: true,
993
+ reportCompressedSize: false
994
+ },
995
+ logLevel: "silent"
996
+ });
997
+ logger.step("Server bundle compiled", performance.now() - serverBuildStart);
998
+ } catch (e) {
999
+ logger.error("SSG Build Failed");
1000
+ console.error(e);
1001
+ process.env.IS_SSG_BUILD = "";
1002
+ return;
1003
+ } finally {
1004
+ process.env.IS_SSG_BUILD = "";
1005
+ }
1006
+ const scanStart = performance.now();
1007
+ const allRoutes = [];
1008
+ const scan = (dir, base = "") => {
1009
+ if (!fs4.existsSync(dir)) return;
1010
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
1011
+ for (const entry of entries) {
1012
+ const fullPath = path4.join(dir, entry.name);
1013
+ if (entry.isDirectory()) {
1014
+ const isRouteGroup = /^\(.+\)$/.test(entry.name);
1015
+ if (isRouteGroup) {
1016
+ scan(fullPath, base);
1017
+ } else {
1018
+ scan(fullPath, `${base}/${entry.name}`);
1019
+ }
1020
+ } else {
1021
+ const ext = path4.extname(entry.name);
1022
+ const supportedExts = [".tsx", ".jsx", ".html", ".md"];
1023
+ if (supportedExts.includes(ext)) {
1024
+ const nameNoExt = entry.name.replace(/\.(tsx|jsx|html|md)$/, "");
1025
+ let route = base;
1026
+ if (nameNoExt !== "index" && nameNoExt !== "App") {
1027
+ route = `${base}/${nameNoExt}`;
1028
+ }
1029
+ if (route === "") route = "/";
1030
+ if (route.includes("$")) continue;
1031
+ if (ext === ".html" || ext === ".md") {
1032
+ allRoutes.push({ route, isStatic: true, filePath: fullPath });
1033
+ } else {
1034
+ const fileContent = fs4.readFileSync(fullPath, "utf-8");
1035
+ const firstLine = fileContent.trim().split("\n")[0].trim();
1036
+ const isStatic = firstLine === '"static"' || firstLine === "'static'";
1037
+ allRoutes.push({ route, isStatic, filePath: fullPath });
1038
+ }
1039
+ }
1040
+ }
1041
+ }
1042
+ };
1043
+ scan(path4.resolve("src"));
1044
+ logger.step(`Found ${allRoutes.length} routes`, performance.now() - scanStart);
1045
+ const serverDir = path4.resolve(".olova/server");
1046
+ let serverFile = fs4.readdirSync(serverDir).find(
1047
+ (f) => (f === "server.js" || f.includes("server-entry")) && f.endsWith(".js")
1048
+ );
1049
+ if (!serverFile) {
1050
+ logger.error("Could not find server build artifact");
1051
+ return;
1052
+ }
1053
+ const serverEntryPath = path4.join(serverDir, serverFile);
1054
+ const { render, renderShell, renderShellWithMetadata, loadRoute } = await import(pathToFileURL(serverEntryPath).href);
1055
+ const assetsStart = performance.now();
1056
+ const distDir = path4.resolve(".olova/dist");
1057
+ const clientHtmlPath = path4.join(distDir, "olova.html");
1058
+ let shellHtml = renderShell();
1059
+ if (!shellHtml.startsWith("<!DOCTYPE html>")) {
1060
+ shellHtml = `<!DOCTYPE html>
1061
+ ${shellHtml}`;
1062
+ }
1063
+ let template = "";
1064
+ if (fs4.existsSync(clientHtmlPath)) {
1065
+ template = fs4.readFileSync(clientHtmlPath, "utf-8");
1066
+ }
1067
+ const scripts = template.match(/<script[\s\S]*?>[\s\S]*?<\/script>/gi) || [];
1068
+ const links = template.match(/<link[\s\S]*?>/gi) || [];
1069
+ const chunksDir = path4.join(distDir, "pro_olova_static", "chunks");
1070
+ let preloadLinks = [];
1071
+ if (fs4.existsSync(chunksDir)) {
1072
+ const chunks = fs4.readdirSync(chunksDir).filter((f) => f.endsWith(".js"));
1073
+ preloadLinks = chunks.map(
1074
+ (chunk) => `<link rel="modulepreload" crossorigin href="/pro_olova_static/chunks/${chunk}" />`
1075
+ );
1076
+ }
1077
+ const mainScriptMatch = template.match(/src="(\/pro_olova_static\/olova-[^"]+\.js)"/);
1078
+ if (mainScriptMatch) {
1079
+ preloadLinks.unshift(`<link rel="modulepreload" crossorigin href="${mainScriptMatch[1]}" />`);
1080
+ }
1081
+ const chunkList = [];
1082
+ if (fs4.existsSync(chunksDir)) {
1083
+ const chunks = fs4.readdirSync(chunksDir).filter((f) => f.endsWith(".js"));
1084
+ chunks.forEach((chunk) => chunkList.push(`/pro_olova_static/chunks/${chunk}`));
1085
+ }
1086
+ const resourceHints = generateResourceHints();
1087
+ const criticalCSS = generateCriticalCSS();
1088
+ const performanceMeta = generatePerformanceMeta();
1089
+ const swScript = generateServiceWorkerScript();
1090
+ const performanceHead = [performanceMeta, resourceHints, criticalCSS].join("");
1091
+ const allAssetsForSW = [
1092
+ "/",
1093
+ "/index.html",
1094
+ ...chunkList,
1095
+ ...links.map((l) => l.match(/href="([^"]+)"/)?.[1]).filter(Boolean)
1096
+ ];
1097
+ const swContent = generateServiceWorkerContent(buildId, allAssetsForSW);
1098
+ fs4.writeFileSync(path4.join(distDir, "sw.js"), swContent);
1099
+ const assets = [performanceHead, ...links, ...preloadLinks, ...scripts, swScript].join("\n");
1100
+ logger.step(`Assets optimized (${preloadLinks.length} preloads, SW ready)`, performance.now() - assetsStart);
1101
+ console.log("");
1102
+ logger.header("Route");
1103
+ logger.legend();
1104
+ console.log("");
1105
+ let staticCount = 0;
1106
+ const routeResults = [];
1107
+ for (const { route, isStatic } of allRoutes) {
1108
+ const routeStart = performance.now();
1109
+ let finalHtml = "";
1110
+ if (isStatic) {
1111
+ staticCount++;
1112
+ const { html, hydrationData } = await render(route);
1113
+ finalHtml = html;
1114
+ if (Object.keys(hydrationData.params || {}).length > 0) {
1115
+ const scriptTag = `<script>window.__OLOVA_DATA__ = ${JSON.stringify(hydrationData)};</script>`;
1116
+ if (finalHtml.includes("</body>")) {
1117
+ finalHtml = finalHtml.replace("</body>", `${scriptTag}
1118
+ </body>`);
1119
+ } else {
1120
+ finalHtml += `
1121
+ ${scriptTag}`;
1122
+ }
1123
+ }
1124
+ const jsonLdScript = generateJsonLd({ route, metadata: hydrationData.metadata });
1125
+ if (finalHtml.includes("</head>")) {
1126
+ finalHtml = finalHtml.replace("</head>", `${jsonLdScript}${assets}
1127
+ </head>`);
1128
+ } else {
1129
+ finalHtml = finalHtml.replace("</body>", `${assets}
1130
+ </body>`);
1131
+ }
1132
+ if (!finalHtml.startsWith("<!DOCTYPE html>")) {
1133
+ finalHtml = `<!DOCTYPE html>
1134
+ ${finalHtml}`;
1135
+ }
1136
+ const flightScripts = generateOlovaHydration({
1137
+ route,
1138
+ metadata: hydrationData.metadata,
1139
+ params: hydrationData.params,
1140
+ chunks: chunkList,
1141
+ isStatic: true
1142
+ }, buildId);
1143
+ if (finalHtml.includes("</body>")) {
1144
+ finalHtml = finalHtml.replace("</body>", `${flightScripts}</body>`);
1145
+ }
1146
+ finalHtml = minifyHtml(finalHtml);
1147
+ } else {
1148
+ let pageMetadata = {};
1149
+ try {
1150
+ const routeResult = await loadRoute(route);
1151
+ if (routeResult && routeResult.metadata) {
1152
+ pageMetadata = routeResult.metadata;
1153
+ }
1154
+ } catch (e) {
1155
+ }
1156
+ finalHtml = renderShellWithMetadata(pageMetadata);
1157
+ if (!finalHtml.startsWith("<!DOCTYPE html>")) {
1158
+ finalHtml = `<!DOCTYPE html>
1159
+ ${finalHtml}`;
1160
+ }
1161
+ const jsonLdScript = generateJsonLd({ route, metadata: pageMetadata });
1162
+ if (finalHtml.includes("</head>")) {
1163
+ finalHtml = finalHtml.replace("</head>", `${jsonLdScript}${assets}
1164
+ </head>`);
1165
+ } else {
1166
+ finalHtml = finalHtml.replace("</body>", `${assets}
1167
+ </body>`);
1168
+ }
1169
+ const flightScripts = generateOlovaHydration({
1170
+ route,
1171
+ metadata: pageMetadata,
1172
+ chunks: chunkList,
1173
+ isStatic: false
1174
+ }, buildId);
1175
+ if (finalHtml.includes("</body>")) {
1176
+ finalHtml = finalHtml.replace("</body>", `${flightScripts}</body>`);
1177
+ }
1178
+ finalHtml = minifyHtml(finalHtml);
1179
+ }
1180
+ const outPath = path4.join(distDir, route === "/" ? "index.html" : `${route}/index.html`);
1181
+ fs4.mkdirSync(path4.dirname(outPath), { recursive: true });
1182
+ fs4.writeFileSync(outPath, finalHtml);
1183
+ const routeTime = performance.now() - routeStart;
1184
+ const routeSize = Buffer.byteLength(finalHtml, "utf8");
1185
+ routeResults.push({ route, type: isStatic ? "static" : "ssr", size: routeSize, time: routeTime });
1186
+ logger.route(route, isStatic ? "static" : "ssr", routeSize, routeTime);
1187
+ }
1188
+ const fallbackPath = path4.join(distDir, "404.html");
1189
+ if (fs4.existsSync(clientHtmlPath)) {
1190
+ fs4.copyFileSync(clientHtmlPath, fallbackPath);
1191
+ }
1192
+ const totalTime = performance.now() - totalStartTime;
1193
+ logger.buildComplete(allRoutes.length, staticCount, totalTime);
1194
+ console.log(`${colors2.dim} Output:${colors2.reset} .olova/dist`);
1195
+ console.log("");
1196
+ }
1197
+ };
1198
+ }
1199
+
1200
+ // router/clean-url.ts
1201
+ import fs5 from "fs";
1202
+ import path5 from "path";
1203
+ function cleanUrlPlugin() {
1204
+ return {
1205
+ name: "olova-clean-url",
1206
+ configurePreviewServer(server) {
1207
+ server.middlewares.use((req, res, next) => {
1208
+ const urlPath = (req.url || "/").split("?")[0];
1209
+ const distDir = path5.resolve(".olova/dist");
1210
+ if (urlPath === "/") {
1211
+ const indexPath2 = path5.join(distDir, "index.html");
1212
+ if (fs5.existsSync(indexPath2)) {
1213
+ const content = fs5.readFileSync(indexPath2, "utf-8");
1214
+ res.setHeader("Content-Type", "text/html");
1215
+ res.end(content);
1216
+ return;
1217
+ }
1218
+ }
1219
+ if (urlPath.includes(".")) return next();
1220
+ const indexPath = path5.join(distDir, urlPath, "index.html");
1221
+ if (fs5.existsSync(indexPath)) {
1222
+ const content = fs5.readFileSync(indexPath, "utf-8");
1223
+ res.setHeader("Content-Type", "text/html");
1224
+ res.end(content);
1225
+ return;
1226
+ }
1227
+ next();
1228
+ });
1229
+ }
1230
+ };
1231
+ }
1232
+
1233
+ // router/auto-generate.ts
1234
+ import fs6 from "fs";
1235
+ import path6 from "path";
1236
+ function toPascalCase(str) {
1237
+ const cleanStr = str.replace(/^\$/, "");
1238
+ return cleanStr.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1239
+ }
1240
+ function generateBoilerplate(folderName, isDynamic) {
1241
+ const componentName = toPascalCase(folderName);
1242
+ if (isDynamic) {
1243
+ const paramName = folderName.replace(/^\$/, "");
1244
+ return `import { useParams } from '../../route';
1245
+
1246
+ export default function ${componentName}() {
1247
+ const { ${paramName} } = useParams();
1248
+ return (
1249
+ <div>
1250
+ <h1>${componentName} Page</h1>
1251
+ <p>{${paramName}}</p>
1252
+ </div>
1253
+ );
1254
+ }
1255
+ `;
1256
+ }
1257
+ return `export default function ${componentName}() {
1258
+ return (
1259
+ <div>
1260
+ <h1>${componentName} Page</h1>
1261
+ <p>Welcome to ${componentName}</p>
1262
+ </div>
1263
+ );
1264
+ }
1265
+ `;
1266
+ }
1267
+ function autoGeneratePlugin() {
1268
+ let srcDir;
1269
+ return {
1270
+ name: "olova-auto-generate",
1271
+ configResolved(config) {
1272
+ srcDir = path6.resolve(config.root, "src");
1273
+ },
1274
+ configureServer(server) {
1275
+ console.log(
1276
+ "\x1B[36m[olova] Auto-generate plugin active - watching for new route files\x1B[0m"
1277
+ );
1278
+ server.watcher.on("add", (filePath) => {
1279
+ const normalizedPath = path6.normalize(filePath);
1280
+ const normalizedSrcDir = path6.normalize(srcDir);
1281
+ const ext = path6.extname(normalizedPath);
1282
+ if (ext !== ".tsx" && ext !== ".jsx") {
1283
+ return;
1284
+ }
1285
+ if (!normalizedPath.startsWith(normalizedSrcDir)) {
1286
+ return;
1287
+ }
1288
+ const fileName = path6.basename(normalizedPath, ext);
1289
+ if (fileName !== "index") {
1290
+ return;
1291
+ }
1292
+ const folderPath = path6.dirname(normalizedPath);
1293
+ const folderName = path6.basename(folderPath);
1294
+ if (folderName === "src" || folderName.startsWith("(")) {
1295
+ return;
1296
+ }
1297
+ try {
1298
+ fs6.statSync(normalizedPath);
1299
+ const content = fs6.readFileSync(normalizedPath, "utf-8");
1300
+ if (content.trim().length > 10) {
1301
+ return;
1302
+ }
1303
+ } catch (err) {
1304
+ return;
1305
+ }
1306
+ const isDynamic = folderName.startsWith("$");
1307
+ const boilerplate = generateBoilerplate(folderName, isDynamic);
1308
+ try {
1309
+ fs6.writeFileSync(normalizedPath, boilerplate, "utf-8");
1310
+ console.log(
1311
+ `\x1B[32m\u2713 [olova] Auto-generated: ${folderName}/index${ext}\x1B[0m`
1312
+ );
1313
+ } catch (err) {
1314
+ console.error(
1315
+ `\x1B[31m\u2717 [olova] Failed to write boilerplate:\x1B[0m`,
1316
+ err
1317
+ );
1318
+ }
1319
+ });
1320
+ }
1321
+ };
1322
+ }
1323
+
1324
+ // router/error-overlay.ts
1325
+ import path7 from "path";
1326
+ function proactiveErrorPlugin() {
1327
+ return {
1328
+ name: "olova-proactive-error",
1329
+ enforce: "post",
1330
+ configureServer(devServer) {
1331
+ const srcDir = path7.resolve(process.cwd(), "src");
1332
+ const validateFile = async (filePath) => {
1333
+ if (!filePath.startsWith(srcDir)) return;
1334
+ if (!/\.(tsx?|jsx?)$/.test(filePath)) return;
1335
+ const relativePath = path7.relative(srcDir, filePath);
1336
+ console.log("\x1B[36m[olova]\x1B[0m Checking:", relativePath);
1337
+ try {
1338
+ const url = "/" + path7.relative(process.cwd(), filePath).replace(/\\/g, "/");
1339
+ await devServer.transformRequest(url);
1340
+ console.log("\x1B[32m[olova]\x1B[0m \u2713", relativePath, "OK");
1341
+ } catch (err) {
1342
+ console.log("\x1B[31m[olova]\x1B[0m \u2717", relativePath);
1343
+ devServer.ws.send({
1344
+ type: "error",
1345
+ err: {
1346
+ message: err.message || String(err),
1347
+ stack: err.stack || "",
1348
+ id: err.id || filePath,
1349
+ frame: err.frame || "",
1350
+ plugin: err.plugin || "olova",
1351
+ loc: err.loc || void 0
1352
+ }
1353
+ });
1354
+ }
1355
+ };
1356
+ devServer.watcher.on("change", async (filePath) => {
1357
+ await new Promise((r) => setTimeout(r, 50));
1358
+ await validateFile(filePath);
1359
+ });
1360
+ devServer.watcher.on("add", async (filePath) => {
1361
+ await new Promise((r) => setTimeout(r, 100));
1362
+ await validateFile(filePath);
1363
+ });
1364
+ }
1365
+ };
1366
+ }
1367
+
1368
+ // router/terminal.ts
1369
+ var colors3 = {
1370
+ // Reset
1371
+ reset: "\x1B[0m",
1372
+ // Styles
1373
+ bold: "\x1B[1m",
1374
+ dim: "\x1B[2m",
1375
+ italic: "\x1B[3m",
1376
+ underline: "\x1B[4m",
1377
+ // Foreground colors
1378
+ black: "\x1B[30m",
1379
+ red: "\x1B[31m",
1380
+ green: "\x1B[32m",
1381
+ yellow: "\x1B[33m",
1382
+ blue: "\x1B[34m",
1383
+ magenta: "\x1B[35m",
1384
+ cyan: "\x1B[36m",
1385
+ white: "\x1B[37m",
1386
+ gray: "\x1B[90m",
1387
+ // Background colors
1388
+ bgBlack: "\x1B[40m",
1389
+ bgRed: "\x1B[41m",
1390
+ bgGreen: "\x1B[42m",
1391
+ bgYellow: "\x1B[43m",
1392
+ bgBlue: "\x1B[44m",
1393
+ bgMagenta: "\x1B[45m",
1394
+ bgCyan: "\x1B[46m",
1395
+ bgWhite: "\x1B[47m"
1396
+ };
1397
+ var symbols2 = {
1398
+ success: "\u2713",
1399
+ error: "\u2717",
1400
+ warning: "\u26A0",
1401
+ info: "\u2139",
1402
+ arrow: "\u2192",
1403
+ arrowRight: "\u279C",
1404
+ bullet: "\u25CB",
1405
+ filled: "\u25CF",
1406
+ lambda: "\u03BB",
1407
+ triangle: "\u25B2",
1408
+ square: "\u25A0",
1409
+ diamond: "\u25C6",
1410
+ star: "\u2605",
1411
+ check: "\u2714",
1412
+ cross: "\u2716",
1413
+ pointer: "\u276F"
1414
+ };
1415
+ function formatTime2(ms) {
1416
+ if (ms < 1) return "<1ms";
1417
+ if (ms < 10) return `${ms.toFixed(1)}ms`;
1418
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
1419
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(2)}s`;
1420
+ const mins = Math.floor(ms / 6e4);
1421
+ const secs = Math.round(ms % 6e4 / 1e3);
1422
+ return `${mins}m ${secs}s`;
1423
+ }
1424
+ function formatBytes2(bytes) {
1425
+ if (bytes === 0) return "0 B";
1426
+ const k = 1024;
1427
+ const sizes = ["B", "kB", "MB", "GB"];
1428
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1429
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
1430
+ }
1431
+ var logger2 = {
1432
+ // Print banner
1433
+ banner: (name, version = "1.0.0") => {
1434
+ console.log("");
1435
+ console.log(` ${colors3.bold}${colors3.cyan}${symbols2.triangle} ${name}${colors3.reset} ${colors3.dim}v${version}${colors3.reset}`);
1436
+ console.log("");
1437
+ },
1438
+ // Print header
1439
+ header: (text) => {
1440
+ console.log(`
1441
+ ${colors3.bold}${colors3.white}${text}${colors3.reset}`);
1442
+ },
1443
+ // Info message
1444
+ info: (text) => {
1445
+ console.log(`${colors3.cyan}${symbols2.info}${colors3.reset} ${text}`);
1446
+ },
1447
+ // Success message
1448
+ success: (text, time) => {
1449
+ const timeStr = time !== void 0 ? ` ${colors3.dim}${formatTime2(time)}${colors3.reset}` : "";
1450
+ console.log(`${colors3.green}${symbols2.success}${colors3.reset} ${text}${timeStr}`);
1451
+ },
1452
+ // Error message
1453
+ error: (text) => {
1454
+ console.log(`${colors3.red}${symbols2.error}${colors3.reset} ${colors3.red}${text}${colors3.reset}`);
1455
+ },
1456
+ // Warning message
1457
+ warn: (text) => {
1458
+ console.log(`${colors3.yellow}${symbols2.warning}${colors3.reset} ${colors3.yellow}${text}${colors3.reset}`);
1459
+ },
1460
+ // Step in a process
1461
+ step: (text, time) => {
1462
+ const timeStr = time !== void 0 ? ` ${colors3.gray}${formatTime2(time)}${colors3.reset}` : "";
1463
+ console.log(` ${colors3.dim}${symbols2.arrow}${colors3.reset} ${text}${timeStr}`);
1464
+ },
1465
+ // Route log (for SSG)
1466
+ route: (route, type, size, time) => {
1467
+ const symbol = type === "static" ? symbols2.bullet : type === "ssr" ? symbols2.filled : symbols2.lambda;
1468
+ const color = type === "static" ? colors3.white : type === "ssr" ? colors3.magenta : colors3.cyan;
1469
+ const sizeStr = size ? ` ${colors3.dim}${formatBytes2(size)}${colors3.reset}` : "";
1470
+ const timeStr = time ? ` ${colors3.gray}(${formatTime2(time)})${colors3.reset}` : "";
1471
+ console.log(`${color}${symbol}${colors3.reset} ${route}${sizeStr}${timeStr}`);
1472
+ },
1473
+ // Indent text
1474
+ indent: (text, level = 1) => {
1475
+ console.log(`${" ".repeat(level)}${text}`);
1476
+ },
1477
+ // Empty line
1478
+ newline: () => console.log(""),
1479
+ // Dim text
1480
+ dim: (text) => `${colors3.dim}${text}${colors3.reset}`,
1481
+ // Bold text
1482
+ bold: (text) => `${colors3.bold}${text}${colors3.reset}`,
1483
+ // Colored text
1484
+ cyan: (text) => `${colors3.cyan}${text}${colors3.reset}`,
1485
+ green: (text) => `${colors3.green}${text}${colors3.reset}`,
1486
+ yellow: (text) => `${colors3.yellow}${text}${colors3.reset}`,
1487
+ red: (text) => `${colors3.red}${text}${colors3.reset}`,
1488
+ magenta: (text) => `${colors3.magenta}${text}${colors3.reset}`
1489
+ };
1490
+ function createTimer() {
1491
+ const start = performance.now();
1492
+ return {
1493
+ elapsed: () => performance.now() - start,
1494
+ format: () => formatTime2(performance.now() - start)
1495
+ };
1496
+ }
1497
+
1498
+ // router/index.ts
1499
+ function olovaPlugins() {
1500
+ return [
1501
+ configPlugin(),
1502
+ // Must be first - creates .olova folder and sets config
1503
+ routerPlugin(),
1504
+ frameworkPlugin(),
1505
+ virtualHtmlPlugin(),
1506
+ ssgPlugin(),
1507
+ cleanUrlPlugin(),
1508
+ autoGeneratePlugin(),
1509
+ // Auto-generates boilerplate for new route files
1510
+ proactiveErrorPlugin()
1511
+ // Checks all files on save for instant error detection
1512
+ ];
1513
+ }
1514
+ export {
1515
+ autoGeneratePlugin,
1516
+ cleanUrlPlugin,
1517
+ colors3 as colors,
1518
+ configPlugin,
1519
+ createTimer,
1520
+ formatBytes2 as formatBytes,
1521
+ formatTime2 as formatTime,
1522
+ frameworkPlugin,
1523
+ generateBuildId,
1524
+ generateCriticalCSS,
1525
+ generateJsonLd,
1526
+ generateOlovaHydration,
1527
+ generatePerformanceMeta,
1528
+ generateResourceHints,
1529
+ generateServiceWorkerContent,
1530
+ generateServiceWorkerScript,
1531
+ logger2 as logger,
1532
+ minifyHtml,
1533
+ olovaPlugins,
1534
+ parseFlightData,
1535
+ proactiveErrorPlugin,
1536
+ routerPlugin,
1537
+ ssgPlugin,
1538
+ symbols2 as symbols,
1539
+ virtualHtmlPlugin
1540
+ };