bertui 1.1.8 → 1.2.0

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.
@@ -1,102 +1,138 @@
1
- // bertui/src/build/processors/css-builder.js - SAFE VERSION
1
+ // bertui/src/build/processors/css-builder.js - WITH SCSS + CACHING
2
2
  import { join } from 'path';
3
3
  import { existsSync, readdirSync, mkdirSync } from 'fs';
4
4
  import logger from '../../logger/logger.js';
5
+ import { globalCache } from '../../utils/cache.js';
6
+ import { minifyCSS, processSCSS } from '../../css/processor.js';
5
7
 
6
8
  export async function buildAllCSS(root, outDir) {
9
+ const startTime = process.hrtime.bigint();
10
+
7
11
  const srcStylesDir = join(root, 'src', 'styles');
8
12
  const stylesOutDir = join(outDir, 'styles');
9
13
 
10
14
  mkdirSync(stylesOutDir, { recursive: true });
11
15
 
12
- if (existsSync(srcStylesDir)) {
13
- const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
14
-
15
- if (cssFiles.length === 0) {
16
- await Bun.write(join(stylesOutDir, 'bertui.min.css'), '/* No CSS */');
17
- return;
18
- }
19
-
20
- logger.info(`Processing ${cssFiles.length} CSS file(s)...`);
21
-
22
- let combinedCSS = '';
23
- for (const cssFile of cssFiles) {
24
- const srcPath = join(srcStylesDir, cssFile);
25
- const file = Bun.file(srcPath);
26
- const cssContent = await file.text();
27
- combinedCSS += `/* ${cssFile} */\n${cssContent}\n\n`;
28
- }
29
-
30
- const combinedPath = join(stylesOutDir, 'bertui.min.css');
31
-
32
- // ✅ SAFE: Try Lightning CSS, fallback to simple minification
33
- try {
34
- const minified = await minifyCSSSafe(combinedCSS);
35
- await Bun.write(combinedPath, minified);
36
-
37
- const originalSize = Buffer.byteLength(combinedCSS);
38
- const minifiedSize = Buffer.byteLength(minified);
39
- const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
40
-
41
- logger.success(`CSS minified: ${(originalSize/1024).toFixed(2)}KB → ${(minifiedSize/1024).toFixed(2)}KB (-${reduction}%)`);
42
- } catch (error) {
43
- logger.warn(`CSS minification failed: ${error.message}`);
44
- logger.info('Falling back to unminified CSS...');
45
- await Bun.write(combinedPath, combinedCSS);
46
- }
47
-
48
- logger.success(`✅ Combined ${cssFiles.length} CSS files`);
49
- } else {
50
- // No styles directory, create empty CSS
16
+ // Check cache for entire CSS build
17
+ const cacheKey = `css-build:${root}:${Date.now()}`;
18
+ const cached = globalCache.get(cacheKey, { ttl: 1000 }); // 1 second cache
19
+
20
+ if (cached) {
21
+ logger.info(`⚡ Using cached CSS (${cached.files} files)`);
22
+ await Bun.write(join(stylesOutDir, 'bertui.min.css'), cached.content);
23
+ return;
24
+ }
25
+
26
+ if (!existsSync(srcStylesDir)) {
51
27
  await Bun.write(join(stylesOutDir, 'bertui.min.css'), '/* No custom styles */');
52
28
  logger.info('No styles directory found, created empty CSS');
29
+ return;
30
+ }
31
+
32
+ // Process SCSS files first
33
+ await processSCSSDirectory(srcStylesDir, root);
34
+
35
+ // Read all CSS files (including compiled SCSS)
36
+ const cssFiles = readdirSync(srcStylesDir).filter(f => f.endsWith('.css'));
37
+
38
+ if (cssFiles.length === 0) {
39
+ await Bun.write(join(stylesOutDir, 'bertui.min.css'), '/* No CSS */');
40
+ return;
53
41
  }
42
+
43
+ logger.info(`Processing ${cssFiles.length} CSS file(s)...`);
44
+
45
+ let combinedCSS = '';
46
+ const fileContents = [];
47
+
48
+ for (const cssFile of cssFiles) {
49
+ const srcPath = join(srcStylesDir, cssFile);
50
+
51
+ // Use file cache
52
+ const fileBuffer = await globalCache.getFile(srcPath, { logSpeed: true });
53
+ if (fileBuffer) {
54
+ const content = fileBuffer.toString('utf-8');
55
+ fileContents.push({ filename: cssFile, content });
56
+ combinedCSS += `/* ${cssFile} */\n${content}\n\n`;
57
+ }
58
+ }
59
+
60
+ const combinedPath = join(stylesOutDir, 'bertui.min.css');
61
+
62
+ // Minify with caching
63
+ const minifyCacheKey = `minify:${Buffer.from(combinedCSS).length}:${combinedCSS.substring(0, 100)}`;
64
+ let minified = globalCache.get(minifyCacheKey);
65
+
66
+ if (!minified) {
67
+ minified = await minifyCSS(combinedCSS, {
68
+ filename: 'bertui.min.css',
69
+ sourceMap: false
70
+ });
71
+ globalCache.set(minifyCacheKey, minified, { ttl: 60000 }); // Cache for 60 seconds
72
+ }
73
+
74
+ await Bun.write(combinedPath, minified);
75
+
76
+ const originalSize = Buffer.byteLength(combinedCSS);
77
+ const minifiedSize = Buffer.byteLength(minified);
78
+ const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
79
+
80
+ const endTime = process.hrtime.bigint();
81
+ const duration = Number(endTime - startTime) / 1000; // Microseconds
82
+
83
+ logger.success(`CSS optimized: ${(originalSize/1024).toFixed(2)}KB → ${(minifiedSize/1024).toFixed(2)}KB (-${reduction}%)`);
84
+ logger.info(`⚡ Processing time: ${duration.toFixed(3)}µs`);
85
+
86
+ // Cache the final result
87
+ globalCache.set(cacheKey, {
88
+ files: cssFiles.length,
89
+ content: minified,
90
+ size: minifiedSize
91
+ }, { ttl: 5000 });
54
92
  }
55
93
 
56
- /**
57
- * Safe CSS minification with fallback
58
- */
59
- async function minifyCSSSafe(css) {
60
- // Try Lightning CSS first
94
+ // NEW: Process SCSS directory
95
+ async function processSCSSDirectory(stylesDir, root) {
61
96
  try {
62
- const { transform } = await import('lightningcss');
97
+ // Check if sass is installed
98
+ const sass = await import('sass').catch(() => null);
99
+ if (!sass) return;
63
100
 
64
- const { code } = transform({
65
- filename: 'styles.css',
66
- code: Buffer.from(css),
67
- minify: true,
68
- sourceMap: false,
69
- targets: {
70
- chrome: 90 << 16,
71
- firefox: 88 << 16,
72
- safari: 14 << 16,
73
- edge: 90 << 16
74
- }
75
- });
101
+ const files = readdirSync(stylesDir);
102
+ const scssFiles = files.filter(f => f.endsWith('.scss') || f.endsWith('.sass'));
76
103
 
77
- return code.toString();
104
+ if (scssFiles.length === 0) return;
78
105
 
79
- } catch (lightningError) {
80
- logger.warn('Lightning CSS failed, using simple minification');
106
+ logger.info(`📝 Compiling ${scssFiles.length} SCSS files...`);
81
107
 
82
- // Fallback: Simple manual minification
83
- return simpleMinifyCSS(css);
108
+ for (const file of scssFiles) {
109
+ const srcPath = join(stylesDir, file);
110
+ const cssPath = join(stylesDir, file.replace(/\.(scss|sass)$/, '.css'));
111
+
112
+ // Check cache
113
+ const fileBuffer = await globalCache.getFile(srcPath);
114
+ const cacheKey = `scss:${file}:${Buffer.from(fileBuffer).length}`;
115
+ const cached = globalCache.get(cacheKey);
116
+
117
+ if (cached && existsSync(cssPath)) {
118
+ logger.debug(`⚡ Cached SCSS: ${file} → ${file.replace(/\.(scss|sass)$/, '.css')}`);
119
+ continue;
120
+ }
121
+
122
+ const result = sass.compile(srcPath, {
123
+ style: 'expanded',
124
+ sourceMap: false,
125
+ loadPaths: [stylesDir, join(root, 'node_modules')]
126
+ });
127
+
128
+ await Bun.write(cssPath, result.css);
129
+ globalCache.set(cacheKey, true, { ttl: 30000 });
130
+
131
+ logger.debug(` ${file} → ${file.replace(/\.(scss|sass)$/, '.css')}`);
132
+ }
133
+
134
+ logger.success(`✅ SCSS compilation complete`);
135
+ } catch (error) {
136
+ logger.warn(`SCSS processing skipped: ${error.message}`);
84
137
  }
85
- }
86
-
87
- /**
88
- * Simple CSS minification without dependencies
89
- */
90
- function simpleMinifyCSS(css) {
91
- return css
92
- // Remove comments
93
- .replace(/\/\*[\s\S]*?\*\//g, '')
94
- // Remove extra whitespace
95
- .replace(/\s+/g, ' ')
96
- // Remove space around { } : ; ,
97
- .replace(/\s*([{}:;,])\s*/g, '$1')
98
- // Remove trailing semicolons before }
99
- .replace(/;}/g, '}')
100
- // Remove leading/trailing whitespace
101
- .trim();
102
138
  }
package/src/build.js CHANGED
@@ -1,8 +1,9 @@
1
- // bertui/src/build.js - CLEANED (No PageBuilder)
1
+ // bertui/src/build.js - WITH LAYOUTS + LOADING + PARTIAL HYDRATION + ANALYZER
2
2
  import { join } from 'path';
3
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 { globalCache } from './utils/cache.js';
6
7
 
7
8
  import { compileForBuild } from './build/compiler/index.js';
8
9
  import { buildAllCSS } from './build/processors/css-builder.js';
@@ -10,159 +11,169 @@ import { copyAllStaticAssets } from './build/processors/asset-processor.js';
10
11
  import { generateProductionHTML } from './build/generators/html-generator.js';
11
12
  import { generateSitemap } from './build/generators/sitemap-generator.js';
12
13
  import { generateRobots } from './build/generators/robots-generator.js';
14
+ import { compileLayouts } from './layouts/index.js';
15
+ import { compileLoadingComponents } from './loading/index.js';
16
+ import { analyzeRoutes, logHydrationReport } from './hydration/index.js';
17
+ import { analyzeBuild } from './analyzer/index.js';
13
18
 
14
19
  export async function buildProduction(options = {}) {
15
20
  const root = options.root || process.cwd();
16
21
  const buildDir = join(root, '.bertuibuild');
17
22
  const outDir = join(root, 'dist');
18
-
19
- // Force production environment
23
+
20
24
  process.env.NODE_ENV = 'production';
21
-
25
+
22
26
  logger.bigLog('BUILDING WITH SERVER ISLANDS 🏝️', { color: 'green' });
23
- logger.info('🔥 OPTIONAL SERVER CONTENT - THE GAME CHANGER');
24
-
25
- if (existsSync(buildDir)) rmSync(buildDir, { recursive: true });
26
- if (existsSync(outDir)) rmSync(outDir, { recursive: true });
27
+
28
+ if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
29
+ if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
27
30
  mkdirSync(buildDir, { recursive: true });
28
31
  mkdirSync(outDir, { recursive: true });
29
-
30
- const startTime = Date.now();
31
-
32
+
33
+ const startTime = process.hrtime.bigint();
34
+
32
35
  try {
33
36
  logger.info('Step 0: Loading environment variables...');
34
37
  const envVars = loadEnvVariables(root);
35
-
38
+
36
39
  const { loadConfig } = await import('./config/loadConfig.js');
37
40
  const config = await loadConfig(root);
38
-
39
- logger.info('Step 1: Compiling and detecting Server Islands...');
41
+
42
+ logger.info('Step 1: Compiling project...');
40
43
  const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars);
41
-
44
+
42
45
  if (serverIslands.length > 0) {
43
46
  logger.bigLog('SERVER ISLANDS DETECTED 🏝️', { color: 'cyan' });
44
47
  logger.table(serverIslands.map(r => ({
45
48
  route: r.route,
46
49
  file: r.file,
47
- mode: '🏝️ Server Island (SSG)'
50
+ mode: '🏝️ Server Island (SSG)',
48
51
  })));
49
52
  }
50
-
51
- logger.info('Step 2: Combining CSS...');
53
+
54
+ // ✅ NEW: Compile layouts
55
+ logger.info('Step 2: Compiling layouts...');
56
+ const layouts = await compileLayouts(root, buildDir);
57
+ const layoutCount = Object.keys(layouts).length;
58
+ if (layoutCount > 0) {
59
+ logger.success(`📐 ${layoutCount} layout(s) compiled`);
60
+ }
61
+
62
+ // ✅ NEW: Compile per-route loading states
63
+ logger.info('Step 3: Compiling loading states...');
64
+ const loadingComponents = await compileLoadingComponents(root, buildDir);
65
+
66
+ // ✅ NEW: Partial hydration analysis
67
+ logger.info('Step 4: Analyzing routes for partial hydration...');
68
+ const analyzedRoutes = await analyzeRoutes(routes);
69
+ logHydrationReport(analyzedRoutes);
70
+
71
+ logger.info('Step 5: Processing CSS...');
52
72
  await buildAllCSS(root, outDir);
53
-
54
- logger.info('Step 3: Copying static assets...');
73
+
74
+ logger.info('Step 6: Copying static assets...');
55
75
  await copyAllStaticAssets(root, outDir);
56
-
57
- logger.info('Step 4: Bundling JavaScript...');
76
+
77
+ logger.info('Step 7: Bundling JavaScript...');
58
78
  const buildEntry = join(buildDir, 'main.js');
59
-
79
+ const routerPath = join(buildDir, 'router.js');
80
+
60
81
  if (!existsSync(buildEntry)) {
61
- logger.error(' main.js not found in build directory!');
62
- throw new Error('Build entry point missing');
82
+ throw new Error('Build entry point missing (main.js not found in build dir)');
63
83
  }
64
-
65
- const result = await bundleJavaScript(buildEntry, outDir, envVars, buildDir);
66
-
67
- logger.info('Step 5: Generating HTML with Server Islands...');
84
+
85
+ const result = await bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes);
86
+
87
+ logger.info('Step 8: Generating HTML...');
68
88
  await generateProductionHTML(root, outDir, result, routes, serverIslands, config);
69
-
70
- logger.info('Step 6: Generating sitemap.xml...');
89
+
90
+ logger.info('Step 9: Generating sitemap.xml...');
71
91
  await generateSitemap(routes, config, outDir);
72
-
73
- logger.info('Step 7: Generating robots.txt...');
92
+
93
+ logger.info('Step 10: Generating robots.txt...');
74
94
  await generateRobots(config, outDir, routes);
75
-
76
- if (existsSync(buildDir)) rmSync(buildDir, { recursive: true });
77
-
78
- const duration = Date.now() - startTime;
79
- showBuildSummary(routes, serverIslands, clientRoutes, duration);
80
-
95
+
96
+ // Cleanup build directory
97
+ if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
98
+
99
+ const endTime = process.hrtime.bigint();
100
+ const durationMs = Number(endTime - startTime) / 1_000_000;
101
+
102
+ showBuildSummary(routes, serverIslands, clientRoutes, analyzedRoutes, durationMs);
103
+
104
+ // ✅ NEW: Auto-generate bundle report
105
+ logger.info('Generating bundle report...');
106
+ await analyzeBuild(outDir, {
107
+ outputFile: join(outDir, 'bundle-report.html'),
108
+ });
109
+
110
+ setTimeout(() => {
111
+ logger.info('✅ Build complete');
112
+ process.exit(0);
113
+ }, 100);
114
+
81
115
  } catch (error) {
82
116
  logger.error(`Build failed: ${error.message}`);
83
117
  if (error.stack) logger.error(error.stack);
84
- if (existsSync(buildDir)) rmSync(buildDir, { recursive: true });
85
- process.exit(1);
118
+ if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
119
+
120
+ setTimeout(() => process.exit(1), 100);
86
121
  }
87
122
  }
