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.
- package/README.md +152 -197
- package/index.js +16 -8
- package/package.json +1 -1
- package/src/build/compiler/file-transpiler.js +92 -141
- package/src/build/compiler/index.js +23 -15
- package/src/build.js +149 -93
- package/src/client/compiler.js +169 -157
- package/src/config/defaultConfig.js +13 -4
- package/src/config/loadConfig.js +47 -32
- package/src/dev.js +34 -30
- package/src/logger/logger.js +294 -16
- package/src/server/dev-handler.js +11 -0
- package/src/server/dev-server-utils.js +262 -160
- package/src/utils/importhow.js +52 -0
package/src/config/loadConfig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/config/loadConfig.js
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
40
|
-
merged.
|
|
41
|
-
merged.
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
19
|
-
logger.
|
|
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:
|
|
23
|
-
logger.
|
|
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
|
-
|
|
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:
|
|
33
|
-
logger.
|
|
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:
|
|
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
|
-
|
|
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:
|
|
44
|
-
logger.
|
|
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();
|
|
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
|
-
//
|
|
50
|
-
|
|
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.
|
|
61
|
-
|
|
64
|
+
logger.stepFail('Dev server', error.message);
|
|
65
|
+
logger.error(error.stack || error.message);
|
|
62
66
|
process.exit(1);
|
|
63
67
|
}
|
|
64
68
|
}
|
package/src/logger/logger.js
CHANGED
|
@@ -1,18 +1,296 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
//
|
|
18
|
-
export default
|
|
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');
|