@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
@@ -1,97 +1,154 @@
1
+ /**
2
+ * Gerenciamento de configuração do XAVVA CLI
3
+ *
4
+ * Responsabilidades:
5
+ * - Parse de argumentos CLI
6
+ * - Load de xavva.json
7
+ * - Setup de Tomcat embutido
8
+ * - Merge de configurações (CLI > xavva.json > env > defaults)
9
+ * - Validação com Zod
10
+ */
11
+
1
12
  import { parseArgs } from "util";
2
13
  import path from "path";
3
14
  import fs from "fs";
4
- import { DEFAULT_TOMCAT_PORT, DEFAULT_DEBUG_PORT } from "./constants";
15
+ import { PORTS } from "../config/versions";
5
16
  import type { AppConfig, CLIArguments, CommandContext } from "../types/config";
6
17
  import { EmbeddedTomcatService } from "../services/EmbeddedTomcatService";
7
- import { Logger } from "./ui";
18
+ import { Logger, C } from "./ui";
19
+ import { validateAppConfig, validatePort, type ValidatedAppConfig } from "../types/configSchema";
20
+
21
+ // Parse options extraídas para reuso
22
+ const PARSE_OPTIONS = {
23
+ path: { type: "string" as const, short: "p" },
24
+ tool: { type: "string" as const, short: "t" },
25
+ name: { type: "string" as const, short: "n" },
26
+ port: { type: "string" as const },
27
+ "no-build": { type: "boolean" as const, short: "s" },
28
+ scan: { type: "boolean" as const },
29
+ clean: { type: "boolean" as const, short: "c" },
30
+ quiet: { type: "boolean" as const, short: "q" },
31
+ help: { type: "boolean" as const, short: "h" },
32
+ version: { type: "boolean" as const, short: "v" },
33
+ debug: { type: "boolean" as const, short: "d" },
34
+ watch: { type: "boolean" as const, short: "w" },
35
+ profile: { type: "string" as const, short: "P" },
36
+ grep: { type: "string" as const, short: "G" },
37
+ verbose: { type: "boolean" as const, short: "V" },
38
+ encoding: { type: "string" as const, short: "e" },
39
+ dp: { type: "string" as const },
40
+ fix: { type: "boolean" as const },
41
+ tui: { type: "boolean" as const },
42
+ output: { type: "string" as const, short: "o" },
43
+ strict: { type: "boolean" as const },
44
+ "tomcat-version": { type: "string" as const },
45
+ yes: { type: "boolean" as const, short: "y" },
46
+ war: { type: "boolean" as const, short: "W" },
47
+ cache: { type: "boolean" as const },
48
+ from: { type: "string" as const },
49
+ to: { type: "string" as const },
50
+ backup: { type: "boolean" as const },
51
+ "dry-run": { type: "boolean" as const },
52
+ force: { type: "boolean" as const },
53
+ src: { type: "string" as const },
54
+ env: { type: "string" as const },
55
+ environment: { type: "string" as const },
56
+ coverage: { type: "boolean" as const },
57
+ "fail-fast": { type: "boolean" as const },
58
+ parallel: { type: "boolean" as const },
59
+ interactive: { type: "boolean" as const, short: "i" },
60
+ "base-url": { type: "string" as const },
61
+ body: { type: "string" as const },
62
+ file: { type: "string" as const },
63
+ header: { type: "string" as const, multiple: true },
64
+ "content-type": { type: "string" as const },
65
+ accept: { type: "string" as const },
66
+ param: { type: "string" as const, multiple: true },
67
+ timeout: { type: "string" as const },
68
+ tag: { type: "string" as const },
69
+ "java-version": { type: "string" as const },
70
+ detached: { type: "boolean" as const, short: "d" },
71
+ registry: { type: "string" as const },
72
+ namespace: { type: "string" as const },
73
+ // Clean command
74
+ all: { type: "boolean" as const },
75
+ build: { type: "boolean" as const },
76
+ // IDE command
77
+ ide: { type: "string" as const },
78
+ };
8
79
 
