@archznn/xavva 3.1.2 → 3.2.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.
Files changed (80) hide show
  1. package/README.md +221 -12
  2. package/package.json +3 -2
  3. package/src/commands/AuditCommand.ts +12 -10
  4. package/src/commands/BuildCommand.ts +9 -7
  5. package/src/commands/ChangelogCommand.ts +5 -5
  6. package/src/commands/CleanCommand.ts +242 -0
  7. package/src/commands/CompletionCommand.ts +7 -7
  8. package/src/commands/DbCommand.ts +43 -14
  9. package/src/commands/DeployCommand.ts +252 -229
  10. package/src/commands/DepsCommand.ts +174 -174
  11. package/src/commands/DockerCommand.ts +35 -4
  12. package/src/commands/DoctorCommand.ts +252 -239
  13. package/src/commands/EncodingCommand.ts +26 -19
  14. package/src/commands/HealthCommand.ts +7 -7
  15. package/src/commands/HelpCommand.ts +34 -14
  16. package/src/commands/HistoryCommand.ts +5 -5
  17. package/src/commands/HttpCommand.ts +27 -1
  18. package/src/commands/IdeCommand.ts +313 -0
  19. package/src/commands/InitCommand.ts +26 -25
  20. package/src/commands/LogsCommand.ts +8 -6
  21. package/src/commands/ProfilesCommand.ts +6 -6
  22. package/src/commands/RedoCommand.ts +2 -2
  23. package/src/commands/RunCommand.ts +64 -24
  24. package/src/commands/StartCommand.ts +9 -7
  25. package/src/commands/TestCommand.ts +25 -1
  26. package/src/commands/TomcatCommand.ts +232 -88
  27. package/src/config/versions.ts +111 -9
  28. package/src/di/container.ts +239 -105
  29. package/src/errors/ErrorHandler.ts +23 -19
  30. package/src/errors/errorMessages.ts +235 -0
  31. package/src/index.ts +20 -6
  32. package/src/logging/FileLogger.ts +235 -0
  33. package/src/logging/Logger.ts +545 -0
  34. package/src/logging/OperationLogger.ts +296 -0
  35. package/src/logging/ProgressLogger.ts +187 -0
  36. package/src/logging/TableLogger.ts +246 -0
  37. package/src/logging/colors.ts +167 -0
  38. package/src/logging/constants.ts +176 -0
  39. package/src/logging/formatters.ts +337 -0
  40. package/src/logging/index.ts +93 -0
  41. package/src/logging/types.ts +64 -0
  42. package/src/plugins/PluginManager.ts +325 -0
  43. package/src/plugins/types.ts +82 -0
  44. package/src/services/AuditService.ts +5 -3
  45. package/src/services/BuildService.ts +15 -17
  46. package/src/services/DashboardService.ts +14 -3
  47. package/src/services/DbService.ts +35 -34
  48. package/src/services/DependencyAnalyzerService.ts +18 -18
  49. package/src/services/DependencyCacheService.ts +303 -0
  50. package/src/services/DeployWatcher.ts +127 -23
  51. package/src/services/DockerService.ts +3 -3
  52. package/src/services/EmbeddedTomcatService.ts +13 -12
  53. package/src/services/FileWatcher.ts +15 -7
  54. package/src/services/HttpService.ts +5 -5
  55. package/src/services/LogAnalyzer.ts +26 -22
  56. package/src/services/PerformanceProfiler.ts +267 -0
  57. package/src/services/ProjectService.ts +3 -0
  58. package/src/services/TestService.ts +3 -3
  59. package/src/services/TomcatService.ts +46 -25
  60. package/src/services/tomcat/TomcatBackupManager.ts +330 -0
  61. package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
  62. package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
  63. package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
  64. package/src/services/tomcat/TomcatDownloadService.ts +335 -0
  65. package/src/services/tomcat/TomcatInstallerService.ts +474 -0
  66. package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
  67. package/src/services/tomcat/index.ts +36 -0
  68. package/src/services/tomcat/types.ts +120 -0
  69. package/src/types/args.ts +68 -1
  70. package/src/types/configSchema.ts +174 -0
  71. package/src/utils/ChangelogGenerator.ts +11 -11
  72. package/src/utils/LoggerLevel.ts +44 -20
  73. package/src/utils/ProgressBar.ts +87 -46
  74. package/src/utils/argsParser.ts +260 -0
  75. package/src/utils/config.ts +340 -189
  76. package/src/utils/constants.ts +87 -9
  77. package/src/utils/dryRun.ts +192 -0
  78. package/src/utils/processManager.ts +23 -7
  79. package/src/utils/security.ts +293 -0
  80. package/src/utils/ui.ts +299 -428
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Cores e estilos ANSI para o sistema de logging
3
+ * Paleta suave e moderna
4
+ */
5
+
6
+ export const Colors = {
7
+ // Reset
8
+ reset: '\x1b[0m',
9
+
10
+ // Estilos
11
+ bold: '\x1b[1m',
12
+ dim: '\x1b[2m',
13
+ italic: '\x1b[3m',
14
+ underline: '\x1b[4m',
15
+
16
+ // Cores principais
17
+ primary: '\x1b[36m', // Cyan
18
+ primaryBright: '\x1b[96m', // Bright Cyan
19
+ secondary: '\x1b[35m', // Magenta
20
+
21
+ // Estados
22
+ success: '\x1b[32m', // Green
23
+ successBright: '\x1b[92m', // Bright Green
24
+ warning: '\x1b[33m', // Yellow
25
+ error: '\x1b[31m', // Red
26
+ info: '\x1b[34m', // Blue
27
+
28
+ // Neutros
29
+ white: '\x1b[37m',
30
+ gray: '\x1b[90m',
31
+ darkGray: '\x1b[38;5;240m',
32
+ lightGray: '\x1b[38;5;250m',
33
+
34
+ // Backgrounds
35
+ bgRed: '\x1b[41m',
36
+ bgGreen: '\x1b[42m',
37
+ bgYellow: '\x1b[43m',
38
+ bgBlue: '\x1b[44m',
39
+ bgMagenta: '\x1b[45m',
40
+ bgCyan: '\x1b[46m',
41
+ bgWhite: '\x1b[47m',
42
+ } as const;
43
+
44
+ export type ColorKey = keyof typeof Colors;
45
+
46
+ /**
47
+ * Aplica cor ao texto
48
+ */
49
+ export function colorize(text: string, color: ColorKey): string {
50
+ return `${Colors[color]}${text}${Colors.reset}`;
51
+ }
52
+
53
+ /**
54
+ * Remove códigos ANSI do texto
55
+ */
56
+ export function stripAnsi(text: string): string {
57
+ return text.replace(/\x1b\[\d+m/g, '');
58
+ }
59
+
60
+ /**
61
+ * Calcula largura visual do texto (ignorando ANSI)
62
+ */
63
+ export function visualWidth(text: string): number {
64
+ return stripAnsi(text).length;
65
+ }
66
+
67
+ /**
68
+ * Preenche string até o tamanho especificado, considerando ANSI codes
69
+ */
70
+ export function padText(text: string, width: number, align: 'left' | 'right' | 'center' = 'left'): string {
71
+ const plain = stripAnsi(text);
72
+ const diff = width - plain.length;
73
+
74
+ if (diff <= 0) return text;
75
+
76
+ const padding = ' '.repeat(diff);
77
+
78
+ switch (align) {
79
+ case 'right':
80
+ return padding + text;
81
+ case 'center':
82
+ const left = Math.floor(diff / 2);
83
+ const right = diff - left;
84
+ return ' '.repeat(left) + text + ' '.repeat(right);
85
+ default:
86
+ return text + padding;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Trunca texto mantendo códigos ANSI
92
+ */
93
+ export function truncateText(text: string, maxLength: number, suffix = '...'): string {
94
+ const plain = stripAnsi(text);
95
+ if (plain.length <= maxLength) return text;
96
+
97
+ // Extrai todos os códigos ANSI para reconstruir
98
+ const ansiCodes: string[] = [];
99
+ let match;
100
+ const ansiRegex = /\x1b\[\d+m/g;
101
+ while ((match = ansiRegex.exec(text)) !== null) {
102
+ ansiCodes.push(match[0]);
103
+ }
104
+
105
+ // Trunca o texto plano
106
+ const truncatedPlain = plain.slice(0, maxLength - suffix.length) + suffix;
107
+
108
+ // Reaplica os códigos ANSI (simplificado - apenas os que aparecem antes do corte)
109
+ return truncatedPlain; // Versão simplificada sem ANSI
110
+ }
111
+
112
+ /**
113
+ * Ícones semânticos para diferentes tipos de log
114
+ * Usando caracteres ASCII/Unicode amplamente suportados
115
+ */
116
+ export const Icons = {
117
+ success: '✓',
118
+ error: '✗',
119
+ warning: '!',
120
+ info: 'i',
121
+ arrow: '->',
122
+ bullet: '*',
123
+ diamond: '>',
124
+ circle: 'o',
125
+ hotswap: '~',
126
+ spinner: '|',
127
+ pending: 'o',
128
+ running: '>',
129
+ ready: '✓',
130
+ sync: '<>',
131
+ build: '[b]',
132
+ deploy: '[d]',
133
+ server: '[s]',
134
+ database: '[db]',
135
+ search: '[?]',
136
+ time: '[t]',
137
+ file: '[f]',
138
+ folder: '[dir]',
139
+ link: '->',
140
+ star: '*',
141
+ check: '✓',
142
+ cross: '✗',
143
+ question: '?',
144
+ exclamation: '!',
145
+ } as const;
146
+
147
+ export type IconKey = keyof typeof Icons;
148
+
149
+ /**
150
+ * Obtém ícone com cor aplicada
151
+ */
152
+ export function getIcon(name: IconKey, color?: ColorKey): string {
153
+ const icon = Icons[name] || Icons.info;
154
+ if (color) {
155
+ return colorize(icon, color);
156
+ }
157
+ return icon;
158
+ }
159
+
160
+ /**
161
+ * Verifica se o terminal suporta cores
162
+ */
163
+ export function supportsColor(): boolean {
164
+ if (process.env.NO_COLOR) return false;
165
+ if (process.env.FORCE_COLOR) return true;
166
+ return process.stdout.isTTY === true;
167
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Constantes do sistema de logging
3
+ */
4
+
5
+ import type { LoggerConfig } from './types';
6
+
7
+ // Configurações padrão
8
+ export const DEFAULT_CONFIG: LoggerConfig = {
9
+ level: 'info',
10
+ mode: 'pretty',
11
+ timestamps: false,
12
+ colors: true,
13
+ icons: true,
14
+ fileLogging: false,
15
+ logDir: '.xavva/logs',
16
+ maxLogFiles: 7,
17
+ rateLimitWindowMs: 10000, // 10 segundos
18
+ maxDuplicateLogs: 5,
19
+ };
20
+
21
+ // Níveis de log com valores numéricos para comparação
22
+ export const LOG_LEVELS = {
23
+ silent: 0,
24
+ error: 1,
25
+ warn: 2,
26
+ info: 3,
27
+ success: 3,
28
+ debug: 4,
29
+ trace: 5,
30
+ silly: 6,
31
+ } as const;
32
+
33
+ // Cores associadas a cada nível de log
34
+ export const LEVEL_COLORS = {
35
+ error: 'error' as const,
36
+ warn: 'warning' as const,
37
+ info: 'info' as const,
38
+ success: 'success' as const,
39
+ debug: 'gray' as const,
40
+ trace: 'darkGray' as const,
41
+ silly: 'darkGray' as const,
42
+ silent: 'reset' as const,
43
+ };
44
+
45
+ // Ícones associados a cada nível de log
46
+ export const LEVEL_ICONS = {
47
+ error: 'error',
48
+ warn: 'warning',
49
+ info: 'info',
50
+ success: 'success',
51
+ debug: 'bullet',
52
+ trace: 'circle',
53
+ silly: 'circle',
54
+ silent: '',
55
+ } as const;
56
+
57
+ // Configurações de layout
58
+ export const LAYOUT = {
59
+ // Larguras de colunas para alinhamento
60
+ columns: {
61
+ timestamp: 12,
62
+ level: 8,
63
+ name: 12,
64
+ status: 10,
65
+ info: 30,
66
+ },
67
+
68
+ // Caracteres de borda
69
+ borders: {
70
+ horizontal: '─',
71
+ vertical: '│',
72
+ topLeft: '┌',
73
+ topRight: '┐',
74
+ bottomLeft: '└',
75
+ bottomRight: '┘',
76
+ leftT: '├',
77
+ rightT: '┤',
78
+ cross: '┼',
79
+ },
80
+
81
+ // Identação
82
+ indentSize: 2,
83
+ maxIndent: 10,
84
+
85
+ // Truncamento
86
+ maxMessageLength: 500,
87
+ maxLineLength: 120,
88
+ };
89
+
90
+ // Padrões de noise para filtragem
91
+ export const NOISE_PATTERNS = {
92
+ // Maven/Gradle noise
93
+ build: [
94
+ /^\[INFO\]\s+Scanning for projects/,
95
+ /^\[INFO\]\s+Building /,
96
+ /^\[INFO\]\s+---\s+.*\s+---$/,
97
+ /^\[INFO\]\s+T+E+\s*$/,
98
+ /^\[INFO\]\s+BUILD\s+SUCCESS/i,
99
+ /^\[INFO\]\s+Total time:/,
100
+ /^\[INFO\]\s+Finished at:/,
101
+ /^\[INFO\]\s+Final Memory:/,
102
+ ],
103
+
104
+ // Tomcat noise
105
+ tomcat: [
106
+ /^Using CATALINA_/,
107
+ /^Using JRE_HOME/,
108
+ /^Using CLASSPATH/,
109
+ /^Using CATALINA_OPTS/,
110
+ /^NOTE: Picked up JDK_JAVA_OPTIONS/,
111
+ /^HOTSWAP AGENT:.*Plugin.*initialized in ClassLoader/,
112
+ /^HOTSWAP AGENT:.*Registering directory/,
113
+ /^HOTSWAP AGENT:.*WARNING.*TreeWatcherNIO.*Unable to watch/,
114
+ /^HOTSWAP AGENT:.*INFO.*TreeWatcherNIO/,
115
+ /^HOTSWAP AGENT:.*INFO.*PluginRegistry.*Discovered plugins/,
116
+ /^HOTSWAP AGENT:.*INFO.*HotswapAgent.*Loading Hotswap agent/,
117
+ /^HOTSWAP AGENT:.*INFO.*TomcatPlugin.*Tomcat plugin initialized/,
118
+ /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*VersionLoggerListener/,
119
+ /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*AprLifecycleListener/,
120
+ ],
121
+
122
+ // System noise
123
+ system: [
124
+ 'Using CATALINA_',
125
+ 'Using JRE_HOME',
126
+ 'Using CLASSPATH',
127
+ 'Scanning for projects...',
128
+ 'Building ',
129
+ '--- ',
130
+ '+++ ',
131
+ 'SLF4J: ',
132
+ 'Discovered plugins:',
133
+ 'enhanced with plugin initialization',
134
+ 'Hotswap ready',
135
+ 'autoHotswap.delay',
136
+ 'watchResources=false',
137
+ 'TreeWatcherNIO',
138
+ 'HOTSWAP AGENT',
139
+ 'org.hotswap.agent',
140
+ 'org.glassfish.jersey',
141
+ 'org.apache.catalina',
142
+ 'org.apache.jasper',
143
+ ],
144
+ };
145
+
146
+ // Padrões essenciais que sempre devem ser mostrados
147
+ export const ESSENTIAL_PATTERNS = [
148
+ 'SEVERE',
149
+ 'ERROR',
150
+ 'Exception',
151
+ 'Caused by',
152
+ 'Server startup in',
153
+ 'HOTSWAP AGENT:.*RELOAD',
154
+ 'BUILD FAILURE',
155
+ 'Compilation failure',
156
+ ];
157
+
158
+ // Spinner frames - usando ASCII para melhor compatibilidade com Windows
159
+ export const SPINNER_FRAMES = {
160
+ default: ['|', '/', '-', '\\'],
161
+ dots: ['.', '..', '...', '....', '.....', '....', '...', '..'],
162
+ line: ['-', '\\', '|', '/'],
163
+ arrow: ['>', '->', '-->', '--->', '---->', '--->', '-->', '->'],
164
+ pulse: ['#', '##', '###', '####', '#####', '####', '###', '##'],
165
+ };
166
+
167
+ // Intervalo do spinner em ms
168
+ export const SPINNER_INTERVAL = 80;
169
+
170
+ // Configurações de arquivo de log
171
+ export const FILE_LOG = {
172
+ extension: '.log',
173
+ dateFormat: 'YYYY-MM-DD',
174
+ maxSize: 10 * 1024 * 1024, // 10MB
175
+ maxFiles: 7,
176
+ };
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Formatadores de saída para diferentes modos de log
3
+ */
4
+
5
+ import type { LogEntry, LogOutputMode, LoggerConfig } from './types';
6
+ import { Colors, stripAnsi, visualWidth, padText } from './colors';
7
+ import { LEVEL_COLORS, LAYOUT } from './constants';
8
+
9
+ /**
10
+ * Formata timestamp
11
+ */
12
+ function formatTimestamp(date: Date, config: LoggerConfig): string {
13
+ if (!config.timestamps) return '';
14
+
15
+ const now = new Date();
16
+ const hours = date.getHours().toString().padStart(2, '0');
17
+ const minutes = date.getMinutes().toString().padStart(2, '0');
18
+ const seconds = date.getSeconds().toString().padStart(2, '0');
19
+
20
+ // Se for o mesmo dia, mostra apenas hora
21
+ if (now.toDateString() === date.toDateString()) {
22
+ return `${Colors.gray}${hours}:${minutes}:${seconds}${Colors.reset} `;
23
+ }
24
+
25
+ // Caso contrário, mostra data completa
26
+ const day = date.getDate().toString().padStart(2, '0');
27
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
28
+ return `${Colors.gray}${day}/${month} ${hours}:${minutes}:${seconds}${Colors.reset} `;
29
+ }
30
+
31
+ /**
32
+ * Formata nível de log
33
+ */
34
+ function formatLevel(level: string, config: LoggerConfig): string {
35
+ if (!config.colors) {
36
+ return `[${level.toUpperCase()}] `;
37
+ }
38
+
39
+ const colorKey = LEVEL_COLORS[level as keyof typeof LEVEL_COLORS] || 'reset';
40
+ const color = Colors[colorKey];
41
+ const padded = level.toUpperCase().padStart(5);
42
+
43
+ return `${color}${padded}${Colors.reset} `;
44
+ }
45
+
46
+ /**
47
+ * Formata indentação
48
+ */
49
+ function formatIndent(indent: number): string {
50
+ const size = Math.min(indent, LAYOUT.maxIndent) * LAYOUT.indentSize;
51
+ return ' '.repeat(size);
52
+ }
53
+
54
+ /**
55
+ * Formata metadados
56
+ */
57
+ function formatMetadata(metadata: Record<string, unknown> | undefined, indent: number): string {
58
+ if (!metadata || Object.keys(metadata).length === 0) return '';
59
+
60
+ const prefix = '\n' + formatIndent(indent + 1);
61
+ const lines: string[] = [];
62
+
63
+ for (const [key, value] of Object.entries(metadata)) {
64
+ let formattedValue: string;
65
+
66
+ if (value === null) {
67
+ formattedValue = 'null';
68
+ } else if (value === undefined) {
69
+ formattedValue = 'undefined';
70
+ } else if (typeof value === 'object') {
71
+ try {
72
+ formattedValue = JSON.stringify(value);
73
+ } catch {
74
+ formattedValue = '[Object]';
75
+ }
76
+ } else if (typeof value === 'string' && value.length > 100) {
77
+ formattedValue = value.slice(0, 97) + '...';
78
+ } else {
79
+ formattedValue = String(value);
80
+ }
81
+
82
+ const keyStr = `${Colors.dim}${key}:${Colors.reset}`;
83
+ const valueStr = `${Colors.white}${formattedValue}${Colors.reset}`;
84
+ lines.push(`${prefix}${keyStr} ${valueStr}`);
85
+ }
86
+
87
+ return lines.join('');
88
+ }
89
+
90
+ /**
91
+ * Formata trace ID
92
+ */
93
+ function formatTraceId(traceId: string | undefined): string {
94
+ if (!traceId) return '';
95
+ return `${Colors.darkGray}[${traceId}]${Colors.reset} `;
96
+ }
97
+
98
+ /**
99
+ * Formata mensagem completa no modo pretty
100
+ */
101
+ export function formatPretty(entry: LogEntry, config: LoggerConfig): string {
102
+ const parts: string[] = [];
103
+
104
+ // Timestamp
105
+ parts.push(formatTimestamp(entry.timestamp, config));
106
+
107
+ // Trace ID (só mostra em modo debug/trace ou quando explicitamente configurado)
108
+ if (config.level === 'debug' || config.level === 'trace' || config.level === 'silly') {
109
+ parts.push(formatTraceId(entry.context?.traceId));
110
+ }
111
+
112
+ // Indentação
113
+ parts.push(formatIndent(entry.context?.indent || 0));
114
+
115
+ // Nível (apenas para debug/trace, outros têm ícones)
116
+ if (['debug', 'trace', 'silly'].includes(entry.level)) {
117
+ parts.push(formatLevel(entry.level, config));
118
+ }
119
+
120
+ // Mensagem
121
+ parts.push(entry.message);
122
+
123
+ // Metadados
124
+ if (entry.metadata) {
125
+ parts.push(formatMetadata(entry.metadata, entry.context?.indent || 0));
126
+ }
127
+
128
+ return parts.join('');
129
+ }
130
+
131
+ /**
132
+ * Formata mensagem no modo minimal
133
+ */
134
+ export function formatMinimal(entry: LogEntry, config: LoggerConfig): string {
135
+ // No modo minimal, ignora debug/trace/silly
136
+ if (['debug', 'trace', 'silly'].includes(entry.level)) {
137
+ return '';
138
+ }
139
+
140
+ const parts: string[] = [];
141
+
142
+ // Timestamp opcional
143
+ if (config.timestamps) {
144
+ const hours = entry.timestamp.getHours().toString().padStart(2, '0');
145
+ const minutes = entry.timestamp.getMinutes().toString().padStart(2, '0');
146
+ parts.push(`${hours}:${minutes} `);
147
+ }
148
+
149
+ // Ícone baseado no nível
150
+ let icon = '';
151
+ switch (entry.level) {
152
+ case 'error':
153
+ icon = '✗';
154
+ break;
155
+ case 'warn':
156
+ icon = '!';
157
+ break;
158
+ case 'success':
159
+ icon = '✓';
160
+ break;
161
+ case 'info':
162
+ icon = '→';
163
+ break;
164
+ }
165
+
166
+ if (icon) {
167
+ parts.push(`${icon} `);
168
+ }
169
+
170
+ // Mensagem (sem cores)
171
+ parts.push(stripAnsi(entry.message));
172
+
173
+ return parts.join('');
174
+ }
175
+
176
+ /**
177
+ * Formata mensagem no modo JSON
178
+ */
179
+ export function formatJson(entry: LogEntry): string {
180
+ const obj = {
181
+ timestamp: entry.timestamp.toISOString(),
182
+ level: entry.level,
183
+ message: stripAnsi(entry.message),
184
+ ...entry.context,
185
+ metadata: entry.metadata,
186
+ };
187
+
188
+ return JSON.stringify(obj);
189
+ }
190
+
191
+ /**
192
+ * Formata mensagem de acordo com o modo configurado
193
+ */
194
+ export function formatEntry(entry: LogEntry, config: LoggerConfig): string {
195
+ switch (config.mode) {
196
+ case 'json':
197
+ return formatJson(entry);
198
+ case 'minimal':
199
+ return formatMinimal(entry, config);
200
+ case 'silent':
201
+ return '';
202
+ case 'pretty':
203
+ default:
204
+ return formatPretty(entry, config);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Formata linha de status (nome, status, info)
210
+ */
211
+ export function formatStatusLine(
212
+ name: string,
213
+ status: 'pending' | 'running' | 'success' | 'error' | 'warning',
214
+ info?: string,
215
+ config?: LoggerConfig
216
+ ): string {
217
+ const colors = config?.colors !== false;
218
+
219
+ const statusConfig = {
220
+ pending: { icon: '○', color: Colors.gray, text: 'pendente' },
221
+ running: { icon: '●', color: Colors.primary, text: 'executando' },
222
+ success: { icon: '✓', color: Colors.success, text: 'concluído' },
223
+ error: { icon: '✗', color: Colors.error, text: 'erro' },
224
+ warning: { icon: '!', color: Colors.warning, text: 'aviso' },
225
+ };
226
+
227
+ const s = statusConfig[status];
228
+ const indent = formatIndent(1);
229
+
230
+ let line = indent;
231
+
232
+ // Nome
233
+ line += padText(name, LAYOUT.columns.name);
234
+
235
+ // Status
236
+ const statusText = colors
237
+ ? `${s.color}${s.icon} ${s.text}${Colors.reset}`
238
+ : `${s.icon} ${s.text}`;
239
+ line += padText(statusText, LAYOUT.columns.status);
240
+
241
+ // Info
242
+ if (info) {
243
+ line += colors ? `${Colors.dim}${info}${Colors.reset}` : info;
244
+ }
245
+
246
+ return line;
247
+ }
248
+
249
+ /**
250
+ * Formata seção com título
251
+ */
252
+ export function formatSection(title: string, config?: LoggerConfig): string {
253
+ const colors = config?.colors !== false;
254
+ const indent = formatIndent(1);
255
+
256
+ let output = '\n';
257
+ output += indent;
258
+ output += colors ? `${Colors.bold}${title}${Colors.reset}` : title;
259
+ output += '\n';
260
+ output += indent;
261
+ output += colors
262
+ ? `${Colors.darkGray}${LAYOUT.borders.horizontal.repeat(40)}${Colors.reset}`
263
+ : '-'.repeat(40);
264
+
265
+ return output;
266
+ }
267
+
268
+ /**
269
+ * Formata configuração (chave: valor)
270
+ */
271
+ export function formatConfig(key: string, value: string | number | boolean, config?: LoggerConfig): string {
272
+ const colors = config?.colors !== false;
273
+ const indent = formatIndent(1);
274
+
275
+ const keyStr = padText(`${key}:`, 12);
276
+ let valueStr = String(value);
277
+
278
+ if (colors) {
279
+ if (typeof value === 'boolean') {
280
+ valueStr = value
281
+ ? `${Colors.success}sim${Colors.reset}`
282
+ : `${Colors.gray}não${Colors.reset}`;
283
+ } else {
284
+ valueStr = `${Colors.white}${valueStr}${Colors.reset}`;
285
+ }
286
+ }
287
+
288
+ return `${indent}${Colors.dim}${keyStr}${Colors.reset}${valueStr}`;
289
+ }
290
+
291
+ /**
292
+ * Formata URL
293
+ */
294
+ export function formatUrl(label: string, url: string, config?: LoggerConfig): string {
295
+ const colors = config?.colors !== false;
296
+ const indent = formatIndent(1);
297
+
298
+ if (colors) {
299
+ return `${indent}${Colors.success}→${Colors.reset} ${label}: ${Colors.primaryBright}${Colors.bold}${url}${Colors.reset}`;
300
+ }
301
+ return `${indent}→ ${label}: ${url}`;
302
+ }
303
+
304
+ /**
305
+ * Formata arquivo (nome, ação, caminho)
306
+ */
307
+ export function formatFile(
308
+ name: string,
309
+ action: 'changed' | 'compiled' | 'synced' | 'error',
310
+ path?: string,
311
+ config?: LoggerConfig
312
+ ): string {
313
+ const colors = config?.colors !== false;
314
+ const indent = formatIndent(1);
315
+
316
+ const actionConfig = {
317
+ changed: { icon: '○', color: Colors.gray },
318
+ compiled: { icon: '✓', color: Colors.success },
319
+ synced: { icon: '✓', color: Colors.success },
320
+ error: { icon: '✗', color: Colors.error },
321
+ };
322
+
323
+ const a = actionConfig[action];
324
+ const displayName = name.length > 25 ? '...' + name.slice(-22) : name;
325
+
326
+ let line = indent;
327
+ line += colors ? `${a.color}${a.icon}${Colors.reset}` : a.icon;
328
+ line += ' ';
329
+ line += colors ? `${Colors.dim}${displayName}${Colors.reset}` : displayName;
330
+
331
+ if (path) {
332
+ line += ' ';
333
+ line += colors ? `${Colors.darkGray}${path}${Colors.reset}` : path;
334
+ }
335
+
336
+ return line;
337
+ }