@archznn/xavva 3.1.3 → 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.
- package/README.md +221 -12
- package/package.json +3 -2
- package/src/commands/AuditCommand.ts +12 -10
- package/src/commands/BuildCommand.ts +9 -7
- package/src/commands/ChangelogCommand.ts +5 -5
- package/src/commands/CleanCommand.ts +242 -0
- package/src/commands/CompletionCommand.ts +7 -7
- package/src/commands/DbCommand.ts +33 -31
- package/src/commands/DeployCommand.ts +252 -229
- package/src/commands/DepsCommand.ts +174 -174
- package/src/commands/DockerCommand.ts +14 -14
- package/src/commands/DoctorCommand.ts +252 -239
- package/src/commands/EncodingCommand.ts +19 -19
- package/src/commands/HealthCommand.ts +7 -7
- package/src/commands/HelpCommand.ts +34 -14
- package/src/commands/HistoryCommand.ts +5 -5
- package/src/commands/HttpCommand.ts +6 -6
- package/src/commands/IdeCommand.ts +313 -0
- package/src/commands/InitCommand.ts +26 -25
- package/src/commands/LogsCommand.ts +8 -6
- package/src/commands/ProfilesCommand.ts +6 -6
- package/src/commands/RedoCommand.ts +2 -2
- package/src/commands/RunCommand.ts +64 -24
- package/src/commands/StartCommand.ts +9 -7
- package/src/commands/TestCommand.ts +4 -4
- package/src/commands/TomcatCommand.ts +219 -100
- package/src/config/versions.ts +111 -9
- package/src/di/container.ts +239 -105
- package/src/errors/ErrorHandler.ts +23 -19
- package/src/errors/errorMessages.ts +235 -0
- package/src/index.ts +11 -3
- package/src/logging/FileLogger.ts +235 -0
- package/src/logging/Logger.ts +545 -0
- package/src/logging/OperationLogger.ts +296 -0
- package/src/logging/ProgressLogger.ts +187 -0
- package/src/logging/TableLogger.ts +246 -0
- package/src/logging/colors.ts +167 -0
- package/src/logging/constants.ts +176 -0
- package/src/logging/formatters.ts +337 -0
- package/src/logging/index.ts +93 -0
- package/src/logging/types.ts +64 -0
- package/src/plugins/PluginManager.ts +325 -0
- package/src/plugins/types.ts +82 -0
- package/src/services/AuditService.ts +5 -3
- package/src/services/BuildService.ts +15 -17
- package/src/services/DashboardService.ts +14 -3
- package/src/services/DbService.ts +35 -34
- package/src/services/DependencyAnalyzerService.ts +18 -18
- package/src/services/DependencyCacheService.ts +303 -0
- package/src/services/DeployWatcher.ts +127 -23
- package/src/services/DockerService.ts +3 -3
- package/src/services/EmbeddedTomcatService.ts +13 -12
- package/src/services/FileWatcher.ts +15 -7
- package/src/services/HttpService.ts +5 -5
- package/src/services/LogAnalyzer.ts +26 -22
- package/src/services/PerformanceProfiler.ts +267 -0
- package/src/services/ProjectService.ts +3 -0
- package/src/services/TestService.ts +3 -3
- package/src/services/TomcatService.ts +46 -25
- package/src/services/tomcat/TomcatBackupManager.ts +330 -0
- package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
- package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
- package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
- package/src/services/tomcat/TomcatDownloadService.ts +335 -0
- package/src/services/tomcat/TomcatInstallerService.ts +474 -0
- package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
- package/src/services/tomcat/index.ts +36 -0
- package/src/services/tomcat/types.ts +120 -0
- package/src/types/args.ts +68 -1
- package/src/types/configSchema.ts +174 -0
- package/src/utils/ChangelogGenerator.ts +11 -11
- package/src/utils/LoggerLevel.ts +44 -20
- package/src/utils/ProgressBar.ts +87 -46
- package/src/utils/argsParser.ts +260 -0
- package/src/utils/config.ts +340 -189
- package/src/utils/constants.ts +87 -9
- package/src/utils/dryRun.ts +192 -0
- package/src/utils/processManager.ts +23 -7
- package/src/utils/security.ts +293 -0
- 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
|
+
}
|