bertui 0.4.2 → 0.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -1,4 +1,4 @@
1
- // bertui/src/build/image-optimizer.js - WASM-POWERED VERSION 🚀
1
+ // bertui/src/build/image-optimizer.js - FIXED VERSION
2
2
  import { join, extname } from 'path';
3
3
  import { existsSync, mkdirSync, readdirSync, cpSync } from 'fs';
4
4
  import logger from '../logger/logger.js';
@@ -46,7 +46,18 @@ export async function optimizeImages(srcDir, outDir) {
46
46
  let optimized = 0;
47
47
  let totalSaved = 0;
48
48
 
49
- logger.info('🖼️ Optimizing images with WASM codecs...');
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
+ }
50
61
 
51
62
  async function processDirectory(dir, targetDir) {
52
63
  const entries = readdirSync(dir, { withFileTypes: true });
@@ -56,10 +67,12 @@ export async function optimizeImages(srcDir, outDir) {
56
67
  const destPath = join(targetDir, entry.name);
57
68
 
58
69
  if (entry.isDirectory()) {
59
- if (!existsSync(destPath)) {
60
- mkdirSync(destPath, { recursive: true });
70
+ // Create subdirectory in target
71
+ const subDestPath = join(targetDir, entry.name);
72
+ if (!existsSync(subDestPath)) {
73
+ mkdirSync(subDestPath, { recursive: true });
61
74
  }
62
- await processDirectory(srcPath, destPath);
75
+ await processDirectory(srcPath, subDestPath);
63
76
  } else if (entry.isFile()) {
64
77
  const ext = extname(entry.name).toLowerCase();
65
78
 
@@ -77,7 +90,12 @@ export async function optimizeImages(srcDir, outDir) {
77
90
  } catch (error) {
78
91
  logger.warn(`⚠️ Failed to optimize ${entry.name}: ${error.message}`);
79
92
  // Fallback: just copy the file
80
- cpSync(srcPath, destPath);
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
+ }
81
99
  }
82
100
  }
83
101
  }
@@ -90,17 +108,45 @@ export async function optimizeImages(srcDir, outDir) {
90
108
  logger.success(
91
109
  `✅ Optimized ${optimized} images (saved ${(totalSaved / 1024).toFixed(2)}KB total)`
92
110
  );
111
+ } else {
112
+ logger.info(`📋 No images optimized (copied ${countFilesInDir(outDir)} files)`);
93
113
  }
94
114
 
95
115
  return { optimized, saved: totalSaved };
96
116
  }
97
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;
136
+ }
137
+
98
138
  /**
99
139
  * Optimize a single image using WASM codecs
100
140
  */
