@zenithbuild/cli 0.6.0 → 0.6.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/dist/build.js +749 -47
- 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/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
|
|