bertui 1.1.4 → 1.1.6
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 +1 -1
- package/src/build/generators/html-generator.js +58 -30
- package/src/build.js +3 -10
- package/src/client/compiler.js +20 -27
- package/src/config/defaultConfig.js +4 -12
- package/src/dev.js +4 -1
- package/src/server/dev-server.js +45 -23
- package/types/config.d.ts +1 -48
- package/src/pagebuilder/core.js +0 -191
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/build/generators/html-generator.js - FIXED
|
|
1
|
+
// bertui/src/build/generators/html-generator.js - FIXED CSS BUILD
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
3
|
import { mkdirSync, existsSync, cpSync } from 'fs';
|
|
4
4
|
import logger from '../../logger/logger.js';
|
|
@@ -17,53 +17,75 @@ export async function generateProductionHTML(root, outDir, buildResult, routes,
|
|
|
17
17
|
const bundlePath = relative(outDir, mainBundle.path).replace(/\\/g, '/');
|
|
18
18
|
const defaultMeta = config.meta || {};
|
|
19
19
|
|
|
20
|
-
// ✅
|
|
21
|
-
const
|
|
20
|
+
// ✅ Copy bertui-icons AND bertui-animate to dist/
|
|
21
|
+
const bertuiPackages = await copyBertuiPackagesToProduction(root, outDir);
|
|
22
22
|
|
|
23
23
|
logger.info(`📄 Generating HTML for ${routes.length} routes...`);
|
|
24
24
|
|
|
25
|
-
// Process in batches to avoid Bun crashes
|
|
26
25
|
const BATCH_SIZE = 5;
|
|
27
26
|
|
|
28
27
|
for (let i = 0; i < routes.length; i += BATCH_SIZE) {
|
|
29
28
|
const batch = routes.slice(i, i + BATCH_SIZE);
|
|
30
29
|
logger.debug(`Processing batch ${Math.floor(i/BATCH_SIZE) + 1}/${Math.ceil(routes.length/BATCH_SIZE)}`);
|
|
31
30
|
|
|
32
|
-
// Process batch sequentially
|
|
33
31
|
for (const route of batch) {
|
|
34
|
-
await processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir,
|
|
32
|
+
await processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir, bertuiPackages);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
logger.success(`✅ HTML generation complete for ${routes.length} routes`);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
// ✅
|
|
42
|
-
async function
|
|
39
|
+
// ✅ UPDATED: Copy ALL bertui-* packages to dist/
|
|
40
|
+
async function copyBertuiPackagesToProduction(root, outDir) {
|
|
43
41
|
const nodeModulesDir = join(root, 'node_modules');
|
|
44
|
-
const
|
|
42
|
+
const packages = {
|
|
43
|
+
bertuiIcons: false,
|
|
44
|
+
bertuiAnimate: false
|
|
45
|
+
};
|
|
45
46
|
|
|
46
|
-
if (!existsSync(
|
|
47
|
-
logger.debug('
|
|
48
|
-
return
|
|
47
|
+
if (!existsSync(nodeModulesDir)) {
|
|
48
|
+
logger.debug('node_modules not found, skipping package copy');
|
|
49
|
+
return packages;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
// Copy bertui-icons
|
|
53
|
+
const bertuiIconsSource = join(nodeModulesDir, 'bertui-icons');
|
|
54
|
+
if (existsSync(bertuiIconsSource)) {
|
|
55
|
+
try {
|
|
56
|
+
const bertuiIconsDest = join(outDir, 'node_modules', 'bertui-icons');
|
|
57
|
+
mkdirSync(join(outDir, 'node_modules'), { recursive: true });
|
|
58
|
+
cpSync(bertuiIconsSource, bertuiIconsDest, { recursive: true });
|
|
59
|
+
logger.success('✅ Copied bertui-icons to dist/node_modules/');
|
|
60
|
+
packages.bertuiIcons = true;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error(`Failed to copy bertui-icons: ${error.message}`);
|
|
63
|
+
}
|
|
63
64
|
}
|
|
65
|
+
|
|
66
|
+
// ✅ NEW: Copy ONLY bertui-animate CSS files (not the whole package)
|
|
67
|
+
const bertuiAnimateSource = join(nodeModulesDir, 'bertui-animate', 'dist');
|
|
68
|
+
if (existsSync(bertuiAnimateSource)) {
|
|
69
|
+
try {
|
|
70
|
+
const bertuiAnimateDest = join(outDir, 'css');
|
|
71
|
+
mkdirSync(bertuiAnimateDest, { recursive: true });
|
|
72
|
+
|
|
73
|
+
// Copy minified CSS
|
|
74
|
+
const minCSSPath = join(bertuiAnimateSource, 'bertui-animate.min.css');
|
|
75
|
+
if (existsSync(minCSSPath)) {
|
|
76
|
+
cpSync(minCSSPath, join(bertuiAnimateDest, 'bertui-animate.min.css'));
|
|
77
|
+
logger.success('✅ Copied bertui-animate.min.css to dist/css/');
|
|
78
|
+
packages.bertuiAnimate = true;
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.error(`Failed to copy bertui-animate: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return packages;
|
|
64
86
|
}
|
|
65
87
|
|
|
66
|
-
async function processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir,
|
|
88
|
+
async function processSingleRoute(route, serverIslands, config, defaultMeta, bundlePath, outDir, bertuiPackages) {
|
|
67
89
|
try {
|
|
68
90
|
const sourceCode = await Bun.file(route.path).text();
|
|
69
91
|
const pageMeta = extractMetaFromSource(sourceCode);
|
|
@@ -84,7 +106,7 @@ async function processSingleRoute(route, serverIslands, config, defaultMeta, bun
|
|
|
84
106
|
}
|
|
85
107
|
}
|
|
86
108
|
|
|
87
|
-
const html = generateHTML(meta, route, bundlePath, staticHTML, isServerIsland,
|
|
109
|
+
const html = generateHTML(meta, route, bundlePath, staticHTML, isServerIsland, bertuiPackages);
|
|
88
110
|
|
|
89
111
|
let htmlPath;
|
|
90
112
|
if (route.route === '/') {
|
|
@@ -239,8 +261,8 @@ async function extractStaticHTMLFromComponent(sourceCode, filePath) {
|
|
|
239
261
|
}
|
|
240
262
|
}
|
|
241
263
|
|
|
242
|
-
// ✅
|
|
243
|
-
function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland = false,
|
|
264
|
+
// ✅ UPDATED: Add bertui-animate CSS to production HTML
|
|
265
|
+
function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland = false, bertuiPackages = {}) {
|
|
244
266
|
const rootContent = staticHTML
|
|
245
267
|
? `<div id="root">${staticHTML}</div>`
|
|
246
268
|
: '<div id="root"></div>';
|
|
@@ -249,11 +271,16 @@ function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland =
|
|
|
249
271
|
? '<!-- 🏝️ Server Island: Static content rendered at build time -->'
|
|
250
272
|
: '<!-- ⚡ Client-only: Content rendered by JavaScript -->';
|
|
251
273
|
|
|
252
|
-
// ✅
|
|
253
|
-
const bertuiIconsImport =
|
|
274
|
+
// ✅ Add bertui-icons to import map
|
|
275
|
+
const bertuiIconsImport = bertuiPackages.bertuiIcons
|
|
254
276
|
? ',\n "bertui-icons": "/node_modules/bertui-icons/generated/index.js"'
|
|
255
277
|
: '';
|
|
256
278
|
|
|
279
|
+
// ✅ Add bertui-animate CSS link
|
|
280
|
+
const bertuiAnimateCSS = bertuiPackages.bertuiAnimate
|
|
281
|
+
? ' <link rel="stylesheet" href="/css/bertui-animate.min.css">'
|
|
282
|
+
: '';
|
|
283
|
+
|
|
257
284
|
return `<!DOCTYPE html>
|
|
258
285
|
<html lang="${meta.lang || 'en'}">
|
|
259
286
|
<head>
|
|
@@ -271,6 +298,7 @@ function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland =
|
|
|
271
298
|
${meta.ogImage ? `<meta property="og:image" content="${meta.ogImage}">` : ''}
|
|
272
299
|
|
|
273
300
|
<link rel="stylesheet" href="/styles/bertui.min.css">
|
|
301
|
+
${bertuiAnimateCSS}
|
|
274
302
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
275
303
|
|
|
276
304
|
<script type="importmap">
|
package/src/build.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// bertui/src/build.js -
|
|
1
|
+
// bertui/src/build.js - CLEANED (No PageBuilder)
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { existsSync, mkdirSync, rmSync
|
|
3
|
+
import { existsSync, mkdirSync, rmSync } from 'fs';
|
|
4
4
|
import logger from './logger/logger.js';
|
|
5
5
|
import { loadEnvVariables } from './utils/env.js';
|
|
6
|
-
import { runPageBuilder } from './pagebuilder/core.js';
|
|
7
6
|
|
|
8
7
|
import { compileForBuild } from './build/compiler/index.js';
|
|
9
8
|
import { buildAllCSS } from './build/processors/css-builder.js';
|
|
@@ -37,11 +36,6 @@ export async function buildProduction(options = {}) {
|
|
|
37
36
|
const { loadConfig } = await import('./config/loadConfig.js');
|
|
38
37
|
const config = await loadConfig(root);
|
|
39
38
|
|
|
40
|
-
if (config.pageBuilder) {
|
|
41
|
-
logger.info('Step 0.5: Running Page Builder...');
|
|
42
|
-
await runPageBuilder(root, config);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
39
|
logger.info('Step 1: Compiling and detecting Server Islands...');
|
|
46
40
|
const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars);
|
|
47
41
|
|
|
@@ -94,7 +88,6 @@ export async function buildProduction(options = {}) {
|
|
|
94
88
|
|
|
95
89
|
async function bundleJavaScript(buildEntry, outDir, envVars, buildDir) {
|
|
96
90
|
try {
|
|
97
|
-
// Change to build directory where bunfig.toml is
|
|
98
91
|
const originalCwd = process.cwd();
|
|
99
92
|
process.chdir(buildDir);
|
|
100
93
|
|
|
@@ -172,4 +165,4 @@ function showBuildSummary(routes, serverIslands, clientRoutes, duration) {
|
|
|
172
165
|
}
|
|
173
166
|
|
|
174
167
|
logger.bigLog('READY TO DEPLOY 🚀', { color: 'green' });
|
|
175
|
-
}
|
|
168
|
+
}
|
package/src/client/compiler.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ============================================
|
|
2
|
+
// FILE: bertui/src/client/compiler.js (UPDATED - Skip templates/)
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
2
5
|
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
3
6
|
import { join, extname, relative, dirname } from 'path';
|
|
4
7
|
import logger from '../logger/logger.js';
|
|
@@ -127,9 +130,7 @@ const RouterContext = createContext(null);
|
|
|
127
130
|
|
|
128
131
|
export function useRouter() {
|
|
129
132
|
const context = useContext(RouterContext);
|
|
130
|
-
if (!context)
|
|
131
|
-
throw new Error('useRouter must be used within a Router component');
|
|
132
|
-
}
|
|
133
|
+
if (!context) throw new Error('useRouter must be used within a Router');
|
|
133
134
|
return context;
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -194,24 +195,13 @@ export function Link({ to, children, ...props }) {
|
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
function NotFound() {
|
|
197
|
-
return React.createElement(
|
|
198
|
-
'
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
display: 'flex',
|
|
202
|
-
flexDirection: 'column',
|
|
203
|
-
alignItems: 'center',
|
|
204
|
-
justifyContent: 'center',
|
|
205
|
-
minHeight: '100vh',
|
|
206
|
-
fontFamily: 'system-ui'
|
|
207
|
-
}
|
|
208
|
-
},
|
|
198
|
+
return React.createElement('div', {
|
|
199
|
+
style: { display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
200
|
+
justifyContent: 'center', minHeight: '100vh', fontFamily: 'system-ui' }
|
|
201
|
+
},
|
|
209
202
|
React.createElement('h1', { style: { fontSize: '6rem', margin: 0 } }, '404'),
|
|
210
203
|
React.createElement('p', { style: { fontSize: '1.5rem', color: '#666' } }, 'Page not found'),
|
|
211
|
-
React.createElement('a', {
|
|
212
|
-
href: '/',
|
|
213
|
-
style: { color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }
|
|
214
|
-
}, 'Go home')
|
|
204
|
+
React.createElement('a', { href: '/', style: { color: '#10b981', textDecoration: 'none' } }, 'Go home')
|
|
215
205
|
);
|
|
216
206
|
}
|
|
217
207
|
|
|
@@ -219,11 +209,9 @@ ${imports}
|
|
|
219
209
|
|
|
220
210
|
export const routes = [
|
|
221
211
|
${routeConfigs}
|
|
222
|
-
]
|
|
223
|
-
`;
|
|
212
|
+
];`;
|
|
224
213
|
|
|
225
|
-
|
|
226
|
-
await Bun.write(routerPath, routerComponentCode);
|
|
214
|
+
await Bun.write(join(outDir, 'router.js'), routerComponentCode);
|
|
227
215
|
}
|
|
228
216
|
|
|
229
217
|
async function compileDirectory(srcDir, outDir, root, envVars) {
|
|
@@ -235,6 +223,12 @@ async function compileDirectory(srcDir, outDir, root, envVars) {
|
|
|
235
223
|
const stat = statSync(srcPath);
|
|
236
224
|
|
|
237
225
|
if (stat.isDirectory()) {
|
|
226
|
+
// ✅ NEW: Skip templates directory
|
|
227
|
+
if (file === 'templates') {
|
|
228
|
+
logger.debug('⏭️ Skipping src/templates/ (PageBuilder templates only)');
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
238
232
|
const subOutDir = join(outDir, file);
|
|
239
233
|
mkdirSync(subOutDir, { recursive: true });
|
|
240
234
|
const subStats = await compileDirectory(srcPath, subOutDir, root, envVars);
|
|
@@ -295,12 +289,11 @@ async function compileFile(srcPath, outDir, filename, relativePath, root, envVar
|
|
|
295
289
|
const outPath = join(outDir, filename.replace(/\.(jsx|tsx|ts)$/, '.js'));
|
|
296
290
|
code = fixRouterImports(code, outPath, root);
|
|
297
291
|
|
|
298
|
-
// ✅ CRITICAL FIX: Force React.createElement instead of jsxDEV
|
|
299
292
|
const transpiler = new Bun.Transpiler({
|
|
300
293
|
loader,
|
|
301
294
|
tsconfig: {
|
|
302
295
|
compilerOptions: {
|
|
303
|
-
jsx: 'react',
|
|
296
|
+
jsx: 'react',
|
|
304
297
|
jsxFactory: 'React.createElement',
|
|
305
298
|
jsxFragmentFactory: 'React.Fragment'
|
|
306
299
|
}
|
|
@@ -369,4 +362,4 @@ function fixRelativeImports(code) {
|
|
|
369
362
|
});
|
|
370
363
|
|
|
371
364
|
return code;
|
|
372
|
-
}
|
|
365
|
+
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
// bertui/src/config/defaultConfig.js
|
|
2
|
-
// Default configuration used when bertui.config.js is not present
|
|
3
|
-
|
|
1
|
+
// bertui/src/config/defaultConfig.js - CLEANED
|
|
4
2
|
export const defaultConfig = {
|
|
5
|
-
// Site information (used for sitemap generation)
|
|
6
3
|
siteName: "BertUI App",
|
|
7
|
-
baseUrl: "http://localhost:3000",
|
|
4
|
+
baseUrl: "http://localhost:3000",
|
|
8
5
|
|
|
9
|
-
// HTML Meta Tags (SEO)
|
|
10
6
|
meta: {
|
|
11
7
|
title: "BertUI - Lightning Fast React",
|
|
12
8
|
description: "Build lightning-fast React applications with file-based routing powered by Bun",
|
|
@@ -14,23 +10,19 @@ export const defaultConfig = {
|
|
|
14
10
|
author: "Pease Ernest",
|
|
15
11
|
themeColor: "#667eea",
|
|
16
12
|
lang: "en",
|
|
17
|
-
|
|
18
|
-
// Open Graph for social sharing
|
|
19
13
|
ogTitle: "BertUI - Lightning Fast React Framework",
|
|
20
14
|
ogDescription: "Build lightning-fast React apps with zero config",
|
|
21
15
|
ogImage: "/og-image.png"
|
|
22
16
|
},
|
|
23
17
|
|
|
24
|
-
// App Shell Configuration
|
|
25
18
|
appShell: {
|
|
26
19
|
loading: true,
|
|
27
20
|
loadingText: "Loading...",
|
|
28
21
|
backgroundColor: "#ffffff"
|
|
29
22
|
},
|
|
30
23
|
|
|
31
|
-
// robots.txt Configuration
|
|
32
24
|
robots: {
|
|
33
|
-
disallow: [],
|
|
34
|
-
crawlDelay: null
|
|
25
|
+
disallow: [],
|
|
26
|
+
crawlDelay: null
|
|
35
27
|
}
|
|
36
28
|
};
|
package/src/dev.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
// src/dev.js
|
|
1
|
+
// bertui/src/dev.js - CLEANED (No PageBuilder)
|
|
2
2
|
import { compileProject } from './client/compiler.js';
|
|
3
3
|
import { startDevServer } from './server/dev-server.js';
|
|
4
4
|
import logger from './logger/logger.js';
|
|
5
|
+
import { loadConfig } from './config/loadConfig.js';
|
|
5
6
|
|
|
6
7
|
export async function startDev(options = {}) {
|
|
7
8
|
const root = options.root || process.cwd();
|
|
8
9
|
const port = options.port || 3000;
|
|
9
10
|
|
|
10
11
|
try {
|
|
12
|
+
const config = await loadConfig(root);
|
|
13
|
+
|
|
11
14
|
// Step 1: Compile project
|
|
12
15
|
logger.info('Step 1: Compiling project...');
|
|
13
16
|
await compileProject(root);
|
package/src/server/dev-server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/server/dev-server.js - FIXED:
|
|
1
|
+
// bertui/src/server/dev-server.js - FIXED: CSS Module Loading
|
|
2
2
|
import { join, extname, dirname } from 'path';
|
|
3
3
|
import { existsSync, readdirSync } from 'fs';
|
|
4
4
|
import logger from '../logger/logger.js';
|
|
@@ -15,7 +15,6 @@ export async function startDevServer(options = {}) {
|
|
|
15
15
|
|
|
16
16
|
const config = await loadConfig(root);
|
|
17
17
|
|
|
18
|
-
// ✅ Track connected WebSocket clients
|
|
19
18
|
const clients = new Set();
|
|
20
19
|
|
|
21
20
|
let hasRouter = false;
|
|
@@ -25,30 +24,29 @@ export async function startDevServer(options = {}) {
|
|
|
25
24
|
logger.info('File-based routing enabled');
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
// ✅ Use Bun.serve() with WebSocket support
|
|
29
27
|
const server = Bun.serve({
|
|
30
28
|
port,
|
|
31
29
|
|
|
32
30
|
async fetch(req, server) {
|
|
33
31
|
const url = new URL(req.url);
|
|
34
32
|
|
|
35
|
-
//
|
|
33
|
+
// WebSocket upgrade for HMR
|
|
36
34
|
if (url.pathname === '/__hmr') {
|
|
37
35
|
const success = server.upgrade(req);
|
|
38
36
|
if (success) {
|
|
39
|
-
return undefined;
|
|
37
|
+
return undefined;
|
|
40
38
|
}
|
|
41
39
|
return new Response('WebSocket upgrade failed', { status: 500 });
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
//
|
|
42
|
+
// Serve HTML
|
|
45
43
|
if (url.pathname === '/' || (!url.pathname.includes('.') && !url.pathname.startsWith('/compiled'))) {
|
|
46
44
|
return new Response(await serveHTML(root, hasRouter, config, port), {
|
|
47
45
|
headers: { 'Content-Type': 'text/html' }
|
|
48
46
|
});
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
//
|
|
49
|
+
// Serve compiled JavaScript
|
|
52
50
|
if (url.pathname.startsWith('/compiled/')) {
|
|
53
51
|
const filepath = join(compiledDir, url.pathname.replace('/compiled/', ''));
|
|
54
52
|
const file = Bun.file(filepath);
|
|
@@ -66,7 +64,22 @@ export async function startDevServer(options = {}) {
|
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
// ✅ Serve CSS
|
|
67
|
+
// ✅ Serve bertui-animate CSS
|
|
68
|
+
if (url.pathname === '/bertui-animate.css') {
|
|
69
|
+
const bertuiAnimatePath = join(root, 'node_modules/bertui-animate/dist/bertui-animate.min.css');
|
|
70
|
+
const file = Bun.file(bertuiAnimatePath);
|
|
71
|
+
|
|
72
|
+
if (await file.exists()) {
|
|
73
|
+
return new Response(file, {
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'text/css',
|
|
76
|
+
'Cache-Control': 'no-store'
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Serve CSS
|
|
70
83
|
if (url.pathname.startsWith('/styles/')) {
|
|
71
84
|
const filepath = join(stylesDir, url.pathname.replace('/styles/', ''));
|
|
72
85
|
const file = Bun.file(filepath);
|
|
@@ -81,7 +94,7 @@ export async function startDevServer(options = {}) {
|
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
//
|
|
97
|
+
// Serve images from src/images/
|
|
85
98
|
if (url.pathname.startsWith('/images/')) {
|
|
86
99
|
const filepath = join(srcDir, 'images', url.pathname.replace('/images/', ''));
|
|
87
100
|
const file = Bun.file(filepath);
|
|
@@ -99,7 +112,7 @@ export async function startDevServer(options = {}) {
|
|
|
99
112
|
}
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
//
|
|
115
|
+
// Serve from public/
|
|
103
116
|
if (url.pathname.startsWith('/public/')) {
|
|
104
117
|
const filepath = join(publicDir, url.pathname.replace('/public/', ''));
|
|
105
118
|
const file = Bun.file(filepath);
|
|
@@ -111,14 +124,23 @@ export async function startDevServer(options = {}) {
|
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
|
|
114
|
-
// ✅ Serve node_modules
|
|
127
|
+
// ✅ FIX: Serve node_modules with proper MIME types
|
|
115
128
|
if (url.pathname.startsWith('/node_modules/')) {
|
|
116
129
|
const filepath = join(root, 'node_modules', url.pathname.replace('/node_modules/', ''));
|
|
117
130
|
const file = Bun.file(filepath);
|
|
118
131
|
|
|
119
132
|
if (await file.exists()) {
|
|
120
133
|
const ext = extname(filepath).toLowerCase();
|
|
121
|
-
|
|
134
|
+
|
|
135
|
+
// ✅ Proper MIME type detection
|
|
136
|
+
let contentType;
|
|
137
|
+
if (ext === '.css') {
|
|
138
|
+
contentType = 'text/css';
|
|
139
|
+
} else if (ext === '.js' || ext === '.mjs') {
|
|
140
|
+
contentType = 'application/javascript; charset=utf-8';
|
|
141
|
+
} else {
|
|
142
|
+
contentType = getContentType(ext);
|
|
143
|
+
}
|
|
122
144
|
|
|
123
145
|
return new Response(file, {
|
|
124
146
|
headers: {
|
|
@@ -132,7 +154,6 @@ export async function startDevServer(options = {}) {
|
|
|
132
154
|
return new Response('Not found', { status: 404 });
|
|
133
155
|
},
|
|
134
156
|
|
|
135
|
-
// ✅ WebSocket handler for HMR
|
|
136
157
|
websocket: {
|
|
137
158
|
open(ws) {
|
|
138
159
|
clients.add(ws);
|
|
@@ -156,7 +177,6 @@ export async function startDevServer(options = {}) {
|
|
|
156
177
|
logger.info(`📦 Public: /public/* → public/`);
|
|
157
178
|
logger.info(`⚡ BertUI Packages: /node_modules/* → node_modules/`);
|
|
158
179
|
|
|
159
|
-
// ✅ Setup file watcher
|
|
160
180
|
setupFileWatcher(root, compiledDir, clients, async () => {
|
|
161
181
|
hasRouter = existsSync(join(compiledDir, 'router.js'));
|
|
162
182
|
});
|
|
@@ -179,6 +199,14 @@ async function serveHTML(root, hasRouter, config, port) {
|
|
|
179
199
|
}
|
|
180
200
|
}
|
|
181
201
|
|
|
202
|
+
// ✅ FIX: Auto-detect bertui-animate CSS (bundled version)
|
|
203
|
+
let bertuiAnimateStylesheet = '';
|
|
204
|
+
const bertuiAnimatePath = join(root, 'node_modules/bertui-animate/dist/bertui-animate.min.css');
|
|
205
|
+
if (existsSync(bertuiAnimatePath)) {
|
|
206
|
+
bertuiAnimateStylesheet = ' <link rel="stylesheet" href="/bertui-animate.css">';
|
|
207
|
+
logger.info('✅ bertui-animate detected');
|
|
208
|
+
}
|
|
209
|
+
|
|
182
210
|
// Build import map
|
|
183
211
|
const importMap = {
|
|
184
212
|
"react": "https://esm.sh/react@18.2.0",
|
|
@@ -186,7 +214,7 @@ async function serveHTML(root, hasRouter, config, port) {
|
|
|
186
214
|
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client"
|
|
187
215
|
};
|
|
188
216
|
|
|
189
|
-
// Auto-detect bertui-* packages
|
|
217
|
+
// Auto-detect bertui-* JavaScript packages
|
|
190
218
|
const nodeModulesDir = join(root, 'node_modules');
|
|
191
219
|
|
|
192
220
|
if (existsSync(nodeModulesDir)) {
|
|
@@ -255,6 +283,7 @@ async function serveHTML(root, hasRouter, config, port) {
|
|
|
255
283
|
<link rel="icon" type="image/svg+xml" href="/public/favicon.svg">
|
|
256
284
|
|
|
257
285
|
${userStylesheets}
|
|
286
|
+
${bertuiAnimateStylesheet}
|
|
258
287
|
|
|
259
288
|
<script type="importmap">
|
|
260
289
|
${JSON.stringify({ imports: importMap }, null, 2)}
|
|
@@ -274,9 +303,7 @@ ${userStylesheets}
|
|
|
274
303
|
<body>
|
|
275
304
|
<div id="root"></div>
|
|
276
305
|
|
|
277
|
-
<!-- ✅ Bun Native HMR Script -->
|
|
278
306
|
<script type="module">
|
|
279
|
-
// WebSocket-based HMR (no polling!)
|
|
280
307
|
const ws = new WebSocket('ws://localhost:${port}/__hmr');
|
|
281
308
|
|
|
282
309
|
ws.onopen = () => {
|
|
@@ -358,7 +385,6 @@ function getContentType(ext) {
|
|
|
358
385
|
return types[ext] || 'text/plain';
|
|
359
386
|
}
|
|
360
387
|
|
|
361
|
-
// ✅ File watcher with proper debouncing
|
|
362
388
|
function setupFileWatcher(root, compiledDir, clients, onRecompile) {
|
|
363
389
|
const srcDir = join(root, 'src');
|
|
364
390
|
const configPath = join(root, 'bertui.config.js');
|
|
@@ -374,7 +400,6 @@ function setupFileWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
374
400
|
let recompileTimeout = null;
|
|
375
401
|
const watchedExtensions = ['.js', '.jsx', '.ts', '.tsx', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.avif'];
|
|
376
402
|
|
|
377
|
-
// Notify all clients
|
|
378
403
|
function notifyClients(message) {
|
|
379
404
|
for (const client of clients) {
|
|
380
405
|
try {
|
|
@@ -395,7 +420,6 @@ function setupFileWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
395
420
|
|
|
396
421
|
logger.info(`📝 File changed: ${filename}`);
|
|
397
422
|
|
|
398
|
-
// ✅ Clear previous timeout and debounce
|
|
399
423
|
clearTimeout(recompileTimeout);
|
|
400
424
|
|
|
401
425
|
recompileTimeout = setTimeout(async () => {
|
|
@@ -414,7 +438,6 @@ function setupFileWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
414
438
|
logger.success('✅ Recompiled successfully');
|
|
415
439
|
notifyClients({ type: 'compiled' });
|
|
416
440
|
|
|
417
|
-
// ✅ Wait 100ms before triggering reload (let compilation finish)
|
|
418
441
|
setTimeout(() => {
|
|
419
442
|
notifyClients({ type: 'reload' });
|
|
420
443
|
}, 100);
|
|
@@ -424,10 +447,9 @@ function setupFileWatcher(root, compiledDir, clients, onRecompile) {
|
|
|
424
447
|
} finally {
|
|
425
448
|
isRecompiling = false;
|
|
426
449
|
}
|
|
427
|
-
}, 150);
|
|
450
|
+
}, 150);
|
|
428
451
|
});
|
|
429
452
|
|
|
430
|
-
// Watch config file
|
|
431
453
|
if (existsSync(configPath)) {
|
|
432
454
|
watch(configPath, async (eventType) => {
|
|
433
455
|
if (eventType === 'change') {
|
package/types/config.d.ts
CHANGED
|
@@ -1,80 +1,33 @@
|
|
|
1
|
-
// bertui/types/config.d.ts
|
|
1
|
+
// bertui/types/config.d.ts - CLEANED
|
|
2
2
|
declare module 'bertui/config' {
|
|
3
|
-
/**
|
|
4
|
-
* BertUI Configuration
|
|
5
|
-
*/
|
|
6
3
|
export interface BertuiConfig {
|
|
7
|
-
/** Site name for SEO */
|
|
8
4
|
siteName?: string;
|
|
9
|
-
|
|
10
|
-
/** Base URL for sitemap generation (e.g., "https://example.com") */
|
|
11
5
|
baseUrl?: string;
|
|
12
6
|
|
|
13
|
-
/** HTML meta tags configuration */
|
|
14
7
|
meta?: {
|
|
15
|
-
/** Page title */
|
|
16
8
|
title?: string;
|
|
17
|
-
/** Meta description */
|
|
18
9
|
description?: string;
|
|
19
|
-
/** Meta keywords */
|
|
20
10
|
keywords?: string;
|
|
21
|
-
/** Author name */
|
|
22
11
|
author?: string;
|
|
23
|
-
/** Open Graph image URL */
|
|
24
12
|
ogImage?: string;
|
|
25
|
-
/** Open Graph title */
|
|
26
13
|
ogTitle?: string;
|
|
27
|
-
/** Open Graph description */
|
|
28
14
|
ogDescription?: string;
|
|
29
|
-
/** Theme color */
|
|
30
15
|
themeColor?: string;
|
|
31
|
-
/** Language code (e.g., "en") */
|
|
32
16
|
lang?: string;
|
|
33
17
|
};
|
|
34
18
|
|
|
35
|
-
/** App shell configuration */
|
|
36
19
|
appShell?: {
|
|
37
|
-
/** Show loading indicator */
|
|
38
20
|
loading?: boolean;
|
|
39
|
-
/** Loading text */
|
|
40
21
|
loadingText?: string;
|
|
41
|
-
/** Background color */
|
|
42
22
|
backgroundColor?: string;
|
|
43
23
|
};
|
|
44
24
|
|
|
45
|
-
/** robots.txt configuration */
|
|
46
25
|
robots?: {
|
|
47
|
-
/** Paths to disallow in robots.txt */
|
|
48
26
|
disallow?: string[];
|
|
49
|
-
/** Crawl delay in seconds */
|
|
50
27
|
crawlDelay?: number;
|
|
51
28
|
};
|
|
52
29
|
}
|
|
53
30
|
|
|
54
|
-
/**
|
|
55
|
-
* Page meta configuration (exported from page files)
|
|
56
|
-
*/
|
|
57
|
-
export interface PageMeta {
|
|
58
|
-
title?: string;
|
|
59
|
-
description?: string;
|
|
60
|
-
keywords?: string;
|
|
61
|
-
author?: string;
|
|
62
|
-
ogTitle?: string;
|
|
63
|
-
ogDescription?: string;
|
|
64
|
-
ogImage?: string;
|
|
65
|
-
themeColor?: string;
|
|
66
|
-
lang?: string;
|
|
67
|
-
publishedDate?: string;
|
|
68
|
-
updatedDate?: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Default BertUI configuration
|
|
73
|
-
*/
|
|
74
31
|
export const defaultConfig: BertuiConfig;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Load BertUI configuration from bertui.config.js
|
|
78
|
-
*/
|
|
79
32
|
export function loadConfig(root: string): Promise<BertuiConfig>;
|
|
80
33
|
}
|
package/src/pagebuilder/core.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
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
|
-
}
|