101
141
  async function optimizeImage(srcPath, destPath) {
102
142
  const ext = extname(srcPath).toLowerCase();
103
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
+
104
150
  const originalSize = originalFile.size;
105
151
 
106
152
  try {
@@ -140,6 +186,10 @@ async function optimizeImage(srcPath, destPath) {
140
186
  // WebP optimization
141
187
  const imageData = await webpDecode(originalBuffer);
142
188
  optimizedBuffer = await webpEncode(imageData, { quality: 85 });
189
+ } else {
190
+ // Unsupported format, just copy
191
+ cpSync(srcPath, destPath);
192
+ return null;
143
193
  }
144
194
 
145
195
  // Only save if we actually reduced the size
@@ -184,9 +234,20 @@ export async function checkOptimizationTools() {
184
234
  * Copy images without optimization (fallback)
185
235
  */
186
236
  export function copyImages(srcDir, outDir) {
187
- const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif'];
237
+ const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg', '.avif', '.ico'];
188
238
  let copied = 0;
189
239
 
240
+ // Check if source directory exists
241
+ if (!existsSync(srcDir)) {
242
+ logger.warn(`⚠️ Source directory not found: ${srcDir}`);
243
+ return 0;
244
+ }
245
+
246
+ // Create output directory if it doesn't exist
247
+ if (!existsSync(outDir)) {
248
+ mkdirSync(outDir, { recursive: true });
249
+ }
250
+
190
251
  function processDirectory(dir, targetDir) {
191
252
  const entries = readdirSync(dir, { withFileTypes: true });
192
253
 
@@ -195,22 +256,36 @@ export function copyImages(srcDir, outDir) {
195
256
  const destPath = join(targetDir, entry.name);
196
257
 
197
258
  if (entry.isDirectory()) {
198
- if (!existsSync(destPath)) {
199
- mkdirSync(destPath, { recursive: true });
259
+ // Create subdirectory in target
260
+ const subDestPath = join(targetDir, entry.name);
261
+ if (!existsSync(subDestPath)) {
262
+ mkdirSync(subDestPath, { recursive: true });
200
263
  }
201
- processDirectory(srcPath, targetDir);
264
+ // FIXED: Use destPath, not targetDir
265
+ processDirectory(srcPath, subDestPath);
202
266
  } else if (entry.isFile()) {
203
267
  const ext = extname(entry.name).toLowerCase();
204
268
 
205
269
  if (imageExtensions.includes(ext)) {
206
- cpSync(srcPath, destPath);
207
- copied++;
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}`);
276
+ }
208
277
  }
209
278
  }
210
279
  }
211
280
  }
212
281
 
213
282
  processDirectory(srcDir, outDir);
214
- logger.info(`📋 Copied ${copied} images without optimization`);
283
+
284
+ if (copied > 0) {
285
+ logger.info(`📋 Copied ${copied} images without optimization`);
286
+ } else {
287
+ logger.warn(`⚠️ No images found in ${srcDir}`);
288
+ }
289
+
215
290
  return copied;
216
291
  }
package/src/build.js CHANGED
@@ -1,3 +1,4 @@
1
+ // src/build.js - FIXED VERSION
1
2
  import { join, relative, basename, extname, dirname } from 'path';
2
3
  import { existsSync, mkdirSync, rmSync, cpSync, readdirSync, statSync } from 'fs';
3
4
  import logger from './logger/logger.js';
@@ -39,18 +40,12 @@ export async function buildProduction(options = {}) {
39
40
  logger.info('Step 2: Building CSS with Lightning CSS...');
40
41
  await buildAllCSS(root, outDir);
41
42
 
42
- // ✅ NEW: Check if image optimization is available
43
43
  logger.info('Step 3: Checking image optimization tools...');
44
44
  const optimizationTools = await checkOptimizationTools();
45
45
 
46
46
  logger.info('Step 4: Copying and optimizing static assets...');
47
- if (optimizationTools.length > 0) {
48
- // Use WASM-powered optimization
49
- await copyAllStaticAssets(root, outDir, true);
50
- } else {
51
- // Fallback: just copy images
52
- await copyAllStaticAssets(root, outDir, false);
53
- }
47
+ // FIX 1: Copy images from BOTH src/images/ and public/
48
+ await copyAllStaticAssets(root, outDir, optimizationTools.length > 0);
54
49
 
55
50
  logger.info('Step 5: Bundling JavaScript with Bun...');
56
51
  const buildEntry = join(buildDir, 'main.js');
@@ -75,9 +70,6 @@ export async function buildProduction(options = {}) {
75
70
  external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
76
71
  define: {
77
72
  'process.env.NODE_ENV': '"production"',
78
- 'process.env.PUBLIC_APP_NAME': JSON.stringify(envVars.PUBLIC_APP_NAME || 'BertUI App'),
79
- 'process.env.PUBLIC_API_URL': JSON.stringify(envVars.PUBLIC_API_URL || ''),
80
- 'process.env.PUBLIC_USERNAME': JSON.stringify(envVars.PUBLIC_USERNAME || ''),
81
73
  ...Object.fromEntries(
82
74
  Object.entries(envVars).map(([key, value]) => [
83
75
  `process.env.${key}`,
@@ -96,6 +88,7 @@ export async function buildProduction(options = {}) {
96
88
  logger.success('JavaScript bundled with tree-shaking');
97
89
 
98
90
  logger.info('Step 6: Generating SEO-optimized HTML files...');
91
+ // ✅ FIX 2: Generate HTML for ALL routes including index.html
99
92
  await generateProductionHTML(root, outDir, result, routes);
100
93
 
101
94
  rmSync(buildDir, { recursive: true });
@@ -131,44 +124,64 @@ export async function buildProduction(options = {}) {
131
124
  }
132
125
  }
133
126
 
134
- // ✅ UPDATED: Copy and optionally optimize static assets
127
+ // ✅ FIX 3: Enhanced asset copying with proper directory structure
128
+ // ✅ FIX 3: Enhanced asset copying with proper directory structure
135
129
  async function copyAllStaticAssets(root, outDir, optimize = true) {
136
130
  const publicDir = join(root, 'public');
137
- const srcDir = join(root, 'src');
131
+ const srcImagesDir = join(root, 'src', 'images');
138
132
 
139
133
  let assetsCopied = 0;
140
134
  let assetsOptimized = 0;
141
135
 
142
- // Copy from public/
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/
143
147
  if (existsSync(publicDir)) {
148
+ logger.info('Copying public/ assets...');
144
149
  if (optimize) {
145
150
  const result = await optimizeImages(publicDir, outDir);
146
151
  assetsOptimized += result.optimized;
147
152
  } else {
148
153
  assetsCopied += copyImages(publicDir, outDir);
149
154
  }
155
+ } else {
156
+ logger.info('No public/ directory found, skipping...');
150
157
  }
151
158
 
152
- // Copy static assets from src/ (images, fonts, etc.)
153
- if (existsSync(srcDir)) {
154
- const assetsOutDir = join(outDir, 'assets');
155
- mkdirSync(assetsOutDir, { recursive: true });
159
+ // FIX: Copy from src/images/ to dist/images/
160
+ 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(', ')}`);
156
166
 
157
167
  if (optimize) {
158
- const result = await optimizeImages(srcDir, assetsOutDir);
168
+ const result = await optimizeImages(srcImagesDir, distImagesDir);
159
169
  assetsOptimized += result.optimized;
160
170
  } else {
161
- assetsCopied += copyImages(srcDir, assetsOutDir);
171
+ assetsCopied += copyImages(srcImagesDir, distImagesDir);
162
172
  }
173
+ } else {
174
+ logger.info('No src/images/ directory found, skipping...');
163
175
  }
164
176
 
165
177
  if (optimize && assetsOptimized > 0) {
166
178
  logger.success(`🎨 Optimized ${assetsOptimized} images with WASM codecs`);
167
- } else {
179
+ } else if (assetsCopied > 0) {
168
180
  logger.success(`📋 Copied ${assetsCopied} static assets`);
181
+ } else {
182
+ logger.warn(`⚠️ No static assets found or copied`);
169
183
  }
170
184
  }
171
-
172
185
  async function buildAllCSS(root, outDir) {
173
186
  const srcStylesDir = join(root, 'src', 'styles');
174
187
  const stylesOutDir = join(outDir, 'styles');
@@ -556,6 +569,7 @@ function extractMetaFromSource(code) {
556
569
  }
557
570
  }
558
571
 
572
+ // ✅ FIX 4: Generate proper HTML files with correct meta tags
559
573
  async function generateProductionHTML(root, outDir, buildResult, routes) {
560
574
  const mainBundle = buildResult.outputs.find(o =>
561
575
  o.path.includes('main') && o.kind === 'entry-point'
@@ -578,18 +592,15 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
578
592
  ).join('\n');
579
593
  }
580
594
 
595
+ // ✅ Load config for default meta
581
596
  const { loadConfig } = await import('./config/loadConfig.js');
582
597
  const config = await loadConfig(root);
583
598
  const defaultMeta = config.meta || {};
584
599
 
585
600
  logger.info('Generating SEO-optimized HTML files...');
586
601
 
602
+ // ✅ FIX: Generate HTML for ALL routes (including dynamic as fallback)
587
603
  for (const route of routes) {
588
- if (route.type === 'dynamic') {
589
- logger.info(`Skipping dynamic route: ${route.route}`);
590
- continue;
591
- }
592
-
593
604
  const sourceCode = await Bun.file(route.path).text();
594
605
  const pageMeta = extractMetaFromSource(sourceCode);
595
606
  const meta = { ...defaultMeta, ...pageMeta };
@@ -598,7 +609,24 @@ async function generateProductionHTML(root, outDir, buildResult, routes) {
598
609
  logger.info(`Extracted meta for ${route.route}: ${JSON.stringify(pageMeta)}`);
599
610
  }
600
611
 
601
- const html = `<!DOCTYPE html>
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');
621
+ }
622
+
623
+ await Bun.write(htmlPath, html);
624
+ logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'}`);
625
+ }
626
+ }
627
+
628
+ function generateHTML(meta, route, bundlePath, userStylesheets) {
629
+ return `<!DOCTYPE html>
602
630
  <html lang="${meta.lang || 'en'}">
603
631
  <head>
604
632
  <meta charset="UTF-8">
@@ -642,17 +670,4 @@ ${userStylesheets}
642
670
  <script type="module" src="/${bundlePath}"></script>
643
671
  </body>
644
672
  </html>`;
645
-
646
- let htmlPath;
647
- if (route.route === '/') {
648
- htmlPath = join(outDir, 'index.html');
649
- } else {
650
- const routeDir = join(outDir, route.route);
651
- mkdirSync(routeDir, { recursive: true });
652
- htmlPath = join(routeDir, 'index.html');
653
- }
654
-
655
- await Bun.write(htmlPath, html);
656
- logger.success(`Generated ${route.route === '/' ? 'index.html' : route.route + '/index.html'} with meta: ${meta.title || 'default'}`);
657
- }
658
673
  }
@@ -1,4 +1,4 @@
1
- // src/server/dev-server.js - FIXED VERSION
1
+ // src/server/dev-server.js - FIXED IMAGE SERVING
2
2
  import { Elysia } from 'elysia';
3
3
  import { watch } from 'fs';
4
4
  import { join, extname } from 'path';
@@ -13,6 +13,7 @@ export async function startDevServer(options = {}) {
13
13
  const compiledDir = join(root, '.bertui', 'compiled');
14
14
  const stylesDir = join(root, '.bertui', 'styles');
15
15
  const srcDir = join(root, 'src');
16
+ const publicDir = join(root, 'public');
16
17
 
17
18
  const config = await loadConfig(root);
18
19
 
@@ -30,10 +31,10 @@ export async function startDevServer(options = {}) {
30
31
  return serveHTML(root, hasRouter, config);
31
32
  })
32
33
 
33
- // ✅ NEW: Serve images from src/images/
34
+ // ✅ FIX: Serve images from src/images/ (CRITICAL)
34
35
  .get('/images/*', async ({ params, set }) => {
35
- const imagesDir = join(srcDir, 'images');
36
- const filepath = join(imagesDir, params['*']);
36
+ const srcImagesDir = join(srcDir, 'images');
37
+ const filepath = join(srcImagesDir, params['*']);
37
38
  const file = Bun.file(filepath);
38
39
 
39
40
  if (!await file.exists()) {
@@ -47,12 +48,27 @@ export async function startDevServer(options = {}) {
47
48
  return new Response(file, {
48
49
  headers: {
49
50
  'Content-Type': contentType,
50
- 'Cache-Control': 'public, max-age=31536000'
51
+ 'Cache-Control': 'no-cache' // Dev server = no cache
51
52
  }
52
53
  });
53
54
  })
54
55
 
55
- // ✅ NEW: Serve any static asset from src/ (fonts, videos, etc.)
56
+ // ✅ Serve from public/ directory
57
+ .get('/public/*', async ({ params, set }) => {
58
+ const filepath = join(publicDir, params['*']);
59
+ const file = Bun.file(filepath);
60
+
61
+ if (!await file.exists()) {
62
+ set.status = 404;
63
+ return 'File not found';
64
+ }
65
+
66
+ return new Response(file, {
67
+ headers: { 'Cache-Control': 'no-cache' }
68
+ });
69
+ })
70
+
71
+ // ✅ Generic asset serving
56
72
  .get('/assets/*', async ({ params, set }) => {
57
73
  const filepath = join(srcDir, params['*']);
58
74
  const file = Bun.file(filepath);
@@ -68,7 +84,7 @@ export async function startDevServer(options = {}) {
68
84
  return new Response(file, {
69
85
  headers: {
70
86
  'Content-Type': contentType,
71
- 'Cache-Control': 'public, max-age=31536000'
87
+ 'Cache-Control': 'no-cache'
72
88
  }
73
89
  });
74
90
  })
@@ -108,16 +124,6 @@ export async function startDevServer(options = {}) {
108
124
  }
109
125
  }
110
126
 
111
- if (path.startsWith('public/')) {
112
- const publicDir = join(root, 'public');
113
- const filepath = join(publicDir, path.replace('public/', ''));
114
- const file = Bun.file(filepath);
115
-
116
- if (await file.exists()) {
117
- return new Response(file);
118
- }
119
- }
120
-
121
127
  set.status = 404;
122
128
  return 'File not found';
123
129
  }
@@ -436,19 +442,6 @@ ws.onclose = () => {
436
442
  });
437
443
  })
