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.
- package/README.md +424 -435
- package/package.json +1 -1
- package/src/build/generators/html-generator.js +41 -7
- package/src/build/processors/css-builder.js +71 -4
- package/src/build.js +77 -35
- package/src/client/compiler.js +23 -42
- package/src/pagebuilder/core.js +191 -0
- package/src/server/dev-server.js +134 -36
- package/src/utils/env.js +59 -39
- package/src/utils/meta-extractor.js +124 -58
|
@@ -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
|
+
}
|
package/src/server/dev-server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/server/dev-server.js - FIXED
|
|
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
|
-
// ✅
|
|
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'
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
|
7
|
-
*
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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
|
|
28
|
-
.map(([key, value]) => `
|
|
29
|
-
.join('
|
|
59
|
+
const exports = Object.entries(envVars)
|
|
60
|
+
.map(([key, value]) => `export const ${key} = ${JSON.stringify(value)};`)
|
|
61
|
+
.join('\n');
|
|
30
62
|
|
|
31
|
-
return `//
|
|
32
|
-
|
|
33
|
-
${envObject}
|
|
34
|
-
};
|
|
63
|
+
return `// Auto-generated environment variables
|
|
64
|
+
// Do not edit this file manually
|
|
35
65
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
66
|
+
${exports}
|
|
67
|
+
|
|
68
|
+
// Access via: import { VAR_NAME } from './env.js'
|
|
40
69
|
`;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
72
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
}
|