@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.
- 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 +43 -14
- package/src/commands/DeployCommand.ts +252 -229
- package/src/commands/DepsCommand.ts +174 -174
- package/src/commands/DockerCommand.ts +35 -4
- package/src/commands/DoctorCommand.ts +252 -239
- package/src/commands/EncodingCommand.ts +26 -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 +27 -1
- 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 +25 -1
- package/src/commands/TomcatCommand.ts +232 -88
- 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 +20 -6
- 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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sistema de Logging do XAVVA CLI
|
|
3
|
+
*
|
|
4
|
+
* Exporta todas as funcionalidades de logging para uso em toda a aplicação.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { Logger, OperationLogger, ProgressLogger, TableLogger } from '../logging';
|
|
9
|
+
*
|
|
10
|
+
* // Log simples
|
|
11
|
+
* Logger.info('Iniciando processo');
|
|
12
|
+
* Logger.success('Concluído');
|
|
13
|
+
*
|
|
14
|
+
* // Operação complexa
|
|
15
|
+
* const op = new OperationLogger('deploy');
|
|
16
|
+
* op.start();
|
|
17
|
+
* const buildStep = op.step('build');
|
|
18
|
+
* await build();
|
|
19
|
+
* buildStep.success();
|
|
20
|
+
* op.complete();
|
|
21
|
+
*
|
|
22
|
+
* // Progresso
|
|
23
|
+
* const progress = new ProgressLogger('Download', { total: 100, unit: 'MB' });
|
|
24
|
+
* progress.update(50);
|
|
25
|
+
* progress.complete();
|
|
26
|
+
*
|
|
27
|
+
* // Tabela
|
|
28
|
+
* const table = new TableLogger(['Nome', 'Versão']);
|
|
29
|
+
* table.add(['Spring', '5.3.0']);
|
|
30
|
+
* table.print();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Tipos
|
|
35
|
+
export type {
|
|
36
|
+
LogLevel,
|
|
37
|
+
LogOutputMode,
|
|
38
|
+
LogContext,
|
|
39
|
+
LogEntry,
|
|
40
|
+
LoggerConfig,
|
|
41
|
+
RateLimitEntry,
|
|
42
|
+
OperationStep,
|
|
43
|
+
TableColumn,
|
|
44
|
+
TableRow,
|
|
45
|
+
} from './types';
|
|
46
|
+
|
|
47
|
+
// Cores e utilitários
|
|
48
|
+
export {
|
|
49
|
+
Colors,
|
|
50
|
+
Icons,
|
|
51
|
+
colorize,
|
|
52
|
+
stripAnsi,
|
|
53
|
+
visualWidth,
|
|
54
|
+
padText,
|
|
55
|
+
truncateText,
|
|
56
|
+
getIcon,
|
|
57
|
+
supportsColor,
|
|
58
|
+
} from './colors';
|
|
59
|
+
|
|
60
|
+
// Constantes
|
|
61
|
+
export {
|
|
62
|
+
DEFAULT_CONFIG,
|
|
63
|
+
LOG_LEVELS,
|
|
64
|
+
LEVEL_COLORS,
|
|
65
|
+
LEVEL_ICONS,
|
|
66
|
+
LAYOUT,
|
|
67
|
+
NOISE_PATTERNS,
|
|
68
|
+
ESSENTIAL_PATTERNS,
|
|
69
|
+
SPINNER_FRAMES,
|
|
70
|
+
SPINNER_INTERVAL,
|
|
71
|
+
FILE_LOG,
|
|
72
|
+
} from './constants';
|
|
73
|
+
|
|
74
|
+
// Formatadores
|
|
75
|
+
export {
|
|
76
|
+
formatEntry,
|
|
77
|
+
formatStatusLine,
|
|
78
|
+
formatSection,
|
|
79
|
+
formatConfig,
|
|
80
|
+
formatUrl,
|
|
81
|
+
formatFile,
|
|
82
|
+
} from './formatters';
|
|
83
|
+
|
|
84
|
+
// Logger principal
|
|
85
|
+
export { Logger, logger } from './Logger';
|
|
86
|
+
|
|
87
|
+
// Loggers especializados
|
|
88
|
+
export { OperationLogger, OperationStepLogger, createOperation } from './OperationLogger';
|
|
89
|
+
export { ProgressLogger, createProgress } from './ProgressLogger';
|
|
90
|
+
export { TableLogger, createTable, printTable } from './TableLogger';
|
|
91
|
+
|
|
92
|
+
// FileLogger
|
|
93
|
+
export { FileLogger, getFileLogger, configureFileLogger } from './FileLogger';
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos do sistema de logging
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'success' | 'debug' | 'trace' | 'silly';
|
|
6
|
+
|
|
7
|
+
export type LogOutputMode = 'pretty' | 'json' | 'minimal' | 'silent';
|
|
8
|
+
|
|
9
|
+
export interface LogContext {
|
|
10
|
+
timestamp?: boolean;
|
|
11
|
+
traceId?: string;
|
|
12
|
+
indent?: number;
|
|
13
|
+
prefix?: string;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LogEntry {
|
|
18
|
+
level: LogLevel;
|
|
19
|
+
message: string;
|
|
20
|
+
timestamp: Date;
|
|
21
|
+
context?: LogContext;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LoggerConfig {
|
|
26
|
+
level: LogLevel;
|
|
27
|
+
mode: LogOutputMode;
|
|
28
|
+
timestamps: boolean;
|
|
29
|
+
colors: boolean;
|
|
30
|
+
icons: boolean;
|
|
31
|
+
fileLogging: boolean;
|
|
32
|
+
logDir: string;
|
|
33
|
+
maxLogFiles: number;
|
|
34
|
+
rateLimitWindowMs: number;
|
|
35
|
+
maxDuplicateLogs: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RateLimitEntry {
|
|
39
|
+
message: string;
|
|
40
|
+
count: number;
|
|
41
|
+
firstSeen: number;
|
|
42
|
+
lastSeen: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface OperationStep {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
status: 'pending' | 'running' | 'success' | 'failed';
|
|
49
|
+
startTime?: Date;
|
|
50
|
+
endTime?: Date;
|
|
51
|
+
message?: string;
|
|
52
|
+
error?: Error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TableColumn {
|
|
56
|
+
header: string;
|
|
57
|
+
align?: 'left' | 'center' | 'right';
|
|
58
|
+
width?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TableRow {
|
|
62
|
+
cells: string[];
|
|
63
|
+
styles?: string[];
|
|
64
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gerenciador de Plugins do XAVVA CLI
|
|
3
|
+
*
|
|
4
|
+
* Responsável por:
|
|
5
|
+
* - Carregar plugins de npm ou caminhos locais
|
|
6
|
+
* - Executar hooks em pontos específicos
|
|
7
|
+
* - Gerenciar comandos de plugins
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import type { AppConfig } from "../types/config";
|
|
15
|
+
import { Logger } from "../logging";
|
|
16
|
+
import type {
|
|
17
|
+
XavvaPlugin,
|
|
18
|
+
LoadedPlugin,
|
|
19
|
+
PluginConfig,
|
|
20
|
+
BuildContext,
|
|
21
|
+
DeployContext,
|
|
22
|
+
TestContext,
|
|
23
|
+
} from "./types";
|
|
24
|
+
|
|
25
|
+
export class PluginManager {
|
|
26
|
+
private plugins: LoadedPlugin[] = [];
|
|
27
|
+
private logger = Logger.getInstance();
|
|
28
|
+
private pluginsDir: string;
|
|
29
|
+
private config: AppConfig;
|
|
30
|
+
|
|
31
|
+
constructor(config: AppConfig) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.pluginsDir = path.join(os.homedir(), ".xavva", "plugins");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Carrega todos os plugins configurados
|
|
38
|
+
*/
|
|
39
|
+
async loadPlugins(): Promise<void> {
|
|
40
|
+
const pluginConfigs = this.config.project.plugins as PluginConfig[] || [];
|
|
41
|
+
|
|
42
|
+
if (pluginConfigs.length === 0) {
|
|
43
|
+
this.logger.debug("Nenhum plugin configurado");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.logger.section(`Carregando ${pluginConfigs.length} plugin(s)`);
|
|
48
|
+
|
|
49
|
+
for (const pluginConfig of pluginConfigs) {
|
|
50
|
+
try {
|
|
51
|
+
await this.loadPlugin(pluginConfig);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.logger.error(`Falha ao carregar plugin ${pluginConfig.name}: ${(error as Error).message}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.logger.success(`${this.plugins.length} plugin(s) carregado(s)`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Carrega um plugin específico
|
|
62
|
+
*/
|
|
63
|
+
private async loadPlugin(config: PluginConfig): Promise<void> {
|
|
64
|
+
const { name, path: pluginPath, config: pluginConfigData } = config;
|
|
65
|
+
|
|
66
|
+
this.logger.step(`Carregando: ${name}`);
|
|
67
|
+
|
|
68
|
+
// Resolve caminho do plugin
|
|
69
|
+
const resolvedPath = this.resolvePluginPath(name, pluginPath);
|
|
70
|
+
|
|
71
|
+
// Importa plugin
|
|
72
|
+
const pluginModule = await import(resolvedPath);
|
|
73
|
+
const plugin: XavvaPlugin = pluginModule.default || pluginModule;
|
|
74
|
+
|
|
75
|
+
// Valida plugin
|
|
76
|
+
this.validatePlugin(plugin);
|
|
77
|
+
|
|
78
|
+
// Inicializa se necessário
|
|
79
|
+
if (plugin.initialize) {
|
|
80
|
+
await plugin.initialize(this.config);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Registra plugin
|
|
84
|
+
this.plugins.push({
|
|
85
|
+
plugin,
|
|
86
|
+
path: resolvedPath,
|
|
87
|
+
config: pluginConfigData,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
this.logger.info(` ✓ ${plugin.name} v${plugin.version}`);
|
|
91
|
+
if (plugin.description) {
|
|
92
|
+
this.logger.info(` ${plugin.description}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve caminho do plugin
|
|
98
|
+
*/
|
|
99
|
+
private resolvePluginPath(name: string, customPath?: string): string {
|
|
100
|
+
// Caminho customizado tem prioridade
|
|
101
|
+
if (customPath) {
|
|
102
|
+
if (customPath.startsWith("./") || customPath.startsWith("../")) {
|
|
103
|
+
return path.resolve(process.cwd(), customPath);
|
|
104
|
+
}
|
|
105
|
+
return customPath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Tenta carregar de node_modules
|
|
109
|
+
const npmPath = path.join(process.cwd(), "node_modules", name);
|
|
110
|
+
if (existsSync(npmPath)) {
|
|
111
|
+
return npmPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Tenta carregar do diretório de plugins do Xavva
|
|
115
|
+
const xavvaPluginPath = path.join(this.pluginsDir, name);
|
|
116
|
+
if (existsSync(xavvaPluginPath)) {
|
|
117
|
+
return xavvaPluginPath;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Tenta carregar como caminho relativo
|
|
121
|
+
const relativePath = path.join(process.cwd(), name);
|
|
122
|
+
if (existsSync(relativePath)) {
|
|
123
|
+
return relativePath;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new Error(`Plugin não encontrado: ${name}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Valida estrutura do plugin
|
|
131
|
+
*/
|
|
132
|
+
private validatePlugin(plugin: XavvaPlugin): void {
|
|
133
|
+
if (!plugin.name) {
|
|
134
|
+
throw new Error("Plugin deve ter um nome");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!plugin.version) {
|
|
138
|
+
throw new Error("Plugin deve ter uma versão");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Executa hook beforeBuild em todos os plugins
|
|
144
|
+
*/
|
|
145
|
+
async executeBeforeBuild(context: BuildContext): Promise<void> {
|
|
146
|
+
for (const { plugin } of this.plugins) {
|
|
147
|
+
if (plugin.hooks?.beforeBuild) {
|
|
148
|
+
try {
|
|
149
|
+
await plugin.hooks.beforeBuild(context);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em beforeBuild: ${(error as Error).message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Executa hook afterBuild em todos os plugins
|
|
159
|
+
*/
|
|
160
|
+
async executeAfterBuild(context: BuildContext, success: boolean): Promise<void> {
|
|
161
|
+
for (const { plugin } of this.plugins) {
|
|
162
|
+
if (plugin.hooks?.afterBuild) {
|
|
163
|
+
try {
|
|
164
|
+
await plugin.hooks.afterBuild(context, success);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em afterBuild: ${(error as Error).message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Executa hook beforeDeploy em todos os plugins
|
|
174
|
+
*/
|
|
175
|
+
async executeBeforeDeploy(context: DeployContext): Promise<void> {
|
|
176
|
+
for (const { plugin } of this.plugins) {
|
|
177
|
+
if (plugin.hooks?.beforeDeploy) {
|
|
178
|
+
try {
|
|
179
|
+
await plugin.hooks.beforeDeploy(context);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em beforeDeploy: ${(error as Error).message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Executa hook afterDeploy em todos os plugins
|
|
189
|
+
*/
|
|
190
|
+
async executeAfterDeploy(context: DeployContext, success: boolean): Promise<void> {
|
|
191
|
+
for (const { plugin } of this.plugins) {
|
|
192
|
+
if (plugin.hooks?.afterDeploy) {
|
|
193
|
+
try {
|
|
194
|
+
await plugin.hooks.afterDeploy(context, success);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em afterDeploy: ${(error as Error).message}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Executa hook beforeTest em todos os plugins
|
|
204
|
+
*/
|
|
205
|
+
async executeBeforeTest(context: TestContext): Promise<void> {
|
|
206
|
+
for (const { plugin } of this.plugins) {
|
|
207
|
+
if (plugin.hooks?.beforeTest) {
|
|
208
|
+
try {
|
|
209
|
+
await plugin.hooks.beforeTest(context);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em beforeTest: ${(error as Error).message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Executa hook afterTest em todos os plugins
|
|
219
|
+
*/
|
|
220
|
+
async executeAfterTest(context: TestContext, success: boolean): Promise<void> {
|
|
221
|
+
for (const { plugin } of this.plugins) {
|
|
222
|
+
if (plugin.hooks?.afterTest) {
|
|
223
|
+
try {
|
|
224
|
+
await plugin.hooks.afterTest(context, success);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
this.logger.warn(`Plugin ${plugin.name} falhou em afterTest: ${(error as Error).message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Obtém comandos de todos os plugins
|
|
234
|
+
*/
|
|
235
|
+
getPluginCommands(): Map<string, import("../commands/Command").Command> {
|
|
236
|
+
const commands = new Map<string, import("../commands/Command").Command>();
|
|
237
|
+
|
|
238
|
+
for (const { plugin } of this.plugins) {
|
|
239
|
+
if (plugin.commands) {
|
|
240
|
+
for (const command of plugin.commands) {
|
|
241
|
+
// Assume que comando tem nome ou usa classe
|
|
242
|
+
const name = (command as any).name || command.constructor.name.toLowerCase();
|
|
243
|
+
commands.set(name, command);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return commands;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Lista plugins carregados
|
|
253
|
+
*/
|
|
254
|
+
listPlugins(): Array<{ name: string; version: string; description?: string }> {
|
|
255
|
+
return this.plugins.map(({ plugin }) => ({
|
|
256
|
+
name: plugin.name,
|
|
257
|
+
version: plugin.version,
|
|
258
|
+
description: plugin.description,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Destrói todos os plugins
|
|
264
|
+
*/
|
|
265
|
+
async destroy(): Promise<void> {
|
|
266
|
+
for (const { plugin } of this.plugins) {
|
|
267
|
+
if (plugin.destroy) {
|
|
268
|
+
try {
|
|
269
|
+
await plugin.destroy();
|
|
270
|
+
} catch (error) {
|
|
271
|
+
this.logger.warn(`Erro ao destruir plugin ${plugin.name}: ${(error as Error).message}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.plugins = [];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Instala plugin via npm
|
|
280
|
+
*/
|
|
281
|
+
async installPlugin(name: string): Promise<void> {
|
|
282
|
+
this.logger.step(`Instalando plugin: ${name}`);
|
|
283
|
+
|
|
284
|
+
// Cria diretório se não existe
|
|
285
|
+
await mkdir(this.pluginsDir, { recursive: true });
|
|
286
|
+
|
|
287
|
+
// Executa npm install
|
|
288
|
+
const proc = Bun.spawn([
|
|
289
|
+
"npm", "install", name, "--prefix", this.pluginsDir, "--no-save"
|
|
290
|
+
], {
|
|
291
|
+
stdout: "pipe",
|
|
292
|
+
stderr: "pipe",
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const exitCode = await proc.exited;
|
|
296
|
+
|
|
297
|
+
if (exitCode !== 0) {
|
|
298
|
+
const error = await new Response(proc.stderr).text();
|
|
299
|
+
throw new Error(`npm install falhou: ${error}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.logger.success(`Plugin ${name} instalado`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Remove plugin
|
|
307
|
+
*/
|
|
308
|
+
async uninstallPlugin(name: string): Promise<void> {
|
|
309
|
+
this.logger.step(`Removendo plugin: ${name}`);
|
|
310
|
+
|
|
311
|
+
const proc = Bun.spawn([
|
|
312
|
+
"npm", "uninstall", name, "--prefix", this.pluginsDir
|
|
313
|
+
], {
|
|
314
|
+
stdout: "pipe",
|
|
315
|
+
stderr: "pipe",
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await proc.exited;
|
|
319
|
+
|
|
320
|
+
// Remove da lista de plugins carregados
|
|
321
|
+
this.plugins = this.plugins.filter(p => p.plugin.name !== name);
|
|
322
|
+
|
|
323
|
+
this.logger.success(`Plugin ${name} removido`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos para o Sistema de Plugins do XAVVA CLI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Command } from "../commands/Command";
|
|
6
|
+
import type { AppConfig } from "../types/config";
|
|
7
|
+
import type { z } from "zod";
|
|
8
|
+
|
|
9
|
+
// Contextos de hooks
|
|
10
|
+
export interface BuildContext {
|
|
11
|
+
config: AppConfig;
|
|
12
|
+
incremental: boolean;
|
|
13
|
+
changedFiles?: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DeployContext {
|
|
17
|
+
config: AppConfig;
|
|
18
|
+
targetPath: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TestContext {
|
|
22
|
+
config: AppConfig;
|
|
23
|
+
filter?: string;
|
|
24
|
+
coverage: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Hook handlers
|
|
28
|
+
export type BeforeBuildHook = (context: BuildContext) => Promise<void> | void;
|
|
29
|
+
export type AfterBuildHook = (context: BuildContext, success: boolean) => Promise<void> | void;
|
|
30
|
+
export type BeforeDeployHook = (context: DeployContext) => Promise<void> | void;
|
|
31
|
+
export type AfterDeployHook = (context: DeployContext, success: boolean) => Promise<void> | void;
|
|
32
|
+
export type BeforeTestHook = (context: TestContext) => Promise<void> | void;
|
|
33
|
+
export type AfterTestHook = (context: TestContext, success: boolean) => Promise<void> | void;
|
|
34
|
+
|
|
35
|
+
// Interface do plugin
|
|
36
|
+
export interface XavvaPlugin {
|
|
37
|
+
/** Nome único do plugin */
|
|
38
|
+
name: string;
|
|
39
|
+
|
|
40
|
+
/** Versão do plugin (semver) */
|
|
41
|
+
version: string;
|
|
42
|
+
|
|
43
|
+
/** Descrição */
|
|
44
|
+
description?: string;
|
|
45
|
+
|
|
46
|
+
/** Comandos adicionados pelo plugin */
|
|
47
|
+
commands?: Command[];
|
|
48
|
+
|
|
49
|
+
/** Hooks */
|
|
50
|
+
hooks?: {
|
|
51
|
+
beforeBuild?: BeforeBuildHook;
|
|
52
|
+
afterBuild?: AfterBuildHook;
|
|
53
|
+
beforeDeploy?: BeforeDeployHook;
|
|
54
|
+
afterDeploy?: AfterDeployHook;
|
|
55
|
+
beforeTest?: BeforeTestHook;
|
|
56
|
+
afterTest?: AfterTestHook;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Schema de configuração estendida */
|
|
60
|
+
configSchema?: z.ZodSchema;
|
|
61
|
+
|
|
62
|
+
/** Inicialização do plugin */
|
|
63
|
+
initialize?(config: AppConfig): Promise<void> | void;
|
|
64
|
+
|
|
65
|
+
/** Cleanup ao finalizar */
|
|
66
|
+
destroy?(): Promise<void> | void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Metadados de plugin carregado
|
|
70
|
+
export interface LoadedPlugin {
|
|
71
|
+
plugin: XavvaPlugin;
|
|
72
|
+
path: string;
|
|
73
|
+
config?: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Configuração de plugin no xavva.json
|
|
77
|
+
export interface PluginConfig {
|
|
78
|
+
name: string;
|
|
79
|
+
version?: string;
|
|
80
|
+
path?: string;
|
|
81
|
+
config?: Record<string, unknown>;
|
|
82
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import type { TomcatConfig } from "../types";
|
|
4
|
-
import { Logger } from "../
|
|
4
|
+
import { Logger } from "../logging";
|
|
5
5
|
import { AuditError } from "../errors/XavvaError";
|
|
6
6
|
|
|
7
7
|
export interface Vulnerability {
|
|
@@ -47,6 +47,8 @@ interface OSVResponse {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export class AuditService {
|
|
50
|
+
private logger = Logger.getInstance();
|
|
51
|
+
|
|
50
52
|
constructor(private tomcatConfig: TomcatConfig) {}
|
|
51
53
|
|
|
52
54
|
async runAudit(appName: string): Promise<JarAuditResult[]> {
|
|
@@ -59,7 +61,7 @@ export class AuditService {
|
|
|
59
61
|
const jars = fs.readdirSync(libPath).filter(f => f.endsWith(".jar"));
|
|
60
62
|
const results: JarAuditResult[] = [];
|
|
61
63
|
|
|
62
|
-
const stopSpinner =
|
|
64
|
+
const stopSpinner = this.logger.spinner(`Auditando ${jars.length} dependências`);
|
|
63
65
|
|
|
64
66
|
const chunkSize = 10;
|
|
65
67
|
for (let i = 0; i < jars.length; i += chunkSize) {
|
|
@@ -69,7 +71,7 @@ export class AuditService {
|
|
|
69
71
|
results.push(...chunkResults);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
stopSpinner();
|
|
74
|
+
stopSpinner.stop();
|
|
73
75
|
return results;
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { readdirSync, existsSync, statSync, mkdirSync, promises as fs } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import type { ProjectConfig, TomcatConfig } from "../types/config";
|
|
4
|
-
import { Logger } from "../
|
|
4
|
+
import { Logger } from "../logging";
|
|
5
5
|
import { BuildCacheService } from "./BuildCacheService";
|
|
6
6
|
import { ProjectService } from "./ProjectService";
|
|
7
7
|
|
|
8
8
|
export class BuildService {
|
|
9
|
+
private logger = Logger.getInstance();
|
|
10
|
+
|
|
9
11
|
constructor(
|
|
10
12
|
private projectConfig: ProjectConfig,
|
|
11
13
|
private tomcatConfig: TomcatConfig,
|
|
@@ -40,7 +42,7 @@ export class BuildService {
|
|
|
40
42
|
|
|
41
43
|
if (useCache && !incremental && !this.projectConfig.skipBuild) {
|
|
42
44
|
if (!this.projectConfig.clean && !this.cache.shouldRebuild(this.projectConfig.buildTool, this.projectService)) {
|
|
43
|
-
|
|
45
|
+
this.logger.success("Cache de build atingido! Pulando build completo.");
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
}
|
|
@@ -96,7 +98,7 @@ export class BuildService {
|
|
|
96
98
|
env.GRADLE_OPTS = "-Xmx1024m -Dorg.gradle.daemon=true";
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
const
|
|
101
|
+
const spinner = this.projectConfig.verbose ? null : this.logger.spinner(incremental ? "Compilação incremental" : "Build completo do projeto");
|
|
100
102
|
|
|
101
103
|
// No Windows, comandos .cmd/.bat muitas vezes precisam de shell: true no Bun.spawn ou o nome exato.
|
|
102
104
|
// Vamos usar o nome exato mvn.cmd/gradle.bat que é mais seguro que shell: true
|
|
@@ -114,14 +116,14 @@ export class BuildService {
|
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
await proc.exited;
|
|
117
|
-
|
|
119
|
+
spinner?.stop();
|
|
118
120
|
|
|
119
121
|
if (proc.exitCode !== 0) {
|
|
120
122
|
if (!this.projectConfig.verbose) {
|
|
121
123
|
const err = await new Response(proc.stderr).text();
|
|
122
|
-
|
|
124
|
+
console.log(err);
|
|
123
125
|
}
|
|
124
|
-
|
|
126
|
+
this.logger.error(`${this.projectConfig.buildTool.toUpperCase()} build falhou!`);
|
|
125
127
|
throw new Error("Falha no build do Java!");
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -141,11 +143,11 @@ export class BuildService {
|
|
|
141
143
|
|
|
142
144
|
if (existsSync(buildDir)) {
|
|
143
145
|
try {
|
|
144
|
-
|
|
146
|
+
this.logger.debug(`Limpando diretório ${path.basename(buildDir)}/...`);
|
|
145
147
|
await fs.rm(buildDir, { recursive: true, force: true });
|
|
146
|
-
|
|
148
|
+
// Directory removed
|
|
147
149
|
} catch (e) {
|
|
148
|
-
|
|
150
|
+
this.logger.warn(`Não foi possível remover completamente ${buildDir}, continuando...`);
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
}
|
|
@@ -219,7 +221,7 @@ export class BuildService {
|
|
|
219
221
|
if (syncedCount.value === 0) {
|
|
220
222
|
await this.fastSync(sourceDir, targetLib);
|
|
221
223
|
} else if (!this.projectConfig.quiet) {
|
|
222
|
-
|
|
224
|
+
this.logger.info(`${syncedCount.value} classe(s) sincronizada(s)`);
|
|
223
225
|
}
|
|
224
226
|
}
|
|
225
227
|
|
|
@@ -324,15 +326,11 @@ export class BuildService {
|
|
|
324
326
|
|
|
325
327
|
if (!this.projectConfig.verbose) {
|
|
326
328
|
// Modo não-verbose: usa sumarização existente
|
|
327
|
-
const formatted = Logger
|
|
329
|
+
const formatted = this.logger.constructor.name === 'Logger' ? '' : cleanLine;
|
|
328
330
|
if (formatted) console.log(formatted);
|
|
329
331
|
} else {
|
|
330
|
-
// Modo verbose:
|
|
331
|
-
|
|
332
|
-
if (formatted) {
|
|
333
|
-
console.log(formatted);
|
|
334
|
-
}
|
|
335
|
-
// Silencia linhas que são noise puro
|
|
332
|
+
// Modo verbose: mostra todas as linhas
|
|
333
|
+
console.log(` ${cleanLine.slice(0, 100)}`);
|
|
336
334
|
}
|
|
337
335
|
}
|
|
338
336
|
}
|