438
444
 
439
- .get('/public/*', async ({ params, set }) => {
440
- const publicDir = join(root, 'public');
441
- const filepath = join(publicDir, params['*']);
442
- const file = Bun.file(filepath);
443
-
444
- if (!await file.exists()) {
445
- set.status = 404;
446
- return 'File not found';
447
- }
448
-
449
- return new Response(file);
450
- })
451
-
452
445
  .listen(port);
453
446
 
454
447
  if (!app.server) {
@@ -458,7 +451,8 @@ ws.onclose = () => {
458
451
 
459
452
  logger.success(`🚀 Server running at http://localhost:${port}`);
460
453
  logger.info(`📁 Serving: ${root}`);
461
- logger.info(`🖼️ Images available at: /images/*`);
454
+ logger.info(`🖼️ Images: /images/* src/images/`);
455
+ logger.info(`📦 Public: /public/* → public/`);
462
456
 
463
457
  setupWatcher(root, compiledDir, clients, async () => {
464
458
  hasRouter = existsSync(join(compiledDir, 'router.js'));
@@ -536,7 +530,6 @@ ${userStylesheets}
536
530
  });
537
531
  }
538
532
 
539
- // ✅ NEW: Helper for image content types
540
533
  function getImageContentType(ext) {
541
534
  const types = {
542
535
  '.jpg': 'image/jpeg',
@@ -594,14 +587,12 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
594
587
  if (!filename) return;
595
588
 
596
589
  const ext = extname(filename);
597
-
598
- // ✅ Watch image changes too
599
590
  const watchedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'];
600
591
 
601
592
  if (watchedExtensions.includes(ext)) {
602
593
  logger.info(`📝 File changed: ${filename}`);
603
594
 
604
- // For images, just reload without recompiling
595
+ // For images, just reload
605
596
  if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
606
597
  for (const client of clients) {
607
598
  try {