@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,296 @@
1
+ /**
2
+ * OperationLogger - Logger especializado para operações complexas com múltiplas etapas
3
+ *
4
+ * Uso:
5
+ * const op = new OperationLogger('deploy');
6
+ * op.start('Iniciando deploy');
7
+ *
8
+ * const buildStep = op.step('build', 'Compilando projeto...');
9
+ * await buildService.build();
10
+ * buildStep.success('Build concluído em 3.2s');
11
+ *
12
+ * op.complete('Deploy finalizado com sucesso');
13
+ */
14
+
15
+ import type { OperationStep } from './types';
16
+ import { Logger } from './Logger';
17
+ import { Colors, Icons } from './colors';
18
+ import { LAYOUT } from './constants';
19
+
20
+ export class OperationStepLogger {
21
+ private operation: OperationLogger;
22
+ private step: OperationStep;
23
+
24
+ constructor(operation: OperationLogger, step: OperationStep) {
25
+ this.operation = operation;
26
+ this.step = step;
27
+ }
28
+
29
+ /**
30
+ * Marca o passo como em execução
31
+ */
32
+ start(message?: string): void {
33
+ this.step.status = 'running';
34
+ this.step.startTime = new Date();
35
+ if (message) {
36
+ this.step.message = message;
37
+ }
38
+ this.operation.logStep(this.step);
39
+ }
40
+
41
+ /**
42
+ * Atualiza mensagem do passo
43
+ */
44
+ update(message: string): void {
45
+ this.step.message = message;
46
+ this.operation.logStep(this.step);
47
+ }
48
+
49
+ /**
50
+ * Marca o passo como concluído com sucesso
51
+ */
52
+ success(message?: string): void {
53
+ this.step.status = 'success';
54
+ this.step.endTime = new Date();
55
+ if (message) {
56
+ this.step.message = message;
57
+ }
58
+ this.operation.logStep(this.step);
59
+ }
60
+
61
+ /**
62
+ * Marca o passo como falho
63
+ */
64
+ fail(error: Error | string): void {
65
+ this.step.status = 'failed';
66
+ this.step.endTime = new Date();
67
+ this.step.error = error instanceof Error ? error : new Error(error);
68
+ this.step.message = error instanceof Error ? error.message : error;
69
+ this.operation.logStep(this.step);
70
+ }
71
+
72
+ /**
73
+ * Obtém dados do passo
74
+ */
75
+ getData(): OperationStep {
76
+ return { ...this.step };
77
+ }
78
+ }
79
+
80
+ export class OperationLogger {
81
+ private name: string;
82
+ private steps: OperationStep[] = [];
83
+ private startTime: Date | null = null;
84
+ private endTime: Date | null = null;
85
+ private status: 'pending' | 'running' | 'success' | 'failed' = 'pending';
86
+ private traceId: string;
87
+ private logger: Logger;
88
+
89
+ constructor(name: string, logger?: Logger) {
90
+ this.name = name;
91
+ this.logger = logger || Logger.getInstance();
92
+ this.traceId = this.logger.generateTraceId() || '';
93
+ }
94
+
95
+ /**
96
+ * Inicia a operação
97
+ */
98
+ start(message?: string): void {
99
+ this.startTime = new Date();
100
+ this.status = 'running';
101
+
102
+ const icon = Icons.arrow;
103
+ const color = Colors.primary;
104
+ const bold = Colors.bold;
105
+ const reset = Colors.reset;
106
+
107
+ // Usa log direto sem ícone duplicado
108
+ this.logger.log('info', `${color}${icon}${reset} ${bold}${this.name}${reset}${message ? ': ' + message : ''}`);
109
+ }
110
+
111
+ /**
112
+ * Cria um novo passo na operação
113
+ */
114
+ step(id: string, message?: string): OperationStepLogger {
115
+ const stepData: OperationStep = {
116
+ id,
117
+ name: id,
118
+ status: 'pending',
119
+ message,
120
+ };
121
+
122
+ this.steps.push(stepData);
123
+ const stepLogger = new OperationStepLogger(this, stepData);
124
+ stepLogger.start(message);
125
+ return stepLogger;
126
+ }
127
+
128
+ /**
129
+ * Loga um passo
130
+ */
131
+ logStep(step: OperationStep): void {
132
+ const indent = ' '.repeat(LAYOUT.indentSize * 2);
133
+
134
+ let icon: string;
135
+ let color: string;
136
+
137
+ switch (step.status) {
138
+ case 'running':
139
+ icon = Icons.spinner;
140
+ color = Colors.primary;
141
+ break;
142
+ case 'success':
143
+ icon = Icons.success;
144
+ color = Colors.success;
145
+ break;
146
+ case 'failed':
147
+ icon = Icons.error;
148
+ color = Colors.error;
149
+ break;
150
+ default:
151
+ icon = Icons.pending;
152
+ color = Colors.gray;
153
+ }
154
+
155
+ const reset = Colors.reset;
156
+ const dim = Colors.dim;
157
+
158
+ let message = step.message || step.name;
159
+
160
+ // Adiciona tempo se concluído
161
+ if ((step.status === 'success' || step.status === 'failed') && step.startTime && step.endTime) {
162
+ const duration = ((step.endTime.getTime() - step.startTime.getTime()) / 1000).toFixed(1);
163
+ message += ` ${dim}(${duration}s)${reset}`;
164
+ }
165
+
166
+ // Usa log direto sem ícone duplicado do logger.info()
167
+ this.logger.log('info', `${indent}${color}${icon}${reset} ${message}`);
168
+ }
169
+
170
+ /**
171
+ * Completa a operação com sucesso
172
+ */
173
+ complete(message?: string): void {
174
+ this.endTime = new Date();
175
+ this.status = 'success';
176
+
177
+ const icon = Icons.success;
178
+ const color = Colors.success;
179
+ const bold = Colors.bold;
180
+ const reset = Colors.reset;
181
+ const dim = Colors.dim;
182
+
183
+ let finalMessage = message || `${this.name} concluído`;
184
+
185
+ // Adiciona tempo total
186
+ if (this.startTime) {
187
+ const duration = ((this.endTime.getTime() - this.startTime.getTime()) / 1000).toFixed(1);
188
+ finalMessage += ` ${dim}(${duration}s)${reset}`;
189
+ }
190
+
191
+ // Usa log direto sem ícone duplicado
192
+ this.logger.log('info', `${color}${icon}${reset} ${bold}${finalMessage}${reset}`);
193
+ }
194
+
195
+ /**
196
+ * Falha na operação
197
+ */
198
+ fail(message?: string, error?: Error): void {
199
+ this.endTime = new Date();
200
+ this.status = 'failed';
201
+
202
+ const icon = Icons.error;
203
+ const color = Colors.error;
204
+ const bold = Colors.bold;
205
+ const reset = Colors.reset;
206
+ const dim = Colors.dim;
207
+
208
+ let finalMessage = message || `${this.name} falhou`;
209
+
210
+ // Adiciona tempo total
211
+ if (this.startTime) {
212
+ const duration = ((this.endTime.getTime() - this.startTime.getTime()) / 1000).toFixed(1);
213
+ finalMessage += ` ${dim}(${duration}s)${reset}`;
214
+ }
215
+
216
+ // Usa log direto para erro
217
+ this.logger.log('error', `${color}${icon}${reset} ${bold}${finalMessage}${reset}`);
218
+
219
+ // Loga detalhes do erro
220
+ if (error) {
221
+ this.logger.group('Detalhes do erro');
222
+ this.logger.error(error.message);
223
+ if (error.stack) {
224
+ const lines = error.stack.split('\n').slice(1, 5);
225
+ for (const line of lines) {
226
+ this.logger.debug(line.trim());
227
+ }
228
+ }
229
+ this.logger.groupEnd();
230
+ }
231
+
232
+ // Loga passos que falharam
233
+ const failedSteps = this.steps.filter(s => s.status === 'failed');
234
+ if (failedSteps.length > 0) {
235
+ this.logger.group('Passos com falha');
236
+ for (const step of failedSteps) {
237
+ this.logger.error(`${step.name}: ${step.error?.message || 'Falha desconhecida'}`);
238
+ }
239
+ this.logger.groupEnd();
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Obtém estatísticas da operação
245
+ */
246
+ getStats(): {
247
+ name: string;
248
+ status: string;
249
+ duration: number;
250
+ steps: { total: number; success: number; failed: number; pending: number };
251
+ } {
252
+ const duration = this.startTime && this.endTime
253
+ ? (this.endTime.getTime() - this.startTime.getTime()) / 1000
254
+ : 0;
255
+
256
+ return {
257
+ name: this.name,
258
+ status: this.status,
259
+ duration,
260
+ steps: {
261
+ total: this.steps.length,
262
+ success: this.steps.filter(s => s.status === 'success').length,
263
+ failed: this.steps.filter(s => s.status === 'failed').length,
264
+ pending: this.steps.filter(s => s.status === 'pending').length,
265
+ },
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Verifica se a operação teve sucesso
271
+ */
272
+ isSuccess(): boolean {
273
+ return this.status === 'success';
274
+ }
275
+
276
+ /**
277
+ * Verifica se a operação falhou
278
+ */
279
+ isFailed(): boolean {
280
+ return this.status === 'failed';
281
+ }
282
+
283
+ /**
284
+ * Obtém trace ID da operação
285
+ */
286
+ getTraceId(): string {
287
+ return this.traceId;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Cria uma nova operação (função helper)
293
+ */
294
+ export function createOperation(name: string, logger?: Logger): OperationLogger {
295
+ return new OperationLogger(name, logger);
296
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * ProgressLogger - Logger especializado para operações com progresso
3
+ *
4
+ * Uso:
5
+ * const progress = new ProgressLogger('Download', { total: 100, unit: 'MB' });
6
+ * progress.update(50);
7
+ * progress.complete();
8
+ *
9
+ * Ou:
10
+ * const progress = new ProgressLogger('Processando', { total: files.length, unit: 'arquivos' });
11
+ * for (let i = 0; i < files.length; i++) {
12
+ * await process(files[i]);
13
+ * progress.increment();
14
+ * }
15
+ * progress.complete();
16
+ */
17
+
18
+ import { Logger } from './Logger';
19
+ import { Colors } from './colors';
20
+
21
+ export interface ProgressOptions {
22
+ total: number;
23
+ unit?: string;
24
+ width?: number;
25
+ showPercentage?: boolean;
26
+ showEta?: boolean;
27
+ showCount?: boolean;
28
+ }
29
+
30
+ export class ProgressLogger {
31
+ private name: string;
32
+ private options: Required<ProgressOptions>;
33
+ private current = 0;
34
+ private startTime: number;
35
+ private logger: Logger;
36
+ private lastUpdate = 0;
37
+ private updateInterval = 100; // ms - limita updates para não flickerar
38
+
39
+ constructor(name: string, options: ProgressOptions, logger?: Logger) {
40
+ this.name = name;
41
+ this.logger = logger || Logger.getInstance();
42
+ this.startTime = Date.now();
43
+
44
+ this.options = {
45
+ total: options.total,
46
+ unit: options.unit || '',
47
+ width: options.width || 30,
48
+ showPercentage: options.showPercentage !== false,
49
+ showEta: options.showEta !== false,
50
+ showCount: options.showCount !== false,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Atualiza o progresso para um valor específico
56
+ */
57
+ update(value: number): void {
58
+ const now = Date.now();
59
+ if (now - this.lastUpdate < this.updateInterval && value < this.options.total) {
60
+ return; // Rate limiting de updates visuais
61
+ }
62
+
63
+ this.current = Math.min(value, this.options.total);
64
+ this.lastUpdate = now;
65
+ this.render();
66
+ }
67
+
68
+ /**
69
+ * Incrementa o progresso
70
+ */
71
+ increment(amount = 1): void {
72
+ this.update(this.current + amount);
73
+ }
74
+
75
+ /**
76
+ * Marca como completo
77
+ */
78
+ complete(message?: string): void {
79
+ this.current = this.options.total;
80
+ this.render();
81
+
82
+ // Limpa a linha atual
83
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
84
+
85
+ const duration = ((Date.now() - this.startTime) / 1000).toFixed(1);
86
+ const finalMessage = message || `${this.name} concluído`;
87
+
88
+ this.logger.success(`${finalMessage} (${duration}s)`);
89
+ }
90
+
91
+ /**
92
+ * Falha no progresso
93
+ */
94
+ fail(message?: string): void {
95
+ // Limpa a linha atual
96
+ process.stdout.write('\r' + ' '.repeat(80) + '\r');
97
+
98
+ const finalMessage = message || `${this.name} falhou`;
99
+ this.logger.error(finalMessage);
100
+ }
101
+
102
+ /**
103
+ * Renderiza a barra de progresso
104
+ */
105
+ private render(): void {
106
+ const { total, width, showPercentage, showEta, showCount, unit } = this.options;
107
+ const ratio = this.current / total;
108
+ const filled = Math.floor(ratio * width);
109
+ const empty = width - filled;
110
+
111
+ // Caracteres da barra
112
+ const barFilled = '█'.repeat(filled);
113
+ const barEmpty = '░'.repeat(empty);
114
+
115
+ const primaryColor = Colors.primary;
116
+ const dimColor = Colors.dim;
117
+ const whiteColor = Colors.white;
118
+ const reset = Colors.reset;
119
+
120
+ let line = '\r ';
121
+
122
+ // Nome da operação
123
+ line += `${primaryColor}▸${reset} ${dimColor}${this.name}${reset} `;
124
+
125
+ // Barra
126
+ line += `${primaryColor}[${barFilled}${barEmpty}]${reset}`;
127
+
128
+ // Porcentagem
129
+ if (showPercentage) {
130
+ const percentage = Math.floor(ratio * 100);
131
+ line += ` ${whiteColor}${percentage}%${reset}`;
132
+ }
133
+
134
+ // Contagem
135
+ if (showCount) {
136
+ line += ` ${dimColor}(${this.current}/${total}${unit ? ' ' + unit : ''})${reset}`;
137
+ }
138
+
139
+ // ETA
140
+ if (showEta && this.current > 0 && this.current < total) {
141
+ const elapsed = Date.now() - this.startTime;
142
+ const rate = this.current / elapsed;
143
+ const remaining = (total - this.current) / rate;
144
+ const eta = Math.ceil(remaining / 1000);
145
+
146
+ line += ` ${dimColor}(faltam ~${eta}s)${reset}`;
147
+ }
148
+
149
+ // Limpa o resto da linha e escreve
150
+ process.stdout.write(line + '\x1b[K');
151
+ }
152
+
153
+ /**
154
+ * Obtém porcentagem atual
155
+ */
156
+ getPercentage(): number {
157
+ return Math.floor((this.current / this.options.total) * 100);
158
+ }
159
+
160
+ /**
161
+ * Obtém valor atual
162
+ */
163
+ getCurrent(): number {
164
+ return this.current;
165
+ }
166
+
167
+ /**
168
+ * Obtém total
169
+ */
170
+ getTotal(): number {
171
+ return this.options.total;
172
+ }
173
+
174
+ /**
175
+ * Verifica se está completo
176
+ */
177
+ isComplete(): boolean {
178
+ return this.current >= this.options.total;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Cria um progress logger (função helper)
184
+ */
185
+ export function createProgress(name: string, options: ProgressOptions, logger?: Logger): ProgressLogger {
186
+ return new ProgressLogger(name, options, logger);
187
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * TableLogger - Logger especializado para exibição de dados tabulares
3
+ *
4
+ * Uso:
5
+ * const table = new TableLogger(['Nome', 'Versão', 'Status']);
6
+ * table.add(['Spring', '5.3.0', '✓']);
7
+ * table.add(['Hibernate', '6.0.0', '⚠']);
8
+ * table.print();
9
+ *
10
+ * Ou com opções de alinhamento:
11
+ * const table = new TableLogger([
12
+ * { header: 'ID', align: 'center', width: 10 },
13
+ * { header: 'Nome', align: 'left' },
14
+ * { header: 'Valor', align: 'right', width: 15 }
15
+ * ]);
16
+ */
17
+
18
+ import type { TableColumn, TableRow } from './types';
19
+ import { Logger } from './Logger';
20
+ import { Colors, stripAnsi, visualWidth, padText } from './colors';
21
+
22
+ export interface TableOptions {
23
+ headerStyle?: boolean;
24
+ border?: boolean;
25
+ compact?: boolean;
26
+ }
27
+
28
+ export class TableLogger {
29
+ private columns: TableColumn[];
30
+ private rows: TableRow[] = [];
31
+ private options: Required<TableOptions>;
32
+ private logger: Logger;
33
+
34
+ constructor(
35
+ columns: (string | TableColumn)[],
36
+ options: TableOptions = {},
37
+ logger?: Logger
38
+ ) {
39
+ this.logger = logger || Logger.getInstance();
40
+
41
+ // Normaliza colunas
42
+ this.columns = columns.map(col => {
43
+ if (typeof col === 'string') {
44
+ return { header: col, align: 'left' };
45
+ }
46
+ return { align: 'left', ...col };
47
+ });
48
+
49
+ this.options = {
50
+ headerStyle: true,
51
+ border: false,
52
+ compact: false,
53
+ ...options,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Adiciona uma linha à tabela
59
+ */
60
+ add(cells: (string | number)[], styles?: string[]): this {
61
+ this.rows.push({
62
+ cells: cells.map(c => String(c)),
63
+ styles,
64
+ });
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Adiciona múltiplas linhas
70
+ */
71
+ addMany(rows: (string | number)[][]): this {
72
+ for (const row of rows) {
73
+ this.add(row);
74
+ }
75
+ return this;
76
+ }
77
+
78
+ /**
79
+ * Limpa todas as linhas
80
+ */
81
+ clear(): this {
82
+ this.rows = [];
83
+ return this;
84
+ }
85
+
86
+ /**
87
+ * Define larguras das colunas baseado no conteúdo
88
+ */
89
+ private calculateWidths(): number[] {
90
+ const widths: number[] = [];
91
+
92
+ for (let i = 0; i < this.columns.length; i++) {
93
+ const col = this.columns[i];
94
+
95
+ // Se já tem largura definida, usa ela
96
+ if (col.width) {
97
+ widths[i] = col.width;
98
+ continue;
99
+ }
100
+
101
+ // Calcula largura máxima baseada no header e conteúdo
102
+ let maxWidth = visualWidth(col.header);
103
+
104
+ for (const row of this.rows) {
105
+ const cell = row.cells[i] || '';
106
+ maxWidth = Math.max(maxWidth, visualWidth(cell));
107
+ }
108
+
109
+ // Adiciona padding
110
+ widths[i] = maxWidth + 2;
111
+ }
112
+
113
+ return widths;
114
+ }
115
+
116
+ /**
117
+ * Renderiza a tabela
118
+ */
119
+ render(): string {
120
+ if (this.rows.length === 0 && this.options.compact) {
121
+ return '';
122
+ }
123
+
124
+ const widths = this.calculateWidths();
125
+ const lines: string[] = [];
126
+
127
+ // Linha de borda superior
128
+ if (this.options.border) {
129
+ let border = ' ┌';
130
+ for (let i = 0; i < widths.length; i++) {
131
+ border += '─'.repeat(widths[i] + 2);
132
+ if (i < widths.length - 1) border += '┬';
133
+ }
134
+ border += '┐';
135
+ lines.push(Colors.darkGray + border + Colors.reset);
136
+ }
137
+
138
+ // Header
139
+ const headerCells = this.columns.map((col, i) => {
140
+ const width = widths[i];
141
+ const padded = padText(col.header, width, col.align);
142
+ return this.options.headerStyle
143
+ ? `${Colors.bold}${padded}${Colors.reset}`
144
+ : padded;
145
+ });
146
+
147
+ const headerLine = this.options.border
148
+ ? ` │ ${headerCells.join(' │ ')} │`
149
+ : ' ' + headerCells.join('');
150
+ lines.push(headerLine);
151
+
152
+ // Linha separadora
153
+ if (this.options.border) {
154
+ let sep = ' ├';
155
+ for (let i = 0; i < widths.length; i++) {
156
+ sep += '─'.repeat(widths[i] + 2);
157
+ if (i < widths.length - 1) sep += '┼';
158
+ }
159
+ sep += '┤';
160
+ lines.push(Colors.darkGray + sep + Colors.reset);
161
+ } else if (!this.options.compact) {
162
+ let sep = ' ';
163
+ for (let i = 0; i < widths.length; i++) {
164
+ sep += '─'.repeat(widths[i]);
165
+ }
166
+ lines.push(Colors.darkGray + sep + Colors.reset);
167
+ }
168
+
169
+ // Linhas de dados
170
+ for (const row of this.rows) {
171
+ const cells = row.cells.map((cell, i) => {
172
+ const width = widths[i];
173
+ const col = this.columns[i];
174
+ const style = row.styles?.[i] || '';
175
+ const reset = style ? Colors.reset : '';
176
+ return style + padText(cell, width, col.align) + reset;
177
+ });
178
+
179
+ const line = this.options.border
180
+ ? ` │ ${cells.join(' │ ')} │`
181
+ : ' ' + cells.join('');
182
+ lines.push(line);
183
+ }
184
+
185
+ // Linha de borda inferior
186
+ if (this.options.border) {
187
+ let border = ' └';
188
+ for (let i = 0; i < widths.length; i++) {
189
+ border += '─'.repeat(widths[i] + 2);
190
+ if (i < widths.length - 1) border += '┴';
191
+ }
192
+ border += '┘';
193
+ lines.push(Colors.darkGray + border + Colors.reset);
194
+ }
195
+
196
+ return lines.join('\n');
197
+ }
198
+
199
+ /**
200
+ * Imprime a tabela
201
+ */
202
+ print(): void {
203
+ const output = this.render();
204
+ if (output) {
205
+ this.logger.info(output);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Obtém número de linhas
211
+ */
212
+ getRowCount(): number {
213
+ return this.rows.length;
214
+ }
215
+
216
+ /**
217
+ * Obtém número de colunas
218
+ */
219
+ getColumnCount(): number {
220
+ return this.columns.length;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Cria uma tabela (função helper)
226
+ */
227
+ export function createTable(
228
+ columns: (string | TableColumn)[],
229
+ options?: TableOptions,
230
+ logger?: Logger
231
+ ): TableLogger {
232
+ return new TableLogger(columns, options, logger);
233
+ }
234
+
235
+ /**
236
+ * Imprime uma tabela simples rapidamente
237
+ */
238
+ export function printTable(
239
+ headers: string[],
240
+ rows: (string | number)[][],
241
+ logger?: Logger
242
+ ): void {
243
+ const table = new TableLogger(headers, {}, logger);
244
+ table.addMany(rows);
245
+ table.print();
246
+ }