bertui 0.4.5 → 1.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.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # BertUI ⚡
2
2
 
3
+ [![Stable: v1.0.0](https://img.shields.io/badge/Stable-v1.0.0-brightgreen)](https://github.com/your-repo)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
5
+
6
+ **The fastest, zero-config React static site generator. Now stable and production-ready.**
3
7
  Lightning-fast React development powered by Bun.
4
8
 
5
9
  ## ⚠️ Important Notice - CSS Animations Temporarily Unavailable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.4.5",
3
+ "version": "1.0.0",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -44,9 +44,6 @@
44
44
  "url": "https://github.com/BunElysiaReact/BERTUI.git"
45
45
  },
46
46
  "dependencies": {
47
- "@jsquash/jpeg": "^1.6.0",
48
- "@jsquash/png": "^3.1.1",
49
- "@jsquash/webp": "^1.5.0",
50
47
  "elysia": "^1.0.0",
51
48
  "ernest-logger": "latest",
52
49
  "lightningcss": "^1.30.2"
@@ -1,291 +1,86 @@
1
- // bertui/src/build/image-optimizer.js - FIXED VERSION
1
+ // bertui/src/build/image-optimizer.js - SIMPLE & STABLE
2
2
  import { join, extname } from 'path';
3
3
  import { existsSync, mkdirSync, readdirSync, cpSync } from 'fs';
4
4
  import logger from '../logger/logger.js';
5
5
 
6
6
  /**
7
- * 🎯 WASM-powered image optimization using @jsquash
8
- * Zero OS dependencies, pure JavaScript, blazing fast!
7
+ * Simple, reliable image copying
8
+ * No WASM, no optimization, just copy files
9
9
  */
10
10
 
11
- // Lazy-load WASM modules (only when needed)
12
- let pngEncode, pngDecode;
13
- let jpegEncode, jpegDecode;
14
- let webpEncode, webpDecode;
15
-
16
- async function initializePNG() {
17
- if (!pngEncode) {
18
- const { encode, decode } = await import('@jsquash/png');
19
- pngEncode = encode;
20
- pngDecode = decode;
21
- }
22
- }
23
-
24
- async function initializeJPEG() {
25
- if (!jpegEncode) {
26
- const { encode, decode } = await import('@jsquash/jpeg');
27
- jpegEncode = encode;
28
- jpegDecode = decode;
29
- }
30
- }
31
-
32
- async function initializeWebP() {
33
- if (!webpEncode) {
34
- const { encode, decode } = await import('@jsquash/webp');
35
- webpEncode = encode;
36
- webpDecode = decode;
37
- }
38
- }
39
-
40
- /**
41
- * Optimize images using WASM-powered codecs
42
- * This is FAST and has ZERO OS dependencies! 🚀
43
- */
44
11
  export async function optimizeImages(srcDir, outDir) {
45
- const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg'];
46
- let optimized = 0;
47
- let totalSaved = 0;
48
-
49
- logger.info(`🖼️ Optimizing images from ${srcDir} to ${outDir}...`);
50
-
51
- // Check if source directory exists
52
- if (!existsSync(srcDir)) {
53
- logger.warn(`⚠️ Source directory not found: ${srcDir}`);
54
- return { optimized: 0, saved: 0 };
55
- }
56
-
57
- // Create output directory if it doesn't exist
58
- if (!existsSync(outDir)) {
59
- mkdirSync(outDir, { recursive: true });
60
- }
61
-
62
- async function processDirectory(dir, targetDir) {
63
- const entries = readdirSync(dir, { withFileTypes: true });
64
-
65
- for (const entry of entries) {
66
- const srcPath = join(dir, entry.name);
67
- const destPath = join(targetDir, entry.name);
68
-
69
- if (entry.isDirectory()) {
70
- // Create subdirectory in target
71
- const subDestPath = join(targetDir, entry.name);
72
- if (!existsSync(subDestPath)) {
73
- mkdirSync(subDestPath, { recursive: true });
74
- }
75
- await processDirectory(srcPath, subDestPath);
76
- } else if (entry.isFile()) {
77
- const ext = extname(entry.name).toLowerCase();
78
-
79
- if (imageExtensions.includes(ext)) {
80
- try {
81
- const result = await optimizeImage(srcPath, destPath);
82
- if (result) {
83
- optimized++;
84
- totalSaved += result.saved;
85
- const savedPercent = ((result.saved / result.originalSize) * 100).toFixed(1);
86
- logger.debug(
87
- `✨ ${entry.name}: ${(result.originalSize / 1024).toFixed(1)}KB → ${(result.newSize / 1024).toFixed(1)}KB (-${savedPercent}%)`
88
- );
89
- }
90
- } catch (error) {
91
- logger.warn(`⚠️ Failed to optimize ${entry.name}: ${error.message}`);
92
- // Fallback: just copy the file
93
- try {
94
- cpSync(srcPath, destPath);
95
- logger.debug(`📋 Copied ${entry.name} (fallback)`);
96
- } catch (copyError) {
97
- logger.error(`❌ Failed to copy ${entry.name}: ${copyError.message}`);
98
- }
99
- }
100
- }
101
- }
102
- }
103
- }
104
-
105
- await processDirectory(srcDir, outDir);
106
-
107
- if (optimized > 0) {
108
- logger.success(
109
- `✅ Optimized ${optimized} images (saved ${(totalSaved / 1024).toFixed(2)}KB total)`
110
- );
111
- } else {
112
- logger.info(`📋 No images optimized (copied ${countFilesInDir(outDir)} files)`);
113
- }
114
-
115
- return { optimized, saved: totalSaved };
116
- }
117
-
118
- /**
119
- * Count files in directory (for logging)
120
- */
121
- function countFilesInDir(dir) {
122
- if (!existsSync(dir)) return 0;
123
-
124
- let count = 0;
125
- const entries = readdirSync(dir, { withFileTypes: true });
126
-
127
- for (const entry of entries) {
128
- if (entry.isFile()) {
129
- count++;
130
- } else if (entry.isDirectory()) {
131
- count += countFilesInDir(join(dir, entry.name));
132
- }
133
- }
134
-
135
- return count;
12
+ // Alias for copyImages to maintain API
13
+ logger.info(`📁 Copying from ${srcDir} to ${outDir}...`);
14
+ const copied = copyImages(srcDir, outDir);
15
+ return { optimized: 0, saved: 0, copied };
136
16
  }
137
17
 
138
- /**
139
- * Optimize a single image using WASM codecs
140
- */
141
- async function optimizeImage(srcPath, destPath) {
142
- const ext = extname(srcPath).toLowerCase();
143
- const originalFile = Bun.file(srcPath);
144
-
145
- // Check if file exists
146
- if (!await originalFile.exists()) {
147
- throw new Error(`File not found: ${srcPath}`);
148
- }
149
-
150
- const originalSize = originalFile.size;
151
-
152
- try {
153
- // For SVG and GIF, just copy (no optimization needed/supported)
154
- if (ext === '.svg' || ext === '.gif') {
155
- cpSync(srcPath, destPath);
156
- return null;
157
- }
158
-
159
- // Read the original image
160
- const originalBuffer = await originalFile.arrayBuffer();
161
-
162
- let optimizedBuffer;
163
-
164
- if (ext === '.png') {
165
- await initializePNG();
166
-
167
- // Decode → Re-encode with compression
168
- const imageData = await pngDecode(originalBuffer);
169
-
170
- // Encode with oxipng-level compression (quality 85, similar to oxipng -o 2)
171
- optimizedBuffer = await pngEncode(imageData, {
172
- quality: 85,
173
- effort: 2 // 0-10, higher = better compression but slower
174
- });
175
-
176
- } else if (ext === '.jpg' || ext === '.jpeg') {
177
- await initializeJPEG();
178
-
179
- // Decode → Re-encode with quality 85 (mozjpeg-like quality)
180
- const imageData = await jpegDecode(originalBuffer);
181
- optimizedBuffer = await jpegEncode(imageData, { quality: 85 });
182
-
183
- } else if (ext === '.webp') {
184
- await initializeWebP();
185
-
186
- // WebP optimization
187
- const imageData = await webpDecode(originalBuffer);
188
- optimizedBuffer = await webpEncode(imageData, { quality: 85 });
189
- } else {
190
- // Unsupported format, just copy
191
- cpSync(srcPath, destPath);
192
- return null;
193
- }
194
-
195
- // Only save if we actually reduced the size
196
- if (optimizedBuffer && optimizedBuffer.byteLength < originalSize) {
197
- await Bun.write(destPath, optimizedBuffer);
198
- const saved = originalSize - optimizedBuffer.byteLength;
199
- return { saved, originalSize, newSize: optimizedBuffer.byteLength };
200
- }
201
-
202
- // If optimization didn't help, just copy the original
203
- cpSync(srcPath, destPath);
204
- return null;
205
-
206
- } catch (error) {
207
- // If anything fails, just copy the original
208
- logger.warn(`Optimization failed for ${srcPath.split('/').pop()}, copying original`);
209
- cpSync(srcPath, destPath);
210
- return null;
211
- }
212
- }
213
-
214
- /**
215
- * Check if optimization is available (always true with WASM! 🎉)
216
- */
217
18
  export async function checkOptimizationTools() {
218
- try {
219
- // Try to import the WASM modules
220
- await import('@jsquash/png');
221
- await import('@jsquash/jpeg');
222
- await import('@jsquash/webp');
223
-
224
- logger.success('✅ WASM image optimization available');
225
- logger.info('📦 Using @jsquash (zero OS dependencies!)');
226
- return ['png', 'jpeg', 'webp'];
227
- } catch (error) {
228
- logger.error('❌ WASM codecs not installed. Run: bun add @jsquash/png @jsquash/jpeg @jsquash/webp');
229
- return [];
230
- }
19
+ // Always return empty array to disable optimization
20
+ return [];
231
21
  }
232
22
 
233
- /**
234
- * Copy images without optimization (fallback)
235
- */
236
23
  export function copyImages(srcDir, outDir) {
237
- const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif', '.ico'];
24
+ // All common image formats
25
+ const imageExtensions = [
26
+ '.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg',
27
+ '.avif', '.ico', '.bmp', '.tiff', '.tif'
28
+ ];
29
+
238
30
  let copied = 0;
31
+ let skipped = 0;
239
32
 
240
- // Check if source directory exists
241
33
  if (!existsSync(srcDir)) {
242
- logger.warn(`⚠️ Source directory not found: ${srcDir}`);
34
+ logger.warn(`⚠️ Source not found: ${srcDir}`);
243
35
  return 0;
244
36
  }
245
37
 
246
- // Create output directory if it doesn't exist
247
- if (!existsSync(outDir)) {
248
- mkdirSync(outDir, { recursive: true });
249
- }
38
+ // Ensure output directory exists
39
+ mkdirSync(outDir, { recursive: true });
250
40
 
251
41
  function processDirectory(dir, targetDir) {
252
- const entries = readdirSync(dir, { withFileTypes: true });
42
+ try {
43
+ const entries = readdirSync(dir, { withFileTypes: true });
253
44
 
254
- for (const entry of entries) {
255
- const srcPath = join(dir, entry.name);
256
- const destPath = join(targetDir, entry.name);
45
+ for (const entry of entries) {
46
+ const srcPath = join(dir, entry.name);
47
+ const destPath = join(targetDir, entry.name);
257
48
 
258
- if (entry.isDirectory()) {
259
- // Create subdirectory in target
260
- const subDestPath = join(targetDir, entry.name);
261
- if (!existsSync(subDestPath)) {
49
+ if (entry.isDirectory()) {
50
+ // Recursively process subdirectories
51
+ const subDestPath = join(targetDir, entry.name);
262
52
  mkdirSync(subDestPath, { recursive: true });
263
- }
264
- // FIXED: Use destPath, not targetDir
265
- processDirectory(srcPath, subDestPath);
266
- } else if (entry.isFile()) {
267
- const ext = extname(entry.name).toLowerCase();
53
+ processDirectory(srcPath, subDestPath);
54
+ } else if (entry.isFile()) {
55
+ const ext = extname(entry.name).toLowerCase();
268
56
 
269
- if (imageExtensions.includes(ext)) {
270
- try {
271
- cpSync(srcPath, destPath);
272
- copied++;
273
- logger.debug(`📋 Copied ${entry.name}`);
274
- } catch (error) {
275
- logger.warn(`Failed to copy ${entry.name}: ${error.message}`);
57
+ if (imageExtensions.includes(ext)) {
58
+ try {
59
+ cpSync(srcPath, destPath);
60
+ copied++;
61
+ } catch (error) {
62
+ logger.warn(` Failed to copy ${entry.name}: ${error.message}`);
63
+ skipped++;
64
+ }
65
+ } else {
66
+ skipped++;
276
67
  }
277
68
  }
278
69
  }
70
+ } catch (error) {
71
+ logger.error(`Error processing ${dir}: ${error.message}`);
279
72
  }
280
73
  }
281
74
 
282
75
  processDirectory(srcDir, outDir);
283
-
76
+
284
77
  if (copied > 0) {
285
- logger.info(`📋 Copied ${copied} images without optimization`);
286
- } else {
287
- logger.warn(`⚠️ No images found in ${srcDir}`);
78
+ logger.success(`✅ Copied ${copied} image(s) to ${outDir}`);
288
79
  }
289
80
 
81
+ if (skipped > 0) {
82
+ logger.info(`📝 Skipped ${skipped} non-image file(s)`);
83
+ }
84
+
290
85
  return copied;
291
86
  }
package/src/build.js CHANGED
@@ -1,4 +1,4 @@
1
- // src/build.js - FIXED VERSION
1
+ // src/build.js - COMPLETELY FIXED VERSION
2
2
  import { join, relative, basename, extname, dirname } from 'path';
3
3
  import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
4
4
  import logger from './logger/logger.js';
@@ -13,6 +13,7 @@ export async function buildProduction(options = {}) {
13
13
 
14
14
  logger.bigLog('BUILDING FOR PRODUCTION', { color: 'green' });
15
15
 
16
+ // Clean up old builds
16
17
  if (existsSync(buildDir)) {
17
18
  rmSync(buildDir, { recursive: true });
18
19
  }
@@ -35,7 +36,7 @@ export async function buildProduction(options = {}) {
35
36
 
36
37
  logger.info('Step 1: Compiling for production...');
37
38
  const { routes } = await compileForBuild(root, buildDir, envVars);
38
- logger.success('Production compilation complete');
39
+ logger.success(`Production compilation complete - ${routes.length} routes`);
39
40
 
40
41
  logger.info('Step 2: Building CSS with Lightning CSS...');
41
42
  await buildAllCSS(root, outDir);
@@ -44,8 +45,8 @@ export async function buildProduction(options = {}) {
44
45
  const optimizationTools = await checkOptimizationTools();
45
46
 
46
47
  logger.info('Step 4: Copying and optimizing static assets...');
47
- // FIX 1: Copy images from BOTH src/images/ and public/
48
- await copyAllStaticAssets(root, outDir, optimizationTools.length > 0);
48
+ // SKIP OPTIMIZATION FOR NOW - JUST COPY
49
+ await copyAllStaticAssets(root, outDir);
49
50
 
50
51
  logger.info('Step 5: Bundling JavaScript with Bun...');
51
52
  const buildEntry = join(buildDir, 'main.js');
@@ -85,22 +86,33 @@ export async function buildProduction(options = {}) {
85
86
  process.exit(1);
86
87
  }
87
88
 
88
- logger.success('JavaScript bundled with tree-shaking');
89
+ logger.success('JavaScript bundled successfully');
89
90
 
90
- logger.info('Step 6: Generating SEO-optimized HTML files...');
91
- // ✅ FIX 2: Generate HTML for ALL routes including index.html
91
+ // DEBUG: Show what was built
92
+ logger.info('Built outputs:');
93
+ result.outputs.forEach((output, i) => {
94
+ logger.info(` ${i + 1}. ${relative(outDir, output.path)} (${output.kind})`);
95
+ });
96
+
97
+ logger.info('Step 6: Generating HTML files...');
98
+ // ✅ CRITICAL FIX: Generate HTML files
92
99
  await generateProductionHTML(root, outDir, result, routes);
93
100
 
94
- rmSync(buildDir, { recursive: true });
95
- logger.info('Cleaned up .bertuibuild/');
101
+ // Clean up build directory
102
+ if (existsSync(buildDir)) {
103
+ rmSync(buildDir, { recursive: true });
104
+ logger.info('Cleaned up .bertuibuild/');
105
+ }
96
106
 
97
107
  const duration = Date.now() - startTime;
98
108
  logger.success(`✨ Build complete in ${duration}ms`);
99
109
  logger.info(`📦 Output: ${outDir}`);
100
110
 
111
+ // Show build summary
101
112
  logger.table(result.outputs.map(o => ({
102
113
  file: o.path.replace(outDir, ''),
103
- size: `${(o.size / 1024).toFixed(2)} KB`
114
+ size: `${(o.size / 1024).toFixed(2)} KB`,
115
+ type: o.kind
104
116
  })));
105
117
 
106
118
  logger.bigLog('READY TO DEPLOY', { color: 'green' });
@@ -108,7 +120,7 @@ export async function buildProduction(options = {}) {
108
120
  console.log(' Vercel: bunx vercel');
109
121
  console.log(' Netlify: bunx netlify deploy');
110
122
  console.log('\n🔍 Preview locally:\n');
111
- console.log(' bun run preview\n');
123
+ console.log(' cd dist && bun run preview\n');
112
124
 
113
125
  } catch (error) {
114
126
  logger.error(`Build failed: ${error.message}`);
@@ -124,64 +136,28 @@ export async function buildProduction(options = {}) {
124
136
  }
125
137
  }
126
138
 
127
- // FIX 3: Enhanced asset copying with proper directory structure
128
- // ✅ FIX 3: Enhanced asset copying with proper directory structure
129
- async function copyAllStaticAssets(root, outDir, optimize = true) {
139
+ async function copyAllStaticAssets(root, outDir) {
130
140
  const publicDir = join(root, 'public');
131
141
  const srcImagesDir = join(root, 'src', 'images');
132
142
 
133
- let assetsCopied = 0;
134
- let assetsOptimized = 0;
143
+ logger.info('📦 Copying static assets...');
135
144
 
136
- logger.info(`🔍 Checking source directories...`);
137
- logger.info(` public/: ${existsSync(publicDir) ? '✅ exists' : '❌ not found'}`);
138
- logger.info(` src/images/: ${existsSync(srcImagesDir) ? '✅ exists' : '❌ not found'}`);
139
-
140
- // Create images directory in dist/
141
- const distImagesDir = join(outDir, 'images');
142
- if (!existsSync(distImagesDir)) {
143
- mkdirSync(distImagesDir, { recursive: true });
144
- }
145
-
146
- // Copy from public/ to root of dist/
145
+ // Copy from public/ to dist/
147
146
  if (existsSync(publicDir)) {
148
- logger.info('Copying public/ assets...');
149
- if (optimize) {
150
- const result = await optimizeImages(publicDir, outDir);
151
- assetsOptimized += result.optimized;
152
- } else {
153
- assetsCopied += copyImages(publicDir, outDir);
154
- }
155
- } else {
156
- logger.info('No public/ directory found, skipping...');
147
+ logger.info(' Copying public/ directory...');
148
+ copyImages(publicDir, outDir);
157
149
  }
158
150
 
159
- // ✅ FIX: Copy from src/images/ to dist/images/
151
+ // Copy from src/images/ to dist/images/
160
152
  if (existsSync(srcImagesDir)) {
161
- logger.info(`Copying src/images/ to dist/images/...`);
162
-
163
- // Debug: List files in src/images/
164
- const files = readdirSync(srcImagesDir);
165
- logger.info(`Found ${files.length} items in src/images/: ${files.join(', ')}`);
166
-
167
- if (optimize) {
168
- const result = await optimizeImages(srcImagesDir, distImagesDir);
169
- assetsOptimized += result.optimized;
170
- } else {
171
- assetsCopied += copyImages(srcImagesDir, distImagesDir);
172
- }
173
- } else {
174
- logger.info('No src/images/ directory found, skipping...');
153
+ const distImagesDir = join(outDir, 'images');
154
+ logger.info(` Copying src/images/ to ${relative(root, distImagesDir)}/...`);
155
+ copyImages(srcImagesDir, distImagesDir);
175
156
  }
176
157
 
177
- if (optimize && assetsOptimized > 0) {
178
- logger.success(`🎨 Optimized ${assetsOptimized} images with WASM codecs`);
179
- } else if (assetsCopied > 0) {
180
- logger.success(`📋 Copied ${assetsCopied} static assets`);
181
- } else {
182
- logger.warn(`⚠️ No static assets found or copied`);
183
- }
158
+ logger.success('✅ All assets copied');
184
159
  }
160
+
185
161
  async function buildAllCSS(root, outDir) {
186
162
  const srcStylesDir = join(root, 'src', 'styles');
187
163
  const stylesOutDir = join(outDir, 'styles');
@@ -569,63 +545,71 @@ function extractMetaFromSource(code) {
569
545
  }
570
546
  }
571
547
 
572
- // ✅ FIX 4: Generate proper HTML files with correct meta tags
548
+ // ✅ CRITICAL FIX: Generate HTML files
573
549
  async function generateProductionHTML(root, outDir, buildResult, routes) {
550
+ if (routes.length === 0) {
551
+ logger.warn('No routes found, skipping HTML generation');
552
+ return;
553
+ }
554
+
555
+ logger.info(`Generating HTML files for ${routes.length} routes...`);
556
+
557
+ // Find main JS bundle
574
558
  const mainBundle = buildResult.outputs.find(o =>
575
559
  o.path.includes('main') && o.kind === 'entry-point'
576
560
  );
577
561
 
578
562
  if (!mainBundle) {
579
- throw new Error('Could not find main bundle in build output');
563
+ logger.error('Could not find main bundle in build output');
564
+ // List all outputs for debugging
565
+ logger.info('Available outputs:');
566
+ buildResult.outputs.forEach((o, i) => {
567
+ logger.info(` ${i + 1}. ${o.path} (${o.kind})`);
568
+ });
569
+ return;
580
570
  }
581
571
 
582
572
  const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
583
- logger.info(`Main bundle path: ${bundlePath}`);
573
+ logger.info(`Main bundle: ${bundlePath}`);
584
574
 
585
- const srcStylesDir = join(root, 'src', 'styles');
586
- let userStylesheets = '';
587
-
588
- if (existsSync(srcStylesDir)) {
589
- const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
590
- userStylesheets = cssFiles.map(f =>
591
- ` <link rel="stylesheet" href="/styles/${f.replace('.css', '.min.css')}">`
592
- ).join('\n');
593
- }
594
-
595
- // ✅ Load config for default meta
575
+ // Load config for default meta
596
576
  const { loadConfig } = await import('./config/loadConfig.js');
597
577
  const config = await loadConfig(root);
598
578
  const defaultMeta = config.meta || {};
599
579
 
600
- logger.info('Generating SEO-optimized HTML files...');
601
-
602
- // ✅ FIX: Generate HTML for ALL routes (including dynamic as fallback)
580
+ // Generate HTML for each route
603
581
  for (const route of routes) {
604
- const sourceCode = await Bun.file(route.path).text();
605
- const pageMeta = extractMetaFromSource(sourceCode);
606
- const meta = { ...defaultMeta, ...pageMeta };
607
-
608
- if (pageMeta) {
609
- logger.info(`Extracted meta for ${route.route}: ${JSON.stringify(pageMeta)}`);
610
- }
611
-
612
- const html = generateHTML(meta, route, bundlePath, userStylesheets);
613
-
614
- let htmlPath;
615
- if (route.route === '/') {
616
- htmlPath = join(outDir, 'index.html');
617
- } else {
618
- const routeDir = join(outDir, route.route);
619
- mkdirSync(routeDir, { recursive: true });
620
- htmlPath = join(routeDir, 'index.html');
582
+ try {
583
+ const sourceCode = await Bun.file(route.path).text();
584
+ const pageMeta = extractMetaFromSource(sourceCode);
585
+ const meta = { ...defaultMeta, ...pageMeta };
586
+
587
+ const html = generateHTML(meta, route, bundlePath);
588
+
589
+ let htmlPath;
590
+ if (route.route === '/') {
591
+ htmlPath = join(outDir, 'index.html');
592
+ } else {
593
+ // Create directory for the route
594
+ const routeDir = join(outDir, route.route.slice(1)); // Remove leading slash
595
+ mkdirSync(routeDir, { recursive: true });
596
+ htmlPath = join(routeDir, 'index.html');
597
+ }
598
+
599
+ await Bun.write(htmlPath, html);
600
+ logger.success(`Generated: ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
601
+ } catch (error) {
602
+ logger.error(`Failed to generate HTML for ${route.route}: ${error.message}`);
621
603
  }
622
-
623
- await Bun.write(htmlPath, html);
624
- logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
625
604
  }
626
605
  }
627
606
 
628
- function generateHTML(meta, route, bundlePath, userStylesheets) {
607
+ function generateHTML(meta, route, bundlePath) {
608
+ const cssFiles = ['global.min.css', 'home.min.css'];
609
+ const stylesheets = cssFiles.map(css =>
610
+ ` <link rel="stylesheet" href="/styles/${css}">`
611
+ ).join('\n');
612
+
629
613
  return `<!DOCTYPE html>
630
614
  <html lang="${meta.lang || 'en'}">
631
615
  <head>
@@ -636,23 +620,10 @@ function generateHTML(meta, route, bundlePath, userStylesheets) {
636
620
  <meta name="description" content="${meta.description || 'Built with BertUI - Lightning fast React development'}">
637
621
  ${meta.keywords ? `<meta name="keywords" content="${meta.keywords}">` : ''}
638
622
  ${meta.author ? `<meta name="author" content="${meta.author}">` : ''}
639
- ${meta.themeColor ? `<meta name="theme-color" content="${meta.themeColor}">` : ''}
640
-
641
- <meta property="og:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
642
- <meta property="og:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
643
- ${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
644
- <meta property="og:type" content="website">
645
- <meta property="og:url" content="${route.route}">
646
-
647
- <meta name="twitter:card" content="summary_large_image">
648
- <meta name="twitter:title" content="${meta.ogTitle || meta.title || 'BertUI App'}">
649
- <meta name="twitter:description" content="${meta.ogDescription || meta.description || 'Built with BertUI'}">
650
- ${meta.ogImage ? `<meta name="twitter:image" content="${meta.ogImage}">` : ''}
651
623
 
652
624
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
653
- <link rel="canonical" href="${route.route}">
654
625
 
655
- ${userStylesheets}
626
+ ${stylesheets}
656
627
 
657
628
  <script type="importmap">
658
629
  {