9
80
  export class ConfigManager {
81
+ /**
82
+ * Carrega e valida configuração completa
83
+ */
10
84
  static async load(): Promise<CommandContext> {
11
- const args = Bun.argv.slice(Bun.argv[0].endsWith("bun.exe") || Bun.argv[0].endsWith("bun") ? 2 : 1);
85
+ const argv = this.getArgv();
86
+ const { values, positionals } = this.parseCliArgs(argv);
87
+ const cliValues = values as CLIArguments;
88
+
89
+ // Detectar contexto
90
+ const commandContext = this.detectCommandContext(positionals);
12
91
 
13
- const { values, positionals } = parseArgs({
14
- args: args,
15
- options: {
16
- path: { type: "string", short: "p" },
17
- tool: { type: "string", short: "t" },
18
- name: { type: "string", short: "n" },
19
- port: { type: "string" },
20
- "no-build": { type: "boolean", short: "s" },
21
- "scan": { type: "boolean" },
22
- clean: { type: "boolean", short: "c" },
23
- quiet: { type: "boolean", short: "q" },
24
- help: { type: "boolean", short: "h" },
25
- version: { type: "boolean", short: "v" },
26
- debug: { type: "boolean", short: "d" },
27
- watch: { type: "boolean", short: "w" },
28
- profile: { type: "string", short: "P" },
29
- grep: { type: "string", short: "G" },
30
- verbose: { type: "boolean", short: "V" },
31
- encoding: { type: "string", short: "e" },
32
- dp: { type: "string" },
33
- fix: { type: "boolean" },
34
- tui: { type: "boolean" },
35
- output: { type: "string", short: "o" },
36
- strict: { type: "boolean" },
37
- "tomcat-version": { type: "string" },
38
- yes: { type: "boolean", short: "y" },
39
- war: { type: "boolean", short: "W" },
40
- cache: { type: "boolean" },
41
- from: { type: "string" },
42
- to: { type: "string" },
43
- backup: { type: "boolean" },
44
- "dry-run": { type: "boolean" },
45
- force: { type: "boolean" },
46
- src: { type: "string" },
47
- // Multi-environment
48
- env: { type: "string" },
49
- environment: { type: "string" },
50
- // Test runner
51
- coverage: { type: "boolean" },
52
- "fail-fast": { type: "boolean" },
53
- parallel: { type: "boolean" },
54
- // HTTP client
55
- interactive: { type: "boolean", short: "i" },
56
- "base-url": { type: "string" },
57
- body: { type: "string" },
58
- file: { type: "string" },
59
- header: { type: "string", multiple: true },
60
- "content-type": { type: "string" },
61
- accept: { type: "string" },
62
- param: { type: "string", multiple: true },
63
- timeout: { type: "string" },
64
- // Docker
65
- tag: { type: "string" },
66
- "java-version": { type: "string" },
67
- detached: { type: "boolean", short: "d" },
68
- registry: { type: "string" },
69
- namespace: { type: "string" },
70
- },
71
- strict: false,
72
- allowPositionals: true,
92
+ // Carregar configs de diferentes fontes
93
+ const fileConfig = await this.loadConfigFile();
94
+ const envConfig = this.loadEnvConfig();
95
+
96
+ // Merge de configurações (prioridade: CLI > env > file > defaults)
97
+ const mergedConfig = await this.mergeConfigurations({
98
+ cli: cliValues,
99
+ file: fileConfig,
100
+ env: envConfig,
101
+ context: commandContext,
73
102
  });
74
103
 
75
- const cliValues = values as CLIArguments;
104
+ // Setup de Tomcat embutido se necessário
105
+ const finalConfig = await this.setupTomcatIfNeeded(mergedConfig, cliValues, commandContext);
76
106
 
77
- // Load xavva.json
78
- const xavvaJsonPath = path.join(process.cwd(), "xavva.json");
79
- let xavvaJson: Partial<CLIArguments> = {};
80
- if (fs.existsSync(xavvaJsonPath)) {
81
- try {
82
- xavvaJson = JSON.parse(fs.readFileSync(xavvaJsonPath, "utf8"));
83
- } catch (e) {
84
- console.error("Error reading xavva.json:", (e as Error).message);
85
- }
107
+ // Validar configuração final
108
+ const validatedConfig = this.validateConfig(finalConfig);
109
+
110
+ // Ajustes baseados no comando
111
+ if (commandContext.isDev) {
112
+ cliValues.watch = true;
86
113
  }
87
114
 
115
+ this.ensureGitIgnore();
116
+
117
+ return {
118
+ config: validatedConfig,
119
+ positionals,
120
+ values: cliValues
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Obtém argumentos do processo
126
+ */
127
+ private static getArgv(): string[] {
128
+ const isBun = Bun.argv[0].endsWith("bun.exe") || Bun.argv[0].endsWith("bun");
129
+ return Bun.argv.slice(isBun ? 2 : 1);
130
+ }
131
+
132
+ /**
133
+ * Parse de argumentos CLI
134
+ */
135
+ private static parseCliArgs(argv: string[]) {
136
+ return parseArgs({
137
+ args: argv,
138
+ options: PARSE_OPTIONS,
139
+ strict: false,
140
+ allowPositionals: true,
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Detecta contexto do comando atual
146
+ */
147
+ private static detectCommandContext(positionals: string[]) {
88
148
  const isDev = positionals.includes("dev");
89
149
  const isRun = positionals.includes("run") || positionals.includes("debug");
90
150
  const isStart = positionals.includes("start") || positionals.includes("deploy") || isDev;
91
151
 
92
- const envTomcatPath = process.env.TOMCAT_HOME || process.env.CATALINA_HOME;
93
- const detectedTool = this.detectBuildTool();
94
-
95
152
  let runClass = "";
96
153
  if (isRun) {
97
154
  const runIdx = positionals.indexOf("run");
@@ -100,151 +157,243 @@ export class ConfigManager {
100
157
  runClass = positionals[idx + 1] || "";
101
158
  }
102
159
 
103
- // Detectar webapp path baseado no build tool
104
- const webappPath = detectedTool === "maven"
105
- ? path.join(process.cwd(), "src", "main", "webapp")
106
- : path.join(process.cwd(), "src", "main", "webapp");
107
-
108
- // Verificar se usar Tomcat embutido
109
- let tomcatPath = String(cliValues.path || xavvaJson.path || envTomcatPath || "");
110
- let useEmbedded = false;
111
- // Versão pode vir de: CLI flag > xavva.json tomcat.version > xavva.json version (legado) > padrão
112
- const xavvaTomcatVersion = (xavvaJson as any).tomcat?.version;
113
- let embeddedVersion = String(cliValues["tomcat-version"] || xavvaTomcatVersion || xavvaJson.version || "10.1.52");
114
-
115
- // Se não há Tomcat configurado ou não existe no path, usar embutido
116
- if (!tomcatPath || (!fs.existsSync(path.join(tomcatPath, "bin", "catalina.bat")) && isStart)) {
117
- useEmbedded = true;
118
- const embeddedService = new EmbeddedTomcatService({
119
- version: embeddedVersion,
120
- port: parseInt(String(cliValues.port || xavvaJson.port || String(DEFAULT_TOMCAT_PORT))),
121
- webappPath: webappPath
122
- });
123
-
124
- // Instala se necessário
125
- if (!embeddedService.checkInstallation()) {
126
- Logger.newline();
127
- Logger.warn("Tomcat não encontrado!");
128
- Logger.info("Versão solicitada", embeddedVersion);
129
- Logger.newline();
130
- Logger.log(`${Logger.C.primary}?${Logger.C.reset} Deseja instalar o Tomcat ${embeddedVersion} automaticamente?`);
131
- Logger.log(`${Logger.C.dim} O download é de ~16MB e será salvo em:~/.xavva/tomcat/${embeddedVersion}${Logger.C.reset}`);
132
- Logger.newline();
133
-
134
- // Garante que não há output pendente antes da pergunta
135
- await new Promise(resolve => setTimeout(resolve, 50));
136
- process.stdout.write('\r\x1b[K'); // Limpa linha atual
137
-
138
- const autoYes = !!cliValues.yes;
139
- const shouldInstall = autoYes || await this.askYesNo("Instalar");
140
-
141
- if (!shouldInstall) {
142
- Logger.newline();
143
- Logger.info("Opções disponíveis", "");
144
- Logger.log(` ${Logger.C.primary}1.${Logger.C.reset} Defina TOMCAT_HOME ou CATALINA_HOME`);
145
- Logger.log(` ${Logger.C.primary}2.${Logger.C.reset} Use --path para especificar o Tomcat`);
146
- Logger.log(` ${Logger.C.primary}3.${Logger.C.reset} Use --tomcat-version para outra versão`);
147
- Logger.newline();
148
- process.exit(0);
149
- }
150
-
151
- Logger.newline();
152
- const installed = await embeddedService.install();
153
- if (!installed) {
154
- Logger.error("Falha ao instalar Tomcat embutido.");
155
- Logger.info("Dica", "Instale o Tomcat manualmente ou defina TOMCAT_HOME");
156
- process.exit(1);
157
- }
158
- } else {
159
- Logger.info("Tomcat", `Usando versão embutida ${embeddedVersion}`);
160
- }
161
-
162
- // Configura contexto da aplicação
163
- await embeddedService.createAppContext();
164
-
165
- tomcatPath = embeddedService.getTomcatHome();
160
+ return {
161
+ isDev,
162
+ isRun,
163
+ isStart,
164
+ runClass,
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Carrega configuração do arquivo xavva.json
170
+ */
171
+ private static async loadConfigFile(): Promise<Partial<CLIArguments>> {
172
+ const xavvaJsonPath = path.join(process.cwd(), "xavva.json");
173
+
174
+ if (!fs.existsSync(xavvaJsonPath)) {
175
+ return {};
166
176
  }
167
177
 
168
- // Detectar environment
169
- const environment = String(cliValues.env || cliValues.environment || xavvaJson.env || xavvaJson.environment || "");
170
- const envConfig = environment && (xavvaJson as any).environments?.[environment];
178
+ try {
179
+ const content = fs.readFileSync(xavvaJsonPath, "utf8");
180
+ return JSON.parse(content);
181
+ } catch (e) {
182
+ console.error("Erro ao ler xavva.json:", (e as Error).message);
183
+ return {};
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Carrega configuração de variáveis de ambiente
189
+ */
190
+ private static loadEnvConfig(): { tomcatPath?: string } {
191
+ return {
192
+ tomcatPath: process.env.TOMCAT_HOME || process.env.CATALINA_HOME,
193
+ };
194
+ }
171
195
 
172
- // Merge environment config
196
+ /**
197
+ * Detecta build tool do projeto
198
+ */
199
+ private static detectBuildTool(): "maven" | "gradle" {
200
+ const cwd = process.cwd();
201
+
202
+ if (fs.existsSync(path.join(cwd, "pom.xml"))) {
203
+ return "maven";
204
+ }
205
+ if (fs.existsSync(path.join(cwd, "build.gradle")) ||
206
+ fs.existsSync(path.join(cwd, "build.gradle.kts"))) {
207
+ return "gradle";
208
+ }
209
+
210
+ return "maven";
211
+ }
212
+
213
+ /**
214
+ * Merge de todas as configurações
215
+ */
216
+ private static async mergeConfigurations({
217
+ cli,
218
+ file,
219
+ env,
220
+ context,
221
+ }: {
222
+ cli: CLIArguments;
223
+ file: Partial<CLIArguments>;
224
+ env: { tomcatPath?: string };
225
+ context: { isDev: boolean; isRun: boolean; isStart: boolean; runClass: string };
226
+ }): Promise<AppConfig> {
227
+ const detectedTool = this.detectBuildTool();
228
+ const environment = String(cli.env || cli.environment || file.env || file.environment || "");
229
+ const envConfig = environment && (file as any).environments?.[environment];
230
+
231
+ // Versão do Tomcat
232
+ const xavvaTomcatVersion = (file as any).tomcat?.version;
233
+ const embeddedVersion = String(cli["tomcat-version"] || xavvaTomcatVersion || file.version || "10.1.52");
234
+
235
+ // Porta
173
236
  const finalPort = envConfig?.port
174
- ? parseInt(String(envConfig.port))
175
- : parseInt(String(cliValues.port || xavvaJson.port || String(DEFAULT_TOMCAT_PORT)));
176
- const finalProfile = envConfig?.profile || String(cliValues.profile || xavvaJson.profile || "");
237
+ ? validatePort(envConfig.port)
238
+ : validatePort(cli.port || file.port || PORTS.DEFAULT_TOMCAT);
177
239
 
178
- const config: AppConfig = {
240
+ // Profile
241
+ const finalProfile = envConfig?.profile || String(cli.profile || file.profile || "");
242
+
243
+ // Tomcat path
244
+ let tomcatPath = String(cli.path || file.path || env.tomcatPath || "");
245
+
246
+ return {
179
247
  tomcat: {
180
248
  path: tomcatPath,
181
249
  port: finalPort,
182
250
  webapps: "webapps",
183
- grep: cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : "",
184
- embedded: useEmbedded,
251
+ grep: cli.grep || file.grep ? String(cli.grep || file.grep) : "",
252
+ embedded: false, // Será atualizado no setupTomcatIfNeeded
185
253
  version: embeddedVersion,
186
- ...(envConfig?.tomcat || {})
254
+ ...(envConfig?.tomcat || {}),
187
255
  },
188
256
  project: {
189
- appName: cliValues.name || xavvaJson.name ? String(cliValues.name || xavvaJson.name) : "",
190
- buildTool: (cliValues.tool as any) || (xavvaJson.tool as any) || detectedTool,
257
+ appName: cli.name || file.name ? String(cli.name || file.name) : "",
258
+ buildTool: (cli.tool as any) || (file.tool as any) || detectedTool,
191
259
  profile: finalProfile,
192
- skipBuild: !!(cliValues["no-build"] ?? xavvaJson["no-build"]),
193
- skipScan: cliValues.scan !== undefined ? !cliValues.scan : (xavvaJson.scan !== undefined ? !xavvaJson.scan : true),
194
- clean: !!(cliValues.clean ?? xavvaJson.clean),
195
- cleanLogs: (cliValues.verbose ?? xavvaJson.verbose) ? false : true,
196
- quiet: (cliValues.verbose ?? xavvaJson.verbose) ? false : true,
197
- verbose: !!(cliValues.verbose ?? xavvaJson.verbose),
198
- debug: !!(cliValues.debug ?? xavvaJson.debug ?? isDev ?? isRun),
199
- debugPort: parseInt(String(cliValues.dp || xavvaJson.dp || String(DEFAULT_DEBUG_PORT))),
200
- grep: runClass || (cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : ""),
201
- tui: !!(cliValues.tui ?? xavvaJson.tui),
202
- encoding: cliValues.encoding || xavvaJson.encoding || "",
203
- war: !!(cliValues.war ?? xavvaJson.war),
204
- cache: !!(cliValues.cache ?? xavvaJson.cache),
260
+ skipBuild: !!(cli["no-build"] ?? file["no-build"]),
261
+ skipScan: cli.scan !== undefined ? !cli.scan : (file.scan !== undefined ? !file.scan : true),
262
+ clean: !!(cli.clean ?? file.clean),
263
+ cleanLogs: (cli.verbose ?? file.verbose) ? false : true,
264
+ quiet: (cli.verbose ?? file.verbose) ? false : true,
265
+ verbose: !!(cli.verbose ?? file.verbose),
266
+ debug: !!(cli.debug ?? file.debug ?? context.isDev ?? context.isRun),
267
+ debugPort: validatePort(cli.dp || file.dp || PORTS.DEFAULT_DEBUG),
268
+ grep: context.runClass || (cli.grep || file.grep ? String(cli.grep || file.grep) : ""),
269
+ tui: !!(cli.tui ?? file.tui),
270
+ encoding: cli.encoding || file.encoding || "",
271
+ war: !!(cli.war ?? file.war),
272
+ cache: !!(cli.cache ?? file.cache),
205
273
  environment,
206
- environments: (xavvaJson as any).environments
274
+ environments: (file as any).environments,
275
+ },
276
+ };
277
+ }
278
+
279
+ /**
280
+ * Setup de Tomcat embutido se necessário
281
+ */
282
+ private static async setupTomcatIfNeeded(
283
+ config: AppConfig,
284
+ cliValues: CLIArguments,
285
+ context: { isStart: boolean }
286
+ ): Promise<AppConfig> {
287
+ const hasCatalina = fs.existsSync(path.join(config.tomcat.path, "bin", "catalina.bat")) ||
288
+ fs.existsSync(path.join(config.tomcat.path, "bin", "catalina.sh"));
289
+
290
+ // Se já tem Tomcat válido ou não precisa iniciar, retorna config atual
291
+ if ((config.tomcat.path && hasCatalina) || !context.isStart) {
292
+ return config;
293
+ }
294
+
295
+ // Usar Tomcat embutido
296
+ const embeddedService = new EmbeddedTomcatService({
297
+ version: config.tomcat.version,
298
+ port: config.tomcat.port,
299
+ webappPath: path.join(process.cwd(), "src", "main", "webapp"),
300
+ });
301
+
302
+ if (!embeddedService.checkInstallation()) {
303
+ const shouldInstall = await this.promptForTomcatInstall(config.tomcat.version, cliValues.yes);
304
+
305
+ if (!shouldInstall) {
306
+ this.printTomcatHelp();
307
+ process.exit(0);
207
308
  }
309
+
310
+ const installed = await embeddedService.install();
311
+ if (!installed) {
312
+ Logger.error("Falha ao instalar Tomcat embutido.");
313
+ Logger.info("Dica", "Instale o Tomcat manualmente ou defina TOMCAT_HOME");
314
+ process.exit(1);
315
+ }
316
+ } else {
317
+ Logger.info("Tomcat", `Usando versão embutida ${config.tomcat.version}`);
318
+ }
319
+
320
+ await embeddedService.createAppContext();
321
+
322
+ return {
323
+ ...config,
324
+ tomcat: {
325
+ ...config.tomcat,
326
+ path: embeddedService.getTomcatHome(),
327
+ embedded: true,
328
+ },
208
329
  };
330
+ }
209
331
 
210
- if (isDev) cliValues.watch = true;
332
+ /**
333
+ * Prompt para instalação do Tomcat
334
+ */
335
+ private static async promptForTomcatInstall(version: string, autoYes?: boolean): Promise<boolean> {
336
+ Logger.newline();
337
+ Logger.warn("Tomcat não encontrado!");
338
+ Logger.info("Versão solicitada", version);
339
+ Logger.newline();
340
+ Logger.log(`${C.primary}?${C.reset} Deseja instalar o Tomcat ${version} automaticamente?`);
341
+ Logger.log(`${C.dim} O download é de ~16MB e será salvo em:~/.xavva/tomcat/${version}${C.reset}`);
342
+ Logger.newline();
211
343
 
212
- this.ensureGitIgnore();
344
+ await new Promise(resolve => setTimeout(resolve, 50));
345
+ process.stdout.write('\r\x1b[K');
346
+
347
+ if (autoYes) return true;
213
348
 
214
- return { config, positionals, values: cliValues };
349
+ return this.askYesNo("Instalar");
215
350
  }
216
351
 
217
- private static detectBuildTool(): "maven" | "gradle" {
218
- if (fs.existsSync(path.join(process.cwd(), "pom.xml"))) {
219
- return "maven";
220
- }
221
- if (fs.existsSync(path.join(process.cwd(), "build.gradle")) || fs.existsSync(path.join(process.cwd(), "build.gradle.kts"))) {
222
- return "gradle";
352
+ /**
353
+ * Print opções de ajuda para Tomcat
354
+ */
355
+ private static printTomcatHelp(): void {
356
+ Logger.newline();
357
+ Logger.info("Opções disponíveis", "");
358
+ Logger.log(` ${C.primary}1.${C.reset} Defina TOMCAT_HOME ou CATALINA_HOME`);
359
+ Logger.log(` ${C.primary}2.${C.reset} Use --path para especificar o Tomcat`);
360
+ Logger.log(` ${C.primary}3.${C.reset} Use --tomcat-version para outra versão`);
361
+ Logger.newline();
362
+ }
363
+
364
+ /**
365
+ * Valida configuração com Zod
366
+ */
367
+ private static validateConfig(config: AppConfig): AppConfig {
368
+ try {
369
+ return validateAppConfig(config);
370
+ } catch (error) {
371
+ Logger.error("Configuração inválida:");
372
+ Logger.error((error as Error).message);
373
+ process.exit(1);
223
374
  }
224
- return "maven";
225
375
  }
226
376
 
377
+ /**
378
+ * Prompt yes/no
379
+ */
227
380
  private static async askYesNo(question: string): Promise<boolean> {
228
- // Pequeno delay para garantir que o output anterior foi processado
229
381
  await new Promise(resolve => setTimeout(resolve, 100));
230
-
231
- // Limpa qualquer coisa pendente no stdout
232
382
  process.stdout.write('\x1b[0m');
233
-
383
+
234
384
  return new Promise((resolve) => {
235
385
  const chunks: Buffer[] = [];
236
-
386
+
237
387
  const cleanup = () => {
238
388
  process.stdin.removeListener('data', onData);
239
389
  process.stdin.removeListener('end', onEnd);
240
390
  process.stdin.pause();
241
391
  };
242
-
392
+
243
393
  const onData = (data: Buffer) => {
244
394
  chunks.push(data);
245
395
  const str = Buffer.concat(chunks).toString();
246
-
247
- // Procura por enter no input
396
+
248
397
  if (str.includes('\n') || str.includes('\r')) {
249
398
  cleanup();
250
399
  const answer = str.replace(/\r?\n/g, '').trim().toLowerCase();
@@ -252,23 +401,24 @@ export class ConfigManager {
252
401
  resolve(answer === '' || answer === 'y' || answer === 'yes');
253
402
  }
254
403
  };
255
-
404
+
256
405
  const onEnd = () => {
257
406
  cleanup();
258
407
  const answer = Buffer.concat(chunks).toString().trim().toLowerCase();
259
408
  resolve(answer === '' || answer === 'y' || answer === 'yes');
260
409
  };
261
-
262
- // Mostra a pergunta
410
+
263
411
  process.stdout.write(`${question} [Y/n]: `);
264
-
265
412
  process.stdin.resume();
266
413
  process.stdin.on('data', onData);
267
414
  process.stdin.on('end', onEnd);
268
415
  });
269
416
  }
270
417
 
271
- private static ensureGitIgnore() {
418
+ /**
419
+ * Garante que .xavva está no .gitignore
420
+ */
421
+ private static ensureGitIgnore(): void {
272
422
  const gitignorePath = path.join(process.cwd(), ".gitignore");
273
423
 
274
424
  if (!fs.existsSync(gitignorePath)) return;
@@ -280,6 +430,7 @@ export class ConfigManager {
280
430
  fs.writeFileSync(gitignorePath, newContent);
281
431
  }
282
432
  } catch (e) {
433
+ // Silently fail
283
434
  }
284
435
  }
285
436
  }