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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bertui",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Lightning-fast React dev server powered by Bun and Elysia - Now with TypeScript support!",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -1,4 +1,4 @@
1
- // bertui/src/build/generators/html-generator.js - FIXED JSX RUNTIME
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
- // ✅ FIX: Check if bertui-icons is installed and copy to dist/
21
- const bertuiIconsInstalled = await copyBertuiIconsToProduction(root, outDir);
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, bertuiIconsInstalled);
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
- // ✅ NEW: Copy bertui-icons to dist/ for production
42
- async function copyBertuiIconsToProduction(root, outDir) {
39
+ // ✅ UPDATED: Copy ALL bertui-* packages to dist/
40
+ async function copyBertuiPackagesToProduction(root, outDir) {
43
41
  const nodeModulesDir = join(root, 'node_modules');
44
- const bertuiIconsSource = join(nodeModulesDir, 'bertui-icons');
42
+ const packages = {
43
+ bertuiIcons: false,
44
+ bertuiAnimate: false
45
+ };
45
46
 
46
- if (!existsSync(bertuiIconsSource)) {
47
- logger.debug('bertui-icons not installed, skipping...');
48
- return false;
47
+ if (!existsSync(nodeModulesDir)) {
48
+ logger.debug('node_modules not found, skipping package copy');
49
+ return packages;
49
50
  }
50
51
 
51
- try {
52
- const bertuiIconsDest = join(outDir, 'node_modules', 'bertui-icons');
53
- mkdirSync(join(outDir, 'node_modules'), { recursive: true });
54
-
55
- // Copy the entire bertui-icons package
56
- cpSync(bertuiIconsSource, bertuiIconsDest, { recursive: true });
57
-
58
- logger.success('✅ Copied bertui-icons to dist/node_modules/');
59
- return true;
60
- } catch (error) {
61
- logger.error(`Failed to copy bertui-icons: ${error.message}`);
62
- return false;
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, bertuiIconsInstalled) {
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, bertuiIconsInstalled);
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
- // ✅ FIXED: Add jsx-runtime to import map
243
- function generateHTML(meta, route, bundlePath, staticHTML = '', isServerIsland = false, bertuiIconsInstalled = 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
- // ✅ FIX: Add jsx-runtime to import map for production
253
- const bertuiIconsImport = bertuiIconsInstalled
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 - FORCE PRODUCTION MODE
1
+ // bertui/src/build.js - CLEANED (No PageBuilder)
2
2
  import { join } from 'path';
3
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
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
+ }
@@ -1,4 +1,7 @@
1
- // bertui/src/client/compiler.js - FIXED: jsxDEV error
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
- 'div',
199
- {
200
- style: {
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
- const routerPath = join(outDir, 'router.js');
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', // ✅ Use 'react' instead of 'react-jsx'
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", // Default to localhost
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: [], // No paths blocked by default
34
- crawlDelay: null // No crawl delay by default
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);
@@ -1,4 +1,4 @@
1
- // bertui/src/server/dev-server.js - FIXED: Bun Native HMR
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
- // WebSocket upgrade for HMR
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; // Don't return a Response
37
+ return undefined;
40
38
  }
41
39
  return new Response('WebSocket upgrade failed', { status: 500 });
42
40
  }
43
41
 
44
- // Serve HTML
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
- // Serve compiled JavaScript
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
- // Serve images from src/images/
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
- // Serve from public/
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 (for bertui-* packages)
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
- const contentType = ext === '.js' ? 'application/javascript; charset=utf-8' : getContentType(ext);
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); // ✅ Debounce: wait 150ms for multiple file changes
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
  }
@@ -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
- }