@adaas/a-utils 0.1.28 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { A_Component, A_Context, A_Error, A_Inject, A_Scope } from "@adaas/a-concept";
2
2
  import { A_Config } from "../A-Config/A-Config.context";
3
3
  import { A_LoggerEnvVariablesType } from "./A-Logger.env";
4
- import { A_LoggerLevel } from "./A-Logger.types";
4
+ import { A_LoggerLevel, A_LoggerColorName } from "./A-Logger.types";
5
5
  import {
6
6
  A_LOGGER_DEFAULT_SCOPE_LENGTH,
7
7
  A_LOGGER_COLORS,
@@ -9,7 +9,8 @@ import {
9
9
  A_LOGGER_TIME_FORMAT,
10
10
  A_LOGGER_FORMAT,
11
11
  A_LOGGER_ENV_KEYS,
12
- A_LOGGER_SAFE_RANDOM_COLORS
12
+ A_LOGGER_SAFE_RANDOM_COLORS,
13
+ A_LOGGER_TERMINAL
13
14
  } from "./A-Logger.constants";
14
15
 
15
16
  /**
@@ -40,7 +41,15 @@ import {
40
41
  * doSomething() {
41
42
  * this.logger.info('Processing started'); // Uses scope-name-based colors, always shows
42
43
  * this.logger.debug('Debug information'); // Only shows when debug level enabled
43
- * this.logger.info('green', 'Custom message color'); // Green message, scope stays default
44
+ *
45
+ * // Color overload methods with enum support
46
+ * this.logger.info('green', 'Success message'); // Green message, scope stays default
47
+ * this.logger.debug('gray', 'Verbose debug info'); // Gray message for less important info
48
+ * this.logger.info('brightBlue', 'Important notification');
49
+ *
50
+ * // Terminal width aware formatting - automatically wraps long lines
51
+ * this.logger.info('This is a very long message that will be automatically wrapped to fit within the terminal width while maintaining proper indentation and formatting');
52
+ *
44
53
  * this.logger.warning('Something might be wrong');
45
54
  * this.logger.error(new Error('Something failed'));
46
55
  * }
@@ -50,6 +59,13 @@ import {
50
59
  * const logger1 = new A_Logger(new A_Scope({name: 'UserService'})); // Gets consistent colors
51
60
  * const logger2 = new A_Logger(new A_Scope({name: 'UserService'})); // Gets same colors as logger1
52
61
  *
62
+ * // Available color names (A_LoggerColorName enum):
63
+ * // 'red', 'yellow', 'green', 'blue', 'cyan', 'magenta', 'gray',
64
+ * // 'brightBlue', 'brightCyan', 'brightMagenta', 'darkGray', 'lightGray',
65
+ * // 'indigo', 'violet', 'purple', 'lavender', 'skyBlue', 'steelBlue',
66
+ * // 'slateBlue', 'deepBlue', 'lightBlue', 'periwinkle', 'cornflower',
67
+ * // 'powder', 'charcoal', 'silver', 'smoke', 'slate'
68
+ *
53
69
  * // Configuration via environment variables or A_Config (overrides automatic selection)
54
70
  * process.env.A_LOGGER_DEFAULT_SCOPE_COLOR = 'magenta';
55
71
  * process.env.A_LOGGER_DEFAULT_LOG_COLOR = 'green';
@@ -93,9 +109,21 @@ export class A_Logger extends A_Component {
93
109
  */
94
110
  private readonly DEFAULT_LOG_COLOR: keyof typeof A_LOGGER_COLORS;
95
111
 
112
+ /**
113
+ * Current terminal width for responsive formatting
114
+ * Automatically detected or falls back to default values
115
+ */
116
+ private readonly TERMINAL_WIDTH: number;
117
+
118
+ /**
119
+ * Maximum content width based on terminal size
120
+ * Used for word wrapping and line length calculations
121
+ */
122
+ private readonly MAX_CONTENT_WIDTH: number;
123
+
96
124
  // =============================================
97
125
  // Constructor and Initialization
98
- // =============================================
126
+ // =============================
99
127
 
100
128
  /**
101
129
  * Initialize A_Logger with dependency injection
@@ -126,6 +154,10 @@ export class A_Logger extends A_Component {
126
154
  this.DEFAULT_SCOPE_COLOR = complementaryColors.scopeColor;
127
155
  this.DEFAULT_LOG_COLOR = complementaryColors.logColor;
128
156
  }
157
+
158
+ // Initialize terminal width detection
159
+ this.TERMINAL_WIDTH = this.detectTerminalWidth();
160
+ this.MAX_CONTENT_WIDTH = Math.floor(this.TERMINAL_WIDTH * A_LOGGER_TERMINAL.MAX_LINE_LENGTH_RATIO);
129
161
  }
130
162
 
131
163
  // =============================================
@@ -191,8 +223,129 @@ export class A_Logger extends A_Component {
191
223
  }
192
224
 
193
225
  // =============================================
194
- // Factory Methods
226
+ // Terminal Width Detection
227
+ // =============================================
228
+
229
+ /**
230
+ * Detect current terminal width based on environment
231
+ *
232
+ * Returns appropriate width for different environments:
233
+ * - Node.js: Uses process.stdout.columns if available
234
+ * - Browser: Returns browser default width
235
+ * - Fallback: Returns default terminal width
236
+ *
237
+ * @returns Terminal width in characters
238
+ */
239
+ private detectTerminalWidth(): number {
240
+ try {
241
+ // Browser environment
242
+ if (A_Context.environment === 'browser') {
243
+ return A_LOGGER_TERMINAL.BROWSER_DEFAULT_WIDTH;
244
+ }
245
+
246
+ // Node.js environment - try to get actual terminal width
247
+ if (typeof process !== 'undefined' && process.stdout && process.stdout.columns) {
248
+ const cols = process.stdout.columns;
249
+ // Ensure minimum width for readability
250
+ return Math.max(cols, A_LOGGER_TERMINAL.MIN_WIDTH);
251
+ }
252
+
253
+ // Fallback to default width
254
+ return A_LOGGER_TERMINAL.DEFAULT_WIDTH;
255
+ } catch (error) {
256
+ // If any error occurs, fall back to default width
257
+ return A_LOGGER_TERMINAL.DEFAULT_WIDTH;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Wrap text to fit within terminal width while preserving formatting
263
+ *
264
+ * @param text - Text to wrap
265
+ * @param scopePadding - The scope padding string for alignment
266
+ * @param isFirstLine - Whether this is the first line (affects available width calculation)
267
+ * @returns Array of wrapped lines with proper indentation
268
+ */
269
+ private wrapText(text: string, scopePadding: string, isFirstLine: boolean = true): string[] {
270
+ if (A_Context.environment === 'browser') {
271
+ // In browser, don't wrap - let browser console handle it
272
+ return [text];
273
+ }
274
+
275
+ // Calculate available width for text content
276
+ // First line: terminal_width - scope_header_length (includes [scope] |time| part)
277
+ // Continuation lines: terminal_width - scope_padding - pipe_length
278
+ const scopeHeaderLength = this.formattedScope.length + 4 + this.getTime().length + 4; // [scope] |time|
279
+ const continuationIndent = `${scopePadding}${A_LOGGER_FORMAT.PIPE}`;
280
+
281
+ const firstLineMaxWidth = Math.max(this.TERMINAL_WIDTH - scopeHeaderLength - 1, 20); // -1 for space
282
+ const continuationMaxWidth = Math.max(this.TERMINAL_WIDTH - continuationIndent.length, 20);
283
+
284
+ // If text fits on first line, return as is
285
+ if (isFirstLine && text.length <= firstLineMaxWidth) {
286
+ return [text];
287
+ }
288
+
289
+ const lines: string[] = [];
290
+ const words = text.split(' ');
291
+ let currentLine = '';
292
+ let currentMaxWidth = isFirstLine ? firstLineMaxWidth : continuationMaxWidth;
293
+ let isCurrentLineFirst = isFirstLine;
294
+
295
+ for (const word of words) {
296
+ const spaceNeeded = currentLine ? 1 : 0; // Space before word
297
+ const totalLength = currentLine.length + spaceNeeded + word.length;
298
+
299
+ // If adding this word would exceed current line's max width
300
+ if (totalLength > currentMaxWidth) {
301
+ if (currentLine) {
302
+ lines.push(currentLine);
303
+ currentLine = word;
304
+ // After first line, all subsequent lines use continuation width
305
+ currentMaxWidth = continuationMaxWidth;
306
+ isCurrentLineFirst = false;
307
+ } else {
308
+ // Word itself is too long, split it
309
+ if (word.length > currentMaxWidth) {
310
+ const chunks = this.splitLongWord(word, currentMaxWidth);
311
+ lines.push(...chunks.slice(0, -1));
312
+ currentLine = chunks[chunks.length - 1];
313
+ } else {
314
+ currentLine = word;
315
+ }
316
+ currentMaxWidth = continuationMaxWidth;
317
+ isCurrentLineFirst = false;
318
+ }
319
+ } else {
320
+ currentLine += (currentLine ? ' ' : '') + word;
321
+ }
322
+ }
323
+
324
+ if (currentLine) {
325
+ lines.push(currentLine);
326
+ }
327
+
328
+ return lines.length ? lines : [text];
329
+ }
330
+
331
+ /**
332
+ * Split a long word that doesn't fit on a single line
333
+ *
334
+ * @param word - Word to split
335
+ * @param maxLength - Maximum length per chunk
336
+ * @returns Array of word chunks
337
+ */
338
+ private splitLongWord(word: string, maxLength: number): string[] {
339
+ const chunks: string[] = [];
340
+ for (let i = 0; i < word.length; i += maxLength) {
341
+ chunks.push(word.slice(i, i + maxLength));
342
+ }
343
+ return chunks;
344
+ }
345
+
195
346
  // =============================================
347
+ // Factory Methods
348
+ // =============================
196
349
 
197
350
 
198
351
 
@@ -249,14 +402,16 @@ export class A_Logger extends A_Component {
249
402
  *
250
403
  * @param messageColor - The color key to apply to the message content
251
404
  * @param args - Variable arguments to format and display
252
- * @returns Array of formatted strings ready for console output
405
+ * @returns Array of formatted strings and/or objects ready for console output
253
406
  */
254
407
  compile(
255
408
  messageColor: keyof typeof this.COLORS,
256
409
  ...args: any[]
257
- ): Array<string> {
410
+ ): Array<any> {
258
411
  const timeString = this.getTime();
259
- const scopePadding = ' '.repeat(this.scopeLength + 3);
412
+ // Calculate padding based on actual displayed scope width (STANDARD_SCOPE_LENGTH)
413
+ // Plus 3 for the brackets and space: [scope]
414
+ const scopePadding = ' '.repeat(this.STANDARD_SCOPE_LENGTH + 3);
260
415
  const isMultiArg = args.length > 1;
261
416
 
262
417
  return [
@@ -293,41 +448,100 @@ export class A_Logger extends A_Component {
293
448
  }
294
449
 
295
450
  /**
296
- * Format an object for display with proper JSON indentation
451
+ * Format an object for display with proper JSON indentation and terminal width awareness
297
452
  *
298
453
  * @param obj - The object to format
299
454
  * @param shouldAddNewline - Whether to add a newline prefix
300
455
  * @param scopePadding - The padding string for consistent alignment
301
- * @returns Formatted object string
456
+ * @returns Formatted object string or the object itself for browser environments
302
457
  */
303
- private formatObject(obj: any, shouldAddNewline: boolean, scopePadding: string): string {
458
+ private formatObject(obj: any, shouldAddNewline: boolean, scopePadding: string): any {
304
459
 
305
- // in case it's browser, just return the object as is to use native console object rendering
306
- if (A_Context.environment === 'browser')
460
+ // In case it's browser, return the object as is to use native console object rendering
461
+ // This allows the browser console to display objects with its native interactive features
462
+ if (A_Context.environment === 'browser') {
307
463
  return obj;
464
+ }
465
+
466
+ // Handle null and undefined values
467
+ if (obj === null) {
468
+ return shouldAddNewline ? `\n${scopePadding}${A_LOGGER_FORMAT.PIPE}null` : 'null';
469
+ }
470
+ if (obj === undefined) {
471
+ return shouldAddNewline ? `\n${scopePadding}${A_LOGGER_FORMAT.PIPE}undefined` : 'undefined';
472
+ }
308
473
 
309
474
  let jsonString: string;
310
475
  try {
311
476
  jsonString = JSON.stringify(obj, null, 2);
312
477
  } catch (error) {
313
- // Handle circular references
314
- const seen = new WeakSet();
315
- jsonString = JSON.stringify(obj, (key, value) => {
316
- if (typeof value === 'object' && value !== null) {
317
- if (seen.has(value)) {
318
- return '[Circular Reference]';
478
+ // Handle circular references and other JSON errors
479
+ try {
480
+ const seen = new WeakSet();
481
+ jsonString = JSON.stringify(obj, (key, value) => {
482
+ if (typeof value === 'object' && value !== null) {
483
+ if (seen.has(value)) {
484
+ return '[Circular Reference]';
485
+ }
486
+ seen.add(value);
319
487
  }
320
- seen.add(value);
488
+ return value;
489
+ }, 2);
490
+ } catch (fallbackError) {
491
+ // If all else fails, convert to string
492
+ jsonString = String(obj);
493
+ }
494
+ }
495
+
496
+ // Apply terminal width wrapping to long JSON string values
497
+ const continuationIndent = `${scopePadding}${A_LOGGER_FORMAT.PIPE}`;
498
+ const maxJsonLineWidth = this.TERMINAL_WIDTH - continuationIndent.length - 4; // -4 for JSON indentation
499
+
500
+ // Split into lines and wrap long string values
501
+ const lines = jsonString.split('\n').map(line => {
502
+ // Check if this line contains a long string value
503
+ const stringValueMatch = line.match(/^(\s*"[^"]+":\s*")([^"]+)(".*)?$/);
504
+ if (stringValueMatch && stringValueMatch[2].length > maxJsonLineWidth - stringValueMatch[1].length - (stringValueMatch[3] || '').length) {
505
+ const [, prefix, value, suffix = ''] = stringValueMatch;
506
+
507
+ // Wrap the string value if it's too long
508
+ if (value.length > maxJsonLineWidth - prefix.length - suffix.length) {
509
+ const wrappedValue = this.wrapJsonStringValue(value, maxJsonLineWidth - prefix.length - suffix.length);
510
+ return prefix + wrappedValue + suffix;
321
511
  }
322
- return value;
323
- }, 2);
512
+ }
513
+ return line;
514
+ });
515
+
516
+ const formatted = lines.join('\n' + continuationIndent);
517
+ return shouldAddNewline ? '\n' + continuationIndent + formatted : formatted;
518
+ }
519
+
520
+ /**
521
+ * Wrap a long JSON string value while preserving readability
522
+ *
523
+ * @param value - The string value to wrap
524
+ * @param maxWidth - Maximum width for the value
525
+ * @returns Wrapped string value
526
+ */
527
+ private wrapJsonStringValue(value: string, maxWidth: number): string {
528
+ if (value.length <= maxWidth) {
529
+ return value;
530
+ }
531
+
532
+ // For JSON string values, truncate with ellipsis to maintain JSON validity
533
+ // This prevents the JSON from becoming unreadable due to excessive wrapping
534
+ // while still showing the most important part of the string
535
+ if (maxWidth > 6) { // Ensure we have room for ellipsis
536
+ return value.substring(0, maxWidth - 3) + '...';
537
+ } else {
538
+ // If maxWidth is very small, just return truncated value
539
+ return value.substring(0, Math.max(1, maxWidth));
324
540
  }
325
- const formatted = jsonString.replace(/\n/g, '\n' + `${scopePadding}${A_LOGGER_FORMAT.PIPE}`);
326
- return shouldAddNewline ? '\n' + `${scopePadding}${A_LOGGER_FORMAT.PIPE}` + formatted : formatted;
327
541
  }
328
542
 
329
543
  /**
330
- * Format a string for display with proper indentation
544
+ * Format a string for display with proper indentation and terminal width wrapping
331
545
  *
332
546
  * @param str - The string to format
333
547
  * @param shouldAddNewline - Whether to add a newline prefix
@@ -335,8 +549,32 @@ export class A_Logger extends A_Component {
335
549
  * @returns Formatted string
336
550
  */
337
551
  private formatString(str: string, shouldAddNewline: boolean, scopePadding: string): string {
338
- const prefix = shouldAddNewline ? '\n' : '';
339
- return (prefix + str).replace(/\n/g, '\n' + `${scopePadding}${A_LOGGER_FORMAT.PIPE}`);
552
+ // In browser environment, keep simple formatting
553
+ if (A_Context.environment === 'browser') {
554
+ const prefix = shouldAddNewline ? '\n' : '';
555
+ return (prefix + str).replace(/\n/g, '\n' + `${scopePadding}${A_LOGGER_FORMAT.PIPE}`);
556
+ }
557
+
558
+ // For terminal, apply intelligent text wrapping
559
+ const wrappedLines = this.wrapText(str, scopePadding, !shouldAddNewline);
560
+ const continuationIndent = `${scopePadding}${A_LOGGER_FORMAT.PIPE}`;
561
+
562
+ // Format the wrapped lines with proper indentation
563
+ const formattedLines = wrappedLines.map((line, index) => {
564
+ if (index === 0 && !shouldAddNewline) {
565
+ // First line in inline mode (no newline prefix)
566
+ return line;
567
+ } else {
568
+ // Continuation lines or first line with newline prefix
569
+ return `${continuationIndent}${line}`;
570
+ }
571
+ });
572
+
573
+ if (shouldAddNewline) {
574
+ return '\n' + formattedLines.join('\n');
575
+ } else {
576
+ return formattedLines.join('\n');
577
+ }
340
578
  }
341
579
 
342
580
  // =============================================
@@ -391,7 +629,7 @@ export class A_Logger extends A_Component {
391
629
  * Note: The scope color always remains the instance's default scope color,
392
630
  * only the message content color changes when explicitly specified.
393
631
  *
394
- * @param color - Optional color key or the first message argument
632
+ * @param color - Optional color name from A_LoggerColorName enum or the first message argument
395
633
  * @param args - Additional arguments to log
396
634
  *
397
635
  * @example
@@ -401,7 +639,7 @@ export class A_Logger extends A_Component {
401
639
  * logger.debug('Processing user:', { id: 1, name: 'John' });
402
640
  * ```
403
641
  */
404
- debug(color: keyof typeof this.COLORS, ...args: any[]): void;
642
+ debug(color: A_LoggerColorName, ...args: any[]): void;
405
643
  debug(...args: any[]): void;
406
644
  debug(param1: any, ...args: any[]): void {
407
645
  if (!this.shouldLog('debug')) return;
@@ -426,7 +664,7 @@ export class A_Logger extends A_Component {
426
664
  * Note: The scope color always remains the instance's default scope color,
427
665
  * only the message content color changes when explicitly specified.
428
666
  *
429
- * @param color - Optional color key or the first message argument
667
+ * @param color - Optional color name from A_LoggerColorName enum or the first message argument
430
668
  * @param args - Additional arguments to log
431
669
  *
432
670
  * @example
@@ -436,7 +674,7 @@ export class A_Logger extends A_Component {
436
674
  * logger.info('Processing user:', { id: 1, name: 'John' });
437
675
  * ```
438
676
  */
439
- info(color: keyof typeof this.COLORS, ...args: any[]): void;
677
+ info(color: A_LoggerColorName, ...args: any[]): void;
440
678
  info(...args: any[]): void;
441
679
  info(param1: any, ...args: any[]): void {
442
680
  if (!this.shouldLog('info')) return;
@@ -454,10 +692,10 @@ export class A_Logger extends A_Component {
454
692
  * Legacy log method (kept for backward compatibility)
455
693
  * @deprecated Use info() method instead
456
694
  *
457
- * @param color - Optional color key or the first message argument
695
+ * @param color - Optional color name from A_LoggerColorName enum or the first message argument
458
696
  * @param args - Additional arguments to log
459
697
  */
460
- log(color: keyof typeof this.COLORS, ...args: any[]): void;
698
+ log(color: A_LoggerColorName, ...args: any[]): void;
461
699
  log(...args: any[]): void;
462
700
  log(param1: any, ...args: any[]): void {
463
701
  // Delegate to info method for backward compatibility
@@ -515,7 +753,7 @@ export class A_Logger extends A_Component {
515
753
  */
516
754
  protected log_A_Error(error: A_Error): void {
517
755
  const time = this.getTime();
518
- const scopePadding = ' '.repeat(this.scopeLength + 3);
756
+ const scopePadding = ' '.repeat(this.STANDARD_SCOPE_LENGTH + 3);
519
757
 
520
758
  console.log(`\x1b[31m[${this.formattedScope}] |${time}| ERROR ${error.code}
521
759
  ${scopePadding}| ${error.message}
@@ -537,59 +775,136 @@ ${scopePadding}|-------------------------------
537
775
  /**
538
776
  * Format A_Error instances for inline display within compiled messages
539
777
  *
540
- * Provides detailed formatting for A_Error objects including:
541
- * - Error code and message
542
- * - Description and stack trace
543
- * - Original error information (if wrapped)
778
+ * Provides detailed formatting for A_Error objects with:
779
+ * - Error code, message, and description
780
+ * - Original error information FIRST (better UX for debugging)
781
+ * - Stack traces with terminal width awareness
544
782
  * - Documentation links (if available)
783
+ * - Consistent formatting with rest of logger
545
784
  *
546
785
  * @param error - The A_Error instance to format
547
786
  * @returns Formatted string ready for display
548
787
  */
549
788
  protected compile_A_Error(error: A_Error): string {
550
- const scopePadding = ' '.repeat(this.scopeLength + 3);
789
+ const continuationIndent = `${' '.repeat(this.STANDARD_SCOPE_LENGTH + 3)}${A_LOGGER_FORMAT.PIPE}`;
790
+ const separator = `${continuationIndent}-------------------------------`;
791
+ const lines: string[] = [];
792
+
793
+ // Add error header
794
+ lines.push('');
795
+ lines.push(separator);
796
+ lines.push(`${continuationIndent}A_ERROR: ${error.code}`);
797
+ lines.push(separator);
798
+
799
+ // Format and wrap error message and description
800
+ const errorMessage = this.wrapText(`Message: ${error.message}`, continuationIndent, false);
801
+ const errorDescription = this.wrapText(`Description: ${error.description}`, continuationIndent, false);
802
+
803
+ lines.push(...errorMessage.map(line => `${continuationIndent}${line}`));
804
+ lines.push(...errorDescription.map(line => `${continuationIndent}${line}`));
805
+
806
+ // Show original error FIRST (more important for debugging)
807
+ if (error.originalError) {
808
+ lines.push(separator);
809
+ lines.push(`${continuationIndent}ORIGINAL ERROR:`);
810
+ lines.push(separator);
811
+
812
+ const originalMessage = this.wrapText(`${error.originalError.name}: ${error.originalError.message}`, continuationIndent, false);
813
+ lines.push(...originalMessage.map(line => `${continuationIndent}${line}`));
814
+
815
+ if (error.originalError.stack) {
816
+ lines.push(`${continuationIndent}Stack trace:`);
817
+ const stackLines = this.formatStackTrace(error.originalError.stack, continuationIndent);
818
+ lines.push(...stackLines);
819
+ }
820
+ }
821
+
822
+ // Then show A_Error stack trace
823
+ if (error.stack) {
824
+ lines.push(separator);
825
+ lines.push(`${continuationIndent}A_ERROR STACK:`);
826
+ lines.push(separator);
827
+ const stackLines = this.formatStackTrace(error.stack, continuationIndent);
828
+ lines.push(...stackLines);
829
+ }
830
+
831
+ // Documentation link at the end
832
+ if (error.link) {
833
+ lines.push(separator);
834
+ const linkText = this.wrapText(`Documentation: ${error.link}`, continuationIndent, false);
835
+ lines.push(...linkText.map(line => `${continuationIndent}${line}`));
836
+ }
837
+
838
+ lines.push(separator);
839
+
840
+ return lines.join('\n');
841
+ }
551
842
 
552
- return '\n' +
553
- `${scopePadding}|-------------------------------` +
554
- '\n' +
555
- `${scopePadding}| Error: | ${error.code}
556
- ${scopePadding}|-------------------------------
557
- ${scopePadding}|${' '.repeat(10)}| ${error.message}
558
- ${scopePadding}|${' '.repeat(10)}| ${error.description}
559
- ${scopePadding}|-------------------------------
560
- ${scopePadding}| ${error.stack?.split('\n').map((line, index) => index === 0 ? line : `${scopePadding}| ${line}`).join('\n') || 'No stack trace'}
561
- ${scopePadding}|-------------------------------`
562
- +
563
- (error.originalError ? `${scopePadding}| Wrapped From ${error.originalError.message}
564
- ${scopePadding}|-------------------------------
565
- ${scopePadding}| ${error.originalError.stack?.split('\n').map((line, index) => index === 0 ? line : `${scopePadding}| ${line}`).join('\n') || 'No stack trace'}
566
- ${scopePadding}|-------------------------------` : '')
567
- +
568
- (error.link ? `${scopePadding}| Read in docs: ${error.link}
569
- ${scopePadding}|-------------------------------` : '');
843
+ /**
844
+ * Format stack trace with proper terminal width wrapping and indentation
845
+ *
846
+ * @param stack - The stack trace string
847
+ * @param baseIndent - Base indentation for continuation lines
848
+ * @returns Array of formatted stack trace lines
849
+ */
850
+ private formatStackTrace(stack: string, baseIndent: string): string[] {
851
+ const stackLines = stack.split('\n');
852
+ const formatted: string[] = [];
853
+
854
+ stackLines.forEach((line, index) => {
855
+ if (line.trim()) {
856
+ // Add extra indentation for stack trace lines
857
+ const stackIndent = index === 0 ? baseIndent : `${baseIndent} `;
858
+ const wrappedLines = this.wrapText(line.trim(), stackIndent, false);
859
+ formatted.push(...wrappedLines.map(wrappedLine =>
860
+ index === 0 && wrappedLine === wrappedLines[0]
861
+ ? `${baseIndent}${wrappedLine}`
862
+ : `${baseIndent} ${wrappedLine}`
863
+ ));
864
+ }
865
+ });
866
+
867
+ return formatted;
570
868
  }
571
869
 
572
870
  /**
573
871
  * Format standard Error instances for inline display within compiled messages
574
872
  *
575
- * Converts standard JavaScript Error objects into a readable JSON format
576
- * with proper indentation and stack trace formatting
873
+ * Provides clean, readable formatting for standard JavaScript errors with:
874
+ * - Terminal width aware message wrapping
875
+ * - Properly formatted stack traces
876
+ * - Consistent indentation with rest of logger
577
877
  *
578
878
  * @param error - The Error instance to format
579
879
  * @returns Formatted string ready for display
580
880
  */
581
881
  protected compile_Error(error: Error): string {
582
- const scopePadding = ' '.repeat(this.scopeLength + 3);
583
-
584
- return JSON.stringify({
585
- name: error.name,
586
- message: error.message,
587
- stack: error.stack?.split('\n')
588
- .map((line, index) => index === 0 ? line : `${scopePadding}| ${line}`)
589
- .join('\n')
590
- }, null, 2)
591
- .replace(/\n/g, '\n' + `${scopePadding}| `)
592
- .replace(/\\n/g, '\n');
882
+ const continuationIndent = `${' '.repeat(this.STANDARD_SCOPE_LENGTH + 3)}${A_LOGGER_FORMAT.PIPE}`;
883
+ const separator = `${continuationIndent}-------------------------------`;
884
+ const lines: string[] = [];
885
+
886
+ // Add error header
887
+ lines.push('');
888
+ lines.push(separator);
889
+ lines.push(`${continuationIndent}ERROR: ${error.name}`);
890
+ lines.push(separator);
891
+
892
+ // Format and wrap error message
893
+ const errorMessage = this.wrapText(`Message: ${error.message}`, continuationIndent, false);
894
+ lines.push(...errorMessage.map(line => `${continuationIndent}${line}`));
895
+
896
+ // Format stack trace if available
897
+ if (error.stack) {
898
+ lines.push(separator);
899
+ lines.push(`${continuationIndent}STACK TRACE:`);
900
+ lines.push(separator);
901
+ const stackLines = this.formatStackTrace(error.stack, continuationIndent);
902
+ lines.push(...stackLines);
903
+ }
904
+
905
+ lines.push(separator);
906
+
907
+ return lines.join('\n');
593
908
  }
594
909
 
595
910
  // =============================================
@@ -98,6 +98,19 @@ export const A_LOGGER_FORMAT = {
98
98
  PIPE: '| '
99
99
  } as const;
100
100
 
101
+ /**
102
+ * Environment variable keys
103
+ */
104
+ /**
105
+ * Terminal width configuration
106
+ */
107
+ export const A_LOGGER_TERMINAL = {
108
+ DEFAULT_WIDTH: 80, // Default terminal width when can't be detected
109
+ MIN_WIDTH: 40, // Minimum width for formatted output
110
+ MAX_LINE_LENGTH_RATIO: 0.8, // Use 80% of terminal width for content
111
+ BROWSER_DEFAULT_WIDTH: 120 // Default width for browser console
112
+ } as const;
113
+
101
114
  /**
102
115
  * Environment variable keys
103
116
  */
@@ -1,3 +1,14 @@
1
1
 
2
2
 
3
- export type A_LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'all';
3
+ export type A_LoggerLevel = 'debug' | 'info' | 'warn' | 'error' | 'all';
4
+
5
+ /**
6
+ * Available color names for the logger
7
+ * Can be used as first parameter in logging methods to specify message color
8
+ */
9
+ export type A_LoggerColorName =
10
+ | 'red' | 'yellow' | 'green' | 'blue' | 'cyan' | 'magenta' | 'gray'
11
+ | 'brightBlue' | 'brightCyan' | 'brightMagenta' | 'darkGray' | 'lightGray'
12
+ | 'indigo' | 'violet' | 'purple' | 'lavender' | 'skyBlue' | 'steelBlue'
13
+ | 'slateBlue' | 'deepBlue' | 'lightBlue' | 'periwinkle' | 'cornflower'
14
+ | 'powder' | 'charcoal' | 'silver' | 'smoke' | 'slate';