bertui 1.1.9 → 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.
- package/index.js +65 -45
- package/package.json +1 -1
- package/src/analyzer/index.js +370 -0
- package/src/build/compiler/route-discoverer.js +2 -0
- package/src/build.js +90 -184
- package/src/cli.js +77 -27
- package/src/client/compiler.js +168 -67
- package/src/dev.js +47 -9
- package/src/hydration/index.js +151 -0
- package/src/layouts/index.js +165 -0
- package/src/loading/index.js +210 -0
- package/src/middleware/index.js +182 -0
- package/src/scaffolder/index.js +310 -0
- package/src/server/dev-handler.js +78 -148
- package/src/server-islands/index.js +1 -1
package/src/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// bertui/src/build.js -
|
|
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';
|
|
@@ -11,263 +11,169 @@ import { copyAllStaticAssets } from './build/processors/asset-processor.js';
|
|
|
11
11
|
import { generateProductionHTML } from './build/generators/html-generator.js';
|
|
12
12
|
import { generateSitemap } from './build/generators/sitemap-generator.js';
|
|
13
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';
|
|
14
18
|
|
|
15
19
|
export async function buildProduction(options = {}) {
|
|
16
20
|
const root = options.root || process.cwd();
|
|
17
21
|
const buildDir = join(root, '.bertuibuild');
|
|
18
22
|
const outDir = join(root, 'dist');
|
|
19
|
-
|
|
20
|
-
// Force production environment
|
|
23
|
+
|
|
21
24
|
process.env.NODE_ENV = 'production';
|
|
22
|
-
|
|
25
|
+
|
|
23
26
|
logger.bigLog('BUILDING WITH SERVER ISLANDS 🏝️', { color: 'green' });
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
|
|
26
28
|
if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
|
|
27
29
|
if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
|
|
28
30
|
mkdirSync(buildDir, { recursive: true });
|
|
29
31
|
mkdirSync(outDir, { recursive: true });
|
|
30
|
-
|
|
31
|
-
const startTime = process.hrtime.bigint();
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
const startTime = process.hrtime.bigint();
|
|
34
|
+
|
|
33
35
|
try {
|
|
34
36
|
logger.info('Step 0: Loading environment variables...');
|
|
35
37
|
const envVars = loadEnvVariables(root);
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
const { loadConfig } = await import('./config/loadConfig.js');
|
|
38
40
|
const config = await loadConfig(root);
|
|
39
|
-
|
|
40
|
-
logger.info('Step 1: Compiling
|
|
41
|
+
|
|
42
|
+
logger.info('Step 1: Compiling project...');
|
|
41
43
|
const { routes, serverIslands, clientRoutes } = await compileForBuild(root, buildDir, envVars);
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
if (serverIslands.length > 0) {
|
|
44
46
|
logger.bigLog('SERVER ISLANDS DETECTED 🏝️', { color: 'cyan' });
|
|
45
47
|
logger.table(serverIslands.map(r => ({
|
|
46
48
|
route: r.route,
|
|
47
49
|
file: r.file,
|
|
48
|
-
mode: '🏝️ Server Island (SSG)'
|
|
50
|
+
mode: '🏝️ Server Island (SSG)',
|
|
49
51
|
})));
|
|
50
52
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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...');
|
|
56
72
|
await buildAllCSS(root, outDir);
|
|
57
|
-
|
|
58
|
-
logger.info('Step
|
|
73
|
+
|
|
74
|
+
logger.info('Step 6: Copying static assets...');
|
|
59
75
|
await copyAllStaticAssets(root, outDir);
|
|
60
|
-
|
|
61
|
-
logger.info('Step
|
|
76
|
+
|
|
77
|
+
logger.info('Step 7: Bundling JavaScript...');
|
|
62
78
|
const buildEntry = join(buildDir, 'main.js');
|
|
63
79
|
const routerPath = join(buildDir, 'router.js');
|
|
64
|
-
|
|
80
|
+
|
|
65
81
|
if (!existsSync(buildEntry)) {
|
|
66
|
-
|
|
67
|
-
throw new Error('Build entry point missing');
|
|
82
|
+
throw new Error('Build entry point missing (main.js not found in build dir)');
|
|
68
83
|
}
|
|
69
|
-
|
|
70
|
-
const result = await bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir);
|
|
71
|
-
|
|
72
|
-
logger.info('Step
|
|
84
|
+
|
|
85
|
+
const result = await bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes);
|
|
86
|
+
|
|
87
|
+
logger.info('Step 8: Generating HTML...');
|
|
73
88
|
await generateProductionHTML(root, outDir, result, routes, serverIslands, config);
|
|
74
|
-
|
|
75
|
-
logger.info('Step
|
|
89
|
+
|
|
90
|
+
logger.info('Step 9: Generating sitemap.xml...');
|
|
76
91
|
await generateSitemap(routes, config, outDir);
|
|
77
|
-
|
|
78
|
-
logger.info('Step
|
|
92
|
+
|
|
93
|
+
logger.info('Step 10: Generating robots.txt...');
|
|
79
94
|
await generateRobots(config, outDir, routes);
|
|
80
|
-
|
|
81
|
-
//
|
|
95
|
+
|
|
96
|
+
// Cleanup build directory
|
|
82
97
|
if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
|
|
83
|
-
|
|
98
|
+
|
|
84
99
|
const endTime = process.hrtime.bigint();
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
|
|
92
110
|
setTimeout(() => {
|
|
93
|
-
logger.info('✅ Build
|
|
111
|
+
logger.info('✅ Build complete');
|
|
94
112
|
process.exit(0);
|
|
95
113
|
}, 100);
|
|
96
|
-
|
|
114
|
+
|
|
97
115
|
} catch (error) {
|
|
98
116
|
logger.error(`Build failed: ${error.message}`);
|
|
99
117
|
if (error.stack) logger.error(error.stack);
|
|
100
118
|
if (existsSync(buildDir)) rmSync(buildDir, { recursive: true, force: true });
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
setTimeout(() => {
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}, 100);
|
|
119
|
+
|
|
120
|
+
setTimeout(() => process.exit(1), 100);
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
|
|
109
|
-
async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir) {
|
|
124
|
+
async function bundleJavaScript(buildEntry, routerPath, outDir, envVars, buildDir, analyzedRoutes) {
|
|
125
|
+
const originalCwd = process.cwd();
|
|
126
|
+
process.chdir(buildDir);
|
|
127
|
+
|
|
110
128
|
try {
|
|
111
129
|
const hasRouter = existsSync(routerPath);
|
|
112
|
-
|
|
113
|
-
const originalCwd = process.cwd();
|
|
114
|
-
process.chdir(buildDir);
|
|
115
|
-
|
|
116
|
-
logger.info('🔧 Bundling with production JSX...');
|
|
117
|
-
|
|
118
130
|
const entrypoints = [buildEntry];
|
|
119
|
-
if (hasRouter)
|
|
120
|
-
|
|
121
|
-
logger.success('✅ Router included in bundle');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
logger.info(`📦 Entry points: ${entrypoints.map(e => e.split('/').pop()).join(', ')}`);
|
|
125
|
-
|
|
131
|
+
if (hasRouter) entrypoints.push(routerPath);
|
|
132
|
+
|
|
126
133
|
const result = await Bun.build({
|
|
127
134
|
entrypoints,
|
|
128
135
|
outdir: join(outDir, 'assets'),
|
|
129
136
|
target: 'browser',
|
|
130
137
|
minify: true,
|
|
131
|
-
splitting: true,
|
|
138
|
+
splitting: true, // Code splitting per route
|
|
132
139
|
sourcemap: 'external',
|
|
140
|
+
metafile: true, // ✅ Enable for analyzer
|
|
133
141
|
naming: {
|
|
134
142
|
entry: '[name]-[hash].js',
|
|
135
143
|
chunk: 'chunks/[name]-[hash].js',
|
|
136
|
-
asset: '[name]-[hash].[ext]'
|
|
144
|
+
asset: '[name]-[hash].[ext]',
|
|
137
145
|
},
|
|
138
146
|
external: ['react', 'react-dom', 'react-dom/client'],
|
|
139
147
|
define: {
|
|
140
148
|
'process.env.NODE_ENV': '"production"',
|
|
141
149
|
...Object.fromEntries(
|
|
142
|
-
Object.entries(envVars).map(([
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
])
|
|
146
|
-
)
|
|
147
|
-
}
|
|
150
|
+
Object.entries(envVars).map(([k, v]) => [`process.env.${k}`, JSON.stringify(v)])
|
|
151
|
+
),
|
|
152
|
+
},
|
|
148
153
|
});
|
|
149
|
-
|
|
150
|
-
process.chdir(originalCwd);
|
|
151
|
-
|
|
154
|
+
|
|
152
155
|
if (!result.success) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (result.logs && result.logs.length > 0) {
|
|
156
|
-
logger.error('\n📋 Build errors:');
|
|
157
|
-
result.logs.forEach((log, i) => {
|
|
158
|
-
logger.error(`\n${i + 1}. ${log.message}`);
|
|
159
|
-
if (log.position) {
|
|
160
|
-
logger.error(` File: ${log.position.file || 'unknown'}`);
|
|
161
|
-
logger.error(` Line: ${log.position.line || 'unknown'}`);
|
|
162
|
-
logger.error(` Column: ${log.position.column || 'unknown'}`);
|
|
163
|
-
}
|
|
164
|
-
if (log.position && log.position.file && existsSync(log.position.file)) {
|
|
165
|
-
try {
|
|
166
|
-
const fileContent = Bun.file(log.position.file).text();
|
|
167
|
-
const lines = fileContent.split('\n');
|
|
168
|
-
const line = lines[log.position.line - 1];
|
|
169
|
-
if (line) {
|
|
170
|
-
logger.error(` Code: ${line.trim()}`);
|
|
171
|
-
logger.error(` ${' '.repeat(log.position.column - 1)}^`);
|
|
172
|
-
}
|
|
173
|
-
} catch (e) {}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
} else {
|
|
177
|
-
logger.error('No detailed logs available');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
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}`);
|
|
181
158
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const chunks = result.outputs.filter(o => o.kind === 'chunk');
|
|
185
|
-
|
|
186
|
-
logger.success('✅ JavaScript bundled successfully');
|
|
187
|
-
logger.info(` Entry points: ${entryPoints.length}`);
|
|
188
|
-
logger.info(` Chunks: ${chunks.length}`);
|
|
189
|
-
|
|
190
|
-
result.outputs.forEach(output => {
|
|
191
|
-
const size = (output.size / 1024).toFixed(2);
|
|
192
|
-
logger.debug(` 📄 ${output.path.split('/').pop()} (${size} KB)`);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const totalSize = result.outputs.reduce((sum, o) => sum + (o.size || 0), 0);
|
|
196
|
-
logger.info(` Total size: ${(totalSize / 1024).toFixed(2)} KB`);
|
|
197
|
-
|
|
159
|
+
|
|
160
|
+
logger.success(`✅ Bundled ${result.outputs.length} files`);
|
|
198
161
|
return result;
|
|
199
|
-
|
|
200
|
-
} catch (error) {
|
|
201
|
-
logger.error('❌ Bundling error: ' + error.message);
|
|
202
|
-
if (error.stack) {
|
|
203
|
-
logger.error('Stack trace:');
|
|
204
|
-
logger.error(error.stack);
|
|
205
|
-
}
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
162
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (!existsSync(srcStylesDir)) return;
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const sass = await import('sass').catch(() => {
|
|
216
|
-
logger.warn('⚠️ sass package not installed. Install with: bun add sass');
|
|
217
|
-
return null;
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
if (!sass) return;
|
|
221
|
-
|
|
222
|
-
const { readdirSync } = await import('fs');
|
|
223
|
-
const scssFiles = readdirSync(srcStylesDir).filter(f =>
|
|
224
|
-
f.endsWith('.scss') || f.endsWith('.sass')
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
if (scssFiles.length === 0) return;
|
|
228
|
-
|
|
229
|
-
logger.info(`📝 Processing ${scssFiles.length} SCSS/SASS files...`);
|
|
230
|
-
|
|
231
|
-
for (const file of scssFiles) {
|
|
232
|
-
const srcPath = join(srcStylesDir, file);
|
|
233
|
-
const cssPath = join(buildDir, 'styles', file.replace(/\.(scss|sass)$/, '.css'));
|
|
234
|
-
|
|
235
|
-
mkdirSync(join(buildDir, 'styles'), { recursive: true });
|
|
236
|
-
|
|
237
|
-
const result = sass.compile(srcPath, {
|
|
238
|
-
style: 'compressed',
|
|
239
|
-
sourceMap: false,
|
|
240
|
-
loadPaths: [srcStylesDir, join(root, 'node_modules')]
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
await Bun.write(cssPath, result.css);
|
|
244
|
-
logger.debug(` ${file} → ${file.replace(/\.(scss|sass)$/, '.css')}`);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
logger.success(`✅ Processed ${scssFiles.length} SCSS files`);
|
|
248
|
-
} catch (error) {
|
|
249
|
-
logger.error(`SCSS processing failed: ${error.message}`);
|
|
163
|
+
} finally {
|
|
164
|
+
process.chdir(originalCwd);
|
|
250
165
|
}
|
|
251
166
|
}
|
|
252
167
|
|
|
253
|
-
function showBuildSummary(routes, serverIslands, clientRoutes,
|
|
254
|
-
logger.success(`✨ Build complete in ${durationMs.toFixed(
|
|
168
|
+
function showBuildSummary(routes, serverIslands, clientRoutes, analyzedRoutes, durationMs) {
|
|
169
|
+
logger.success(`✨ Build complete in ${durationMs.toFixed(1)}ms`);
|
|
255
170
|
logger.bigLog('BUILD SUMMARY', { color: 'green' });
|
|
256
171
|
logger.info(`📄 Total routes: ${routes.length}`);
|
|
257
172
|
logger.info(`🏝️ Server Islands (SSG): ${serverIslands.length}`);
|
|
258
|
-
logger.info(`⚡
|
|
173
|
+
logger.info(`⚡ Interactive (hydrated): ${analyzedRoutes.interactive.length}`);
|
|
174
|
+
logger.info(`🧊 Static (no JS): ${analyzedRoutes.static.length}`);
|
|
259
175
|
logger.info(`🗺️ Sitemap: dist/sitemap.xml`);
|
|
260
176
|
logger.info(`🤖 robots.txt: dist/robots.txt`);
|
|
261
|
-
|
|
262
|
-
const cacheStats = globalCache.getStats();
|
|
263
|
-
logger.info(`📊 Cache: ${cacheStats.hitRate} hit rate (${cacheStats.hits}/${cacheStats.hits + cacheStats.misses})`);
|
|
264
|
-
|
|
265
|
-
if (serverIslands.length > 0) {
|
|
266
|
-
logger.success('✅ Server Islands enabled - INSTANT content delivery!');
|
|
267
|
-
}
|
|
268
|
-
|
|
177
|
+
logger.info(`📊 Bundle report: dist/bundle-report.html`);
|
|
269
178
|
logger.bigLog('READY TO DEPLOY 🚀', { color: 'green' });
|
|
270
|
-
|
|
271
|
-
// ✅ Force log flush
|
|
272
|
-
logger.debug('Build complete, exiting...');
|
|
273
179
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,49 +1,87 @@
|
|
|
1
|
-
// bertui/src/cli.js - WITH
|
|
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';
|
|
4
|
+
import { startPreviewServer } from './serve.js';
|
|
5
|
+
import { scaffold, parseCreateArgs } from './scaffolder/index.js';
|
|
6
|
+
import { analyzeBuild } from './analyzer/index.js';
|
|
5
7
|
import logger from './logger/logger.js';
|
|
8
|
+
import { join } from 'path';
|
|
6
9
|
|
|
7
10
|
export function program() {
|
|
8
11
|
const args = process.argv.slice(2);
|
|
9
12
|
const command = args[0] || 'dev';
|
|
10
13
|
|
|
11
14
|
switch (command) {
|
|
12
|
-
case 'dev':
|
|
15
|
+
case 'dev': {
|
|
13
16
|
const devPort = getArg('--port', '-p') || 3000;
|
|
14
|
-
startDev({
|
|
15
|
-
port: parseInt(devPort),
|
|
16
|
-
root: process.cwd()
|
|
17
|
+
startDev({
|
|
18
|
+
port: parseInt(devPort),
|
|
19
|
+
root: process.cwd(),
|
|
17
20
|
});
|
|
18
21
|
break;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
case 'build': {
|
|
25
|
+
buildProduction({ root: process.cwd() });
|
|
24
26
|
break;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
27
29
|
case 'serve':
|
|
28
|
-
case 'preview':
|
|
30
|
+
case 'preview': {
|
|
29
31
|
const previewPort = getArg('--port', '-p') || 5000;
|
|
30
32
|
startPreviewServer({
|
|
31
33
|
port: parseInt(previewPort),
|
|
32
34
|
root: process.cwd(),
|
|
33
|
-
dir: 'dist'
|
|
35
|
+
dir: 'dist',
|
|
34
36
|
});
|
|
35
37
|
break;
|
|
36
|
-
|
|
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
|
+
|
|
37
75
|
case '--version':
|
|
38
76
|
case '-v':
|
|
39
|
-
console.log('bertui v1.
|
|
77
|
+
console.log('bertui v1.2.0');
|
|
40
78
|
break;
|
|
41
|
-
|
|
79
|
+
|
|
42
80
|
case '--help':
|
|
43
81
|
case '-h':
|
|
44
82
|
showHelp();
|
|
45
83
|
break;
|
|
46
|
-
|
|
84
|
+
|
|
47
85
|
default:
|
|
48
86
|
logger.error(`Unknown command: ${command}`);
|
|
49
87
|
showHelp();
|
|
@@ -62,20 +100,32 @@ function showHelp() {
|
|
|
62
100
|
logger.bigLog('BERTUI CLI', { color: 'blue' });
|
|
63
101
|
console.log(`
|
|
64
102
|
Commands:
|
|
65
|
-
bertui dev [--port]
|
|
66
|
-
bertui build
|
|
67
|
-
bertui serve [--port]
|
|
68
|
-
bertui --
|
|
69
|
-
|
|
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
|
|
70
113
|
|
|
71
114
|
Options:
|
|
72
|
-
--port, -p <number>
|
|
115
|
+
--port, -p <number> Port for server
|
|
116
|
+
--open Open browser after command
|
|
73
117
|
|
|
74
118
|
Examples:
|
|
75
119
|
bertui dev
|
|
76
120
|
bertui dev --port 8080
|
|
77
121
|
bertui build
|
|
78
|
-
bertui
|
|
79
|
-
bertui
|
|
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
|
|
80
130
|
`);
|
|
81
131
|
}
|