bertui 1.2.1 โ†’ 1.2.3

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,4 +1,4 @@
1
- // src/config/loadConfig.js - COMPLETE CORRECTED VERSION
1
+ // bertui/src/config/loadConfig.js
2
2
  import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
4
  import { defaultConfig } from './defaultConfig.js';
@@ -6,44 +6,59 @@ import logger from '../logger/logger.js';
6
6
 
7
7
  export async function loadConfig(root) {
8
8
  const configPath = join(root, 'bertui.config.js');
9
-
10
- // Check if user created config
11
- if (existsSync(configPath)) {
12
- try {
13
- const userConfig = await import(configPath);
14
- logger.success('Loaded bertui.config.js');
15
-
16
- // DEBUG: Show what we loaded
17
- logger.info(`๐Ÿ“‹ Config loaded: ${JSON.stringify({
18
- hasSiteName: !!(userConfig.default?.siteName || userConfig.siteName),
19
- hasBaseUrl: !!(userConfig.default?.baseUrl || userConfig.baseUrl),
20
- hasRobots: !!(userConfig.default?.robots || userConfig.robots)
21
- })}`);
22
-
23
- // Merge user config with defaults
24
- return mergeConfig(defaultConfig, userConfig.default || userConfig);
25
- } catch (error) {
26
- logger.error(`Failed to load config. Make sure bertui.config.js is in the root directory: ${error.message}`);
9
+
10
+ if (!existsSync(configPath)) {
11
+ logger.info('No config found, using defaults');
12
+ return defaultConfig;
13
+ }
14
+
15
+ try {
16
+ // Read and transpile the config file manually โ€”
17
+ // avoids Bun's dynamic import() build step which errors on plain JS configs
18
+ const source = await Bun.file(configPath).text();
19
+
20
+ const transpiler = new Bun.Transpiler({
21
+ loader: 'js',
22
+ target: 'bun',
23
+ });
24
+
25
+ let code = await transpiler.transform(source);
26
+
27
+ // Strip any leftover 'export default' so we can eval it
28
+ // and grab the value directly
29
+ code = code.replace(/export\s+default\s+/, 'globalThis.__bertuiConfig = ');
30
+
31
+ // Run it in the current context
32
+ const fn = new Function('globalThis', code);
33
+ fn(globalThis);
34
+
35
+ const userConfig = globalThis.__bertuiConfig;
36
+ delete globalThis.__bertuiConfig;
37
+
38
+ if (!userConfig) {
39
+ logger.warn('bertui.config.js did not export a default value, using defaults');
27
40
  return defaultConfig;
28
41
  }
42
+
43
+ logger.success('Loaded bertui.config.js');
44
+
45
+ logger.info(`๐Ÿ“‹ Config: importhow=${JSON.stringify(Object.keys(userConfig.importhow || {}))}`);
46
+
47
+ return mergeConfig(defaultConfig, userConfig);
48
+
49
+ } catch (error) {
50
+ logger.error(`Failed to load bertui.config.js: ${error.message}`);
51
+ return defaultConfig;
29
52
  }
30
-
31
- logger.info('No config found, using defaults');
32
- return defaultConfig;
33
53
  }
34
54
 
35
55
  function mergeConfig(defaults, user) {
36
- // Start with user config (so user values override defaults)
37
56
  const merged = { ...user };
38
-
39
- // Deep merge for nested objects
40
- merged.meta = { ...defaults.meta, ...(user.meta || {}) };
41
- merged.appShell = { ...defaults.appShell, ...(user.appShell || {}) };
42
- merged.robots = { ...defaults.robots, ...(user.robots || {}) };
43
-
44
- // Ensure we have required top-level fields
57
+ merged.meta = { ...defaults.meta, ...(user.meta || {}) };
58
+ merged.appShell = { ...defaults.appShell, ...(user.appShell || {}) };
59
+ merged.robots = { ...defaults.robots, ...(user.robots || {}) };
60
+ merged.importhow = { ...(defaults.importhow || {}), ...(user.importhow || {}) };
45
61
  if (!merged.siteName) merged.siteName = defaults.siteName;
46
- if (!merged.baseUrl) merged.baseUrl = defaults.baseUrl;
47
-
62
+ if (!merged.baseUrl) merged.baseUrl = defaults.baseUrl;
48
63
  return merged;
49
64
  }
package/src/dev.js CHANGED
@@ -1,64 +1,68 @@
1
- // bertui/src/dev.js - WITH MIDDLEWARE + LAYOUTS + LOADING + PARTIAL HYDRATION
1
+ // bertui/src/dev.js
2
2
  import { compileProject } from './client/compiler.js';
3
3
  import { startDevServer } from './server/dev-server.js';
4
4
  import { MiddlewareManager } from './middleware/index.js';
5
- import { compileLayouts, discoverLayouts } from './layouts/index.js';
5
+ import { compileLayouts } from './layouts/index.js';
6
6
  import { compileLoadingComponents } from './loading/index.js';
7
7
  import { analyzeRoutes, logHydrationReport } from './hydration/index.js';
8
8
  import logger from './logger/logger.js';
9
9
  import { loadConfig } from './config/loadConfig.js';
10
10
 
11
+ const TOTAL_STEPS = 6;
12
+
11
13
  export async function startDev(options = {}) {
12
14
  const root = options.root || process.cwd();
13
15
  const port = options.port || 3000;
14
16
 
17
+ logger.printHeader('DEV');
18
+
15
19
  try {
16
20
  const config = await loadConfig(root);
17
21
 
18
- // Step 1: Compile project
19
- logger.info('Step 1: Compiling project...');
22
+ // โ”€โ”€ Step 1: Compile โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23
+ logger.step(1, TOTAL_STEPS, 'Compiling');
20
24
  const { routes, outDir } = await compileProject(root);
25
+ logger.stepDone('Compiling', `${routes.length} routes`);
21
26
 
22
- // Step 2: Compile layouts
23
- logger.info('Step 2: Loading layouts...');
27
+ // โ”€โ”€ Step 2: Layouts โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
28
+ logger.step(2, TOTAL_STEPS, 'Layouts');
24
29
  const layouts = await compileLayouts(root, outDir);
25
30
  const layoutCount = Object.keys(layouts).length;
26
- if (layoutCount > 0) {
27
- logger.success(`๐Ÿ“ ${layoutCount} layout(s) active`);
28
- } else {
29
- logger.info('No layouts found (create src/layouts/default.tsx to wrap all pages)');
30
- }
31
+ logger.stepDone('Layouts', layoutCount > 0 ? `${layoutCount} found` : 'none');
31
32
 
32
- // Step 3: Compile loading states
33
- logger.info('Step 3: Loading per-route loading states...');
33
+ // โ”€โ”€ Step 3: Loading states โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
+ logger.step(3, TOTAL_STEPS, 'Loading states');
34
35
  const loadingComponents = await compileLoadingComponents(root, outDir);
36
+ logger.stepDone('Loading states');
35
37
 
36
- // Step 4: Analyze routes for partial hydration
38
+ // โ”€โ”€ Step 4: Hydration analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
39
+ logger.step(4, TOTAL_STEPS, 'Hydration analysis');
37
40
  if (routes && routes.length > 0) {
38
- logger.info('Step 4: Analyzing routes for partial hydration...');
39
41
  const analyzedRoutes = await analyzeRoutes(routes);
40
- logHydrationReport(analyzedRoutes);
42
+ logger.stepDone('Hydration analysis',
43
+ `${analyzedRoutes.interactive.length} interactive ยท ${analyzedRoutes.static.length} static`);
44
+ } else {
45
+ logger.stepDone('Hydration analysis', 'no routes');
41
46
  }
42
47
 
43
- // Step 5: Load middleware
44
- logger.info('Step 5: Loading middleware...');
48
+ // โ”€โ”€ Step 5: Middleware โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49
+ logger.step(5, TOTAL_STEPS, 'Middleware');
45
50
  const middlewareManager = new MiddlewareManager(root);
46
51
  await middlewareManager.load();
47
- middlewareManager.watch(); // Hot-reload middleware on change
52
+ middlewareManager.watch();
53
+ logger.stepDone('Middleware');
54
+
55
+ // โ”€โ”€ Step 6: Dev server โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56
+ logger.step(6, TOTAL_STEPS, 'Starting server');
57
+ await startDevServer({ root, port, middleware: middlewareManager, layouts, loadingComponents });
58
+ logger.stepDone('Starting server', `http://localhost:${port}`);
48
59
 
49
- // Step 6: Start dev server with all features
50
- logger.info('Step 6: Starting dev server...');
51
- await startDevServer({
52
- root,
53
- port,
54
- middleware: middlewareManager,
55
- layouts,
56
- loadingComponents,
57
- });
60
+ // โ”€โ”€ Ready โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
61
+ process.stdout.write(`\n ${'\x1b[1m'}\x1b[32mโ–ถ Ready on http://localhost:${port}\x1b[0m\n\n`);
58
62
 
59
63
  } catch (error) {
60
- logger.error(`Dev server failed: ${error.message}`);
61
- if (error.stack) logger.error(error.stack);
64
+ logger.stepFail('Dev server', error.message);
65
+ logger.error(error.stack || error.message);
62
66
  process.exit(1);
63
67
  }
64
68
  }
@@ -1,18 +1,296 @@
1
- // src/utils/logger.js
2
- import { createLogger } from 'ernest-logger';
3
-
4
- // Create logger instance for BertUI
5
- const logger = createLogger({
6
- time: true,
7
- emoji: true,
8
- level: 'info',
9
- prefix: '[BertUI]',
10
- customLevels: {
11
- server: { color: 'brightCyan', emoji: '๐ŸŒ', priority: 2 },
12
- build: { color: 'brightGreen', emoji: '๐Ÿ“ฆ', priority: 2 },
13
- hmr: { color: 'brightYellow', emoji: '๐Ÿ”ฅ', priority: 2 }
1
+ // bertui/src/logger/logger.js
2
+ // Compact, progress-style CLI โ€” replaces verbose line-by-line logs
3
+
4
+ import { createWriteStream } from 'fs';
5
+ import { join } from 'path';
6
+
7
+ // โ”€โ”€ ANSI helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ const C = {
9
+ reset: '\x1b[0m',
10
+ bold: '\x1b[1m',
11
+ dim: '\x1b[2m',
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ blue: '\x1b[34m',
16
+ magenta: '\x1b[35m',
17
+ cyan: '\x1b[36m',
18
+ white: '\x1b[37m',
19
+ bgBlack: '\x1b[40m',
20
+ bgRed: '\x1b[41m',
21
+ bgGreen: '\x1b[42m',
22
+ bgBlue: '\x1b[44m',
23
+ bgCyan: '\x1b[46m',
24
+ bgWhite: '\x1b[47m',
25
+ gray: '\x1b[90m',
26
+ };
27
+
28
+ const isTTY = process.stdout.isTTY;
29
+
30
+ // โ”€โ”€ Spinner frames โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
31
+ const SPINNER = ['โ ‹','โ ™','โ น','โ ธ','โ ผ','โ ด','โ ฆ','โ ง','โ ‡','โ '];
32
+ let _spinnerFrame = 0;
33
+ let _spinnerTimer = null;
34
+ let _currentSpinnerLine = '';
35
+
36
+ // โ”€โ”€ Internal state โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
37
+ let _mode = 'idle'; // 'idle' | 'build' | 'dev'
38
+ let _totalSteps = 10;
39
+ let _stepIndex = 0;
40
+ let _stepLabel = '';
41
+ let _stepDetail = '';
42
+ let _errors = [];
43
+ let _warnings = [];
44
+ let _startTime = null;
45
+
46
+ // โ”€โ”€ Header โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
47
+ export function printHeader(mode = 'BUILD') {
48
+ _mode = mode.toLowerCase();
49
+ _startTime = Date.now();
50
+ _errors = [];
51
+ _warnings = [];
52
+
53
+ const W = 46;
54
+ const bar = 'โ–ˆ'.repeat(W);
55
+
56
+ // Big block-letter BERTUI (6 rows)
57
+ const BIG = [
58
+ ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—',
59
+ ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘',
60
+ ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘',
61
+ ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘',
62
+ ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘',
63
+ ' โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ•',
64
+ ];
65
+
66
+ process.stdout.write('\n');
67
+ process.stdout.write(`${C.cyan}${C.bold} ${bar}${C.reset}\n`);
68
+ for (const row of BIG) {
69
+ process.stdout.write(`${C.cyan}${C.bold}${row}${C.reset}\n`);
70
+ }
71
+ process.stdout.write(`${C.gray} by Pease Ernest${C.reset}${C.gray} ยท ${C.reset}${C.white}${C.bold}${mode.toUpperCase()}${C.reset}\n`);
72
+ process.stdout.write(`${C.cyan}${C.bold} ${bar}${C.reset}\n`);
73
+ process.stdout.write('\n');
74
+ }
75
+
76
+ // โ”€โ”€ Step progress โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+ export function step(index, total, label, detail = '') {
78
+ _stepIndex = index;
79
+ _totalSteps = total;
80
+ _stepLabel = label;
81
+ _stepDetail = detail;
82
+ _stopSpinner();
83
+ _renderStep('running', detail);
84
+ _startSpinner();
85
+ }
86
+
87
+ export function stepDone(label, detail = '') {
88
+ _stopSpinner();
89
+ _clearLine();
90
+ const idx = String(_stepIndex).padStart(2, ' ');
91
+ const lbl = (label || _stepLabel).padEnd(24, ' ');
92
+ const det = detail ? `${C.gray}${_truncate(detail, 38)}${C.reset}` : '';
93
+ process.stdout.write(
94
+ ` ${C.gray}[${idx}/${_totalSteps}]${C.reset} ${C.green}โœ“${C.reset} ${C.white}${lbl}${C.reset} ${det}\n`
95
+ );
96
+ }
97
+
98
+ export function stepFail(label, detail = '') {
99
+ _stopSpinner();
100
+ _clearLine();
101
+ const idx = String(_stepIndex).padStart(2, ' ');
102
+ const lbl = (label || _stepLabel).padEnd(24, ' ');
103
+ process.stdout.write(
104
+ ` ${C.gray}[${idx}/${_totalSteps}]${C.reset} ${C.red}โœ—${C.reset} ${C.white}${lbl}${C.reset} ${C.red}${detail}${C.reset}\n`
105
+ );
106
+ }
107
+
108
+ // โ”€โ”€ Inline file progress (replaces "Progress: 2/2 (100%)") โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
109
+ export function fileProgress(current, total, filename) {
110
+ if (!isTTY) return;
111
+ _clearLine();
112
+ const pct = Math.round((current / total) * 100);
113
+ const bar = _bar(pct, 16);
114
+ const name = _truncate(filename, 30);
115
+ _currentSpinnerLine =
116
+ ` ${C.gray}[${String(_stepIndex).padStart(2,' ')}/${_totalSteps}]${C.reset}` +
117
+ ` ${C.cyan}โ ธ${C.reset} ${C.white}${_stepLabel.padEnd(24,' ')}${C.reset}` +
118
+ ` ${bar} ${C.gray}${current}/${total}${C.reset} ${C.dim}${name}${C.reset}`;
119
+ process.stdout.write('\r' + _currentSpinnerLine);
120
+ }
121
+
122
+ // โ”€โ”€ Simple log levels (used internally, suppressed in compact mode) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
123
+ export function info(msg) {
124
+ // In compact mode swallow routine info โ€” only pass through to debug log
125
+ _debugLog('INFO', msg);
126
+ }
127
+
128
+ export function success(msg) {
129
+ // Swallow โ€” stepDone() is the visual replacement
130
+ _debugLog('SUCCESS', msg);
131
+ }
132
+
133
+ export function warn(msg) {
134
+ _warnings.push(msg);
135
+ _stopSpinner();
136
+ _clearLine();
137
+ process.stdout.write(` ${C.yellow}โš ${C.reset} ${C.yellow}${msg}${C.reset}\n`);
138
+ if (_stepLabel) _startSpinner();
139
+ }
140
+
141
+ export function error(msg) {
142
+ _errors.push(msg);
143
+ _stopSpinner();
144
+ _clearLine();
145
+ process.stdout.write(` ${C.red}โœ—${C.reset} ${C.red}${msg}${C.reset}\n`);
146
+ }
147
+
148
+ export function debug(msg) {
149
+ _debugLog('DEBUG', msg);
150
+ }
151
+
152
+ // โ”€โ”€ Table (kept for route/island tables but made compact) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
153
+ export function table(rows) {
154
+ if (!rows || rows.length === 0) return;
155
+ _stopSpinner();
156
+ const keys = Object.keys(rows[0]).filter(k => k !== '');
157
+ const widths = keys.map(k =>
158
+ Math.max(k.length, ...rows.map(r => String(r[k] ?? '').length))
159
+ );
160
+
161
+ const hr = ' ' + widths.map(w => 'โ”€'.repeat(w + 2)).join('โ”ผ') ;
162
+ const header = ' ' + keys.map((k, i) => ` ${C.bold}${k.padEnd(widths[i])}${C.reset} `).join('โ”‚');
163
+
164
+ process.stdout.write(`${C.gray}${hr}${C.reset}\n`);
165
+ process.stdout.write(`${header}\n`);
166
+ process.stdout.write(`${C.gray}${hr}${C.reset}\n`);
167
+
168
+ for (const row of rows) {
169
+ const line = ' ' + keys.map((k, i) => ` ${String(row[k] ?? '').padEnd(widths[i])} `).join(`${C.gray}โ”‚${C.reset}`);
170
+ process.stdout.write(`${line}\n`);
171
+ }
172
+ process.stdout.write(`${C.gray}${hr}${C.reset}\n`);
173
+
174
+ if (_stepLabel) _startSpinner();
175
+ }
176
+
177
+ // โ”€โ”€ bigLog โ€” replaced by section headers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
178
+ export function bigLog(title, opts = {}) {
179
+ _stopSpinner();
180
+ _clearLine();
181
+ process.stdout.write(`\n ${C.bold}${C.cyan}โ”€โ”€ ${title} โ”€โ”€${C.reset}\n\n`);
182
+ if (_stepLabel) _startSpinner();
183
+ }
184
+
185
+ // โ”€โ”€ Build/Dev summary โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
186
+ export function printSummary(stats = {}) {
187
+ _stopSpinner();
188
+ process.stdout.write('\n');
189
+
190
+ const dur = _startTime ? `${((Date.now() - _startTime) / 1000).toFixed(2)}s` : '';
191
+
192
+ process.stdout.write(`${C.cyan}${C.bold} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€${C.reset}\n`);
193
+ process.stdout.write(`${C.green}${C.bold} โœ“ Done${C.reset}${dur ? ` ${C.gray}${dur}${C.reset}` : ''}\n`);
194
+
195
+ if (stats.routes) _summaryLine('Routes', stats.routes);
196
+ if (stats.serverIslands)_summaryLine('Server Islands', stats.serverIslands);
197
+ if (stats.interactive) _summaryLine('Interactive', stats.interactive);
198
+ if (stats.staticRoutes) _summaryLine('Static', stats.staticRoutes);
199
+ if (stats.jsSize) _summaryLine('JS bundle', stats.jsSize);
200
+ if (stats.cssSize) _summaryLine('CSS bundle', stats.cssSize);
201
+ if (stats.outDir) _summaryLine('Output', stats.outDir);
202
+
203
+ if (_warnings.length > 0) {
204
+ process.stdout.write(`\n ${C.yellow}${_warnings.length} warning(s)${C.reset}\n`);
205
+ }
206
+ if (_errors.length > 0) {
207
+ process.stdout.write(`\n ${C.red}${_errors.length} error(s)${C.reset}\n`);
208
+ _errors.forEach(e => process.stdout.write(` ${C.red} ยท ${e}${C.reset}\n`));
209
+ }
210
+
211
+ process.stdout.write(`${C.cyan}${C.bold} โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€${C.reset}\n\n`);
212
+ }
213
+
214
+ function _summaryLine(label, value) {
215
+ process.stdout.write(
216
+ ` ${C.gray}${label.padEnd(18)}${C.reset}${C.white}${value}${C.reset}\n`
217
+ );
218
+ }
219
+
220
+ // โ”€โ”€ Spinner internals โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
221
+ function _startSpinner() {
222
+ if (!isTTY || _spinnerTimer) return;
223
+ _spinnerTimer = setInterval(() => {
224
+ _spinnerFrame = (_spinnerFrame + 1) % SPINNER.length;
225
+ _renderStep('running', _stepDetail);
226
+ }, 80);
227
+ }
228
+
229
+ function _stopSpinner() {
230
+ if (_spinnerTimer) {
231
+ clearInterval(_spinnerTimer);
232
+ _spinnerTimer = null;
233
+ }
234
+ }
235
+
236
+ function _renderStep(state, detail = '') {
237
+ if (!isTTY) return;
238
+ _clearLine();
239
+ const spin = SPINNER[_spinnerFrame];
240
+ const idx = String(_stepIndex).padStart(2, ' ');
241
+ const lbl = _stepLabel.padEnd(24, ' ');
242
+ const det = detail ? `${C.gray}${_truncate(detail, 38)}${C.reset}` : '';
243
+ _currentSpinnerLine =
244
+ ` ${C.gray}[${idx}/${_totalSteps}]${C.reset} ${C.cyan}${spin}${C.reset} ${C.white}${lbl}${C.reset} ${det}`;
245
+ process.stdout.write('\r' + _currentSpinnerLine);
246
+ }
247
+
248
+ function _clearLine() {
249
+ if (!isTTY) return;
250
+ process.stdout.write('\r\x1b[2K');
251
+ }
252
+
253
+ // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
254
+ function _bar(pct, width) {
255
+ const filled = Math.round((pct / 100) * width);
256
+ const empty = width - filled;
257
+ return `${C.cyan}${'โ–ˆ'.repeat(filled)}${C.gray}${'โ–‘'.repeat(empty)}${C.reset}`;
258
+ }
259
+
260
+ function _truncate(str, max) {
261
+ if (!str) return '';
262
+ str = String(str);
263
+ return str.length > max ? 'โ€ฆ' + str.slice(-(max - 1)) : str;
264
+ }
265
+
266
+ // โ”€โ”€ Debug file log (always written, never shown in terminal) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
267
+ let _logStream = null;
268
+ function _debugLog(level, msg) {
269
+ if (!_logStream) {
270
+ try {
271
+ _logStream = createWriteStream(
272
+ join(process.cwd(), '.bertui', 'dev.log'),
273
+ { flags: 'a' }
274
+ );
275
+ } catch { return; }
14
276
  }
15
- });
277
+ const ts = new Date().toISOString().substring(11, 23);
278
+ _logStream.write(`[${ts}] [${level}] ${msg}\n`);
279
+ }
16
280
 
17
- // Export the logger
18
- export default logger;
281
+ // โ”€โ”€ Default export (matches existing logger.method() call sites) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
282
+ export default {
283
+ printHeader,
284
+ step,
285
+ stepDone,
286
+ stepFail,
287
+ fileProgress,
288
+ info,
289
+ success,
290
+ warn,
291
+ error,
292
+ debug,
293
+ table,
294
+ bigLog,
295
+ printSummary,
296
+ };
@@ -99,6 +99,17 @@ export async function createDevHandler(options = {}) {
99
99
  }
100
100
  }
101
101
 
102
+ // Error overlay script
103
+ if (url.pathname === '/error-overlay.js') {
104
+ const overlayPath = join(root, 'node_modules/bertui/error-overlay.js');
105
+ const file = Bun.file(overlayPath);
106
+ if (await file.exists()) {
107
+ return new Response(file, {
108
+ headers: { 'Content-Type': 'application/javascript; charset=utf-8', 'Cache-Control': 'no-store' },
109
+ });
110
+ }
111
+ }
112
+
102
113
  // bertui-animate CSS
103
114
  if (url.pathname === '/bertui-animate.css') {
104
115
  const animPath = join(root, 'node_modules/bertui-animate/dist/bertui-animate.min.css');