88
123
 
89
- async function bundleJavaScript(buildEntry, outDir, envVars, buildDir) {
124
+ async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes) {
125
+ const originalCwd = process.cwd();
126
+ process.chdir(buildDir);
127
+
90
128
  try {
91
- const originalCwd = process.cwd();
92
- process.chdir(buildDir);
93
-
94
- logger.info('🔧 Bundling with production JSX...');
95
-
129
+ const hasRouter = existsSync(routerPath);
130
+ const entrypoints = [buildEntry];
131
+ if (hasRouter) entrypoints.push(routerPath);
132
+
96
133
  const result = await Bun.build({
97
- entrypoints: [buildEntry],
134
+ entrypoints,
98
135
  outdir: join(outDir, 'assets'),
99
136
  target: 'browser',
100
137
  minify: true,
101
- splitting: true,
138
+ splitting: true, // Code splitting per route
102
139
  sourcemap: 'external',
140
+ metafile: true, // ✅ Enable for analyzer
103
141
  naming: {
104
142
  entry: '[name]-[hash].js',
105
143
  chunk: 'chunks/[name]-[hash].js',
106
- asset: '[name]-[hash].[ext]'
144
+ asset: '[name]-[hash].[ext]',
107
145
  },
108
146
  external: ['react', 'react-dom', 'react-dom/client'],
109
147
  define: {
110
148
  'process.env.NODE_ENV': '"production"',
111
149
  ...Object.fromEntries(
112
- Object.entries(envVars).map(([key, value]) => [
113
- `process.env.${key}`,
114
- JSON.stringify(value)
115
- ])
116
- )
117
- }
150
+ Object.entries(envVars).map(([k, v]) => [`process.env.${k}`, JSON.stringify(v)])
151
+ ),
152
+ },
118
153
  });
