@zenithbuild/cli 0.6.0 → 0.6.2
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/dist/build.js +132 -9
- package/dist/dev-server.js +61 -14
- package/dist/index.js +13 -13
- package/dist/preview.js +13 -5
- package/dist/ui/env.js +17 -1
- package/dist/ui/format.js +80 -42
- package/dist/ui/logger.js +239 -74
- package/package.json +3 -2
package/dist/build.js
CHANGED
|
@@ -92,6 +92,35 @@ export function createCompilerWarningEmitter(sink = (line) => console.warn(line)
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Forward child-process output line-by-line through the structured logger.
|
|
97
|
+
*
|
|
98
|
+
* @param {import('node:stream').Readable | null | undefined} stream
|
|
99
|
+
* @param {(line: string) => void} onLine
|
|
100
|
+
*/
|
|
101
|
+
function forwardStreamLines(stream, onLine) {
|
|
102
|
+
if (!stream || typeof stream.on !== 'function') {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let pending = '';
|
|
106
|
+
stream.setEncoding?.('utf8');
|
|
107
|
+
stream.on('data', (chunk) => {
|
|
108
|
+
pending += String(chunk || '');
|
|
109
|
+
const lines = pending.split(/\r?\n/);
|
|
110
|
+
pending = lines.pop() || '';
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (line.trim().length > 0) {
|
|
113
|
+
onLine(line);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
stream.on('end', () => {
|
|
118
|
+
if (pending.trim().length > 0) {
|
|
119
|
+
onLine(pending);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
95
124
|
/**
|
|
96
125
|
* Run the compiler process and parse its JSON stdout.
|
|
97
126
|
*
|
|
@@ -244,6 +273,57 @@ function mergeExpressionRewriteMaps(pageMap, pageAmbiguous, componentRewrite) {
|
|
|
244
273
|
}
|
|
245
274
|
}
|
|
246
275
|
|
|
276
|
+
function resolveStateKeyFromBindings(identifier, stateBindings, preferredKeys = null) {
|
|
277
|
+
const ident = String(identifier || '').trim();
|
|
278
|
+
if (!ident) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const exact = stateBindings.find((entry) => String(entry?.key || '') === ident);
|
|
283
|
+
if (exact && typeof exact.key === 'string') {
|
|
284
|
+
return exact.key;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const suffix = `_${ident}`;
|
|
288
|
+
const matches = stateBindings
|
|
289
|
+
.map((entry) => String(entry?.key || ''))
|
|
290
|
+
.filter((key) => key.endsWith(suffix));
|
|
291
|
+
|
|
292
|
+
if (preferredKeys instanceof Set && preferredKeys.size > 0) {
|
|
293
|
+
const preferredMatches = matches.filter((key) => preferredKeys.has(key));
|
|
294
|
+
if (preferredMatches.length === 1) {
|
|
295
|
+
return preferredMatches[0];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (matches.length === 1) {
|
|
300
|
+
return matches[0];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function rewriteRefBindingIdentifiers(pageIr, preferredKeys = null) {
|
|
307
|
+
if (!Array.isArray(pageIr?.ref_bindings) || pageIr.ref_bindings.length === 0) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const stateBindings = Array.isArray(pageIr?.hoisted?.state) ? pageIr.hoisted.state : [];
|
|
312
|
+
if (stateBindings.length === 0) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (const binding of pageIr.ref_bindings) {
|
|
317
|
+
if (!binding || typeof binding !== 'object' || typeof binding.identifier !== 'string') {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const resolved = resolveStateKeyFromBindings(binding.identifier, stateBindings, preferredKeys);
|
|
321
|
+
if (resolved) {
|
|
322
|
+
binding.identifier = resolved;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
247
327
|
/**
|
|
248
328
|
* Rewrite unresolved page expressions using component script-aware mappings.
|
|
249
329
|
*
|
|
@@ -751,7 +831,7 @@ function collectComponentUsageAttrs(source, registry) {
|
|
|
751
831
|
* @param {{ includeCode: boolean, cssImportsOnly: boolean, documentMode?: boolean, componentAttrs?: string }} options
|
|
752
832
|
* @param {Set<string>} seenStaticImports
|
|
753
833
|
*/
|
|
754
|
-
function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStaticImports) {
|
|
834
|
+
function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStaticImports, knownRefKeys = null) {
|
|
755
835
|
// Merge components_scripts
|
|
756
836
|
if (compIr.components_scripts) {
|
|
757
837
|
for (const [hoistId, script] of Object.entries(compIr.components_scripts)) {
|
|
@@ -766,6 +846,17 @@ function mergeComponentIr(pageIr, compIr, compPath, pageFile, options, seenStati
|
|
|
766
846
|
pageIr.component_instances.push(...compIr.component_instances);
|
|
767
847
|
}
|
|
768
848
|
|
|
849
|
+
if (knownRefKeys instanceof Set && Array.isArray(compIr.ref_bindings)) {
|
|
850
|
+
const componentStateBindings = Array.isArray(compIr?.hoisted?.state) ? compIr.hoisted.state : [];
|
|
851
|
+
for (const binding of compIr.ref_bindings) {
|
|
852
|
+
if (!binding || typeof binding.identifier !== 'string' || binding.identifier.length === 0) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const resolved = resolveStateKeyFromBindings(binding.identifier, componentStateBindings);
|
|
856
|
+
knownRefKeys.add(resolved || binding.identifier);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
769
860
|
// Merge hoisted imports (deduplicated, rebased to the page file path)
|
|
770
861
|
if (compIr.hoisted?.imports?.length) {
|
|
771
862
|
for (const imp of compIr.hoisted.imports) {
|
|
@@ -1253,16 +1344,32 @@ function deferComponentRuntimeBlock(source) {
|
|
|
1253
1344
|
*
|
|
1254
1345
|
* @param {object|object[]} envelope
|
|
1255
1346
|
* @param {string} outDir
|
|
1347
|
+
* @param {string} projectRoot
|
|
1348
|
+
* @param {object | null} [logger]
|
|
1349
|
+
* @param {boolean} [showInfo]
|
|
1256
1350
|
* @returns {Promise<void>}
|
|
1257
1351
|
*/
|
|
1258
|
-
function runBundler(envelope, outDir) {
|
|
1352
|
+
function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true) {
|
|
1259
1353
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
1354
|
+
const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
|
|
1260
1355
|
const child = spawn(
|
|
1261
1356
|
getBundlerBin(),
|
|
1262
1357
|
['--out-dir', outDir],
|
|
1263
|
-
{
|
|
1358
|
+
{
|
|
1359
|
+
cwd: projectRoot,
|
|
1360
|
+
stdio: useStructuredLogger ? ['pipe', 'pipe', 'pipe'] : ['pipe', 'inherit', 'inherit']
|
|
1361
|
+
}
|
|
1264
1362
|
);
|
|
1265
1363
|
|
|
1364
|
+
if (useStructuredLogger) {
|
|
1365
|
+
forwardStreamLines(child.stdout, (line) => {
|
|
1366
|
+
logger.childLine('bundler', line, { stream: 'stdout', showInfo });
|
|
1367
|
+
});
|
|
1368
|
+
forwardStreamLines(child.stderr, (line) => {
|
|
1369
|
+
logger.childLine('bundler', line, { stream: 'stderr', showInfo: true });
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1266
1373
|
child.on('error', (err) => {
|
|
1267
1374
|
rejectPromise(new Error(`Bundler spawn failed: ${err.message}`));
|
|
1268
1375
|
});
|
|
@@ -1328,11 +1435,12 @@ async function collectAssets(rootDir) {
|
|
|
1328
1435
|
* d. Merge component IRs into page IR
|
|
1329
1436
|
* 3. Send all envelopes to bundler
|
|
1330
1437
|
*
|
|
1331
|
-
* @param {{ pagesDir: string, outDir: string, config?: object }} options
|
|
1438
|
+
* @param {{ pagesDir: string, outDir: string, config?: object, logger?: object | null, showBundlerInfo?: boolean }} options
|
|
1332
1439
|
* @returns {Promise<{ pages: number, assets: string[] }>}
|
|
1333
1440
|
*/
|
|
1334
1441
|
export async function build(options) {
|
|
1335
|
-
const { pagesDir, outDir, config = {} } = options;
|
|
1442
|
+
const { pagesDir, outDir, config = {}, logger = null, showBundlerInfo = true } = options;
|
|
1443
|
+
const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
|
|
1336
1444
|
const softNavigationEnabled = config.softNavigation === true || config.router === true;
|
|
1337
1445
|
const compilerOpts = {
|
|
1338
1446
|
typescriptDefault: config.typescriptDefault === true,
|
|
@@ -1349,7 +1457,13 @@ export async function build(options) {
|
|
|
1349
1457
|
// 1. Build component registry
|
|
1350
1458
|
const registry = buildComponentRegistry(srcDir);
|
|
1351
1459
|
if (registry.size > 0) {
|
|
1352
|
-
|
|
1460
|
+
if (logger && typeof logger.build === 'function') {
|
|
1461
|
+
logger.build(`registry=${registry.size} components`, {
|
|
1462
|
+
onceKey: `component-registry:${registry.size}`
|
|
1463
|
+
});
|
|
1464
|
+
} else {
|
|
1465
|
+
console.log(`[zenith] Component registry: ${registry.size} components`);
|
|
1466
|
+
}
|
|
1353
1467
|
}
|
|
1354
1468
|
|
|
1355
1469
|
const manifest = await generateManifest(pagesDir);
|
|
@@ -1362,7 +1476,13 @@ export async function build(options) {
|
|
|
1362
1476
|
const componentDocumentModeCache = new Map();
|
|
1363
1477
|
/** @type {Map<string, { map: Map<string, string>, ambiguous: Set<string> }>} */
|
|
1364
1478
|
const componentExpressionRewriteCache = new Map();
|
|
1365
|
-
const emitCompilerWarning = createCompilerWarningEmitter((line) =>
|
|
1479
|
+
const emitCompilerWarning = createCompilerWarningEmitter((line) => {
|
|
1480
|
+
if (logger && typeof logger.warn === 'function') {
|
|
1481
|
+
logger.warn(line, { onceKey: `compiler-warning:${line}` });
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
console.warn(line);
|
|
1485
|
+
});
|
|
1366
1486
|
|
|
1367
1487
|
const envelopes = [];
|
|
1368
1488
|
for (const entry of manifest) {
|
|
@@ -1431,6 +1551,7 @@ export async function build(options) {
|
|
|
1431
1551
|
const seenStaticImports = new Set();
|
|
1432
1552
|
const pageExpressionRewriteMap = new Map();
|
|
1433
1553
|
const pageAmbiguousExpressionMap = new Set();
|
|
1554
|
+
const knownRefKeys = new Set();
|
|
1434
1555
|
|
|
1435
1556
|
// 2c. Compile each used component separately for its script IR
|
|
1436
1557
|
for (const compName of usedComponents) {
|
|
@@ -1481,7 +1602,8 @@ export async function build(options) {
|
|
|
1481
1602
|
documentMode: isDocMode,
|
|
1482
1603
|
componentAttrs: (componentUsageAttrs.get(compName) || [])[0] || ''
|
|
1483
1604
|
},
|
|
1484
|
-
seenStaticImports
|
|
1605
|
+
seenStaticImports,
|
|
1606
|
+
knownRefKeys
|
|
1485
1607
|
);
|
|
1486
1608
|
}
|
|
1487
1609
|
|
|
@@ -1492,6 +1614,7 @@ export async function build(options) {
|
|
|
1492
1614
|
);
|
|
1493
1615
|
|
|
1494
1616
|
rewriteLegacyMarkupIdentifiers(pageIr);
|
|
1617
|
+
rewriteRefBindingIdentifiers(pageIr, knownRefKeys);
|
|
1495
1618
|
|
|
1496
1619
|
envelopes.push({
|
|
1497
1620
|
route: entry.path,
|
|
@@ -1502,7 +1625,7 @@ export async function build(options) {
|
|
|
1502
1625
|
}
|
|
1503
1626
|
|
|
1504
1627
|
if (envelopes.length > 0) {
|
|
1505
|
-
|
|
1628
|
+
await runBundler(envelopes, outDir, projectRoot, logger, showBundlerInfo);
|
|
1506
1629
|
}
|
|
1507
1630
|
|
|
1508
1631
|
const assets = await collectAssets(outDir);
|
package/dist/dev-server.js
CHANGED
|
@@ -16,6 +16,7 @@ import { existsSync, watch } from 'node:fs';
|
|
|
16
16
|
import { readFile, stat } from 'node:fs/promises';
|
|
17
17
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
18
18
|
import { build } from './build.js';
|
|
19
|
+
import { createSilentLogger } from './ui/logger.js';
|
|
19
20
|
import {
|
|
20
21
|
executeServerRoute,
|
|
21
22
|
injectSsrPayload,
|
|
@@ -41,7 +42,7 @@ const MIME_TYPES = {
|
|
|
41
42
|
/**
|
|
42
43
|
* Create and start a development server.
|
|
43
44
|
*
|
|
44
|
-
* @param {{ pagesDir: string, outDir: string, port?: number, host?: string, config?: object }} options
|
|
45
|
+
* @param {{ pagesDir: string, outDir: string, port?: number, host?: string, config?: object, logger?: object | null }} options
|
|
45
46
|
* @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
|
|
46
47
|
*/
|
|
47
48
|
export async function createDevServer(options) {
|
|
@@ -50,8 +51,10 @@ export async function createDevServer(options) {
|
|
|
50
51
|
outDir,
|
|
51
52
|
port = 3000,
|
|
52
53
|
host = '127.0.0.1',
|
|
53
|
-
config = {}
|
|
54
|
+
config = {},
|
|
55
|
+
logger: providedLogger = null
|
|
54
56
|
} = options;
|
|
57
|
+
const logger = providedLogger || createSilentLogger();
|
|
55
58
|
|
|
56
59
|
const resolvedPagesDir = resolve(pagesDir);
|
|
57
60
|
const resolvedOutDir = resolve(outDir);
|
|
@@ -83,6 +86,7 @@ export async function createDevServer(options) {
|
|
|
83
86
|
let durationMs = 0;
|
|
84
87
|
let buildError = null;
|
|
85
88
|
const traceEnabled = config.devTrace === true || process.env.ZENITH_DEV_TRACE === '1';
|
|
89
|
+
const verboseLogging = traceEnabled || logger.mode?.logLevel === 'verbose';
|
|
86
90
|
|
|
87
91
|
// Stable dev CSS endpoint points to this backing asset.
|
|
88
92
|
let currentCssAssetPath = '';
|
|
@@ -104,8 +108,10 @@ export async function createDevServer(options) {
|
|
|
104
108
|
function _trace(event, payload = {}) {
|
|
105
109
|
if (!traceEnabled) return;
|
|
106
110
|
try {
|
|
107
|
-
const
|
|
108
|
-
|
|
111
|
+
const detail = Object.keys(payload).length > 0
|
|
112
|
+
? `${event} ${JSON.stringify(payload)}`
|
|
113
|
+
: event;
|
|
114
|
+
logger.verbose('BUILD', detail);
|
|
109
115
|
} catch {
|
|
110
116
|
// tracing must never break the dev server
|
|
111
117
|
}
|
|
@@ -243,11 +249,19 @@ export async function createDevServer(options) {
|
|
|
243
249
|
|
|
244
250
|
// Initial build
|
|
245
251
|
try {
|
|
246
|
-
|
|
252
|
+
logger.build('Initial build (id=0)', { onceKey: 'dev-initial-build' });
|
|
253
|
+
const initialBuild = await build({ pagesDir, outDir, config, logger });
|
|
247
254
|
await _syncCssStateFromBuild(initialBuild, buildId);
|
|
255
|
+
if (currentCssHref.length > 0) {
|
|
256
|
+
logger.css(`ready (${currentCssHref})`, { onceKey: `css-ready:${buildId}:${currentCssHref}` });
|
|
257
|
+
}
|
|
248
258
|
} catch (err) {
|
|
249
259
|
buildStatus = 'error';
|
|
250
260
|
buildError = { message: err instanceof Error ? err.message : String(err) };
|
|
261
|
+
logger.error('initial build failed', {
|
|
262
|
+
hint: 'fix the error and restart dev',
|
|
263
|
+
error: err
|
|
264
|
+
});
|
|
251
265
|
}
|
|
252
266
|
|
|
253
267
|
const server = createServer(async (req, res) => {
|
|
@@ -265,7 +279,10 @@ export async function createDevServer(options) {
|
|
|
265
279
|
'Connection': 'keep-alive',
|
|
266
280
|
'X-Zenith-Deprecated': 'true'
|
|
267
281
|
});
|
|
268
|
-
|
|
282
|
+
logger.warn('legacy HMR endpoint in use', {
|
|
283
|
+
hint: 'use /__zenith_dev/events',
|
|
284
|
+
onceKey: 'legacy-hmr-endpoint'
|
|
285
|
+
});
|
|
269
286
|
res.write(': connected\n\n');
|
|
270
287
|
hmrClients.push(res);
|
|
271
288
|
req.on('close', () => {
|
|
@@ -448,7 +465,11 @@ export async function createDevServer(options) {
|
|
|
448
465
|
let filePath = null;
|
|
449
466
|
|
|
450
467
|
if (resolved.matched && resolved.route) {
|
|
451
|
-
|
|
468
|
+
if (verboseLogging) {
|
|
469
|
+
logger.router(
|
|
470
|
+
`${req.method || 'GET'} ${pathname} -> ${resolved.route.path} params=${JSON.stringify(resolved.params)}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
452
473
|
const output = resolved.route.output.startsWith('/')
|
|
453
474
|
? resolved.route.output.slice(1)
|
|
454
475
|
: resolved.route.output;
|
|
@@ -490,8 +511,11 @@ export async function createDevServer(options) {
|
|
|
490
511
|
|
|
491
512
|
const trace = routeExecution?.trace || { guard: 'none', load: 'none' };
|
|
492
513
|
const routeId = resolved.route.route_id || '';
|
|
493
|
-
|
|
494
|
-
|
|
514
|
+
if (verboseLogging) {
|
|
515
|
+
logger.router(
|
|
516
|
+
`${routeId || resolved.route.path} guard=${trace.guard} load=${trace.load}`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
495
519
|
|
|
496
520
|
const result = routeExecution?.result;
|
|
497
521
|
if (result && result.kind === 'redirect') {
|
|
@@ -609,17 +633,25 @@ export async function createDevServer(options) {
|
|
|
609
633
|
const cycleBuildId = pendingBuildId + 1;
|
|
610
634
|
pendingBuildId = cycleBuildId;
|
|
611
635
|
buildStatus = 'building';
|
|
636
|
+
logger.build(`Rebuild (id=${cycleBuildId})`);
|
|
612
637
|
_broadcastEvent('build_start', { buildId: cycleBuildId, changedFiles: changed });
|
|
613
638
|
|
|
614
639
|
const startTime = Date.now();
|
|
640
|
+
const previousCssAssetPath = currentCssAssetPath;
|
|
641
|
+
const previousCssContent = currentCssContent;
|
|
615
642
|
try {
|
|
616
|
-
const buildResult = await build({ pagesDir, outDir, config });
|
|
643
|
+
const buildResult = await build({ pagesDir, outDir, config, logger });
|
|
617
644
|
const cssReady = await _syncCssStateFromBuild(buildResult, cycleBuildId);
|
|
645
|
+
const cssChanged = cssReady && (
|
|
646
|
+
currentCssAssetPath !== previousCssAssetPath ||
|
|
647
|
+
currentCssContent !== previousCssContent
|
|
648
|
+
);
|
|
618
649
|
buildId = cycleBuildId;
|
|
619
650
|
buildStatus = 'ok';
|
|
620
651
|
buildError = null;
|
|
621
652
|
lastBuildMs = Date.now();
|
|
622
653
|
durationMs = lastBuildMs - startTime;
|
|
654
|
+
logger.build(`Complete (id=${cycleBuildId}, ${durationMs}ms)`);
|
|
623
655
|
|
|
624
656
|
_broadcastEvent('build_complete', {
|
|
625
657
|
buildId: cycleBuildId,
|
|
@@ -637,12 +669,23 @@ export async function createDevServer(options) {
|
|
|
637
669
|
changedFiles: changed
|
|
638
670
|
});
|
|
639
671
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
672
|
+
if (cssChanged && currentCssHref.length > 0) {
|
|
673
|
+
logger.css(`ready (${currentCssHref})`);
|
|
674
|
+
logger.hmr(`css_update (buildId=${cycleBuildId})`);
|
|
643
675
|
_broadcastEvent('css_update', { href: currentCssHref, changedFiles: changed });
|
|
644
|
-
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const onlyCss = changed.length > 0 && changed.every((f) => f.endsWith('.css'));
|
|
679
|
+
if (!onlyCss) {
|
|
680
|
+
logger.hmr(`reload (buildId=${cycleBuildId})`);
|
|
645
681
|
_broadcastEvent('reload', { changedFiles: changed });
|
|
682
|
+
} else {
|
|
683
|
+
_trace('css_only_update', {
|
|
684
|
+
buildId: cycleBuildId,
|
|
685
|
+
cssHref: currentCssHref,
|
|
686
|
+
cssChanged,
|
|
687
|
+
changedFiles: changed
|
|
688
|
+
});
|
|
646
689
|
}
|
|
647
690
|
} catch (err) {
|
|
648
691
|
const fullError = err instanceof Error ? err.message : String(err);
|
|
@@ -650,6 +693,10 @@ export async function createDevServer(options) {
|
|
|
650
693
|
buildError = { message: fullError.length > 10000 ? fullError.slice(0, 10000) + '... (truncated)' : fullError };
|
|
651
694
|
lastBuildMs = Date.now();
|
|
652
695
|
durationMs = lastBuildMs - startTime;
|
|
696
|
+
logger.error('rebuild failed', {
|
|
697
|
+
hint: 'fix the error and save again',
|
|
698
|
+
error: err
|
|
699
|
+
});
|
|
653
700
|
|
|
654
701
|
_broadcastEvent('build_error', { buildId: cycleBuildId, ...buildError, changedFiles: changed });
|
|
655
702
|
_trace('state_snapshot', {
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { resolve, join, dirname } from 'node:path';
|
|
14
14
|
import { existsSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import {
|
|
16
|
+
import { createZenithLogger } from './ui/logger.js';
|
|
17
17
|
|
|
18
18
|
const COMMANDS = ['dev', 'build', 'preview'];
|
|
19
19
|
const DEFAULT_VERSION = '0.0.0';
|
|
@@ -90,7 +90,7 @@ async function loadConfig(projectRoot) {
|
|
|
90
90
|
* @param {string} [cwd] - Working directory override
|
|
91
91
|
*/
|
|
92
92
|
export async function cli(args, cwd) {
|
|
93
|
-
const logger =
|
|
93
|
+
const logger = createZenithLogger(process);
|
|
94
94
|
const command = args[0];
|
|
95
95
|
const cliVersion = getCliVersion();
|
|
96
96
|
|
|
@@ -118,10 +118,10 @@ export async function cli(args, cwd) {
|
|
|
118
118
|
|
|
119
119
|
if (command === 'build') {
|
|
120
120
|
const { build } = await import('./build.js');
|
|
121
|
-
logger.
|
|
122
|
-
const result = await build({ pagesDir, outDir, config });
|
|
123
|
-
logger.
|
|
124
|
-
logger.summary([{ label: 'Output', value: './dist' }]);
|
|
121
|
+
logger.build('Building…');
|
|
122
|
+
const result = await build({ pagesDir, outDir, config, logger, showBundlerInfo: false });
|
|
123
|
+
logger.ok(`Built ${result.pages} page(s), ${result.assets.length} asset(s)`);
|
|
124
|
+
logger.summary([{ label: 'Output', value: './dist' }], 'BUILD');
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
if (command === 'dev') {
|
|
@@ -130,9 +130,9 @@ export async function cli(args, cwd) {
|
|
|
130
130
|
? Number.parseInt(process.env.ZENITH_DEV_PORT, 10)
|
|
131
131
|
: resolvePort(args.slice(1), 3000);
|
|
132
132
|
const host = process.env.ZENITH_DEV_HOST || '127.0.0.1';
|
|
133
|
-
logger.
|
|
134
|
-
const dev = await createDevServer({ pagesDir, outDir, port, host, config });
|
|
135
|
-
logger.
|
|
133
|
+
logger.dev('Starting dev server…');
|
|
134
|
+
const dev = await createDevServer({ pagesDir, outDir, port, host, config, logger });
|
|
135
|
+
logger.ok(`http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${dev.port}`);
|
|
136
136
|
|
|
137
137
|
// Graceful shutdown
|
|
138
138
|
process.on('SIGINT', () => {
|
|
@@ -149,9 +149,9 @@ export async function cli(args, cwd) {
|
|
|
149
149
|
const { createPreviewServer } = await import('./preview.js');
|
|
150
150
|
const port = resolvePort(args.slice(1), 4000);
|
|
151
151
|
const host = process.env.ZENITH_PREVIEW_HOST || '127.0.0.1';
|
|
152
|
-
logger.
|
|
153
|
-
const preview = await createPreviewServer({ distDir: outDir, port, host });
|
|
154
|
-
logger.
|
|
152
|
+
logger.dev('Starting preview server…');
|
|
153
|
+
const preview = await createPreviewServer({ distDir: outDir, port, host, logger });
|
|
154
|
+
logger.ok(`http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${preview.port}`);
|
|
155
155
|
|
|
156
156
|
process.on('SIGINT', () => {
|
|
157
157
|
preview.close();
|
|
@@ -172,7 +172,7 @@ const isDirectRun = process.argv[1] && (
|
|
|
172
172
|
|
|
173
173
|
if (isDirectRun) {
|
|
174
174
|
cli(process.argv.slice(2)).catch((error) => {
|
|
175
|
-
const logger =
|
|
175
|
+
const logger = createZenithLogger(process);
|
|
176
176
|
logger.error(error);
|
|
177
177
|
process.exit(1);
|
|
178
178
|
});
|
package/dist/preview.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createServer } from 'node:http';
|
|
|
14
14
|
import { access, readFile } from 'node:fs/promises';
|
|
15
15
|
import { extname, join, normalize, resolve, sep, dirname } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { createSilentLogger } from './ui/logger.js';
|
|
17
18
|
import {
|
|
18
19
|
compareRouteSpecificity,
|
|
19
20
|
matchRoute as matchManifestRoute,
|
|
@@ -386,11 +387,13 @@ try {
|
|
|
386
387
|
/**
|
|
387
388
|
* Create and start a preview server.
|
|
388
389
|
*
|
|
389
|
-
* @param {{ distDir: string, port?: number, host?: string }} options
|
|
390
|
+
* @param {{ distDir: string, port?: number, host?: string, logger?: object | null }} options
|
|
390
391
|
* @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
|
|
391
392
|
*/
|
|
392
393
|
export async function createPreviewServer(options) {
|
|
393
|
-
const { distDir, port = 4000, host = '127.0.0.1' } = options;
|
|
394
|
+
const { distDir, port = 4000, host = '127.0.0.1', logger: providedLogger = null } = options;
|
|
395
|
+
const logger = providedLogger || createSilentLogger();
|
|
396
|
+
const verboseLogging = logger.mode?.logLevel === 'verbose';
|
|
394
397
|
let actualPort = port;
|
|
395
398
|
|
|
396
399
|
function publicHost() {
|
|
@@ -502,7 +505,11 @@ export async function createPreviewServer(options) {
|
|
|
502
505
|
let htmlPath = null;
|
|
503
506
|
|
|
504
507
|
if (resolved.matched && resolved.route) {
|
|
505
|
-
|
|
508
|
+
if (verboseLogging) {
|
|
509
|
+
logger.router(
|
|
510
|
+
`${req.method || 'GET'} ${url.pathname} -> ${resolved.route.path} params=${JSON.stringify(resolved.params)}`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
506
513
|
const output = resolved.route.output.startsWith('/')
|
|
507
514
|
? resolved.route.output.slice(1)
|
|
508
515
|
: resolved.route.output;
|
|
@@ -541,8 +548,9 @@ export async function createPreviewServer(options) {
|
|
|
541
548
|
|
|
542
549
|
const trace = routeExecution?.trace || { guard: 'none', load: 'none' };
|
|
543
550
|
const routeId = resolved.route.route_id || routeIdFromSourcePath(resolved.route.server_script_path || '');
|
|
544
|
-
|
|
545
|
-
|
|
551
|
+
if (verboseLogging) {
|
|
552
|
+
logger.router(`${routeId} guard=${trace.guard} load=${trace.load}`);
|
|
553
|
+
}
|
|
546
554
|
|
|
547
555
|
const result = routeExecution?.result;
|
|
548
556
|
if (result && result.kind === 'redirect') {
|
package/dist/ui/env.js
CHANGED
|
@@ -10,6 +10,14 @@ function flagEnabled(value) {
|
|
|
10
10
|
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function parseLogLevel(value) {
|
|
14
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
15
|
+
if (normalized === 'quiet' || normalized === 'verbose') {
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
return 'normal';
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
/**
|
|
14
22
|
* @param {{ env?: Record<string, string | undefined>, stdout?: { isTTY?: boolean } }} runtime
|
|
15
23
|
*/
|
|
@@ -21,10 +29,17 @@ export function getUiMode(runtime = process) {
|
|
|
21
29
|
const noColor = env.NO_COLOR !== undefined && String(env.NO_COLOR).length >= 0;
|
|
22
30
|
const forceColor = flagEnabled(env.FORCE_COLOR);
|
|
23
31
|
const debug = flagEnabled(env.ZENITH_DEBUG);
|
|
32
|
+
let logLevel = parseLogLevel(env.ZENITH_LOG_LEVEL);
|
|
24
33
|
|
|
25
34
|
const plain = noUi || ci || !tty;
|
|
26
35
|
const color = !plain && !noColor && (forceColor || tty);
|
|
27
36
|
const spinner = tty && !plain && !ci;
|
|
37
|
+
if (flagEnabled(env.ZENITH_DEV_TRACE)) {
|
|
38
|
+
logLevel = 'verbose';
|
|
39
|
+
}
|
|
40
|
+
if (debug && logLevel !== 'quiet') {
|
|
41
|
+
logLevel = 'verbose';
|
|
42
|
+
}
|
|
28
43
|
|
|
29
44
|
return {
|
|
30
45
|
plain,
|
|
@@ -32,7 +47,8 @@ export function getUiMode(runtime = process) {
|
|
|
32
47
|
tty,
|
|
33
48
|
ci,
|
|
34
49
|
spinner,
|
|
35
|
-
debug
|
|
50
|
+
debug,
|
|
51
|
+
logLevel
|
|
36
52
|
};
|
|
37
53
|
}
|
|
38
54
|
|
package/dist/ui/format.js
CHANGED
|
@@ -1,53 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
* Deterministic text formatters for CLI UX.
|
|
3
|
-
*/
|
|
4
|
-
|
|
1
|
+
import pc from 'picocolors';
|
|
5
2
|
import { relative, sep } from 'node:path';
|
|
6
3
|
|
|
7
|
-
const ANSI = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
red: '\x1b[31m',
|
|
12
|
-
yellow: '\x1b[33m',
|
|
13
|
-
green: '\x1b[32m',
|
|
14
|
-
cyan: '\x1b[36m'
|
|
15
|
-
};
|
|
16
4
|
const DEFAULT_PHASE = 'cli';
|
|
17
5
|
const DEFAULT_FILE = '.';
|
|
18
6
|
const DEFAULT_HINT_BASE = 'https://github.com/zenithbuild/zenith/blob/main/zenith-cli/CLI_CONTRACT.md';
|
|
7
|
+
const PREFIX = '[zenith]';
|
|
8
|
+
const TAG_WIDTH = 6;
|
|
9
|
+
|
|
10
|
+
const TAG_COLORS = {
|
|
11
|
+
DEV: (colors, value) => colors.cyan(value),
|
|
12
|
+
BUILD: (colors, value) => colors.blue(value),
|
|
13
|
+
HMR: (colors, value) => colors.magenta(value),
|
|
14
|
+
ROUTER: (colors, value) => colors.cyan(value),
|
|
15
|
+
CSS: (colors, value) => colors.yellow(value),
|
|
16
|
+
OK: (colors, value) => colors.green(value),
|
|
17
|
+
WARN: (colors, value) => colors.bold(colors.yellow(value)),
|
|
18
|
+
ERR: (colors, value) => colors.bold(colors.red(value))
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getColors(mode) {
|
|
22
|
+
return pc.createColors(Boolean(mode?.color));
|
|
23
|
+
}
|
|
19
24
|
|
|
20
|
-
function
|
|
25
|
+
export function formatPrefix(mode) {
|
|
26
|
+
return mode.color ? getColors(mode).dim(PREFIX) : PREFIX;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function colorizeTag(mode, tag) {
|
|
30
|
+
const padded = String(tag || '').padEnd(TAG_WIDTH, ' ');
|
|
21
31
|
if (!mode.color) {
|
|
22
|
-
return
|
|
32
|
+
return padded;
|
|
23
33
|
}
|
|
24
|
-
|
|
34
|
+
const colors = getColors(mode);
|
|
35
|
+
const colorizer = TAG_COLORS[tag] || ((_colors, value) => colors.white(value));
|
|
36
|
+
return colorizer(colors, padded);
|
|
25
37
|
}
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
function colorizeGlyph(mode, glyph, tag) {
|
|
40
|
+
if (!mode.color) {
|
|
41
|
+
return glyph;
|
|
42
|
+
}
|
|
43
|
+
const colors = getColors(mode);
|
|
44
|
+
const colorizer = TAG_COLORS[tag] || ((_colors, value) => value);
|
|
45
|
+
return colorizer(colors, glyph);
|
|
30
46
|
}
|
|
31
47
|
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
return `[zenith] INFO: ${text}`;
|
|
35
|
-
}
|
|
36
|
-
const bullet = colorize(mode, 'cyan', '•');
|
|
37
|
-
return `[zenith] ${bullet} ${text}`;
|
|
48
|
+
export function formatLine(mode, { glyph = '•', tag = 'DEV', text = '' }) {
|
|
49
|
+
return `${formatPrefix(mode)} ${colorizeGlyph(mode, glyph, tag)} ${colorizeTag(mode, tag)} ${String(text || '')}`;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
export function
|
|
52
|
+
export function formatStep(mode, text, tag = 'BUILD') {
|
|
53
|
+
return formatLine(mode, { glyph: '•', tag, text });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function formatHint(mode, text) {
|
|
57
|
+
const body = ` hint: ${String(text || '').trim()}`;
|
|
58
|
+
return mode.color ? getColors(mode).dim(body) : body;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatHeading(mode, text) {
|
|
62
|
+
const label = mode.color ? getColors(mode).bold('Zenith CLI') : 'Zenith CLI';
|
|
63
|
+
return `${label} ${String(text || '').trim()}`.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatSummaryTable(mode, rows, tag = 'BUILD') {
|
|
41
67
|
if (!Array.isArray(rows) || rows.length === 0) {
|
|
42
68
|
return '';
|
|
43
69
|
}
|
|
44
|
-
const maxLabel = rows.reduce((acc, row) => Math.max(acc, String(row.label || '').length), 0);
|
|
45
70
|
return rows
|
|
46
|
-
.map((row) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})
|
|
71
|
+
.map((row) => formatLine(mode, {
|
|
72
|
+
glyph: '•',
|
|
73
|
+
tag,
|
|
74
|
+
text: `${String(row.label || '')}: ${String(row.value || '')}`
|
|
75
|
+
}))
|
|
51
76
|
.join('\n');
|
|
52
77
|
}
|
|
53
78
|
|
|
@@ -131,36 +156,49 @@ export function normalizeError(err) {
|
|
|
131
156
|
return new Error(sanitizeErrorMessage(err));
|
|
132
157
|
}
|
|
133
158
|
|
|
159
|
+
function firstMeaningfulLine(text) {
|
|
160
|
+
return String(text || '')
|
|
161
|
+
.split('\n')
|
|
162
|
+
.map((line) => line.trim())
|
|
163
|
+
.find((line) => line.length > 0) || '';
|
|
164
|
+
}
|
|
165
|
+
|
|
134
166
|
/**
|
|
135
167
|
* @param {unknown} err
|
|
136
|
-
* @param {{ plain: boolean, color: boolean, debug
|
|
168
|
+
* @param {{ plain: boolean, color: boolean, debug?: boolean, logLevel?: string }} mode
|
|
137
169
|
*/
|
|
138
170
|
export function formatErrorBlock(err, mode) {
|
|
139
171
|
const normalized = normalizeError(err);
|
|
140
172
|
const maybe = /** @type {{ code?: unknown, phase?: unknown, kind?: unknown, file?: unknown, hint?: unknown }} */ (normalized);
|
|
141
|
-
const kind = sanitizeErrorMessage(maybe.kind || maybe.code || 'CLI_ERROR');
|
|
142
173
|
const phase = maybe.phase ? sanitizeErrorMessage(maybe.phase) : inferPhaseFromArgv();
|
|
143
174
|
const code = maybe.code
|
|
144
175
|
? sanitizeErrorMessage(maybe.code)
|
|
145
176
|
: `${phase.toUpperCase().replace(/[^A-Z0-9]+/g, '_') || 'CLI'}_FAILED`;
|
|
146
177
|
const rawMessage = sanitizeErrorMessage(normalized.message || String(normalized));
|
|
147
178
|
const message = normalizeErrorMessagePaths(rawMessage);
|
|
179
|
+
const compactMessage = firstMeaningfulLine(message) || 'Command failed';
|
|
148
180
|
const file = normalizePathForDisplay(
|
|
149
181
|
sanitizeErrorMessage(maybe.file || extractFileFromMessage(message) || DEFAULT_FILE)
|
|
150
182
|
);
|
|
151
183
|
const hint = sanitizeErrorMessage(maybe.hint || formatHintUrl(code));
|
|
152
184
|
|
|
185
|
+
if (mode.logLevel !== 'verbose' && !mode.debug) {
|
|
186
|
+
return [
|
|
187
|
+
formatLine(mode, { glyph: '✖', tag: 'ERR', text: compactMessage }),
|
|
188
|
+
formatHint(mode, hint)
|
|
189
|
+
].join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
153
192
|
const lines = [];
|
|
154
|
-
lines.push('
|
|
155
|
-
lines.push(
|
|
156
|
-
lines.push(
|
|
157
|
-
lines.push(
|
|
158
|
-
lines.push(
|
|
159
|
-
lines.push(
|
|
160
|
-
lines.push(`[zenith] Message: ${message}`);
|
|
193
|
+
lines.push(formatLine(mode, { glyph: '✖', tag: 'ERR', text: compactMessage }));
|
|
194
|
+
lines.push(formatHint(mode, hint || formatHintUrl(code)));
|
|
195
|
+
lines.push(`${formatPrefix(mode)} code: ${code || 'CLI_FAILED'}`);
|
|
196
|
+
lines.push(`${formatPrefix(mode)} phase: ${phase || DEFAULT_PHASE}`);
|
|
197
|
+
lines.push(`${formatPrefix(mode)} file: ${file || DEFAULT_FILE}`);
|
|
198
|
+
lines.push(`${formatPrefix(mode)} detail: ${message}`);
|
|
161
199
|
|
|
162
200
|
if (mode.debug && normalized.stack) {
|
|
163
|
-
lines.push(
|
|
201
|
+
lines.push(`${formatPrefix(mode)} stack:`);
|
|
164
202
|
lines.push(...String(normalized.stack).split('\n').slice(0, 20));
|
|
165
203
|
}
|
|
166
204
|
|
package/dist/ui/logger.js
CHANGED
|
@@ -1,105 +1,270 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
formatErrorBlock,
|
|
3
|
+
formatHeading,
|
|
4
|
+
formatHint,
|
|
5
|
+
formatLine,
|
|
6
|
+
formatSummaryTable
|
|
7
|
+
} from './format.js';
|
|
2
8
|
import { getUiMode } from './env.js';
|
|
3
9
|
|
|
4
|
-
const SPINNER_FRAMES = ['-', '\\', '|', '/'];
|
|
5
|
-
|
|
6
10
|
function write(out, text) {
|
|
7
11
|
out.write(`${text}\n`);
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const SILENT_MODE = {
|
|
15
|
+
plain: true,
|
|
16
|
+
color: false,
|
|
17
|
+
tty: false,
|
|
18
|
+
ci: true,
|
|
19
|
+
spinner: false,
|
|
20
|
+
debug: false,
|
|
21
|
+
logLevel: 'quiet'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function createNoopSpinner() {
|
|
25
|
+
return {
|
|
26
|
+
start() { },
|
|
27
|
+
update() { },
|
|
28
|
+
stop() { },
|
|
29
|
+
succeed() { },
|
|
30
|
+
fail() { }
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeLevel(level) {
|
|
35
|
+
return level === 'quiet' || level === 'verbose' ? level : 'normal';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function shouldEmit(mode, tag) {
|
|
39
|
+
const level = normalizeLevel(mode.logLevel);
|
|
40
|
+
if (level === 'quiet') {
|
|
41
|
+
return tag === 'OK' || tag === 'WARN' || tag === 'ERR';
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function createWriter(runtime, mode, sink = null) {
|
|
47
|
+
if (typeof sink === 'function') {
|
|
48
|
+
return sink;
|
|
49
|
+
}
|
|
50
|
+
return (stream, text) => {
|
|
51
|
+
const out = stream === 'stderr' ? runtime.stderr : runtime.stdout;
|
|
52
|
+
write(out, text);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function classifyChildLine(line) {
|
|
57
|
+
const trimmed = String(line || '').trim();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const vendorCache = trimmed.match(/^\[zenith\]\s+Vendor cache (hit|miss):\s+(.+)$/);
|
|
63
|
+
if (vendorCache) {
|
|
64
|
+
return {
|
|
65
|
+
tag: 'BUILD',
|
|
66
|
+
glyph: '•',
|
|
67
|
+
message: `vendor cache ${vendorCache[1]} (${vendorCache[2]})`,
|
|
68
|
+
onceKey: `vendor-cache:${vendorCache[1]}:${vendorCache[2]}`
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const vendorBundle = trimmed.match(/^\[zenith\]\s+Vendor bundle:\s+(.+)$/);
|
|
73
|
+
if (vendorBundle) {
|
|
12
74
|
return {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
fail() { }
|
|
75
|
+
tag: 'BUILD',
|
|
76
|
+
glyph: '•',
|
|
77
|
+
message: `vendor bundle ${vendorBundle[1]}`,
|
|
78
|
+
onceKey: `vendor-bundle:${vendorBundle[1]}`
|
|
18
79
|
};
|
|
19
80
|
}
|
|
20
81
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
82
|
+
const bundler = trimmed.match(/^\[zenith-bundler\]\s*(.+)$/);
|
|
83
|
+
if (bundler) {
|
|
84
|
+
const message = bundler[1].trim();
|
|
85
|
+
const lower = message.toLowerCase();
|
|
86
|
+
if (lower.includes('warning')) {
|
|
87
|
+
return {
|
|
88
|
+
tag: 'WARN',
|
|
89
|
+
glyph: '⚠',
|
|
90
|
+
message,
|
|
91
|
+
onceKey: `bundler-warning:${message}`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (lower.includes('error') || lower.includes('failed')) {
|
|
95
|
+
return {
|
|
96
|
+
tag: 'ERR',
|
|
97
|
+
glyph: '✖',
|
|
98
|
+
message
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
tag: 'BUILD',
|
|
103
|
+
glyph: '•',
|
|
104
|
+
message
|
|
105
|
+
};
|
|
106
|
+
}
|
|
24
107
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
108
|
+
const zenith = trimmed.match(/^\[zenith\]\s+(.+)$/);
|
|
109
|
+
if (zenith) {
|
|
110
|
+
return {
|
|
111
|
+
tag: 'BUILD',
|
|
112
|
+
glyph: '•',
|
|
113
|
+
message: zenith[1].trim(),
|
|
114
|
+
onceKey: `zenith-child:${zenith[1].trim()}`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
30
117
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
118
|
+
const compilerWarning = trimmed.match(/warning\[[^\]]+\]/i);
|
|
119
|
+
if (compilerWarning) {
|
|
120
|
+
return {
|
|
121
|
+
tag: 'WARN',
|
|
122
|
+
glyph: '⚠',
|
|
123
|
+
message: trimmed,
|
|
124
|
+
onceKey: `compiler-warning:${trimmed}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
36
127
|
|
|
37
128
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
frame = 0;
|
|
42
|
-
paint();
|
|
43
|
-
interval = setInterval(paint, 80);
|
|
44
|
-
},
|
|
45
|
-
update(nextMessage) {
|
|
46
|
-
message = String(nextMessage || '');
|
|
47
|
-
},
|
|
48
|
-
stop() {
|
|
49
|
-
clearInterval(interval);
|
|
50
|
-
interval = null;
|
|
51
|
-
clear();
|
|
52
|
-
},
|
|
53
|
-
succeed(nextMessage) {
|
|
54
|
-
this.stop();
|
|
55
|
-
write(stderr, `[zenith] OK: ${nextMessage}`);
|
|
56
|
-
},
|
|
57
|
-
fail(nextMessage) {
|
|
58
|
-
this.stop();
|
|
59
|
-
write(stderr, `[zenith] ERROR: ${nextMessage}`);
|
|
60
|
-
}
|
|
129
|
+
tag: 'BUILD',
|
|
130
|
+
glyph: '•',
|
|
131
|
+
message: trimmed
|
|
61
132
|
};
|
|
62
133
|
}
|
|
63
134
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
135
|
+
function createBaseLogger({ runtime = process, mode, sink = null, silent = false } = {}) {
|
|
136
|
+
const resolvedMode = mode || (silent ? SILENT_MODE : getUiMode(runtime));
|
|
137
|
+
const once = new Set();
|
|
138
|
+
const writeLine = createWriter(runtime, resolvedMode, sink);
|
|
139
|
+
|
|
140
|
+
function emit(tag, glyph, message, options = {}) {
|
|
141
|
+
if (options.onceKey) {
|
|
142
|
+
if (once.has(options.onceKey)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
once.add(options.onceKey);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!shouldEmit(resolvedMode, tag)) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const stream = tag === 'WARN' || tag === 'ERR' ? 'stderr' : 'stdout';
|
|
153
|
+
writeLine(stream, formatLine(resolvedMode, { glyph, tag, text: message }));
|
|
154
|
+
if (options.hint) {
|
|
155
|
+
writeLine(stream, formatHint(resolvedMode, options.hint));
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
72
159
|
|
|
73
160
|
return {
|
|
74
|
-
mode,
|
|
75
|
-
spinner,
|
|
161
|
+
mode: resolvedMode,
|
|
162
|
+
spinner: createNoopSpinner(),
|
|
76
163
|
heading(text) {
|
|
77
|
-
|
|
164
|
+
writeLine('stdout', formatHeading(resolvedMode, text));
|
|
165
|
+
},
|
|
166
|
+
print(text) {
|
|
167
|
+
writeLine('stdout', String(text));
|
|
78
168
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
169
|
+
summary(rows, tag = 'BUILD') {
|
|
170
|
+
const table = formatSummaryTable(resolvedMode, rows, tag);
|
|
171
|
+
if (table) {
|
|
172
|
+
writeLine('stdout', table);
|
|
83
173
|
}
|
|
84
|
-
write(stdout, formatStep(mode, text));
|
|
85
174
|
},
|
|
86
|
-
|
|
87
|
-
|
|
175
|
+
dev(message, options = {}) {
|
|
176
|
+
return emit('DEV', '•', message, options);
|
|
88
177
|
},
|
|
89
|
-
|
|
90
|
-
|
|
178
|
+
build(message, options = {}) {
|
|
179
|
+
return emit('BUILD', '•', message, options);
|
|
91
180
|
},
|
|
92
|
-
|
|
93
|
-
|
|
181
|
+
hmr(message, options = {}) {
|
|
182
|
+
return emit('HMR', '•', message, options);
|
|
94
183
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
184
|
+
router(message, options = {}) {
|
|
185
|
+
return emit('ROUTER', '•', message, options);
|
|
186
|
+
},
|
|
187
|
+
css(message, options = {}) {
|
|
188
|
+
return emit('CSS', '•', message, options);
|
|
189
|
+
},
|
|
190
|
+
ok(message, options = {}) {
|
|
191
|
+
return emit('OK', '✓', message, options);
|
|
192
|
+
},
|
|
193
|
+
warn(message, options = {}) {
|
|
194
|
+
return emit('WARN', '⚠', message, options);
|
|
195
|
+
},
|
|
196
|
+
error(messageOrError, options = {}) {
|
|
197
|
+
const hasStructuredError = messageOrError instanceof Error || typeof messageOrError === 'object';
|
|
198
|
+
if (hasStructuredError && !options.hint && !options.onceKey && !options.error) {
|
|
199
|
+
writeLine('stderr', formatErrorBlock(messageOrError, resolvedMode));
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
const detail = options.error || messageOrError;
|
|
203
|
+
const formatted = detail instanceof Error || typeof detail === 'object'
|
|
204
|
+
? formatErrorBlock(detail, resolvedMode)
|
|
205
|
+
: null;
|
|
206
|
+
if (formatted && (resolvedMode.logLevel === 'verbose' || resolvedMode.debug)) {
|
|
207
|
+
writeLine('stderr', formatted);
|
|
208
|
+
return true;
|
|
99
209
|
}
|
|
210
|
+
const text = typeof messageOrError === 'string'
|
|
211
|
+
? messageOrError
|
|
212
|
+
: (detail instanceof Error ? detail.message : String(detail || 'Command failed'));
|
|
213
|
+
return emit('ERR', '✖', text, options);
|
|
100
214
|
},
|
|
101
|
-
|
|
102
|
-
|
|
215
|
+
verbose(tag, message, options = {}) {
|
|
216
|
+
if (resolvedMode.logLevel !== 'verbose') {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return emit(tag, '•', message, options);
|
|
220
|
+
},
|
|
221
|
+
childLine(source, line, options = {}) {
|
|
222
|
+
const entry = classifyChildLine(line);
|
|
223
|
+
if (!entry) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const streamHint = options.stream === 'stderr';
|
|
227
|
+
const isVerbose = resolvedMode.logLevel === 'verbose';
|
|
228
|
+
const isSeverity = entry.tag === 'WARN' || entry.tag === 'ERR';
|
|
229
|
+
if (!isVerbose && !isSeverity && options.showInfo === false) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const onceKey = options.onceKey || entry.onceKey;
|
|
233
|
+
const message = options.prefix
|
|
234
|
+
? `${options.prefix}${entry.message}`
|
|
235
|
+
: entry.message;
|
|
236
|
+
return emit(entry.tag, entry.glyph, message, {
|
|
237
|
+
...options,
|
|
238
|
+
onceKey,
|
|
239
|
+
hint: options.hint,
|
|
240
|
+
stream: streamHint ? 'stderr' : undefined
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
info(message) {
|
|
244
|
+
return emit('DEV', '•', message);
|
|
245
|
+
},
|
|
246
|
+
success(message) {
|
|
247
|
+
return emit('OK', '✓', message);
|
|
103
248
|
}
|
|
104
249
|
};
|
|
105
250
|
}
|
|
251
|
+
|
|
252
|
+
export function createZenithLogger(runtime = process, options = {}) {
|
|
253
|
+
const mode = getUiMode(runtime);
|
|
254
|
+
if (options.logLevel) {
|
|
255
|
+
mode.logLevel = normalizeLevel(options.logLevel);
|
|
256
|
+
}
|
|
257
|
+
return createBaseLogger({ runtime, mode });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function createSilentLogger() {
|
|
261
|
+
return createBaseLogger({
|
|
262
|
+
mode: SILENT_MODE,
|
|
263
|
+
sink: () => { },
|
|
264
|
+
silent: true
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function createLogger(runtime = process, options = {}) {
|
|
269
|
+
return createZenithLogger(runtime, options);
|
|
270
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Deterministic project orchestrator for Zenith framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zenithbuild/compiler": "0.6.
|
|
27
|
+
"@zenithbuild/compiler": "0.6.2",
|
|
28
|
+
"picocolors": "^1.1.1"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@jest/globals": "^30.2.0",
|