@mks2508/better-logger 3.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +45 -1
- package/dist/Logger.d.ts +129 -4
- package/dist/Logger.d.ts.map +1 -1
- package/dist/ScopedLogger.d.ts +17 -0
- package/dist/ScopedLogger.d.ts.map +1 -1
- package/dist/chunks/{Logger-C086w3EM.js → Logger-BZ7B7OXE.js} +258 -16
- package/dist/chunks/Logger-BZ7B7OXE.js.map +1 -0
- package/dist/chunks/Logger-Cbi1f10o.js +2 -0
- package/dist/chunks/Logger-Cbi1f10o.js.map +1 -0
- package/dist/chunks/cli-module-CadqgZ1Z.js +2 -0
- package/dist/chunks/cli-module-CadqgZ1Z.js.map +1 -0
- package/dist/chunks/cli-module-ClIJmaT8.js +383 -0
- package/dist/chunks/cli-module-ClIJmaT8.js.map +1 -0
- package/dist/chunks/color-converter-CCSQRztd.js +2 -0
- package/dist/chunks/color-converter-CCSQRztd.js.map +1 -0
- package/dist/chunks/color-converter-_Xdmsy7E.js +462 -0
- package/dist/chunks/color-converter-_Xdmsy7E.js.map +1 -0
- package/dist/chunks/{environment-DXHouGD4.js → environment-C1xxvc8l.js} +28 -478
- package/dist/chunks/environment-C1xxvc8l.js.map +1 -0
- package/dist/chunks/environment-Cprkw3g9.js +4 -0
- package/dist/chunks/environment-Cprkw3g9.js.map +1 -0
- package/dist/chunks/{formatting-BNnVUPcw.js → formatting-DMJxYq9o.js} +18 -18
- package/dist/chunks/{formatting-BNnVUPcw.js.map → formatting-DMJxYq9o.js.map} +1 -1
- package/dist/chunks/{formatting-Cg5YhB9Y.js → formatting-DpKxCsXq.js} +2 -2
- package/dist/chunks/{formatting-Cg5YhB9Y.js.map → formatting-DpKxCsXq.js.map} +1 -1
- package/dist/cli-module.d.ts +15 -0
- package/dist/cli-module.d.ts.map +1 -0
- package/dist/cli-primitives/box.d.ts +11 -0
- package/dist/cli-primitives/box.d.ts.map +1 -0
- package/dist/cli-primitives/cli-table.d.ts +11 -0
- package/dist/cli-primitives/cli-table.d.ts.map +1 -0
- package/dist/cli-primitives/divider.d.ts +11 -0
- package/dist/cli-primitives/divider.d.ts.map +1 -0
- package/dist/cli-primitives/header.d.ts +12 -0
- package/dist/cli-primitives/header.d.ts.map +1 -0
- package/dist/cli-primitives/index.d.ts +13 -0
- package/dist/cli-primitives/index.d.ts.map +1 -0
- package/dist/cli-primitives/server-fallback.d.ts +25 -0
- package/dist/cli-primitives/server-fallback.d.ts.map +1 -0
- package/dist/cli-primitives/spinner.d.ts +47 -0
- package/dist/cli-primitives/spinner.d.ts.map +1 -0
- package/dist/cli-primitives/step.d.ts +11 -0
- package/dist/cli-primitives/step.d.ts.map +1 -0
- package/dist/cli.cjs +2 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +12 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants.d.ts +9 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.js +2 -2
- package/dist/exports-module.d.ts +1 -1
- package/dist/exports-module.d.ts.map +1 -1
- package/dist/exports.cjs +1 -1
- package/dist/exports.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +162 -195
- package/dist/index.js.map +1 -1
- package/dist/styling-module.d.ts +1 -1
- package/dist/styling-module.d.ts.map +1 -1
- package/dist/styling.cjs +1 -1
- package/dist/styling.js +3 -3
- package/dist/types/core.d.ts +91 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/writers/BufferWriter.d.ts +96 -0
- package/dist/writers/BufferWriter.d.ts.map +1 -0
- package/dist/writers/index.d.ts +6 -0
- package/dist/writers/index.d.ts.map +1 -0
- package/package.json +28 -3
- package/packages/core/package.json +1 -1
- package/playground/demo-all.ts +95 -0
- package/playground/demo-box.ts +85 -0
- package/playground/demo-levels.ts +56 -0
- package/playground/demo-real-world.ts +98 -0
- package/playground/demo-spinner.ts +77 -0
- package/playground/demo-steps.ts +72 -0
- package/playground/demo-table.ts +83 -0
- package/src/Logger.ts +263 -16
- package/src/ScopedLogger.ts +43 -1
- package/src/cli-module.ts +21 -0
- package/src/cli-primitives/box.ts +86 -0
- package/src/cli-primitives/cli-table.ts +62 -0
- package/src/cli-primitives/divider.ts +17 -0
- package/src/cli-primitives/header.ts +18 -0
- package/src/cli-primitives/index.ts +13 -0
- package/src/cli-primitives/server-fallback.ts +54 -0
- package/src/cli-primitives/spinner.ts +133 -0
- package/src/cli-primitives/step.ts +22 -0
- package/src/constants.ts +14 -2
- package/src/index.ts +23 -2
- package/src/types/core.ts +99 -0
- package/src/types/index.ts +4 -0
- package/src/writers/BufferWriter.ts +157 -0
- package/src/writers/index.ts +6 -0
- package/vite.config.ts +33 -0
- package/dist/chunks/Logger-BV0v1AyC.js +0 -2
- package/dist/chunks/Logger-BV0v1AyC.js.map +0 -1
- package/dist/chunks/Logger-C086w3EM.js.map +0 -1
- package/dist/chunks/environment-C_8J-zQ_.js +0 -4
- package/dist/chunks/environment-C_8J-zQ_.js.map +0 -1
- package/dist/chunks/environment-DXHouGD4.js.map +0 -1
package/src/Logger.ts
CHANGED
|
@@ -36,7 +36,8 @@ import { TransportManager } from './transports/index.js';
|
|
|
36
36
|
import { parseStackTrace } from './utils/stackTrace.js';
|
|
37
37
|
import { formatTimestamp } from './utils/timestamps.js';
|
|
38
38
|
import { createStyledOutput, setupThemeChangeListener } from './utils/output.js';
|
|
39
|
-
import { getEnvironment } from './utils/environment-detector.js';
|
|
39
|
+
import { getEnvironment, getColorCapability, isRunningInTerminal } from './utils/environment-detector.js';
|
|
40
|
+
import { formatBadge } from './terminal/formatter.js';
|
|
40
41
|
|
|
41
42
|
// Styling imports
|
|
42
43
|
import {
|
|
@@ -61,7 +62,17 @@ import { ExportLogHandler } from './handlers/index.js';
|
|
|
61
62
|
import { createDefaultCLI, type CommandProcessor } from './cli/index.js';
|
|
62
63
|
|
|
63
64
|
// Constants
|
|
64
|
-
import { DEFAULT_CONFIG } from './constants.js';
|
|
65
|
+
import { DEFAULT_CONFIG, CLI_LEVEL_MAP } from './constants.js';
|
|
66
|
+
|
|
67
|
+
// CLI Primitives
|
|
68
|
+
import type { CLILogLevel, ISpinnerHandle, IBoxOptions, ITableOptions } from './types/index.js';
|
|
69
|
+
import { renderStep } from './cli-primitives/step.js';
|
|
70
|
+
import { renderHeader } from './cli-primitives/header.js';
|
|
71
|
+
import { renderDivider } from './cli-primitives/divider.js';
|
|
72
|
+
import { renderBox } from './cli-primitives/box.js';
|
|
73
|
+
import { renderTable } from './cli-primitives/cli-table.js';
|
|
74
|
+
import { SpinnerManager, NoopSpinner } from './cli-primitives/spinner.js';
|
|
75
|
+
import { ServerFallback } from './cli-primitives/server-fallback.js';
|
|
65
76
|
|
|
66
77
|
/**
|
|
67
78
|
* Estilos del tema activo actual
|
|
@@ -115,6 +126,11 @@ export class Logger {
|
|
|
115
126
|
private hookManager: HookManager;
|
|
116
127
|
private transportManager?: TransportManager;
|
|
117
128
|
|
|
129
|
+
/** Whether CLI primitives (step, box, header, etc.) should be shown @since 5.0.0 */
|
|
130
|
+
private _showPrimitives = true;
|
|
131
|
+
/** Server-mode fallback for non-TTY environments @since 5.0.0 */
|
|
132
|
+
private _serverFallback?: ServerFallback;
|
|
133
|
+
|
|
118
134
|
/**
|
|
119
135
|
* Crea una nueva instancia del Logger
|
|
120
136
|
*
|
|
@@ -942,12 +958,8 @@ export class Logger {
|
|
|
942
958
|
const groupIndent = ' '.repeat(this.groupDepth);
|
|
943
959
|
const finalFormat = groupIndent + format;
|
|
944
960
|
|
|
945
|
-
// Output
|
|
946
|
-
|
|
947
|
-
console.log(finalFormat, ...styles, ...additionalArgs);
|
|
948
|
-
} else {
|
|
949
|
-
console.log(finalFormat, ...styles);
|
|
950
|
-
}
|
|
961
|
+
// Output via configured writer (console by default)
|
|
962
|
+
this.writeOutput(finalFormat, level, styles, additionalArgs);
|
|
951
963
|
|
|
952
964
|
// Update export handler group info
|
|
953
965
|
if (this.exportHandler) {
|
|
@@ -999,11 +1011,13 @@ export class Logger {
|
|
|
999
1011
|
if (!this.shouldLog(level)) return;
|
|
1000
1012
|
|
|
1001
1013
|
let prefix = '';
|
|
1014
|
+
const colorCapability = getColorCapability();
|
|
1015
|
+
|
|
1002
1016
|
if (bindings.badges?.length) {
|
|
1003
|
-
prefix += bindings.badges.map(b =>
|
|
1017
|
+
prefix += bindings.badges.map(b => formatBadge(b, 'pill', colorCapability, '#00ff88')).join(' ') + ' ';
|
|
1004
1018
|
}
|
|
1005
1019
|
if (bindings.scope) {
|
|
1006
|
-
prefix +=
|
|
1020
|
+
prefix += formatBadge(bindings.scope, 'pill', colorCapability, '#00ffff') + ' ';
|
|
1007
1021
|
}
|
|
1008
1022
|
|
|
1009
1023
|
if (prefix && args.length > 0) {
|
|
@@ -1293,21 +1307,22 @@ export class Logger {
|
|
|
1293
1307
|
|
|
1294
1308
|
/**
|
|
1295
1309
|
* Finaliza un temporizador y muestra el tiempo transcurrido
|
|
1296
|
-
*
|
|
1310
|
+
*
|
|
1297
1311
|
* @param {string} label - Etiqueta del temporizador a finalizar
|
|
1298
|
-
*
|
|
1312
|
+
* @returns {number} Elapsed milliseconds, or -1 if timer not found
|
|
1313
|
+
*
|
|
1299
1314
|
* @example
|
|
1300
1315
|
* logger.time('consulta-db');
|
|
1301
1316
|
* await consultarBaseDatos();
|
|
1302
|
-
* logger.timeEnd('consulta-db'); // ⏱️ Timer ended: consulta-db - 234.56ms
|
|
1303
|
-
*
|
|
1317
|
+
* const elapsed = logger.timeEnd('consulta-db'); // ⏱️ Timer ended: consulta-db - 234.56ms
|
|
1318
|
+
*
|
|
1304
1319
|
* @since 0.3.0
|
|
1305
1320
|
*/
|
|
1306
|
-
timeEnd(label: string):
|
|
1321
|
+
timeEnd(label: string): number {
|
|
1307
1322
|
const timer = this.timers.get(label);
|
|
1308
1323
|
if (!timer) {
|
|
1309
1324
|
this.warn(`Timer '${label}' does not exist`);
|
|
1310
|
-
return;
|
|
1325
|
+
return -1;
|
|
1311
1326
|
}
|
|
1312
1327
|
|
|
1313
1328
|
const elapsed = performance.now() - timer.startTime;
|
|
@@ -1315,6 +1330,8 @@ export class Logger {
|
|
|
1315
1330
|
|
|
1316
1331
|
const timerStyle = StylePresets.success().build();
|
|
1317
1332
|
console.log(`%c⏱️ Timer ended: ${label} - ${elapsed.toFixed(2)}ms`, timerStyle);
|
|
1333
|
+
|
|
1334
|
+
return elapsed;
|
|
1318
1335
|
}
|
|
1319
1336
|
|
|
1320
1337
|
// ===== ADVANCED VISUAL FEATURES =====
|
|
@@ -1463,6 +1480,236 @@ export class Logger {
|
|
|
1463
1480
|
}
|
|
1464
1481
|
}
|
|
1465
1482
|
|
|
1483
|
+
// ===== CLI PRIMITIVES (v5.0) =====
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Displays a step progress indicator in the terminal
|
|
1487
|
+
*
|
|
1488
|
+
* @param {number} current - Current step number
|
|
1489
|
+
* @param {number} total - Total number of steps
|
|
1490
|
+
* @param {string} message - Step description
|
|
1491
|
+
*
|
|
1492
|
+
* @example
|
|
1493
|
+
* logger.step(1, 5, 'Analyzing repository...');
|
|
1494
|
+
* logger.step(2, 5, 'Generating commit message...');
|
|
1495
|
+
*
|
|
1496
|
+
* @since 5.0.0
|
|
1497
|
+
*/
|
|
1498
|
+
step(current: number, total: number, message: string): void {
|
|
1499
|
+
if (!this._showPrimitives) return;
|
|
1500
|
+
if (!isRunningInTerminal()) {
|
|
1501
|
+
this.getServerFallback().step(current, total, message);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
const colorCap = getColorCapability();
|
|
1505
|
+
const output = renderStep(current, total, message, colorCap);
|
|
1506
|
+
process.stderr.write(output + '\n');
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Displays a styled header with optional subtitle
|
|
1511
|
+
*
|
|
1512
|
+
* @param {string} title - Main title text
|
|
1513
|
+
* @param {string} subtitle - Optional subtitle (rendered dimmed)
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* logger.header('Commit Wizard', 'v2.0.0');
|
|
1517
|
+
*
|
|
1518
|
+
* @since 5.0.0
|
|
1519
|
+
*/
|
|
1520
|
+
header(title: string, subtitle?: string): void {
|
|
1521
|
+
if (!this._showPrimitives) return;
|
|
1522
|
+
if (!isRunningInTerminal()) {
|
|
1523
|
+
this.getServerFallback().header(title, subtitle);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const output = renderHeader(title, subtitle);
|
|
1527
|
+
process.stderr.write(output + '\n');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Displays a horizontal divider line
|
|
1532
|
+
*
|
|
1533
|
+
* @example
|
|
1534
|
+
* logger.divider();
|
|
1535
|
+
*
|
|
1536
|
+
* @since 5.0.0
|
|
1537
|
+
*/
|
|
1538
|
+
divider(): void {
|
|
1539
|
+
if (!this._showPrimitives) return;
|
|
1540
|
+
if (!isRunningInTerminal()) {
|
|
1541
|
+
this.getServerFallback().divider();
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
const output = renderDivider();
|
|
1545
|
+
process.stderr.write(output + '\n');
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* Outputs a blank line
|
|
1550
|
+
*
|
|
1551
|
+
* @example
|
|
1552
|
+
* logger.blank();
|
|
1553
|
+
*
|
|
1554
|
+
* @since 5.0.0
|
|
1555
|
+
*/
|
|
1556
|
+
blank(): void {
|
|
1557
|
+
if (!this._showPrimitives) return;
|
|
1558
|
+
if (!isRunningInTerminal()) {
|
|
1559
|
+
this.getServerFallback().blank();
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
process.stderr.write('\n');
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Renders content inside a bordered box
|
|
1567
|
+
*
|
|
1568
|
+
* @param {string} content - Content string (may contain newlines)
|
|
1569
|
+
* @param {IBoxOptions} options - Box rendering options
|
|
1570
|
+
*
|
|
1571
|
+
* @example
|
|
1572
|
+
* logger.box('3 commits generated\nProvider: Groq', { title: 'Done', borderColor: '#00ff00' });
|
|
1573
|
+
*
|
|
1574
|
+
* @since 5.0.0
|
|
1575
|
+
*/
|
|
1576
|
+
box(content: string, options?: IBoxOptions): void {
|
|
1577
|
+
if (!this._showPrimitives) return;
|
|
1578
|
+
if (!isRunningInTerminal()) {
|
|
1579
|
+
this.getServerFallback().box(content, options);
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
const colorCap = getColorCapability();
|
|
1583
|
+
const output = renderBox(content, options, colorCap);
|
|
1584
|
+
process.stderr.write(output + '\n');
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Renders an array of objects as a formatted ASCII table.
|
|
1589
|
+
* Note: This is distinct from the existing table() method which uses console.table.
|
|
1590
|
+
*
|
|
1591
|
+
* @param {Record<string, unknown>[]} rows - Array of row objects
|
|
1592
|
+
* @param {ITableOptions} options - Table rendering options
|
|
1593
|
+
*
|
|
1594
|
+
* @example
|
|
1595
|
+
* logger.cliTable([
|
|
1596
|
+
* { provider: 'Groq', status: 'Available', model: 'llama-3.3-70b' },
|
|
1597
|
+
* { provider: 'Gemini', status: 'Configured', model: 'gemini-2.5-flash' },
|
|
1598
|
+
* ]);
|
|
1599
|
+
*
|
|
1600
|
+
* @since 5.0.0
|
|
1601
|
+
*/
|
|
1602
|
+
cliTable(rows: Record<string, unknown>[], options?: ITableOptions): void {
|
|
1603
|
+
if (!this._showPrimitives) return;
|
|
1604
|
+
if (!isRunningInTerminal()) {
|
|
1605
|
+
this.getServerFallback().cliTable(rows, options);
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
const colorCap = getColorCapability();
|
|
1609
|
+
const output = renderTable(rows, options, colorCap);
|
|
1610
|
+
process.stderr.write(output + '\n');
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
/**
|
|
1614
|
+
* Creates a spinner handle for showing progress during async operations.
|
|
1615
|
+
* Returns a NoopSpinner in non-TTY environments.
|
|
1616
|
+
*
|
|
1617
|
+
* @param {string} message - Initial spinner text
|
|
1618
|
+
* @returns {ISpinnerHandle} Spinner controller
|
|
1619
|
+
*
|
|
1620
|
+
* @example
|
|
1621
|
+
* const s = logger.spinner('Analyzing repository...');
|
|
1622
|
+
* s.start();
|
|
1623
|
+
* await analyzeRepo();
|
|
1624
|
+
* s.succeed('Analysis complete (1.2s)');
|
|
1625
|
+
*
|
|
1626
|
+
* @since 5.0.0
|
|
1627
|
+
*/
|
|
1628
|
+
spinner(message: string): ISpinnerHandle {
|
|
1629
|
+
if (!isRunningInTerminal() || this.config.outputMode === 'silent') {
|
|
1630
|
+
return new NoopSpinner(message, this);
|
|
1631
|
+
}
|
|
1632
|
+
return new SpinnerManager(message, this.config, this);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* Sets the CLI verbosity level, controlling both log verbosity and primitive visibility
|
|
1637
|
+
*
|
|
1638
|
+
* @param {CLILogLevel} level - CLI log level
|
|
1639
|
+
*
|
|
1640
|
+
* @example
|
|
1641
|
+
* logger.setCLILevel('quiet'); // Only errors, no CLI primitives
|
|
1642
|
+
* logger.setCLILevel('verbose'); // Debug logs + all CLI primitives
|
|
1643
|
+
*
|
|
1644
|
+
* @since 5.0.0
|
|
1645
|
+
*/
|
|
1646
|
+
setCLILevel(level: CLILogLevel): void {
|
|
1647
|
+
const mapping = CLI_LEVEL_MAP[level];
|
|
1648
|
+
this.setVerbosity(mapping.verbosity);
|
|
1649
|
+
this._showPrimitives = mapping.showPrimitives;
|
|
1650
|
+
this.config.cliLevel = level;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/**
|
|
1654
|
+
* Returns the current CLI log level
|
|
1655
|
+
* @returns {CLILogLevel} Current CLI log level
|
|
1656
|
+
* @since 5.0.0
|
|
1657
|
+
*/
|
|
1658
|
+
get cliLevel(): CLILogLevel {
|
|
1659
|
+
return this.config.cliLevel ?? 'normal';
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/** Lazily creates the server fallback instance @private */
|
|
1663
|
+
private getServerFallback(): ServerFallback {
|
|
1664
|
+
if (!this._serverFallback) {
|
|
1665
|
+
this._serverFallback = new ServerFallback(this);
|
|
1666
|
+
}
|
|
1667
|
+
return this._serverFallback;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// ===== OUTPUT WRITER SYSTEM =====
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Writes formatted output to the configured destination.
|
|
1674
|
+
* Respects outputMode configuration for console, silent, or custom output.
|
|
1675
|
+
*
|
|
1676
|
+
* @private
|
|
1677
|
+
* @param {string} message - Formatted log message
|
|
1678
|
+
* @param {LogLevel} level - Log level
|
|
1679
|
+
* @param {string[]} styles - CSS styles for browser console
|
|
1680
|
+
* @param {any[]} additionalArgs - Additional arguments to log
|
|
1681
|
+
* @since 4.0.0
|
|
1682
|
+
*/
|
|
1683
|
+
private writeOutput(
|
|
1684
|
+
message: string,
|
|
1685
|
+
level: LogLevel,
|
|
1686
|
+
styles: string[],
|
|
1687
|
+
additionalArgs: any[]
|
|
1688
|
+
): void {
|
|
1689
|
+
const mode = this.config.outputMode ?? 'console';
|
|
1690
|
+
|
|
1691
|
+
// Silent mode: no output
|
|
1692
|
+
if (mode === 'silent') {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// Custom mode: use configured writer
|
|
1697
|
+
if (mode === 'custom' && this.config.outputWriter) {
|
|
1698
|
+
const fullMessage = additionalArgs.length > 0
|
|
1699
|
+
? `${message} ${additionalArgs.map(a => String(a)).join(' ')}`
|
|
1700
|
+
: message;
|
|
1701
|
+
this.config.outputWriter.write(fullMessage, level, styles);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// Default: console output
|
|
1706
|
+
if (additionalArgs.length > 0) {
|
|
1707
|
+
console.log(message, ...styles, ...additionalArgs);
|
|
1708
|
+
} else {
|
|
1709
|
+
console.log(message, ...styles);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1466
1713
|
// ===== CLI SYSTEM =====
|
|
1467
1714
|
|
|
1468
1715
|
/**
|
package/src/ScopedLogger.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Logger } from './Logger.js';
|
|
2
|
-
import type { TimerEntry, Bindings } from './types/index.js';
|
|
2
|
+
import type { TimerEntry, Bindings, ISpinnerHandle, IBoxOptions, ITableOptions, CLILogLevel } from './types/index.js';
|
|
3
3
|
|
|
4
4
|
export class ScopedLogger {
|
|
5
5
|
private readonly parent: Logger;
|
|
@@ -105,6 +105,48 @@ export class ScopedLogger {
|
|
|
105
105
|
console.trace(`[${this.scopeName}]`);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// ===== CLI PRIMITIVES (v5.0 delegation) =====
|
|
109
|
+
|
|
110
|
+
/** @see Logger.step */
|
|
111
|
+
step(current: number, total: number, message: string): void {
|
|
112
|
+
this.parent.step(current, total, message);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** @see Logger.header */
|
|
116
|
+
header(title: string, subtitle?: string): void {
|
|
117
|
+
this.parent.header(title, subtitle);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @see Logger.divider */
|
|
121
|
+
divider(): void {
|
|
122
|
+
this.parent.divider();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** @see Logger.blank */
|
|
126
|
+
blank(): void {
|
|
127
|
+
this.parent.blank();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @see Logger.box */
|
|
131
|
+
box(content: string, options?: IBoxOptions): void {
|
|
132
|
+
this.parent.box(content, options);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** @see Logger.cliTable */
|
|
136
|
+
cliTable(rows: Record<string, unknown>[], options?: ITableOptions): void {
|
|
137
|
+
this.parent.cliTable(rows, options);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** @see Logger.spinner */
|
|
141
|
+
spinner(message: string): ISpinnerHandle {
|
|
142
|
+
return this.parent.spinner(message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @see Logger.setCLILevel */
|
|
146
|
+
setCLILevel(level: CLILogLevel): void {
|
|
147
|
+
this.parent.setCLILevel(level);
|
|
148
|
+
}
|
|
149
|
+
|
|
108
150
|
_pushContext(context: string): void {
|
|
109
151
|
this.contextStack.push(context);
|
|
110
152
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI-only entry point for @mks2508/better-logger/cli
|
|
3
|
+
* Exports only CLI primitives for minimal bundle size in CLI tools.
|
|
4
|
+
* @version 5.0.0
|
|
5
|
+
* @since 5.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { renderStep } from './cli-primitives/step.js';
|
|
9
|
+
export { renderHeader } from './cli-primitives/header.js';
|
|
10
|
+
export { renderDivider } from './cli-primitives/divider.js';
|
|
11
|
+
export { renderBox } from './cli-primitives/box.js';
|
|
12
|
+
export { renderTable } from './cli-primitives/cli-table.js';
|
|
13
|
+
export { SpinnerManager, NoopSpinner } from './cli-primitives/spinner.js';
|
|
14
|
+
export { ServerFallback } from './cli-primitives/server-fallback.js';
|
|
15
|
+
|
|
16
|
+
export type {
|
|
17
|
+
CLILogLevel,
|
|
18
|
+
ISpinnerHandle,
|
|
19
|
+
IBoxOptions,
|
|
20
|
+
ITableOptions,
|
|
21
|
+
} from './types/index.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Box renderer for CLI primitives
|
|
3
|
+
* @since 5.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IBoxOptions } from '../types/core.js';
|
|
7
|
+
import type { ColorCapability } from '../terminal/color-converter.js';
|
|
8
|
+
import { getANSIForeground, ANSI } from '../terminal/color-converter.js';
|
|
9
|
+
import { getTerminalWidth } from '../utils/environment-detector.js';
|
|
10
|
+
import { stripAnsi, getVisibleLength } from '../terminal/formatter.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Border character sets for different styles
|
|
14
|
+
*/
|
|
15
|
+
const BORDER_CHARS = {
|
|
16
|
+
single: { tl: '\u250c', tr: '\u2510', bl: '\u2514', br: '\u2518', h: '\u2500', v: '\u2502' },
|
|
17
|
+
rounded: { tl: '\u256d', tr: '\u256e', bl: '\u2570', br: '\u256f', h: '\u2500', v: '\u2502' },
|
|
18
|
+
double: { tl: '\u2554', tr: '\u2557', bl: '\u255a', br: '\u255d', h: '\u2550', v: '\u2551' },
|
|
19
|
+
bold: { tl: '\u250f', tr: '\u2513', bl: '\u2517', br: '\u251b', h: '\u2501', v: '\u2503' },
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Renders content inside a bordered box
|
|
24
|
+
* @param content - Content string (may contain newlines)
|
|
25
|
+
* @param options - Box rendering options
|
|
26
|
+
* @param colorCap - Terminal color capability
|
|
27
|
+
* @returns Formatted box string with border
|
|
28
|
+
*/
|
|
29
|
+
export function renderBox(content: string, options: IBoxOptions = {}, colorCap: ColorCapability = 'full'): string {
|
|
30
|
+
const {
|
|
31
|
+
title,
|
|
32
|
+
borderColor,
|
|
33
|
+
borderStyle = 'rounded',
|
|
34
|
+
padding = 0,
|
|
35
|
+
} = options;
|
|
36
|
+
|
|
37
|
+
const chars = BORDER_CHARS[borderStyle] ?? BORDER_CHARS.rounded;
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
const maxTermWidth = Math.min(getTerminalWidth() - 4, 80);
|
|
40
|
+
|
|
41
|
+
// Calculate content width from visible text
|
|
42
|
+
const contentWidths = lines.map(l => getVisibleLength(l));
|
|
43
|
+
const titleWidth = title ? stripAnsi(title).length + 2 : 0; // +2 for spaces around title
|
|
44
|
+
const maxContentWidth = Math.max(...contentWidths, titleWidth);
|
|
45
|
+
const innerWidth = Math.min(maxContentWidth + 2, maxTermWidth); // +2 for horizontal padding
|
|
46
|
+
|
|
47
|
+
// Color wrapper for border chars
|
|
48
|
+
const bc = borderColor && colorCap !== 'none'
|
|
49
|
+
? getANSIForeground(borderColor, colorCap)
|
|
50
|
+
: '';
|
|
51
|
+
const reset = bc ? ANSI.reset : '';
|
|
52
|
+
|
|
53
|
+
const wrap = (char: string) => `${bc}${char}${reset}`;
|
|
54
|
+
|
|
55
|
+
// Build top border (with optional title)
|
|
56
|
+
let topBorder: string;
|
|
57
|
+
if (title) {
|
|
58
|
+
const titleStr = ` ${title} `;
|
|
59
|
+
const afterTitle = innerWidth - stripAnsi(titleStr).length;
|
|
60
|
+
topBorder = ` ${wrap(chars.tl)}${wrap(chars.h)}${wrap(titleStr)}${wrap(chars.h.repeat(Math.max(0, afterTitle - 1)))}${wrap(chars.tr)}`;
|
|
61
|
+
} else {
|
|
62
|
+
topBorder = ` ${wrap(chars.tl)}${wrap(chars.h.repeat(innerWidth))}${wrap(chars.tr)}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Build bottom border
|
|
66
|
+
const bottomBorder = ` ${wrap(chars.bl)}${wrap(chars.h.repeat(innerWidth))}${wrap(chars.br)}`;
|
|
67
|
+
|
|
68
|
+
// Build padding lines
|
|
69
|
+
const emptyLine = ` ${wrap(chars.v)}${' '.repeat(innerWidth)}${wrap(chars.v)}`;
|
|
70
|
+
const paddingLines = padding > 0 ? Array(padding).fill(emptyLine) : [];
|
|
71
|
+
|
|
72
|
+
// Build content lines
|
|
73
|
+
const contentLines = lines.map(line => {
|
|
74
|
+
const visible = getVisibleLength(line);
|
|
75
|
+
const pad = innerWidth - visible - 1; // -1 for left space
|
|
76
|
+
return ` ${wrap(chars.v)} ${line}${' '.repeat(Math.max(0, pad))}${wrap(chars.v)}`;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return [
|
|
80
|
+
topBorder,
|
|
81
|
+
...paddingLines,
|
|
82
|
+
...contentLines,
|
|
83
|
+
...paddingLines,
|
|
84
|
+
bottomBorder,
|
|
85
|
+
].join('\n');
|
|
86
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI table renderer for CLI primitives
|
|
3
|
+
* @since 5.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ITableOptions } from '../types/core.js';
|
|
7
|
+
import type { ColorCapability } from '../terminal/color-converter.js';
|
|
8
|
+
import { getANSIForeground, ANSI } from '../terminal/color-converter.js';
|
|
9
|
+
import { getVisibleLength, padToWidth } from '../terminal/formatter.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders an array of objects as a formatted ASCII table
|
|
13
|
+
* @param rows - Array of row objects
|
|
14
|
+
* @param options - Table rendering options
|
|
15
|
+
* @param colorCap - Terminal color capability
|
|
16
|
+
* @returns Formatted table string
|
|
17
|
+
*/
|
|
18
|
+
export function renderTable(
|
|
19
|
+
rows: Record<string, unknown>[],
|
|
20
|
+
options: ITableOptions = {},
|
|
21
|
+
colorCap: ColorCapability = 'full'
|
|
22
|
+
): string {
|
|
23
|
+
if (rows.length === 0) return '';
|
|
24
|
+
|
|
25
|
+
// Determine columns from options or first row's keys
|
|
26
|
+
const columns = options.columns ?? Object.keys(rows[0]!);
|
|
27
|
+
const headers = options.head ?? columns;
|
|
28
|
+
|
|
29
|
+
// Calculate column widths (max of header and all values)
|
|
30
|
+
const colWidths = columns.map((col, i) => {
|
|
31
|
+
const headerLen = getVisibleLength(headers[i] ?? col);
|
|
32
|
+
const maxValueLen = rows.reduce((max, row) => {
|
|
33
|
+
const val = String(row[col] ?? '');
|
|
34
|
+
return Math.max(max, getVisibleLength(val));
|
|
35
|
+
}, 0);
|
|
36
|
+
return Math.max(headerLen, maxValueLen) + 2; // +2 for padding
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Color helpers
|
|
40
|
+
const cyan = colorCap !== 'none' ? getANSIForeground('#00bcd4', colorCap) : '';
|
|
41
|
+
const dim = colorCap !== 'none' ? ANSI.dim : '';
|
|
42
|
+
const reset = colorCap !== 'none' ? ANSI.reset : '';
|
|
43
|
+
|
|
44
|
+
// Build header row
|
|
45
|
+
const headerRow = ' ' + columns.map((col, i) => {
|
|
46
|
+
const label = headers[i] ?? col;
|
|
47
|
+
return cyan + ANSI.bold + padToWidth(` ${label}`, colWidths[i]!) + reset;
|
|
48
|
+
}).join('');
|
|
49
|
+
|
|
50
|
+
// Build separator
|
|
51
|
+
const separator = ' ' + dim + colWidths.map(w => '\u2500'.repeat(w)).join('\u2500') + reset;
|
|
52
|
+
|
|
53
|
+
// Build data rows
|
|
54
|
+
const dataRows = rows.map(row => {
|
|
55
|
+
return ' ' + columns.map((col, i) => {
|
|
56
|
+
const val = String(row[col] ?? '');
|
|
57
|
+
return padToWidth(` ${val}`, colWidths[i]!);
|
|
58
|
+
}).join('');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return [headerRow, separator, ...dataRows].join('\n');
|
|
62
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Divider renderer for CLI primitives
|
|
3
|
+
* @since 5.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ANSI } from '../terminal/color-converter.js';
|
|
7
|
+
import { getTerminalWidth } from '../utils/environment-detector.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Renders a horizontal divider line
|
|
11
|
+
* @param width - Optional explicit width (defaults to terminal width capped at 60)
|
|
12
|
+
* @returns Formatted divider string
|
|
13
|
+
*/
|
|
14
|
+
export function renderDivider(width?: number): string {
|
|
15
|
+
const w = width ?? Math.min(getTerminalWidth() - 4, 60);
|
|
16
|
+
return ANSI.dim + ' ' + '\u2500'.repeat(w) + ANSI.reset;
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Header renderer for CLI primitives
|
|
3
|
+
* @since 5.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ANSI } from '../terminal/color-converter.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Renders a styled header with optional subtitle
|
|
10
|
+
* @param title - Main title text
|
|
11
|
+
* @param subtitle - Optional subtitle (rendered dimmed)
|
|
12
|
+
* @returns Formatted header string
|
|
13
|
+
*/
|
|
14
|
+
export function renderHeader(title: string, subtitle?: string): string {
|
|
15
|
+
const t = ANSI.bold + title + ANSI.reset;
|
|
16
|
+
const s = subtitle ? ANSI.dim + ` ${subtitle}` + ANSI.reset : '';
|
|
17
|
+
return ` ${t}${s}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview CLI Primitives - Built-in terminal UI components for better-logger
|
|
3
|
+
* @version 5.0.0
|
|
4
|
+
* @since 5.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { renderStep } from './step.js';
|
|
8
|
+
export { renderHeader } from './header.js';
|
|
9
|
+
export { renderDivider } from './divider.js';
|
|
10
|
+
export { renderBox } from './box.js';
|
|
11
|
+
export { renderTable } from './cli-table.js';
|
|
12
|
+
export { SpinnerManager, NoopSpinner } from './spinner.js';
|
|
13
|
+
export { ServerFallback } from './server-fallback.js';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Server/JSON fallback for CLI primitives in non-TTY environments
|
|
3
|
+
* @since 5.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IBoxOptions, ITableOptions } from '../types/core.js';
|
|
7
|
+
import type { Logger } from '../Logger.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Server-mode fallback: outputs CLI primitives as plain logger calls
|
|
11
|
+
* when not running in an interactive terminal.
|
|
12
|
+
*
|
|
13
|
+
* @since 5.0.0
|
|
14
|
+
*/
|
|
15
|
+
export class ServerFallback {
|
|
16
|
+
private logger: Logger;
|
|
17
|
+
|
|
18
|
+
constructor(logger: Logger) {
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Render step as plain info log */
|
|
23
|
+
step(current: number, total: number, msg: string): void {
|
|
24
|
+
this.logger.info(`[${current}/${total}] ${msg}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Render header as plain info log */
|
|
28
|
+
header(title: string, subtitle?: string): void {
|
|
29
|
+
const text = subtitle ? `${title} ${subtitle}` : title;
|
|
30
|
+
this.logger.info(text);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Divider is a no-op in server mode */
|
|
34
|
+
divider(): void {
|
|
35
|
+
// No-op in server/JSON mode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Blank line is a no-op in server mode */
|
|
39
|
+
blank(): void {
|
|
40
|
+
// No-op in server/JSON mode
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Render box content as plain info log */
|
|
44
|
+
box(content: string, _options?: IBoxOptions): void {
|
|
45
|
+
this.logger.info(content);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Render table rows as plain info logs */
|
|
49
|
+
cliTable(rows: Record<string, unknown>[], _options?: ITableOptions): void {
|
|
50
|
+
for (const row of rows) {
|
|
51
|
+
this.logger.info(JSON.stringify(row));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|