119
-
120
- process.chdir(originalCwd);
121
-
154
+
122
155
  if (!result.success) {
123
- logger.error(' JavaScript build failed!');
124
-
125
- if (result.logs && result.logs.length > 0) {
126
- logger.error('\n📋 Build errors:');
127
- result.logs.forEach((log, i) => {
128
- logger.error(`\n${i + 1}. ${log.message}`);
129
- if (log.position) {
130
- logger.error(` File: ${log.position.file || 'unknown'}`);
131
- logger.error(` Line: ${log.position.line || 'unknown'}`);
132
- }
133
- });
134
- }
135
-
136
- throw new Error('JavaScript bundling failed');
156
+ const errors = result.logs?.map(l => l.message).join('\n') || 'Unknown error';
157
+ throw new Error(`JavaScript bundling failed:\n${errors}`);
137
158
  }
138
-
139
- logger.success('✅ JavaScript bundled successfully');
140
- logger.info(` Entry points: ${result.outputs.filter(o => o.kind === 'entry-point').length}`);
141
- logger.info(` Chunks: ${result.outputs.filter(o => o.kind === 'chunk').length}`);
142
-
143
- const totalSize = result.outputs.reduce((sum, o) => sum + (o.size || 0), 0);
144
- logger.info(` Total size: ${(totalSize / 1024).toFixed(2)} KB`);
145
-
159
+
160
+ logger.success(`✅ Bundled ${result.outputs.length} files`);
146
161
  return result;
