@archznn/xavva 2.6.0 → 2.7.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,226 @@
1
+ /**
2
+ * Container de Injeção de Dependência (DI) simplificado
3
+ * Centraliza a criação e injeção de serviços
4
+ */
5
+
6
+ import type { AppConfig } from "../types/config";
7
+ import { ProjectService } from "../services/ProjectService";
8
+ import { TomcatService } from "../services/TomcatService";
9
+ import { BuildService } from "../services/BuildService";
10
+ import { BuildCacheService } from "../services/BuildCacheService";
11
+ import { AuditService } from "../services/AuditService";
12
+ import { DashboardService } from "../services/DashboardService";
13
+ import { LogAnalyzer } from "../services/LogAnalyzer";
14
+ import { DeployCommand } from "../commands/DeployCommand";
15
+ import { BuildCommand } from "../commands/BuildCommand";
16
+ import { StartCommand } from "../commands/StartCommand";
17
+ import { LogsCommand } from "../commands/LogsCommand";
18
+ import { AuditCommand } from "../commands/AuditCommand";
19
+ import { ProfilesCommand } from "../commands/ProfilesCommand";
20
+ import { RunCommand } from "../commands/RunCommand";
21
+ import { HelpCommand } from "../commands/HelpCommand";
22
+ import { DoctorCommand } from "../commands/DoctorCommand";
23
+ import { DepsCommand } from "../commands/DepsCommand";
24
+ import { TomcatCommand } from "../commands/TomcatCommand";
25
+ import { EncodingCommand } from "../commands/EncodingCommand";
26
+ import { DocsCommand } from "../commands/DocsCommand";
27
+ import type { Command } from "../commands/Command";
28
+ import { Logger } from "../utils/ui";
29
+
30
+ export interface Services {
31
+ projectService: ProjectService;
32
+ tomcatService: TomcatService;
33
+ buildCacheService: BuildCacheService;
34
+ buildService: BuildService;
35
+ auditService: AuditService;
36
+ dashboardService: DashboardService;
37
+ logAnalyzer: LogAnalyzer;
38
+ }
39
+
40
+ export interface Commands {
41
+ deploy: DeployCommand;
42
+ dev: DeployCommand;
43
+ build: BuildCommand;
44
+ start: StartCommand;
45
+ logs: LogsCommand;
46
+ audit: AuditCommand;
47
+ profiles: ProfilesCommand;
48
+ run: RunCommand;
49
+ debug: RunCommand;
50
+ help: HelpCommand;
51
+ doctor: DoctorCommand;
52
+ deps: DepsCommand;
53
+ tomcat: TomcatCommand;
54
+ encoding: EncodingCommand;
55
+ docs: DocsCommand;
56
+ }
57
+
58
+ export class DIContainer {
59
+ private config: AppConfig;
60
+ private services: Partial<Services> = {};
61
+ private commands: Partial<Commands> = {};
62
+ private isInitialized = false;
63
+
64
+ constructor(config: AppConfig) {
65
+ this.config = config;
66
+ }
67
+
68
+ /**
69
+ * Inicializa todos os serviços e comandos
70
+ */
71
+ initialize(): void {
72
+ if (this.isInitialized) {
73
+ Logger.debug("DI Container já inicializado");
74
+ return;
75
+ }
76
+
77
+ this.initializeServices();
78
+ this.initializeCommands();
79
+ this.isInitialized = true;
80
+ }
81
+
82
+ private initializeServices(): void {
83
+ // Serviços base (sem dependências ou com dependências simples)
84
+ const projectService = new ProjectService(this.config.project);
85
+ const buildCacheService = new BuildCacheService();
86
+ const dashboardService = new DashboardService(this.config);
87
+ const logAnalyzer = new LogAnalyzer(this.config.project);
88
+
89
+ // Configura Logger com dashboard
90
+ Logger.setDashboard(dashboardService);
91
+
92
+ // Serviços com dependências
93
+ const buildService = new BuildService(
94
+ this.config.project,
95
+ this.config.tomcat,
96
+ projectService,
97
+ buildCacheService
98
+ );
99
+
100
+ const tomcatService = new TomcatService(this.config.tomcat);
101
+ tomcatService.setProjectService(projectService);
102
+
103
+ const auditService = new AuditService(this.config.tomcat);
104
+
105
+ this.services = {
106
+ projectService,
107
+ buildCacheService,
108
+ buildService,
109
+ tomcatService,
110
+ auditService,
111
+ dashboardService,
112
+ logAnalyzer,
113
+ };
114
+ }
115
+
116
+ private initializeCommands(): void {
117
+ const { tomcatService, buildService, auditService, dashboardService, logAnalyzer } = this.services;
118
+
119
+ if (!tomcatService || !buildService || !auditService || !dashboardService || !logAnalyzer) {
120
+ throw new Error("Serviços não inicializados corretamente");
121
+ }
122
+
123
+ // Comandos que compartilham instâncias
124
+ const deployCmd = new DeployCommand(tomcatService, buildService);
125
+ const logsCmd = new LogsCommand(dashboardService, logAnalyzer);
126
+
127
+ this.commands = {
128
+ deploy: deployCmd,
129
+ dev: deployCmd, // dev reusa deploy
130
+ build: new BuildCommand(buildService),
131
+ start: new StartCommand(tomcatService),
132
+ logs: logsCmd,
133
+ audit: new AuditCommand(auditService),
134
+ profiles: new ProfilesCommand(this.services.projectService!),
135
+ run: new RunCommand(),
136
+ debug: new RunCommand(),
137
+ help: new HelpCommand(),
138
+ doctor: new DoctorCommand(),
139
+ deps: new DepsCommand(),
140
+ tomcat: new TomcatCommand(),
141
+ encoding: new EncodingCommand(),
142
+ docs: new DocsCommand(),
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Obtém um serviço pelo nome
148
+ */
149
+ getService<K extends keyof Services>(name: K): Services[K] {
150
+ if (!this.isInitialized) {
151
+ this.initialize();
152
+ }
153
+ const service = this.services[name];
154
+ if (!service) {
155
+ throw new Error(`Serviço '${name}' não encontrado no container`);
156
+ }
157
+ return service;
158
+ }
159
+
160
+ /**
161
+ * Obtém um comando pelo nome
162
+ */
163
+ getCommand<K extends keyof Commands>(name: K): Commands[K] {
164
+ if (!this.isInitialized) {
165
+ this.initialize();
166
+ }
167
+ const command = this.commands[name];
168
+ if (!command) {
169
+ throw new Error(`Comando '${name}' não encontrado no container`);
170
+ }
171
+ return command;
172
+ }
173
+
174
+ /**
175
+ * Obtém todos os serviços
176
+ */
177
+ getAllServices(): Services {
178
+ if (!this.isInitialized) {
179
+ this.initialize();
180
+ }
181
+ return this.services as Services;
182
+ }
183
+
184
+ /**
185
+ * Obtém todos os comandos
186
+ */
187
+ getAllCommands(): Commands {
188
+ if (!this.isInitialized) {
189
+ this.initialize();
190
+ }
191
+ return this.commands as Commands;
192
+ }
193
+
194
+ /**
195
+ * Registra um serviço customizado (útil para testes)
196
+ */
197
+ registerService<K extends keyof Services>(name: K, service: Services[K]): void {
198
+ this.services[name] = service;
199
+ }
200
+
201
+ /**
202
+ * Registra um comando customizado (útil para testes)
203
+ */
204
+ registerCommand<K extends keyof Commands>(name: K, command: Commands[K]): void {
205
+ this.commands[name] = command;
206
+ }
207
+ }
208
+
209
+ // Singleton para uso global
210
+ let globalContainer: DIContainer | null = null;
211
+
212
+ export function createContainer(config: AppConfig): DIContainer {
213
+ globalContainer = new DIContainer(config);
214
+ return globalContainer;
215
+ }
216
+
217
+ export function getContainer(): DIContainer {
218
+ if (!globalContainer) {
219
+ throw new Error("Container não inicializado. Chame createContainer primeiro.");
220
+ }
221
+ return globalContainer;
222
+ }
223
+
224
+ export function resetContainer(): void {
225
+ globalContainer = null;
226
+ }
@@ -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
+ }