bertui 1.1.1 → 1.1.2

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,191 @@
1
+ // bertui/src/pagebuilder/core.js
2
+ import { join } from 'path';
3
+ import { existsSync, mkdirSync } from 'fs';
4
+ import logger from '../logger/logger.js';
5
+
6
+ /**
7
+ * Run page builder to generate pages from config
8
+ * @param {string} root - Project root directory
9
+ * @param {Object} config - BertUI configuration
10
+ */
11
+ export async function runPageBuilder(root, config) {
12
+ const pagesDir = join(root, 'src', 'pages');
13
+
14
+ if (!config.pageBuilder || typeof config.pageBuilder !== 'object') {
15
+ logger.debug('No page builder configuration found');
16
+ return;
17
+ }
18
+
19
+ const { pages } = config.pageBuilder;
20
+
21
+ if (!pages || !Array.isArray(pages) || pages.length === 0) {
22
+ logger.debug('No pages defined in page builder');
23
+ return;
24
+ }
25
+
26
+ logger.info(`📄 Page Builder: Generating ${pages.length} page(s)...`);
27
+
28
+ // Ensure pages directory exists
29
+ const generatedDir = join(pagesDir, 'generated');
30
+ if (!existsSync(generatedDir)) {
31
+ mkdirSync(generatedDir, { recursive: true });
32
+ }
33
+
34
+ for (const page of pages) {
35
+ try {
36
+ await generatePage(page, generatedDir);
37
+ logger.success(`✅ Generated: ${page.name}`);
38
+ } catch (error) {
39
+ logger.error(`❌ Failed to generate ${page.name}: ${error.message}`);
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Generate a single page from configuration
46
+ * @param {Object} pageConfig - Page configuration
47
+ * @param {string} outputDir - Output directory
48
+ */
49
+ async function generatePage(pageConfig, outputDir) {
50
+ const { name, type, data } = pageConfig;
51
+
52
+ if (!name) {
53
+ throw new Error('Page name is required');
54
+ }
55
+
56
+ let pageContent = '';
57
+
58
+ switch (type) {
59
+ case 'markdown':
60
+ pageContent = generateMarkdownPage(name, data);
61
+ break;
62
+
63
+ case 'json':
64
+ pageContent = generateJsonPage(name, data);
65
+ break;
66
+
67
+ case 'custom':
68
+ pageContent = data.template || generateDefaultPage(name, data);
69
+ break;
70
+
71
+ default:
72
+ pageContent = generateDefaultPage(name, data);
73
+ }
74
+
75
+ // Write the generated page
76
+ const filename = name.toLowerCase().replace(/\s+/g, '-') + '.jsx';
77
+ const filepath = join(outputDir, filename);
78
+
79
+ await Bun.write(filepath, pageContent);
80
+ }
81
+
82
+ /**
83
+ * Generate a default React page
84
+ */
85
+ function generateDefaultPage(name, data) {
86
+ const title = data?.title || name;
87
+ const content = data?.content || `<p>Welcome to ${name}</p>`;
88
+
89
+ return `// Auto-generated page: ${name}
90
+ import React from 'react';
91
+
92
+ export const title = "${title}";
93
+ export const description = "${data?.description || `${name} page`}";
94
+
95
+ export default function ${sanitizeComponentName(name)}() {
96
+ return (
97
+ <div>
98
+ <h1>${title}</h1>
99
+ ${content}
100
+ </div>
101
+ );
102
+ }
103
+ `;
104
+ }
105
+
106
+ /**
107
+ * Generate a page from Markdown data
108
+ */
109
+ function generateMarkdownPage(name, data) {
110
+ const title = data?.title || name;
111
+ const markdown = data?.markdown || '';
112
+
113
+ // Simple markdown to JSX conversion
114
+ const jsxContent = convertMarkdownToJSX(markdown);
115
+
116
+ return `// Auto-generated markdown page: ${name}
117
+ import React from 'react';
118
+
119
+ export const title = "${title}";
120
+ export const description = "${data?.description || `${name} page`}";
121
+
122
+ export default function ${sanitizeComponentName(name)}() {
123
+ return (
124
+ <div className="markdown-content">
125
+ ${jsxContent}
126
+ </div>
127
+ );
128
+ }
129
+ `;
130
+ }
131
+
132
+ /**
133
+ * Generate a page from JSON data
134
+ */
135
+ function generateJsonPage(name, data) {
136
+ const title = data?.title || name;
137
+ const items = data?.items || [];
138
+
139
+ return `// Auto-generated JSON page: ${name}
140
+ import React from 'react';
141
+
142
+ export const title = "${title}";
143
+ export const description = "${data?.description || `${name} page`}";
144
+
145
+ const items = ${JSON.stringify(items, null, 2)};
146
+
147
+ export default function ${sanitizeComponentName(name)}() {
148
+ return (
149
+ <div>
150
+ <h1>${title}</h1>
151
+ <ul>
152
+ {items.map((item, index) => (
153
+ <li key={index}>{item.title || item.name || item}</li>
154
+ ))}
155
+ </ul>
156
+ </div>
157
+ );
158
+ }
159
+ `;
160
+ }
161
+
162
+ /**
163
+ * Convert markdown to JSX (basic implementation)
164
+ */
165
+ function convertMarkdownToJSX(markdown) {
166
+ let jsx = markdown
167
+ // Headers
168
+ .replace(/^### (.*$)/gm, '<h3>$1</h3>')
169
+ .replace(/^## (.*$)/gm, '<h2>$1</h2>')
170
+ .replace(/^# (.*$)/gm, '<h1>$1</h1>')
171
+ // Bold
172
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
173
+ // Italic
174
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
175
+ // Paragraphs
176
+ .split('\n\n')
177
+ .map(para => para.trim() ? `<p>${para}</p>` : '')
178
+ .join('\n ');
179
+
180
+ return jsx;
181
+ }
182
+
183
+ /**
184
+ * Sanitize component name (must be valid React component name)
185
+ */
186
+ function sanitizeComponentName(name) {
187
+ return name
188
+ .replace(/[^a-zA-Z0-9]/g, '')
189
+ .replace(/^[0-9]/, 'Page$&')
190
+ .replace(/^./, c => c.toUpperCase());
191
+ }
@@ -1,4 +1,4 @@
1
- // src/server/dev-server.js - FIXED IMAGE SERVING
1
+ // src/server/dev-server.js - FIXED: bertui-icons Support + Import Map
2
2
  import { Elysia } from 'elysia';
3
3
  import { watch } from 'fs';
4
4
  import { join, extname } from 'path';
@@ -28,10 +28,10 @@ export async function startDevServer(options = {}) {
28
28
 
29
29
  const app = new Elysia()
30
30
  .get('/', async () => {
31
- return serveHTML(root, hasRouter, config);
31
+ return await serveHTML(root, hasRouter, config); // ✅ FIX 1/3: Added await
32
32
  })
33
33
 
34
- // ✅ FIX: Serve images from src/images/ (CRITICAL)
34
+ // ✅ Serve images from src/images/
35
35
  .get('/images/*', async ({ params, set }) => {
36
36
  const srcImagesDir = join(srcDir, 'images');
37
37
  const filepath = join(srcImagesDir, params['*']);
@@ -48,7 +48,7 @@ export async function startDevServer(options = {}) {
48
48
  return new Response(file, {
49
49
  headers: {
50
50
  'Content-Type': contentType,
51
- 'Cache-Control': 'no-cache' // Dev server = no cache
51
+ 'Cache-Control': 'no-cache'
52
52
  }
53
53
  });
54
54
  })
@@ -89,6 +89,27 @@ export async function startDevServer(options = {}) {
89
89
  });
90
90
  })
91
91
 
92
+ // ✅ CRITICAL FIX: Serve node_modules with correct MIME type
93
+ .get('/node_modules/*', async ({ params, set }) => {
94
+ const filepath = join(root, 'node_modules', params['*']);
95
+ const file = Bun.file(filepath);
96
+
97
+ if (!await file.exists()) {
98
+ set.status = 404;
99
+ return 'Module not found';
100
+ }
101
+
102
+ const ext = extname(filepath).toLowerCase();
103
+ const contentType = ext === '.js' ? 'application/javascript; charset=utf-8' : getContentType(ext);
104
+
105
+ return new Response(file, {
106
+ headers: {
107
+ 'Content-Type': contentType,
108
+ 'Cache-Control': 'no-cache'
109
+ }
110
+ });
111
+ })
112
+
92
113
  .get('/*', async ({ params, set }) => {
93
114
  const path = params['*'];
94
115
 
@@ -128,7 +149,7 @@ export async function startDevServer(options = {}) {
128
149
  return 'File not found';
129
150
  }
130
151
 
131
- return serveHTML(root, hasRouter, config);
152
+ return await serveHTML(root, hasRouter, config); // ✅ FIX 2/3: Added await
132
153
  })
133
154
 
134
155
  .get('/hmr-client.js', () => {
@@ -405,26 +426,43 @@ ws.onclose = () => {
405
426
  }
406
427
  })
407
428
 
408
- .get('/compiled/*', async ({ params, set }) => {
409
- const filepath = join(compiledDir, params['*']);
410
- const file = Bun.file(filepath);
411
-
412
- if (!await file.exists()) {
413
- set.status = 404;
414
- return 'File not found';
415
- }
416
-
417
- const ext = extname(filepath);
418
- const contentType = ext === '.js' ? 'application/javascript' : getContentType(ext);
419
-
420
- return new Response(await file.text(), {
421
- headers: {
422
- 'Content-Type': contentType,
423
- 'Cache-Control': 'no-store'
424
- }
425
- });
426
- })
427
-
429
+ // Replace the /compiled/* handler in bertui/src/server/dev-server.js
430
+
431
+ .get('/compiled/*', async ({ params, set }) => {
432
+ const requestedPath = params['*'];
433
+ const filepath = join(compiledDir, requestedPath);
434
+
435
+ // Check if file exists
436
+ const file = Bun.file(filepath);
437
+ if (!await file.exists()) {
438
+ logger.warn(`File not found: /compiled/${requestedPath}`);
439
+ set.status = 404;
440
+ return 'File not found';
441
+ }
442
+
443
+ // CRITICAL FIX: Always return JavaScript files with correct MIME type
444
+ const ext = extname(filepath).toLowerCase();
445
+
446
+ let contentType;
447
+ if (ext === '.js' || ext === '.jsx' || ext === '.mjs') {
448
+ contentType = 'application/javascript; charset=utf-8';
449
+ } else if (ext === '.json') {
450
+ contentType = 'application/json; charset=utf-8';
451
+ } else if (ext === '.css') {
452
+ contentType = 'text/css; charset=utf-8';
453
+ } else {
454
+ contentType = 'text/plain; charset=utf-8';
455
+ }
456
+
457
+ logger.debug(`Serving: /compiled/${requestedPath} (${contentType})`);
458
+
459
+ return new Response(file, {
460
+ headers: {
461
+ 'Content-Type': contentType,
462
+ 'Cache-Control': 'no-store, no-cache, must-revalidate'
463
+ }
464
+ });
465
+ })
428
466
  .get('/styles/*', async ({ params, set }) => {
429
467
  const filepath = join(stylesDir, params['*']);
430
468
  const file = Bun.file(filepath);
@@ -453,6 +491,7 @@ ws.onclose = () => {
453
491
  logger.info(`📁 Serving: ${root}`);
454
492
  logger.info(`🖼️ Images: /images/* → src/images/`);
455
493
  logger.info(`📦 Public: /public/* → public/`);
494
+ logger.info(`⚡ BertUI Packages: /node_modules/* → node_modules/`);
456
495
 
457
496
  setupWatcher(root, compiledDir, clients, async () => {
458
497
  hasRouter = existsSync(join(compiledDir, 'router.js'));
@@ -460,8 +499,10 @@ ws.onclose = () => {
460
499
 
461
500
  return app;
462
501
  }
502
+ // Update serveHTML function in bertui/src/server/dev-server.js
503
+ // This version auto-detects ALL bertui-* packages
463
504
 
464
- function serveHTML(root, hasRouter, config) {
505
+ async function serveHTML(root, hasRouter, config) { // ✅ FIX 3/3: Added async
465
506
  const meta = config.meta || {};
466
507
 
467
508
  const srcStylesDir = join(root, 'src', 'styles');
@@ -476,6 +517,72 @@ function serveHTML(root, hasRouter, config) {
476
517
  }
477
518
  }
478
519
 
520
+ // Build import map
521
+ const importMap = {
522
+ "react": "https://esm.sh/react@18.2.0",
523
+ "react-dom": "https://esm.sh/react-dom@18.2.0",
524
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
525
+ };
526
+
527
+ // ✅ AUTO-DETECT ALL bertui-* PACKAGES
528
+ const nodeModulesDir = join(root, 'node_modules');
529
+
530
+ if (existsSync(nodeModulesDir)) {
531
+ try {
532
+ const packages = readdirSync(nodeModulesDir);
533
+
534
+ for (const pkg of packages) {
535
+ // Skip non-bertui packages
536
+ if (!pkg.startsWith('bertui-')) continue;
537
+
538
+ const pkgDir = join(nodeModulesDir, pkg);
539
+ const pkgJsonPath = join(pkgDir, 'package.json');
540
+
541
+ // Skip if no package.json
542
+ if (!existsSync(pkgJsonPath)) continue;
543
+
544
+ try {
545
+ const pkgJsonContent = await Bun.file(pkgJsonPath).text();
546
+ const pkgJson = JSON.parse(pkgJsonContent);
547
+
548
+ // Resolve main entry point
549
+ let mainFile = null;
550
+
551
+ // Try exports field first (modern)
552
+ if (pkgJson.exports) {
553
+ const rootExport = pkgJson.exports['.'];
554
+ if (typeof rootExport === 'string') {
555
+ mainFile = rootExport;
556
+ } else if (typeof rootExport === 'object') {
557
+ // Prefer browser build, fallback to default
558
+ mainFile = rootExport.browser || rootExport.default || rootExport.import;
559
+ }
560
+ }
561
+
562
+ // Fallback to main field
563
+ if (!mainFile) {
564
+ mainFile = pkgJson.main || 'index.js';
565
+ }
566
+
567
+ // Verify file exists
568
+ const fullPath = join(pkgDir, mainFile);
569
+ if (existsSync(fullPath)) {
570
+ importMap[pkg] = `/node_modules/${pkg}/${mainFile}`;
571
+ logger.info(`✅ ${pkg} available`);
572
+ } else {
573
+ logger.warn(`⚠️ ${pkg} main file not found: ${mainFile}`);
574
+ }
575
+
576
+ } catch (error) {
577
+ logger.warn(`⚠️ Failed to parse ${pkg}/package.json: ${error.message}`);
578
+ }
579
+ }
580
+
581
+ } catch (error) {
582
+ logger.warn(`Failed to scan node_modules: ${error.message}`);
583
+ }
584
+ }
585
+
479
586
  const html = `<!DOCTYPE html>
480
587
  <html lang="${meta.lang || 'en'}">
481
588
  <head>
@@ -497,13 +604,7 @@ function serveHTML(root, hasRouter, config) {
497
604
  ${userStylesheets}
498
605
 
499
606
  <script type="importmap">
500
- {
501
- "imports": {
502
- "react": "https://esm.sh/react@18.2.0",
503
- "react-dom": "https://esm.sh/react-dom@18.2.0",
504
- "react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
505
- }
506
- }
607
+ ${JSON.stringify({ imports: importMap }, null, 2)}
507
608
  </script>
508
609
 
509
610
  <style>
@@ -529,7 +630,6 @@ ${userStylesheets}
529
630
  headers: { 'Content-Type': 'text/html' }
530
631
  });
531
632
  }
532
-
533
633
  function getImageContentType(ext) {
534
634
  const types = {
535
635
  '.jpg': 'image/jpeg',
@@ -592,7 +692,6 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
592
692
  if (watchedExtensions.includes(ext)) {
593
693
  logger.info(`📝 File changed: ${filename}`);
594
694
 
595
- // For images, just reload
596
695
  if (['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'].includes(ext)) {
597
696
  for (const client of clients) {
598
697
  try {
@@ -604,7 +703,6 @@ function setupWatcher(root, compiledDir, clients, onRecompile) {
604
703
  return;
605
704
  }
606
705
 
607
- // For code/CSS, recompile
608
706
  for (const client of clients) {
609
707
  try {
610
708
  client.send(JSON.stringify({ type: 'recompiling' }));
package/src/utils/env.js CHANGED
@@ -1,19 +1,49 @@
1
- // bertui/src/utils/env.js
2
- import { existsSync } from 'fs';
1
+ // bertui/src/utils/env.js - FIXED
3
2
  import { join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import logger from '../logger/logger.js';
4
5
 
5
6
  /**
6
- * Load environment variables for BertUI
7
- * This runs at BUILD TIME (Node.js), not in the browser
7
+ * Load environment variables from .env file
8
+ * @param {string} root - Project root directory
9
+ * @returns {Object} Environment variables
8
10
  */
9
11
  export function loadEnvVariables(root) {
12
+ const envPath = join(root, '.env');
10
13
  const envVars = {};
11
14
 
12
- // Load from process.env (already loaded by Bun/Node)
13
- for (const [key, value] of Object.entries(process.env)) {
14
- // Only expose variables that start with BERTUI_ or PUBLIC_
15
- if (key.startsWith('BERTUI_') || key.startsWith('PUBLIC_')) {
16
- envVars[key] = value;
15
+ if (existsSync(envPath)) {
16
+ try {
17
+ // FIXED: Use synchronous read for consistency
18
+ const file = Bun.file(envPath);
19
+ const envContent = file.text();
20
+ const lines = envContent.split('\n');
21
+
22
+ for (const line of lines) {
23
+ // Skip empty lines and comments
24
+ if (!line.trim() || line.trim().startsWith('#')) continue;
25
+
26
+ // Parse KEY=VALUE
27
+ const match = line.match(/^([^=]+)=(.*)$/);
28
+ if (match) {
29
+ const key = match[1].trim();
30
+ let value = match[2].trim();
31
+
32
+ // Remove quotes if present
33
+ if ((value.startsWith('"') && value.endsWith('"')) ||
34
+ (value.startsWith("'") && value.endsWith("'"))) {
35
+ value = value.slice(1, -1);
36
+ }
37
+
38
+ envVars[key] = value;
39
+ }
40
+ }
41
+
42
+ if (Object.keys(envVars).length > 0) {
43
+ logger.debug(`Loaded ${Object.keys(envVars).length} environment variables from .env`);
44
+ }
45
+ } catch (error) {
46
+ logger.warn(`Failed to load .env: ${error.message}`);
17
47
  }
18
48
  }
19
49
 
@@ -21,48 +51,38 @@ export function loadEnvVariables(root) {
21
51
  }
22
52
 
23
53
  /**
24
- * Generate code to inject env variables into the browser
54
+ * Generate JavaScript code to expose environment variables
55
+ * @param {Object} envVars - Environment variables object
56
+ * @returns {string} JavaScript code
25
57
  */
26
58
  export function generateEnvCode(envVars) {
27
- const envObject = Object.entries(envVars)
28
- .map(([key, value]) => ` "${key}": ${JSON.stringify(value)}`)
29
- .join(',\n');
59
+ const exports = Object.entries(envVars)
60
+ .map(([key, value]) => `export const ${key} = ${JSON.stringify(value)};`)
61
+ .join('\n');
30
62
 
31
- return `// Environment variables injected at build time
32
- export const env = {
33
- ${envObject}
34
- };
63
+ return `// Auto-generated environment variables
64
+ // Do not edit this file manually
35
65
 
36
- // Make it available globally (optional)
37
- if (typeof window !== 'undefined') {
38
- window.__BERTUI_ENV__ = env;
39
- }
66
+ ${exports}
67
+
68
+ // Access via: import { VAR_NAME } from './env.js'
40
69
  `;
41
70
  }
42
71
 
43
72
  /**
44
- * ✅ CRITICAL FIX: Replace ALL process.env references with actual values
45
- * This prevents "process is not defined" errors in the browser
73
+ * Replace process.env references in code
74
+ * @param {string} code - Source code
75
+ * @param {Object} envVars - Environment variables
76
+ * @returns {string} Code with replaced env vars
46
77
  */
47
78
  export function replaceEnvInCode(code, envVars) {
48
- let result = code;
79
+ let modified = code;
49
80
 
50
- // Replace process.env.VARIABLE_NAME with actual values
51
81
  for (const [key, value] of Object.entries(envVars)) {
52
- const regex = new RegExp(`process\\.env\\.${key}`, 'g');
53
- result = result.replace(regex, JSON.stringify(value));
82
+ // Replace process.env.KEY with actual value
83
+ const regex = new RegExp(`process\\.env\\.${key}\\b`, 'g');
84
+ modified = modified.replace(regex, JSON.stringify(value));
54
85
  }
55
86
 
56
- // ✅ NEW: Also replace generic process.env.NODE_ENV
57
- // This is commonly used by React and other libraries
58
- result = result.replace(
59
- /process\.env\.NODE_ENV/g,
60
- JSON.stringify('production')
61
- );
62
-
63
- // ✅ NEW: Remove any remaining process references that might cause errors
64
- // Replace with undefined to avoid runtime errors
65
- result = result.replace(/\bprocess\b/g, 'undefined');
66
-
67
- return result;
87
+ return modified;
68
88
  }