147
-
148
- } catch (error) {
149
- logger.error('❌ Bundling error: ' + error.message);
150
- throw error;
162
+
163
+ } finally {
164
+ process.chdir(originalCwd);
151
165
  }
152
166
  }
153
167
 
154
- function showBuildSummary(routes, serverIslands, clientRoutes, duration) {
155
- logger.success(`✨ Build complete in ${duration}ms`);
168
+ function showBuildSummary(routes, serverIslands, clientRoutes, analyzedRoutes, durationMs) {
169
+ logger.success(`✨ Build complete in ${durationMs.toFixed(1)}ms`);
156
170
  logger.bigLog('BUILD SUMMARY', { color: 'green' });
157
171
  logger.info(`📄 Total routes: ${routes.length}`);
158
172
  logger.info(`🏝️ Server Islands (SSG): ${serverIslands.length}`);
159
- logger.info(`⚡ Client-only: ${clientRoutes.length}`);
173
+ logger.info(`⚡ Interactive (hydrated): ${analyzedRoutes.interactive.length}`);
174
+ logger.info(`🧊 Static (no JS): ${analyzedRoutes.static.length}`);
160
175
  logger.info(`🗺️ Sitemap: dist/sitemap.xml`);
161
176
  logger.info(`🤖 robots.txt: dist/robots.txt`);
162
-
163
- if (serverIslands.length > 0) {
164
- logger.success('✅ Server Islands enabled - INSTANT content delivery!');
165
- }
166
-
177
+ logger.info(`📊 Bundle report: dist/bundle-report.html`);
167
178
  logger.bigLog('READY TO DEPLOY 🚀', { color: 'green' });
168
179
  }
package/src/cli.js CHANGED
@@ -1,37 +1,87 @@
1
- // src/cli.js
1
+ // bertui/src/cli.js - WITH ALL COMMANDS
2
2
  import { startDev } from './dev.js';
3
3
  import { buildProduction } from './build.js';
4
+ import { startPreviewServer } from './serve.js';
5
+ import { scaffold, parseCreateArgs } from './scaffolder/index.js';
6
+ import { analyzeBuild } from './analyzer/index.js';
4
7
  import logger from './logger/logger.js';
8
+ import { join } from 'path';
5
9
 
6
10
  export function program() {
7
11
  const args = process.argv.slice(2);
8
12
  const command = args[0] || 'dev';
9
13
 
10
14
  switch (command) {
11
- case 'dev':
15
+ case 'dev': {
12
16
  const devPort = getArg('--port', '-p') || 3000;
13
- startDev({
14
- port: parseInt(devPort),
15
- root: process.cwd()
17
+ startDev({
18
+ port: parseInt(devPort),
19
+ root: process.cwd(),
16
20
  });
17
21
  break;
18
-
19
- case 'build':
20
- buildProduction({
21
- root: process.cwd()
22
+ }
23
+
24
+ case 'build': {
25
+ buildProduction({ root: process.cwd() });
26
+ break;
27
+ }
28
+
29
+ case 'serve':
30
+ case 'preview': {
31
+ const previewPort = getArg('--port', '-p') || 5000;
32
+ startPreviewServer({
33
+ port: parseInt(previewPort),
34
+ root: process.cwd(),
35
+ dir: 'dist',
22
36
  });
23
37
  break;
24
-
38
+ }
39
+
40
+ // ✅ NEW: Component/page/layout scaffolder
41
+ case 'create': {
42
+ const createArgs = args.slice(1);
43
+ const parsed = parseCreateArgs(createArgs);
44
+ if (parsed) {
45
+ scaffold(parsed.type, parsed.name, { root: process.cwd() })
46
+ .then(result => {
47
+ if (!result) process.exit(1);
48
+ })
49
+ .catch(err => {
50
+ logger.error(`Create failed: ${err.message}`);
51
+ process.exit(1);
52
+ });
53
+ }
54
+ break;
55
+ }
56
+
57
+ // ✅ NEW: Bundle analyzer
58
+ case 'analyze': {
59
+ const outDir = join(process.cwd(), 'dist');
60
+ const open = args.includes('--open');
61
+ analyzeBuild(outDir, { open })
62
+ .then(result => {
63
+ if (!result) {
64
+ logger.error('No build found. Run: bertui build');
65
+ process.exit(1);
66
+ }
67
+ })
68
+ .catch(err => {
69
+ logger.error(`Analyze failed: ${err.message}`);
70
+ process.exit(1);
71
+ });
72
+ break;
73
+ }
74
+
25
75
  case '--version':
26
76
  case '-v':
27
- console.log('bertui v0.1.0');
77
+ console.log('bertui v1.2.0');
28
78
  break;
29
-
79
+
30
80
  case '--help':
31
81
  case '-h':
32
82
  showHelp();
33
83
  break;
34
-
84
+
35
85
  default:
36
86
  logger.error(`Unknown command: ${command}`);
37
87
  showHelp();
@@ -50,17 +100,32 @@ function showHelp() {
50
100
  logger.bigLog('BERTUI CLI', { color: 'blue' });
51
101
  console.log(`
52
102
  Commands:
53
- bertui dev Start development server
54
- bertui build Build for production
55
- bertui --version Show version
56
- bertui --help Show help
103
+ bertui dev [--port] Start development server (default: 3000)
104
+ bertui build Build for production
105
+ bertui serve [--port] Preview production build (default: 5000)
106
+ bertui analyze [--open] Analyze bundle size (opens report in browser)
107
+
108
+ bertui create component <Name> Scaffold a React component
109
+ bertui create page <name> Scaffold a page (adds to file-based routing)
110
+ bertui create layout <name> Scaffold a layout (default wraps all pages)
111
+ bertui create loading <route> Scaffold a per-route loading state
112
+ bertui create middleware Scaffold src/middleware.ts
57
113
 
58
114
  Options:
59
- --port, -p <number> Port for dev server (default: 3000)
115
+ --port, -p <number> Port for server
116
+ --open Open browser after command
60
117
 
61
118
  Examples:
62
119
  bertui dev
63
120
  bertui dev --port 8080
64
121
  bertui build
122
+ bertui analyze --open
123
+ bertui create component Button
124
+ bertui create page About
125
+ bertui create page blog/[slug]
126
+ bertui create layout default
127
+ bertui create layout blog
128
+ bertui create loading blog
129
+ bertui create middleware
65
130
  `);
66
131
  }