@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.
- package/README.md +55 -0
- package/package.json +4 -2
- package/src/commands/CompletionCommand.ts +212 -0
- package/src/commands/ConfigCommand.ts +184 -0
- package/src/commands/DeployCommand.ts +2 -1
- package/src/commands/EncodingCommand.ts +351 -0
- package/src/commands/HealthCommand.ts +302 -0
- package/src/commands/HelpCommand.ts +42 -0
- package/src/commands/HistoryCommand.ts +49 -0
- package/src/commands/InitCommand.ts +148 -0
- package/src/commands/RedoCommand.ts +36 -0
- package/src/config/versions.ts +63 -0
- package/src/di/container.ts +249 -0
- package/src/errors/ErrorHandler.ts +249 -0
- package/src/errors/XavvaError.ts +273 -0
- package/src/index.ts +136 -96
- package/src/services/AuditService.ts +3 -2
- package/src/services/BrowserService.ts +127 -16
- package/src/services/DeployWatcher.ts +183 -0
- package/src/services/EmbeddedTomcatService.ts +67 -37
- package/src/services/EncodingService.ts +548 -0
- package/src/services/FileWatcher.ts +243 -0
- package/src/services/HistoryService.ts +73 -0
- package/src/services/NotificationService.ts +145 -0
- package/src/services/TomcatService.ts +59 -26
- package/src/types/args.ts +151 -0
- package/src/types/config.ts +6 -0
- package/src/types/index.ts +7 -0
- package/src/utils/PathUtils.ts +221 -0
- package/src/utils/ProgressBar.ts +182 -0
- package/src/utils/config.ts +6 -0
- package/src/utils/parsers/JavaParser.ts +413 -0
- package/src/utils/platform.ts +2 -2
- package/src/services/WatcherService.ts +0 -117
|
@@ -30,6 +30,14 @@ export class HelpCommand implements Command {
|
|
|
30
30
|
${this.c("green", "profiles")} List available Maven/Gradle profiles
|
|
31
31
|
${this.c("green", "tomcat")} Manage embedded Tomcat (install, list, installed, use, status)
|
|
32
32
|
${this.c("green", "docs")} Generate endpoint documentation
|
|
33
|
+
${this.c("green", "encoding")} Convert file encoding (detect, convert, fix, list)
|
|
34
|
+
|
|
35
|
+
${this.c("cyan", "init")} Initialize project configuration (wizard)
|
|
36
|
+
${this.c("cyan", "config")} View/edit configuration (--interactive)
|
|
37
|
+
${this.c("cyan", "history")} Show command history
|
|
38
|
+
${this.c("cyan", "redo")} Repeat last command
|
|
39
|
+
${this.c("cyan", "health")} Check environment health
|
|
40
|
+
${this.c("cyan", "completion")} Generate shell completions (bash/zsh/fish)
|
|
33
41
|
|
|
34
42
|
${this.c("yellow", "GENERAL OPTIONS")}
|
|
35
43
|
${this.c("cyan", "-p, --path")} <path> Tomcat installation path
|
|
@@ -65,6 +73,13 @@ export class HelpCommand implements Command {
|
|
|
65
73
|
${this.c("cyan", "--strict")} Fail on critical conflicts (for CI/CD)
|
|
66
74
|
${this.c("cyan", "-o, --output")} <file> Export report as JSON
|
|
67
75
|
|
|
76
|
+
${this.c("yellow", "ENCODING OPTIONS")} ${this.c("dim", "(for xavva encoding)")}
|
|
77
|
+
${this.c("cyan", "--from")} <encoding> Source encoding (auto-detect if not specified)
|
|
78
|
+
${this.c("cyan", "--to")} <encoding> Target encoding (default: from xavva.json or UTF-8)
|
|
79
|
+
${this.c("cyan", "--backup")} Create backup before conversion
|
|
80
|
+
${this.c("cyan", "--dry-run")} Simulate without modifying files
|
|
81
|
+
${this.c("cyan", "--src")} <path> Source directory (default: src/)
|
|
82
|
+
|
|
68
83
|
${this.c("yellow", "EXAMPLES")}
|
|
69
84
|
${this.c("dim", "# Development with hot reload and dashboard")}
|
|
70
85
|
xavva dev --tui --watch
|
|
@@ -97,6 +112,33 @@ export class HelpCommand implements Command {
|
|
|
97
112
|
xavva tomcat status
|
|
98
113
|
xavva tomcat uninstall 9.0.115
|
|
99
114
|
|
|
115
|
+
${this.c("dim", "# Convert file encoding")}
|
|
116
|
+
xavva encoding detect src/main/java/MinhaClasse.java
|
|
117
|
+
xavva encoding convert --from utf-8 --to cp1252 src/main/java/
|
|
118
|
+
xavva encoding convert --to cp1252 --backup src/main/java/MinhaClasse.java
|
|
119
|
+
xavva encoding fix src/main/java/MinhaClasse.java # Fix mojibake
|
|
120
|
+
xavva encoding list # List all file encodings
|
|
121
|
+
|
|
122
|
+
${this.c("dim", "# Initialize new project")}
|
|
123
|
+
xavva init # Interactive wizard
|
|
124
|
+
|
|
125
|
+
${this.c("dim", "# Manage configuration")}
|
|
126
|
+
xavva config # View current config
|
|
127
|
+
xavva config --interactive # Edit config interactively
|
|
128
|
+
|
|
129
|
+
${this.c("dim", "# Command history")}
|
|
130
|
+
xavva history # Show recent commands
|
|
131
|
+
xavva history --clear # Clear history
|
|
132
|
+
xavva redo # Repeat last command
|
|
133
|
+
|
|
134
|
+
${this.c("dim", "# Health check")}
|
|
135
|
+
xavva health # Check environment health
|
|
136
|
+
|
|
137
|
+
${this.c("dim", "# Shell completions")}
|
|
138
|
+
xavva completion bash # Generate bash completions
|
|
139
|
+
xavva completion zsh # Generate zsh completions
|
|
140
|
+
eval "$(xavva completion bash)" # Enable in current shell
|
|
141
|
+
|
|
100
142
|
${this.c("yellow", "CONFIGURATION")}
|
|
101
143
|
Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
|
|
102
144
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { HistoryService } from "../services/HistoryService";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
|
|
6
|
+
export class HistoryCommand implements Command {
|
|
7
|
+
private historyService = new HistoryService();
|
|
8
|
+
|
|
9
|
+
async execute(_config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
10
|
+
const clear = args?.["clear"] || false;
|
|
11
|
+
const limit = parseInt(String(args?.["limit"] || "10"));
|
|
12
|
+
|
|
13
|
+
if (clear) {
|
|
14
|
+
await this.historyService.clear();
|
|
15
|
+
Logger.success("Histórico limpo!");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const entries = await this.historyService.getRecent(limit);
|
|
20
|
+
const stats = await this.historyService.getStats();
|
|
21
|
+
|
|
22
|
+
Logger.banner("history");
|
|
23
|
+
Logger.section(`Últimos ${entries.length} comandos`);
|
|
24
|
+
|
|
25
|
+
if (entries.length === 0) {
|
|
26
|
+
Logger.dim("Nenhum comando no histórico");
|
|
27
|
+
Logger.endSection();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < entries.length; i++) {
|
|
32
|
+
const entry = entries[i];
|
|
33
|
+
const date = new Date(entry.timestamp);
|
|
34
|
+
const time = date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
35
|
+
const icon = entry.success
|
|
36
|
+
? `${Logger.C.success}✓${Logger.C.reset}`
|
|
37
|
+
: `${Logger.C.error}✗${Logger.C.reset}`;
|
|
38
|
+
|
|
39
|
+
const args = entry.args.length > 0 ? entry.args.join(" ") : "";
|
|
40
|
+
const duration = entry.duration ? `${Logger.C.gray}(${entry.duration.toFixed(1)}s)${Logger.C.reset}` : "";
|
|
41
|
+
|
|
42
|
+
Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.dim}${time}${Logger.C.reset} ${icon} ${Logger.C.white}xavva ${entry.command}${Logger.C.reset} ${Logger.C.gray}${args}${Logger.C.reset} ${duration}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Logger.endSection();
|
|
46
|
+
Logger.info(`Total: ${stats.total} | Sucesso: ${stats.successful} | Falha: ${stats.failed}`);
|
|
47
|
+
Logger.dim("Use 'xavva redo' para repetir o último comando");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { input, select, confirm, number } from "@inquirer/prompts";
|
|
2
|
+
import { writeFile, access } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { constants } from "fs";
|
|
5
|
+
import type { Command } from "./Command";
|
|
6
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
7
|
+
import { Logger } from "../utils/ui";
|
|
8
|
+
|
|
9
|
+
export class InitCommand implements Command {
|
|
10
|
+
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
11
|
+
Logger.banner("init");
|
|
12
|
+
Logger.section("Wizard de Configuração");
|
|
13
|
+
Logger.info("Vamos configurar seu projeto Xavva");
|
|
14
|
+
Logger.newline();
|
|
15
|
+
|
|
16
|
+
// Detectar build tool
|
|
17
|
+
const buildTool = await this.detectBuildTool();
|
|
18
|
+
|
|
19
|
+
// Nome da aplicação
|
|
20
|
+
const appName = await input({
|
|
21
|
+
message: "Nome da aplicação:",
|
|
22
|
+
default: process.cwd().split(/[/\\]/).pop() || "my-app",
|
|
23
|
+
validate: (value) => value.length > 0 || "Nome é obrigatório"
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Profile
|
|
27
|
+
const profile = await select({
|
|
28
|
+
message: "Profile padrão:",
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: "desenvolvimento", value: "dev" },
|
|
31
|
+
{ name: "teste", value: "test" },
|
|
32
|
+
{ name: "produção", value: "prod" },
|
|
33
|
+
{ name: "customizado", value: "custom" }
|
|
34
|
+
],
|
|
35
|
+
default: "dev"
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const customProfile = profile === "custom" ? await input({
|
|
39
|
+
message: "Nome do profile:",
|
|
40
|
+
default: "local"
|
|
41
|
+
}) : profile;
|
|
42
|
+
|
|
43
|
+
// Porta do Tomcat
|
|
44
|
+
const port = await number({
|
|
45
|
+
message: "Porta do Tomcat:",
|
|
46
|
+
default: 8080,
|
|
47
|
+
validate: (value) => (value && value > 0 && value < 65536) || "Porta inválida"
|
|
48
|
+
}) || 8080;
|
|
49
|
+
|
|
50
|
+
// Configurações opcionais
|
|
51
|
+
Logger.newline();
|
|
52
|
+
Logger.dim("Configurações avançadas:");
|
|
53
|
+
|
|
54
|
+
const useEmbedded = await confirm({
|
|
55
|
+
message: "Usar Tomcat embutido (auto-download)?",
|
|
56
|
+
default: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const enableCache = await confirm({
|
|
60
|
+
message: "Habilitar cache de build?",
|
|
61
|
+
default: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const enableTui = await confirm({
|
|
65
|
+
message: "Habilitar dashboard TUI?",
|
|
66
|
+
default: true
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const encoding = await select({
|
|
70
|
+
message: "Encoding:",
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: "UTF-8", value: "UTF-8" },
|
|
73
|
+
{ name: "ISO-8859-1", value: "ISO-8859-1" },
|
|
74
|
+
{ name: "Windows-1252", value: "Windows-1252" }
|
|
75
|
+
],
|
|
76
|
+
default: "UTF-8"
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Montar configuração
|
|
80
|
+
const config: Record<string, unknown> = {
|
|
81
|
+
appName,
|
|
82
|
+
buildTool,
|
|
83
|
+
profile: customProfile,
|
|
84
|
+
port,
|
|
85
|
+
cache: enableCache,
|
|
86
|
+
tui: enableTui,
|
|
87
|
+
encoding
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (useEmbedded) {
|
|
91
|
+
config.embedded = true;
|
|
92
|
+
config.tomcatVersion = "10.1.52";
|
|
93
|
+
} else {
|
|
94
|
+
const tomcatPath = await input({
|
|
95
|
+
message: "Caminho do Tomcat (CATALINA_HOME):",
|
|
96
|
+
validate: async (value) => {
|
|
97
|
+
if (!value) return "Caminho é obrigatório";
|
|
98
|
+
try {
|
|
99
|
+
await access(value, constants.R_OK);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return "Caminho não acessível";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
config.tomcatPath = tomcatPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Salvar arquivo
|
|
110
|
+
Logger.newline();
|
|
111
|
+
Logger.step("Salvando configuração...");
|
|
112
|
+
|
|
113
|
+
const configPath = join(process.cwd(), "xavva.json");
|
|
114
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
115
|
+
|
|
116
|
+
Logger.success(`Configuração salva em ${configPath}`);
|
|
117
|
+
Logger.newline();
|
|
118
|
+
Logger.ready("Projeto configurado!");
|
|
119
|
+
Logger.info("Próximos passos:");
|
|
120
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva build${Logger.C.reset} ${Logger.C.gray}- Compilar projeto${Logger.C.reset}`);
|
|
121
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva deploy${Logger.C.reset} ${Logger.C.gray}- Build + deploy${Logger.C.reset}`);
|
|
122
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva doctor${Logger.C.reset} ${Logger.C.gray}- Verificar ambiente${Logger.C.reset}`);
|
|
123
|
+
Logger.done();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async detectBuildTool(): Promise<"maven" | "gradle"> {
|
|
127
|
+
try {
|
|
128
|
+
await access(join(process.cwd(), "pom.xml"), constants.R_OK);
|
|
129
|
+
Logger.info("Detectado: Projeto Maven");
|
|
130
|
+
return "maven";
|
|
131
|
+
} catch {
|
|
132
|
+
try {
|
|
133
|
+
await access(join(process.cwd(), "build.gradle"), constants.R_OK);
|
|
134
|
+
Logger.info("Detectado: Projeto Gradle");
|
|
135
|
+
return "gradle";
|
|
136
|
+
} catch {
|
|
137
|
+
const choice = await select({
|
|
138
|
+
message: "Build tool:",
|
|
139
|
+
choices: [
|
|
140
|
+
{ name: "Maven", value: "maven" },
|
|
141
|
+
{ name: "Gradle", value: "gradle" }
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
return choice;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { HistoryService } from "../services/HistoryService";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
import { ProcessManager } from "../utils/processManager";
|
|
6
|
+
|
|
7
|
+
export class RedoCommand implements Command {
|
|
8
|
+
private historyService = new HistoryService();
|
|
9
|
+
|
|
10
|
+
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
11
|
+
const lastEntry = await this.historyService.getLast();
|
|
12
|
+
|
|
13
|
+
if (!lastEntry) {
|
|
14
|
+
Logger.error("Nenhum comando no histórico");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args = lastEntry.args.length > 0 ? lastEntry.args.join(" ") : "";
|
|
19
|
+
Logger.banner("redo");
|
|
20
|
+
Logger.info(`Repetindo: ${Logger.C.white}xavva ${lastEntry.command}${Logger.C.reset} ${Logger.C.gray}${args}${Logger.C.reset}`);
|
|
21
|
+
Logger.newline();
|
|
22
|
+
|
|
23
|
+
// Re-executar o comando
|
|
24
|
+
const proc = Bun.spawn([
|
|
25
|
+
"bun", "run", "src/index.ts",
|
|
26
|
+
lastEntry.command,
|
|
27
|
+
...lastEntry.args
|
|
28
|
+
], {
|
|
29
|
+
stdio: "inherit",
|
|
30
|
+
cwd: process.cwd()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await proc.exited;
|
|
34
|
+
ProcessManager.getInstance().shutdown(proc.exitCode || 0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurações centralizadas de versões
|
|
3
|
+
* Evita hardcoding espalhado pelo código
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const VERSIONS = {
|
|
7
|
+
// Versões padrão do Tomcat
|
|
8
|
+
TOMCAT: {
|
|
9
|
+
DEFAULT: "10.1.52",
|
|
10
|
+
AVAILABLE: {
|
|
11
|
+
"10.1.52": { sha512: "" },
|
|
12
|
+
"9.0.115": { sha512: "" },
|
|
13
|
+
"11.0.18": { sha512: "" },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
// HotswapAgent
|
|
18
|
+
HOTSWAP_AGENT: {
|
|
19
|
+
VERSION: "2.0.3",
|
|
20
|
+
URL: "https://github.com/HotswapProjects/HotswapAgent/releases/download/RELEASE-{version}/hotswap-agent-{version}.jar",
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Configurações padrão
|
|
24
|
+
DEFAULTS: {
|
|
25
|
+
TOMCAT_PORT: 8080,
|
|
26
|
+
DEBUG_PORT: 5005,
|
|
27
|
+
DEBOUNCE_MS: 300,
|
|
28
|
+
COOLING_MS: 1000,
|
|
29
|
+
},
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
// Type helpers
|
|
33
|
+
export type TomcatVersion = keyof typeof VERSIONS.TOMCAT.AVAILABLE;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Obtém URL de download do HotswapAgent
|
|
37
|
+
*/
|
|
38
|
+
export function getHotswapAgentUrl(version: string = VERSIONS.HOTSWAP_AGENT.VERSION): string {
|
|
39
|
+
return VERSIONS.HOTSWAP_AGENT.URL
|
|
40
|
+
.replace(/{version}/g, version);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Verifica se versão do Tomcat é suportada
|
|
45
|
+
*/
|
|
46
|
+
export function isSupportedTomcatVersion(version: string): version is TomcatVersion {
|
|
47
|
+
return version in VERSIONS.TOMCAT.AVAILABLE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Obtém versões disponíveis do Tomcat
|
|
52
|
+
*/
|
|
53
|
+
export function getAvailableTomcatVersions(): string[] {
|
|
54
|
+
return Object.keys(VERSIONS.TOMCAT.AVAILABLE);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Obtém SHA512 de uma versão do Tomcat
|
|
59
|
+
*/
|
|
60
|
+
export function getTomcatSha512(version: string): string | undefined {
|
|
61
|
+
const info = VERSIONS.TOMCAT.AVAILABLE[version as TomcatVersion];
|
|
62
|
+
return info?.sha512;
|
|
63
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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 { InitCommand } from "../commands/InitCommand";
|
|
28
|
+
import { ConfigCommand } from "../commands/ConfigCommand";
|
|
29
|
+
import { HistoryCommand } from "../commands/HistoryCommand";
|
|
30
|
+
import { RedoCommand } from "../commands/RedoCommand";
|
|
31
|
+
import { HealthCommand } from "../commands/HealthCommand";
|
|
32
|
+
import { CompletionCommand } from "../commands/CompletionCommand";
|
|
33
|
+
import { HistoryService } from "../services/HistoryService";
|
|
34
|
+
import { NotificationService } from "../services/NotificationService";
|
|
35
|
+
import type { Command } from "../commands/Command";
|
|
36
|
+
import { Logger } from "../utils/ui";
|
|
37
|
+
|
|
38
|
+
export interface Services {
|
|
39
|
+
projectService: ProjectService;
|
|
40
|
+
tomcatService: TomcatService;
|
|
41
|
+
buildCacheService: BuildCacheService;
|
|
42
|
+
buildService: BuildService;
|
|
43
|
+
auditService: AuditService;
|
|
44
|
+
dashboardService: DashboardService;
|
|
45
|
+
logAnalyzer: LogAnalyzer;
|
|
46
|
+
historyService: HistoryService;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Commands {
|
|
50
|
+
deploy: DeployCommand;
|
|
51
|
+
dev: DeployCommand;
|
|
52
|
+
build: BuildCommand;
|
|
53
|
+
start: StartCommand;
|
|
54
|
+
logs: LogsCommand;
|
|
55
|
+
audit: AuditCommand;
|
|
56
|
+
profiles: ProfilesCommand;
|
|
57
|
+
run: RunCommand;
|
|
58
|
+
debug: RunCommand;
|
|
59
|
+
help: HelpCommand;
|
|
60
|
+
doctor: DoctorCommand;
|
|
61
|
+
deps: DepsCommand;
|
|
62
|
+
tomcat: TomcatCommand;
|
|
63
|
+
encoding: EncodingCommand;
|
|
64
|
+
docs: DocsCommand;
|
|
65
|
+
init: InitCommand;
|
|
66
|
+
config: ConfigCommand;
|
|
67
|
+
history: HistoryCommand;
|
|
68
|
+
redo: RedoCommand;
|
|
69
|
+
health: HealthCommand;
|
|
70
|
+
completion: CompletionCommand;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class DIContainer {
|
|
74
|
+
private config: AppConfig;
|
|
75
|
+
private services: Partial<Services> = {};
|
|
76
|
+
private commands: Partial<Commands> = {};
|
|
77
|
+
private isInitialized = false;
|
|
78
|
+
|
|
79
|
+
constructor(config: AppConfig) {
|
|
80
|
+
this.config = config;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Inicializa todos os serviços e comandos
|
|
85
|
+
*/
|
|
86
|
+
initialize(): void {
|
|
87
|
+
if (this.isInitialized) {
|
|
88
|
+
Logger.debug("DI Container já inicializado");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.initializeServices();
|
|
93
|
+
this.initializeCommands();
|
|
94
|
+
this.isInitialized = true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private initializeServices(): void {
|
|
98
|
+
// Serviços base (sem dependências ou com dependências simples)
|
|
99
|
+
const projectService = new ProjectService(this.config.project);
|
|
100
|
+
const buildCacheService = new BuildCacheService();
|
|
101
|
+
const dashboardService = new DashboardService(this.config);
|
|
102
|
+
const logAnalyzer = new LogAnalyzer(this.config.project);
|
|
103
|
+
|
|
104
|
+
// Configura Logger com dashboard
|
|
105
|
+
Logger.setDashboard(dashboardService);
|
|
106
|
+
|
|
107
|
+
// Serviços com dependências
|
|
108
|
+
const buildService = new BuildService(
|
|
109
|
+
this.config.project,
|
|
110
|
+
this.config.tomcat,
|
|
111
|
+
projectService,
|
|
112
|
+
buildCacheService
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const tomcatService = new TomcatService(this.config.tomcat);
|
|
116
|
+
tomcatService.setProjectService(projectService);
|
|
117
|
+
|
|
118
|
+
const auditService = new AuditService(this.config.tomcat);
|
|
119
|
+
const historyService = new HistoryService();
|
|
120
|
+
|
|
121
|
+
this.services = {
|
|
122
|
+
projectService,
|
|
123
|
+
buildCacheService,
|
|
124
|
+
buildService,
|
|
125
|
+
tomcatService,
|
|
126
|
+
auditService,
|
|
127
|
+
dashboardService,
|
|
128
|
+
logAnalyzer,
|
|
129
|
+
historyService,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private initializeCommands(): void {
|
|
134
|
+
const { tomcatService, buildService, auditService, dashboardService, logAnalyzer } = this.services;
|
|
135
|
+
|
|
136
|
+
if (!tomcatService || !buildService || !auditService || !dashboardService || !logAnalyzer) {
|
|
137
|
+
throw new Error("Serviços não inicializados corretamente");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Comandos que compartilham instâncias
|
|
141
|
+
const deployCmd = new DeployCommand(tomcatService, buildService);
|
|
142
|
+
const logsCmd = new LogsCommand(dashboardService, logAnalyzer);
|
|
143
|
+
|
|
144
|
+
this.commands = {
|
|
145
|
+
deploy: deployCmd,
|
|
146
|
+
dev: deployCmd, // dev reusa deploy
|
|
147
|
+
build: new BuildCommand(buildService),
|
|
148
|
+
start: new StartCommand(tomcatService),
|
|
149
|
+
logs: logsCmd,
|
|
150
|
+
audit: new AuditCommand(auditService),
|
|
151
|
+
profiles: new ProfilesCommand(this.services.projectService!),
|
|
152
|
+
run: new RunCommand(),
|
|
153
|
+
debug: new RunCommand(),
|
|
154
|
+
help: new HelpCommand(),
|
|
155
|
+
doctor: new DoctorCommand(),
|
|
156
|
+
deps: new DepsCommand(),
|
|
157
|
+
tomcat: new TomcatCommand(),
|
|
158
|
+
encoding: new EncodingCommand(),
|
|
159
|
+
docs: new DocsCommand(),
|
|
160
|
+
init: new InitCommand(),
|
|
161
|
+
config: new ConfigCommand(),
|
|
162
|
+
history: new HistoryCommand(),
|
|
163
|
+
redo: new RedoCommand(),
|
|
164
|
+
health: new HealthCommand(),
|
|
165
|
+
completion: new CompletionCommand(),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Obtém um serviço pelo nome
|
|
171
|
+
*/
|
|
172
|
+
getService<K extends keyof Services>(name: K): Services[K] {
|
|
173
|
+
if (!this.isInitialized) {
|
|
174
|
+
this.initialize();
|
|
175
|
+
}
|
|
176
|
+
const service = this.services[name];
|
|
177
|
+
if (!service) {
|
|
178
|
+
throw new Error(`Serviço '${name}' não encontrado no container`);
|
|
179
|
+
}
|
|
180
|
+
return service;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Obtém um comando pelo nome
|
|
185
|
+
*/
|
|
186
|
+
getCommand<K extends keyof Commands>(name: K): Commands[K] {
|
|
187
|
+
if (!this.isInitialized) {
|
|
188
|
+
this.initialize();
|
|
189
|
+
}
|
|
190
|
+
const command = this.commands[name];
|
|
191
|
+
if (!command) {
|
|
192
|
+
throw new Error(`Comando '${name}' não encontrado no container`);
|
|
193
|
+
}
|
|
194
|
+
return command;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Obtém todos os serviços
|
|
199
|
+
*/
|
|
200
|
+
getAllServices(): Services {
|
|
201
|
+
if (!this.isInitialized) {
|
|
202
|
+
this.initialize();
|
|
203
|
+
}
|
|
204
|
+
return this.services as Services;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Obtém todos os comandos
|
|
209
|
+
*/
|
|
210
|
+
getAllCommands(): Commands {
|
|
211
|
+
if (!this.isInitialized) {
|
|
212
|
+
this.initialize();
|
|
213
|
+
}
|
|
214
|
+
return this.commands as Commands;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Registra um serviço customizado (útil para testes)
|
|
219
|
+
*/
|
|
220
|
+
registerService<K extends keyof Services>(name: K, service: Services[K]): void {
|
|
221
|
+
this.services[name] = service;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Registra um comando customizado (útil para testes)
|
|
226
|
+
*/
|
|
227
|
+
registerCommand<K extends keyof Commands>(name: K, command: Commands[K]): void {
|
|
228
|
+
this.commands[name] = command;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Singleton para uso global
|
|
233
|
+
let globalContainer: DIContainer | null = null;
|
|
234
|
+
|
|
235
|
+
export function createContainer(config: AppConfig): DIContainer {
|
|
236
|
+
globalContainer = new DIContainer(config);
|
|
237
|
+
return globalContainer;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function getContainer(): DIContainer {
|
|
241
|
+
if (!globalContainer) {
|
|
242
|
+
throw new Error("Container não inicializado. Chame createContainer primeiro.");
|
|
243
|
+
}
|
|
244
|
+
return globalContainer;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function resetContainer(): void {
|
|
248
|
+
globalContainer = null;
|
|
249
|
+
}
|