@archznn/xavva 2.6.0 → 2.8.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.
@@ -0,0 +1,249 @@
1
+ /**
2
+ * ErrorHandler centralizado
3
+ * Trata todos os erros do aplicativo de forma uniforme
4
+ */
5
+
6
+ import {
7
+ XavvaError,
8
+ isOperationalError,
9
+ getExitCode,
10
+ NetworkError,
11
+ FileSystemError,
12
+ BuildError,
13
+ TomcatError,
14
+ ConfigError
15
+ } from "./XavvaError";
16
+ import { Logger } from "../utils/ui";
17
+ import { ProcessManager } from "../utils/processManager";
18
+
19
+ export interface ErrorReport {
20
+ error: Error;
21
+ code: string;
22
+ exitCode: number;
23
+ isOperational: boolean;
24
+ context?: Record<string, unknown>;
25
+ timestamp: Date;
26
+ }
27
+
28
+ export class ErrorHandler {
29
+ private static instance: ErrorHandler;
30
+ private errorHistory: ErrorReport[] = [];
31
+ private maxHistorySize = 10;
32
+ private isShuttingDown = false;
33
+
34
+ private constructor() {}
35
+
36
+ static getInstance(): ErrorHandler {
37
+ if (!ErrorHandler.instance) {
38
+ ErrorHandler.instance = new ErrorHandler();
39
+ }
40
+ return ErrorHandler.instance;
41
+ }
42
+
43
+ /**
44
+ * Trata qualquer erro de forma apropriada
45
+ */
46
+ async handle(error: unknown, context?: Record<string, unknown>): Promise<void> {
47
+ if (this.isShuttingDown) return;
48
+
49
+ const normalizedError = this.normalizeError(error);
50
+ const report = this.createReport(normalizedError, context);
51
+
52
+ // Adiciona ao histórico
53
+ this.addToHistory(report);
54
+
55
+ // Log apropriado baseado no tipo de erro
56
+ if (normalizedError instanceof XavvaError) {
57
+ this.handleXavvaError(normalizedError);
58
+ } else {
59
+ this.handleUnexpectedError(normalizedError);
60
+ }
61
+
62
+ // Se não for erro operacional, mostra stack trace em verbose
63
+ if (!report.isOperational) {
64
+ Logger.debug("Stack trace:");
65
+ Logger.debug(normalizedError.stack || "N/A");
66
+ }
67
+
68
+ // Shutdown com código apropriado
69
+ const processManager = ProcessManager.getInstance();
70
+ await processManager.shutdown(report.exitCode);
71
+ }
72
+
73
+ /**
74
+ * Trata erro sem fazer shutdown (para operações que podem continuar)
75
+ */
76
+ handleGraceful(error: unknown, context?: Record<string, unknown>): ErrorReport {
77
+ const normalizedError = this.normalizeError(error);
78
+ const report = this.createReport(normalizedError, context);
79
+
80
+ this.addToHistory(report);
81
+
82
+ if (normalizedError instanceof XavvaError) {
83
+ this.logXavvaError(normalizedError, false);
84
+ } else {
85
+ Logger.warn(`Erro inesperado: ${normalizedError.message}`);
86
+ }
87
+
88
+ return report;
89
+ }
90
+
91
+ /**
92
+ * Normaliza qualquer valor para Error
93
+ */
94
+ private normalizeError(error: unknown): Error {
95
+ if (error instanceof Error) {
96
+ return error;
97
+ }
98
+
99
+ if (typeof error === "string") {
100
+ return new Error(error);
101
+ }
102
+
103
+ if (error && typeof error === "object" && "message" in error) {
104
+ return new Error(String((error as { message: unknown }).message));
105
+ }
106
+
107
+ return new Error("Erro desconhecido");
108
+ }
109
+
110
+ /**
111
+ * Cria um relatório de erro estruturado
112
+ */
113
+ private createReport(error: Error, context?: Record<string, unknown>): ErrorReport {
114
+ return {
115
+ error,
116
+ code: error instanceof XavvaError ? error.code : "UNKNOWN_ERROR",
117
+ exitCode: getExitCode(error),
118
+ isOperational: isOperationalError(error),
119
+ context,
120
+ timestamp: new Date(),
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Adiciona erro ao histórico (com limite)
126
+ */
127
+ private addToHistory(report: ErrorReport): void {
128
+ this.errorHistory.push(report);
129
+ if (this.errorHistory.length > this.maxHistorySize) {
130
+ this.errorHistory.shift();
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Trata erros específicos do Xavva
136
+ */
137
+ private handleXavvaError(error: XavvaError): void {
138
+ this.logXavvaError(error, true);
139
+ }
140
+
141
+ /**
142
+ * Loga erro Xavva com formatação adequada
143
+ */
144
+ private logXavvaError(error: XavvaError, isFatal: boolean): void {
145
+ const logMethod = isFatal ? Logger.error : Logger.warn;
146
+
147
+ // Cabeçalho do erro
148
+ logMethod.call(Logger, error.message);
149
+
150
+ // Detalhes adicionais se houver
151
+ if (error instanceof NetworkError) {
152
+ Logger.info("Dica", "Verifique sua conexão de internet e tente novamente");
153
+ } else if (error instanceof FileSystemError) {
154
+ Logger.info("Dica", "Verifique as permissões do arquivo/diretório");
155
+ } else if (error instanceof BuildError) {
156
+ Logger.info("Dica", "Use --verbose para ver detalhes completos do build");
157
+ } else if (error instanceof TomcatError) {
158
+ Logger.info("Dica", "Verifique se o Tomcat está configurado corretamente");
159
+ } else if (error instanceof ConfigError) {
160
+ Logger.info("Dica", "Verifique seu arquivo xavva.json");
161
+ }
162
+
163
+ // Código do erro em debug
164
+ Logger.debug(`Código do erro: ${error.code} (exit ${error.exitCode})`);
165
+ }
166
+
167
+ /**
168
+ * Trata erros inesperados (bugs)
169
+ */
170
+ private handleUnexpectedError(error: Error): void {
171
+ Logger.error("Erro inesperado:");
172
+ Logger.error(error.message);
173
+ Logger.newline();
174
+ Logger.info("Isso parece ser um bug", "Por favor, reporte em github.com/leorsousa05/Xavva/issues");
175
+
176
+ // Sempre mostra stack trace para erros inesperados
177
+ if (error.stack) {
178
+ Logger.newline();
179
+ Logger.dim("Stack trace:");
180
+ Logger.dim(error.stack.split("\n").slice(1, 5).join("\n"));
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Obtém histórico de erros
186
+ */
187
+ getErrorHistory(): ErrorReport[] {
188
+ return [...this.errorHistory];
189
+ }
190
+
191
+ /**
192
+ * Limpa histórico de erros
193
+ */
194
+ clearHistory(): void {
195
+ this.errorHistory = [];
196
+ }
197
+
198
+ /**
199
+ * Verifica se houve erros recentes de um tipo específico
200
+ */
201
+ hasRecentError(code: string, withinMs: number = 60000): boolean {
202
+ const now = Date.now();
203
+ return this.errorHistory.some(
204
+ report =>
205
+ report.code === code &&
206
+ (now - report.timestamp.getTime()) < withinMs
207
+ );
208
+ }
209
+
210
+ /**
211
+ * Wrapper para executar função com tratamento de erro automático
212
+ */
213
+ async wrap<T>(
214
+ fn: () => Promise<T>,
215
+ context?: Record<string, unknown>
216
+ ): Promise<T | undefined> {
217
+ try {
218
+ return await fn();
219
+ } catch (error) {
220
+ await this.handle(error, context);
221
+ return undefined;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Wrapper para executar função sem shutdown em caso de erro
227
+ */
228
+ wrapGraceful<T>(
229
+ fn: () => T,
230
+ fallback: T,
231
+ context?: Record<string, unknown>
232
+ ): T {
233
+ try {
234
+ return fn();
235
+ } catch (error) {
236
+ this.handleGraceful(error, context);
237
+ return fallback;
238
+ }
239
+ }
240
+ }
241
+
242
+ // Helper para uso rápido
243
+ export function handleError(error: unknown, context?: Record<string, unknown>): Promise<void> {
244
+ return ErrorHandler.getInstance().handle(error, context);
245
+ }
246
+
247
+ export function handleGraceful(error: unknown, context?: Record<string, unknown>): ErrorReport {
248
+ return ErrorHandler.getInstance().handleGraceful(error, context);
249
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Hierarquia de erros específicos do Xavva
3
+ * Permite tratamento granular de diferentes tipos de falhas
4
+ */
5
+
6
+ export class XavvaError extends Error {
7
+ public readonly code: string;
8
+ public readonly exitCode: number;
9
+ public readonly isOperational: boolean;
10
+
11
+ constructor(
12
+ message: string,
13
+ code: string = "XAVVA_ERROR",
14
+ exitCode: number = 1,
15
+ isOperational: boolean = true
16
+ ) {
17
+ super(message);
18
+ this.name = this.constructor.name;
19
+ this.code = code;
20
+ this.exitCode = exitCode;
21
+ this.isOperational = isOperational;
22
+
23
+ // Mantém o stack trace correto
24
+ Error.captureStackTrace(this, this.constructor);
25
+ }
26
+ }
27
+
28
+ // ===== Erros de Build =====
29
+ export class BuildError extends XavvaError {
30
+ constructor(message: string, details?: string) {
31
+ super(
32
+ details ? `${message}: ${details}` : message,
33
+ "BUILD_ERROR",
34
+ 3,
35
+ true
36
+ );
37
+ }
38
+ }
39
+
40
+ export class MavenError extends BuildError {
41
+ constructor(message: string, exitCode?: number) {
42
+ super(
43
+ `Maven build failed${exitCode ? ` (exit ${exitCode})` : ""}: ${message}`,
44
+ );
45
+ this.code = "MAVEN_ERROR";
46
+ }
47
+ }
48
+
49
+ export class GradleError extends BuildError {
50
+ constructor(message: string, exitCode?: number) {
51
+ super(
52
+ `Gradle build failed${exitCode ? ` (exit ${exitCode})` : ""}: ${message}`,
53
+ );
54
+ this.code = "GRADLE_ERROR";
55
+ }
56
+ }
57
+
58
+ // ===== Erros de Deploy =====
59
+ export class DeployError extends XavvaError {
60
+ constructor(message: string, details?: string) {
61
+ super(
62
+ details ? `${message}: ${details}` : message,
63
+ "DEPLOY_ERROR",
64
+ 4,
65
+ true
66
+ );
67
+ }
68
+ }
69
+
70
+ export class ArtifactNotFoundError extends DeployError {
71
+ constructor(buildDir: string) {
72
+ super(
73
+ `Nenhum artefato (.war ou pasta exploded) encontrado em ${buildDir}`,
74
+ "Certifique-se de que o build foi executado com sucesso"
75
+ );
76
+ this.code = "ARTIFACT_NOT_FOUND";
77
+ }
78
+ }
79
+
80
+ // ===== Erros de Tomcat =====
81
+ export class TomcatError extends XavvaError {
82
+ constructor(message: string, details?: string) {
83
+ super(
84
+ details ? `${message}: ${details}` : message,
85
+ "TOMCAT_ERROR",
86
+ 5,
87
+ true
88
+ );
89
+ }
90
+ }
91
+
92
+ export class TomcatNotFoundError extends TomcatError {
93
+ constructor(path: string) {
94
+ super(
95
+ `Tomcat não encontrado em ${path}`,
96
+ "Defina TOMCAT_HOME, CATALINA_HOME ou use --path"
97
+ );
98
+ this.code = "TOMCAT_NOT_FOUND";
99
+ }
100
+ }
101
+
102
+ export class PortInUseError extends TomcatError {
103
+ constructor(port: number) {
104
+ super(
105
+ `Porta ${port} já está em uso`,
106
+ "Pare o processo que está usando a porta ou especifique outra com --port"
107
+ );
108
+ this.code = "PORT_IN_USE";
109
+ }
110
+ }
111
+
112
+ export class EmbeddedTomcatError extends TomcatError {
113
+ constructor(message: string) {
114
+ super(
115
+ `Falha no Tomcat embutido: ${message}`,
116
+ "Tente instalar manualmente ou use outra versão com --tomcat-version"
117
+ );
118
+ this.code = "EMBEDDED_TOMCAT_ERROR";
119
+ }
120
+ }
121
+
122
+ // ===== Erros de Configuração =====
123
+ export class ConfigError extends XavvaError {
124
+ constructor(message: string, details?: string) {
125
+ super(
126
+ details ? `${message}: ${details}` : message,
127
+ "CONFIG_ERROR",
128
+ 2,
129
+ true
130
+ );
131
+ }
132
+ }
133
+
134
+ export class InvalidConfigError extends ConfigError {
135
+ constructor(field: string, value: string, expected?: string) {
136
+ super(
137
+ `Configuração inválida: ${field} = '${value}'`,
138
+ expected || "Verifique sua configuração"
139
+ );
140
+ this.code = "INVALID_CONFIG";
141
+ }
142
+ }
143
+
144
+ export class MissingConfigError extends ConfigError {
145
+ constructor(field: string) {
146
+ super(
147
+ `Configuração obrigatória ausente: ${field}`,
148
+ `Defina ${field} no xavva.json ou via linha de comando`
149
+ );
150
+ this.code = "MISSING_CONFIG";
151
+ }
152
+ }
153
+
154
+ // ===== Erros de Projeto =====
155
+ export class ProjectError extends XavvaError {
156
+ constructor(message: string, details?: string) {
157
+ super(
158
+ details ? `${message}: ${details}` : message,
159
+ "PROJECT_ERROR",
160
+ 6,
161
+ true
162
+ );
163
+ }
164
+ }
165
+
166
+ export class BuildToolNotFoundError extends ProjectError {
167
+ constructor() {
168
+ super(
169
+ "Não foi possível detectar a ferramenta de build",
170
+ "Certifique-se de estar no diretório raiz do projeto (pom.xml ou build.gradle)"
171
+ );
172
+ this.code = "BUILD_TOOL_NOT_FOUND";
173
+ }
174
+ }
175
+
176
+ export class JavaNotFoundError extends ProjectError {
177
+ constructor() {
178
+ super(
179
+ "Java não encontrado",
180
+ "Defina JAVA_HOME ou certifique-se de que 'java' está no PATH"
181
+ );
182
+ this.code = "JAVA_NOT_FOUND";
183
+ }
184
+ }
185
+
186
+ // ===== Erros de Audit/Security =====
187
+ export class AuditError extends XavvaError {
188
+ constructor(message: string) {
189
+ super(
190
+ `Erro na auditoria: ${message}`,
191
+ "AUDIT_ERROR",
192
+ 7,
193
+ true
194
+ );
195
+ }
196
+ }
197
+
198
+ export class NetworkError extends XavvaError {
199
+ public readonly url: string;
200
+ public readonly originalError?: Error;
201
+
202
+ constructor(url: string, originalError?: Error) {
203
+ super(
204
+ `Falha na conexão com ${url}${originalError ? `: ${originalError.message}` : ""}`,
205
+ "NETWORK_ERROR",
206
+ 8,
207
+ true
208
+ );
209
+ this.url = url;
210
+ this.originalError = originalError;
211
+ }
212
+ }
213
+
214
+ // ===== Erros de File System =====
215
+ export class FileSystemError extends XavvaError {
216
+ constructor(message: string, path?: string) {
217
+ super(
218
+ path ? `${message}: ${path}` : message,
219
+ "FILESYSTEM_ERROR",
220
+ 9,
221
+ true
222
+ );
223
+ }
224
+ }
225
+
226
+ export class FileNotFoundError extends FileSystemError {
227
+ constructor(path: string) {
228
+ super("Arquivo não encontrado", path);
229
+ this.code = "FILE_NOT_FOUND";
230
+ }
231
+ }
232
+
233
+ export class PermissionError extends FileSystemError {
234
+ constructor(path: string) {
235
+ super("Permissão negada", path);
236
+ this.code = "PERMISSION_DENIED";
237
+ }
238
+ }
239
+
240
+ // ===== Erros de Comando =====
241
+ export class CommandError extends XavvaError {
242
+ constructor(command: string, message: string) {
243
+ super(
244
+ `Erro no comando '${command}': ${message}`,
245
+ "COMMAND_ERROR",
246
+ 10,
247
+ true
248
+ );
249
+ }
250
+ }
251
+
252
+ export class UnknownCommandError extends CommandError {
253
+ constructor(command: string) {
254
+ super(command, "Comando desconhecido");
255
+ this.code = "UNKNOWN_COMMAND";
256
+ }
257
+ }
258
+
259
+ // Helper para identificar se é erro operacional (vs programação)
260
+ export function isOperationalError(error: Error): boolean {
261
+ if (error instanceof XavvaError) {
262
+ return error.isOperational;
263
+ }
264
+ return false;
265
+ }
266
+
267
+ // Helper para obter exit code de qualquer erro
268
+ export function getExitCode(error: Error): number {
269
+ if (error instanceof XavvaError) {
270
+ return error.exitCode;
271
+ }
272
+ return 1; // Erro genérico
273
+ }