deepdebug-local-agent 0.3.1
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/.dockerignore +24 -0
- package/.idea/deepdebug-local-agent.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/Dockerfile +46 -0
- package/cloudbuild.yaml +42 -0
- package/index.js +42 -0
- package/mcp-server.js +533 -0
- package/package.json +22 -0
- package/src/ai-engine.js +861 -0
- package/src/analyzers/config-analyzer.js +446 -0
- package/src/analyzers/controller-analyzer.js +429 -0
- package/src/analyzers/dto-analyzer.js +455 -0
- package/src/detectors/build-tool-detector.js +0 -0
- package/src/detectors/framework-detector.js +91 -0
- package/src/detectors/language-detector.js +89 -0
- package/src/detectors/multi-project-detector.js +191 -0
- package/src/detectors/service-detector.js +244 -0
- package/src/detectors.js +30 -0
- package/src/exec-utils.js +215 -0
- package/src/fs-utils.js +34 -0
- package/src/git/base-git-provider.js +384 -0
- package/src/git/git-provider-registry.js +110 -0
- package/src/git/github-provider.js +502 -0
- package/src/mcp-http-server.js +313 -0
- package/src/patch/patch-engine.js +339 -0
- package/src/patch-manager.js +816 -0
- package/src/patch.js +607 -0
- package/src/patch_bkp.js +154 -0
- package/src/ports.js +69 -0
- package/src/routes/workspace.route.js +528 -0
- package/src/runtimes/base-runtime.js +290 -0
- package/src/runtimes/java/gradle-runtime.js +378 -0
- package/src/runtimes/java/java-integrations.js +339 -0
- package/src/runtimes/java/maven-runtime.js +418 -0
- package/src/runtimes/node/node-integrations.js +247 -0
- package/src/runtimes/node/npm-runtime.js +466 -0
- package/src/runtimes/node/yarn-runtime.js +354 -0
- package/src/runtimes/runtime-registry.js +256 -0
- package/src/server-local.js +576 -0
- package/src/server.js +4565 -0
- package/src/utils/environment-diagnostics.js +666 -0
- package/src/utils/exec-utils.js +264 -0
- package/src/utils/fs-utils.js +218 -0
- package/src/workspace/detect-port.js +176 -0
- package/src/workspace/file-reader.js +54 -0
- package/src/workspace/git-client.js +0 -0
- package/src/workspace/process-manager.js +619 -0
- package/src/workspace/scanner.js +72 -0
- package/src/workspace-manager.js +172 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseRuntime
|
|
3
|
+
*
|
|
4
|
+
* Interface abstrata para todos os runtimes de linguagem.
|
|
5
|
+
* Cada linguagem (Java, Node, Python, etc.) deve implementar esta interface.
|
|
6
|
+
*
|
|
7
|
+
* @abstract
|
|
8
|
+
*/
|
|
9
|
+
export class BaseRuntime {
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
if (this.constructor === BaseRuntime) {
|
|
13
|
+
throw new Error('BaseRuntime is abstract and cannot be instantiated directly');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ==========================================
|
|
18
|
+
// IDENTIFICATION
|
|
19
|
+
// ==========================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Identificador único do runtime
|
|
23
|
+
* @returns {string} Ex: 'java-maven', 'node-npm', 'python-pip'
|
|
24
|
+
*/
|
|
25
|
+
getId() {
|
|
26
|
+
throw new Error('Method getId() must be implemented');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Nome amigável do runtime
|
|
31
|
+
* @returns {string} Ex: 'Java (Maven)', 'Node.js (npm)'
|
|
32
|
+
*/
|
|
33
|
+
getName() {
|
|
34
|
+
throw new Error('Method getName() must be implemented');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Linguagem do runtime
|
|
39
|
+
* @returns {string} Ex: 'java', 'node', 'python', 'dotnet', 'go'
|
|
40
|
+
*/
|
|
41
|
+
getLanguage() {
|
|
42
|
+
throw new Error('Method getLanguage() must be implemented');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build tool usado
|
|
47
|
+
* @returns {string} Ex: 'maven', 'gradle', 'npm', 'yarn', 'pip'
|
|
48
|
+
*/
|
|
49
|
+
getBuildTool() {
|
|
50
|
+
throw new Error('Method getBuildTool() must be implemented');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ==========================================
|
|
54
|
+
// DETECTION
|
|
55
|
+
// ==========================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Arquivos marcadores que indicam este runtime
|
|
59
|
+
* @returns {string[]} Ex: ['pom.xml'], ['package.json']
|
|
60
|
+
*/
|
|
61
|
+
getMarkerFiles() {
|
|
62
|
+
throw new Error('Method getMarkerFiles() must be implemented');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Verifica se este runtime é compatível com o workspace
|
|
67
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
68
|
+
* @returns {Promise<boolean>}
|
|
69
|
+
*/
|
|
70
|
+
async isCompatible(workspacePath) {
|
|
71
|
+
throw new Error('Method isCompatible() must be implemented');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Detecta informações do projeto
|
|
76
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
77
|
+
* @returns {Promise<ProjectInfo>}
|
|
78
|
+
*/
|
|
79
|
+
async detectProject(workspacePath) {
|
|
80
|
+
throw new Error('Method detectProject() must be implemented');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ==========================================
|
|
84
|
+
// BUILD & RUN
|
|
85
|
+
// ==========================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Instala dependências do projeto
|
|
89
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
90
|
+
* @param {object} options - Opções adicionais
|
|
91
|
+
* @returns {Promise<CommandResult>}
|
|
92
|
+
*/
|
|
93
|
+
async install(workspacePath, options = {}) {
|
|
94
|
+
throw new Error('Method install() must be implemented');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Compila o projeto
|
|
99
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
100
|
+
* @param {object} options - Opções adicionais
|
|
101
|
+
* @returns {Promise<CommandResult>}
|
|
102
|
+
*/
|
|
103
|
+
async build(workspacePath, options = {}) {
|
|
104
|
+
throw new Error('Method build() must be implemented');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Retorna o comando para iniciar o serviço
|
|
109
|
+
* @param {object} options - Opções (port, profile, etc.)
|
|
110
|
+
* @returns {StartCommand} { cmd: string, args: string[], env?: object }
|
|
111
|
+
*/
|
|
112
|
+
getStartCommand(options = {}) {
|
|
113
|
+
throw new Error('Method getStartCommand() must be implemented');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Padrão regex para detectar quando o serviço está pronto
|
|
118
|
+
* @returns {RegExp}
|
|
119
|
+
*/
|
|
120
|
+
getReadyPattern() {
|
|
121
|
+
throw new Error('Method getReadyPattern() must be implemented');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Porta padrão do framework
|
|
126
|
+
* @returns {number}
|
|
127
|
+
*/
|
|
128
|
+
getDefaultPort() {
|
|
129
|
+
return 8080;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Detecta a porta configurada no projeto
|
|
134
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
135
|
+
* @returns {Promise<number|null>}
|
|
136
|
+
*/
|
|
137
|
+
async detectPort(workspacePath) {
|
|
138
|
+
return this.getDefaultPort();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ==========================================
|
|
142
|
+
// TEST
|
|
143
|
+
// ==========================================
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Executa testes do projeto
|
|
147
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
148
|
+
* @param {object} options - Opções (testFile, coverage, etc.)
|
|
149
|
+
* @returns {Promise<TestResult>}
|
|
150
|
+
*/
|
|
151
|
+
async test(workspacePath, options = {}) {
|
|
152
|
+
throw new Error('Method test() must be implemented');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Executa um teste específico
|
|
157
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
158
|
+
* @param {string} testFile - Arquivo de teste
|
|
159
|
+
* @returns {Promise<TestResult>}
|
|
160
|
+
*/
|
|
161
|
+
async testSingle(workspacePath, testFile) {
|
|
162
|
+
throw new Error('Method testSingle() must be implemented');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ==========================================
|
|
166
|
+
// CODE ANALYSIS
|
|
167
|
+
// ==========================================
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extensões de arquivo suportadas
|
|
171
|
+
* @returns {string[]} Ex: ['.java'], ['.js', '.ts']
|
|
172
|
+
*/
|
|
173
|
+
getFileExtensions() {
|
|
174
|
+
throw new Error('Method getFileExtensions() must be implemented');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Encontra integrações HTTP no código
|
|
179
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
180
|
+
* @returns {Promise<Integration[]>}
|
|
181
|
+
*/
|
|
182
|
+
async findIntegrations(workspacePath) {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Analisa imports/dependências de um arquivo
|
|
188
|
+
* @param {string} filePath - Caminho do arquivo
|
|
189
|
+
* @param {string} content - Conteúdo do arquivo
|
|
190
|
+
* @returns {Promise<Dependency[]>}
|
|
191
|
+
*/
|
|
192
|
+
async analyzeImports(filePath, content) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ==========================================
|
|
197
|
+
// PATCH
|
|
198
|
+
// ==========================================
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Valida se um diff é aplicável
|
|
202
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
203
|
+
* @param {string} diff - Unified diff
|
|
204
|
+
* @returns {Promise<ValidationResult>}
|
|
205
|
+
*/
|
|
206
|
+
async validatePatch(workspacePath, diff) {
|
|
207
|
+
return { valid: true, errors: [] };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Ações pós-patch (ex: reformatar código)
|
|
212
|
+
* @param {string} workspacePath - Caminho do workspace
|
|
213
|
+
* @param {string[]} modifiedFiles - Arquivos modificados
|
|
214
|
+
* @returns {Promise<void>}
|
|
215
|
+
*/
|
|
216
|
+
async postPatchActions(workspacePath, modifiedFiles) {
|
|
217
|
+
// Override nas implementações específicas
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ==========================================
|
|
222
|
+
// TYPE DEFINITIONS
|
|
223
|
+
// ==========================================
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @typedef {object} ProjectInfo
|
|
227
|
+
* @property {string} name - Nome do projeto
|
|
228
|
+
* @property {string} version - Versão
|
|
229
|
+
* @property {string} language - Linguagem
|
|
230
|
+
* @property {string} framework - Framework detectado
|
|
231
|
+
* @property {string} buildTool - Build tool
|
|
232
|
+
* @property {number} port - Porta configurada
|
|
233
|
+
*/
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @typedef {object} CommandResult
|
|
237
|
+
* @property {number} code - Exit code
|
|
238
|
+
* @property {string} stdout - Standard output
|
|
239
|
+
* @property {string} stderr - Standard error
|
|
240
|
+
* @property {number} duration - Duração em ms
|
|
241
|
+
*/
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @typedef {object} StartCommand
|
|
245
|
+
* @property {string} cmd - Comando
|
|
246
|
+
* @property {string[]} args - Argumentos
|
|
247
|
+
* @property {object} [env] - Variáveis de ambiente adicionais
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @typedef {object} TestResult
|
|
252
|
+
* @property {boolean} success - Se todos os testes passaram
|
|
253
|
+
* @property {number} passed - Número de testes que passaram
|
|
254
|
+
* @property {number} failed - Número de testes que falharam
|
|
255
|
+
* @property {number} skipped - Número de testes ignorados
|
|
256
|
+
* @property {number} duration - Duração em ms
|
|
257
|
+
* @property {string} output - Output completo
|
|
258
|
+
* @property {TestFailure[]} failures - Lista de falhas
|
|
259
|
+
*/
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @typedef {object} TestFailure
|
|
263
|
+
* @property {string} testName - Nome do teste
|
|
264
|
+
* @property {string} message - Mensagem de erro
|
|
265
|
+
* @property {string} stackTrace - Stack trace
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @typedef {object} Integration
|
|
270
|
+
* @property {string} type - Tipo (rest-template, feign, axios, etc.)
|
|
271
|
+
* @property {string} file - Arquivo onde foi encontrado
|
|
272
|
+
* @property {number} line - Linha
|
|
273
|
+
* @property {string} targetUrl - URL alvo (se detectável)
|
|
274
|
+
* @property {string} method - Método HTTP (se detectável)
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @typedef {object} Dependency
|
|
279
|
+
* @property {string} name - Nome da dependência
|
|
280
|
+
* @property {string} type - Tipo (import, require, using)
|
|
281
|
+
* @property {boolean} isExternal - Se é externa ao projeto
|
|
282
|
+
*/
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @typedef {object} ValidationResult
|
|
286
|
+
* @property {boolean} valid - Se o patch é válido
|
|
287
|
+
* @property {string[]} errors - Lista de erros
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
export default BaseRuntime;
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { BaseRuntime } from '../base-runtime.js';
|
|
3
|
+
import { exists, readFile } from '../../utils/fs-utils.js';
|
|
4
|
+
import { run } from '../../utils/exec-utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* GradleRuntime
|
|
8
|
+
*
|
|
9
|
+
* Runtime para projetos Java com Gradle.
|
|
10
|
+
* Suporta Spring Boot, Quarkus, e projetos Gradle genéricos.
|
|
11
|
+
*/
|
|
12
|
+
export class GradleRuntime extends BaseRuntime {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.framework = null;
|
|
17
|
+
this.useWrapper = true; // Usar ./gradlew por padrão
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ==========================================
|
|
21
|
+
// IDENTIFICATION
|
|
22
|
+
// ==========================================
|
|
23
|
+
|
|
24
|
+
getId() {
|
|
25
|
+
return 'java-gradle';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getName() {
|
|
29
|
+
return 'Java (Gradle)';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getLanguage() {
|
|
33
|
+
return 'java';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getBuildTool() {
|
|
37
|
+
return 'gradle';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ==========================================
|
|
41
|
+
// DETECTION
|
|
42
|
+
// ==========================================
|
|
43
|
+
|
|
44
|
+
getMarkerFiles() {
|
|
45
|
+
return ['build.gradle', 'build.gradle.kts'];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async isCompatible(workspacePath) {
|
|
49
|
+
const groovyPath = path.join(workspacePath, 'build.gradle');
|
|
50
|
+
const kotlinPath = path.join(workspacePath, 'build.gradle.kts');
|
|
51
|
+
|
|
52
|
+
return await exists(groovyPath) || await exists(kotlinPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async detectProject(workspacePath) {
|
|
56
|
+
let buildContent = '';
|
|
57
|
+
|
|
58
|
+
const groovyPath = path.join(workspacePath, 'build.gradle');
|
|
59
|
+
const kotlinPath = path.join(workspacePath, 'build.gradle.kts');
|
|
60
|
+
|
|
61
|
+
if (await exists(groovyPath)) {
|
|
62
|
+
buildContent = await readFile(groovyPath, 'utf8');
|
|
63
|
+
} else if (await exists(kotlinPath)) {
|
|
64
|
+
buildContent = await readFile(kotlinPath, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Verificar se existe wrapper
|
|
68
|
+
const wrapperPath = path.join(workspacePath, 'gradlew');
|
|
69
|
+
this.useWrapper = await exists(wrapperPath);
|
|
70
|
+
|
|
71
|
+
// Detectar nome do projeto
|
|
72
|
+
const settingsPath = path.join(workspacePath, 'settings.gradle');
|
|
73
|
+
const settingsKtsPath = path.join(workspacePath, 'settings.gradle.kts');
|
|
74
|
+
let name = path.basename(workspacePath);
|
|
75
|
+
|
|
76
|
+
if (await exists(settingsPath)) {
|
|
77
|
+
const settings = await readFile(settingsPath, 'utf8');
|
|
78
|
+
const match = settings.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
|
|
79
|
+
if (match) name = match[1];
|
|
80
|
+
} else if (await exists(settingsKtsPath)) {
|
|
81
|
+
const settings = await readFile(settingsKtsPath, 'utf8');
|
|
82
|
+
const match = settings.match(/rootProject\.name\s*=\s*"([^"]+)"/);
|
|
83
|
+
if (match) name = match[1];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Detectar versão
|
|
87
|
+
const versionMatch = buildContent.match(/version\s*=?\s*['"]([^'"]+)['"]/);
|
|
88
|
+
const version = versionMatch ? versionMatch[1] : '0.0.1';
|
|
89
|
+
|
|
90
|
+
// Detectar framework
|
|
91
|
+
const framework = this.detectFramework(buildContent);
|
|
92
|
+
this.framework = framework;
|
|
93
|
+
|
|
94
|
+
// Detectar porta
|
|
95
|
+
const port = await this.detectPort(workspacePath);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
name,
|
|
99
|
+
version,
|
|
100
|
+
language: 'java',
|
|
101
|
+
framework,
|
|
102
|
+
buildTool: 'gradle',
|
|
103
|
+
port
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
detectFramework(buildContent) {
|
|
108
|
+
if (buildContent.includes('org.springframework.boot') ||
|
|
109
|
+
buildContent.includes('spring-boot')) {
|
|
110
|
+
return 'spring-boot';
|
|
111
|
+
}
|
|
112
|
+
if (buildContent.includes('quarkus')) {
|
|
113
|
+
return 'quarkus';
|
|
114
|
+
}
|
|
115
|
+
if (buildContent.includes('micronaut')) {
|
|
116
|
+
return 'micronaut';
|
|
117
|
+
}
|
|
118
|
+
return 'java-plain';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ==========================================
|
|
122
|
+
// BUILD & RUN
|
|
123
|
+
// ==========================================
|
|
124
|
+
|
|
125
|
+
getGradleCommand() {
|
|
126
|
+
return this.useWrapper ? './gradlew' : 'gradle';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async install(workspacePath, options = {}) {
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
const cmd = this.getGradleCommand();
|
|
132
|
+
const result = await run(cmd, ['dependencies', '--quiet'], workspacePath);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
...result,
|
|
136
|
+
duration: Date.now() - startTime
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async build(workspacePath, options = {}) {
|
|
141
|
+
const startTime = Date.now();
|
|
142
|
+
const cmd = this.getGradleCommand();
|
|
143
|
+
const args = ['build', '-x', 'test', '--quiet'];
|
|
144
|
+
|
|
145
|
+
const result = await run(cmd, args, workspacePath);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
...result,
|
|
149
|
+
duration: Date.now() - startTime
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getStartCommand(options = {}) {
|
|
154
|
+
const cmd = this.getGradleCommand();
|
|
155
|
+
const framework = options.framework || this.framework || 'spring-boot';
|
|
156
|
+
const port = options.port || 8080;
|
|
157
|
+
const profile = options.profile || 'default';
|
|
158
|
+
|
|
159
|
+
if (framework === 'spring-boot') {
|
|
160
|
+
return {
|
|
161
|
+
cmd,
|
|
162
|
+
args: [
|
|
163
|
+
'bootRun',
|
|
164
|
+
`--args='--server.port=${port} --spring.profiles.active=${profile}'`,
|
|
165
|
+
'--quiet'
|
|
166
|
+
],
|
|
167
|
+
env: {
|
|
168
|
+
SERVER_PORT: String(port)
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (framework === 'quarkus') {
|
|
174
|
+
return {
|
|
175
|
+
cmd,
|
|
176
|
+
args: [
|
|
177
|
+
'quarkusDev',
|
|
178
|
+
`-Dquarkus.http.port=${port}`,
|
|
179
|
+
'--quiet'
|
|
180
|
+
],
|
|
181
|
+
env: {
|
|
182
|
+
QUARKUS_HTTP_PORT: String(port)
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Fallback
|
|
188
|
+
return {
|
|
189
|
+
cmd,
|
|
190
|
+
args: ['run', '--quiet'],
|
|
191
|
+
env: {}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
getReadyPattern() {
|
|
196
|
+
if (this.framework === 'spring-boot') {
|
|
197
|
+
return /Started \w+ in \d+\.?\d* seconds/i;
|
|
198
|
+
}
|
|
199
|
+
if (this.framework === 'quarkus') {
|
|
200
|
+
return /Quarkus .* started in/i;
|
|
201
|
+
}
|
|
202
|
+
return /started|listening|ready/i;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getDefaultPort() {
|
|
206
|
+
return 8080;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async detectPort(workspacePath) {
|
|
210
|
+
// Mesma lógica do Maven - configs são iguais
|
|
211
|
+
const propsPath = path.join(workspacePath, 'src/main/resources/application.properties');
|
|
212
|
+
if (await exists(propsPath)) {
|
|
213
|
+
const content = await readFile(propsPath, 'utf8');
|
|
214
|
+
const match = content.match(/server\.port\s*=\s*(\d+)/);
|
|
215
|
+
if (match) return parseInt(match[1]);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const ymlPath = path.join(workspacePath, 'src/main/resources/application.yml');
|
|
219
|
+
if (await exists(ymlPath)) {
|
|
220
|
+
const content = await readFile(ymlPath, 'utf8');
|
|
221
|
+
const match = content.match(/port:\s*(\d+)/);
|
|
222
|
+
if (match) return parseInt(match[1]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return this.getDefaultPort();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ==========================================
|
|
229
|
+
// TEST
|
|
230
|
+
// ==========================================
|
|
231
|
+
|
|
232
|
+
async test(workspacePath, options = {}) {
|
|
233
|
+
const startTime = Date.now();
|
|
234
|
+
const cmd = this.getGradleCommand();
|
|
235
|
+
|
|
236
|
+
// Clean primeiro
|
|
237
|
+
await run(cmd, ['clean', '--quiet'], workspacePath);
|
|
238
|
+
|
|
239
|
+
// Rodar testes
|
|
240
|
+
const result = await run(cmd, ['test', '--quiet'], workspacePath);
|
|
241
|
+
|
|
242
|
+
const testResult = this.parseTestOutput(result, workspacePath);
|
|
243
|
+
testResult.duration = Date.now() - startTime;
|
|
244
|
+
|
|
245
|
+
return testResult;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async testSingle(workspacePath, testFile) {
|
|
249
|
+
const startTime = Date.now();
|
|
250
|
+
const cmd = this.getGradleCommand();
|
|
251
|
+
|
|
252
|
+
// Extrair nome da classe
|
|
253
|
+
const className = path.basename(testFile, '.java');
|
|
254
|
+
|
|
255
|
+
const result = await run(cmd, [
|
|
256
|
+
'test',
|
|
257
|
+
'--tests',
|
|
258
|
+
className,
|
|
259
|
+
'--quiet'
|
|
260
|
+
], workspacePath);
|
|
261
|
+
|
|
262
|
+
const testResult = this.parseTestOutput(result, workspacePath);
|
|
263
|
+
testResult.duration = Date.now() - startTime;
|
|
264
|
+
|
|
265
|
+
return testResult;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async parseTestOutput(result, workspacePath) {
|
|
269
|
+
const output = result.stdout + result.stderr;
|
|
270
|
+
|
|
271
|
+
// Tentar ler relatório XML do Gradle se disponível
|
|
272
|
+
const reportDir = path.join(workspacePath, 'build/test-results/test');
|
|
273
|
+
|
|
274
|
+
// Parse básico do output
|
|
275
|
+
let passed = 0;
|
|
276
|
+
let failed = 0;
|
|
277
|
+
let skipped = 0;
|
|
278
|
+
|
|
279
|
+
// Gradle output format
|
|
280
|
+
const summaryMatch = output.match(/(\d+)\s+tests?\s+completed,\s+(\d+)\s+failed/);
|
|
281
|
+
if (summaryMatch) {
|
|
282
|
+
const total = parseInt(summaryMatch[1]);
|
|
283
|
+
failed = parseInt(summaryMatch[2]);
|
|
284
|
+
passed = total - failed;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const skippedMatch = output.match(/(\d+)\s+skipped/);
|
|
288
|
+
if (skippedMatch) {
|
|
289
|
+
skipped = parseInt(skippedMatch[1]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
success: result.code === 0 && failed === 0,
|
|
294
|
+
passed,
|
|
295
|
+
failed,
|
|
296
|
+
skipped,
|
|
297
|
+
duration: 0,
|
|
298
|
+
output,
|
|
299
|
+
failures: this.extractFailures(output)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
extractFailures(output) {
|
|
304
|
+
const failures = [];
|
|
305
|
+
|
|
306
|
+
// Gradle mostra falhas com formato específico
|
|
307
|
+
const failureRegex = /(\w+Test)\s+>\s+(\w+).*FAILED/g;
|
|
308
|
+
let match;
|
|
309
|
+
|
|
310
|
+
while ((match = failureRegex.exec(output)) !== null) {
|
|
311
|
+
failures.push({
|
|
312
|
+
testName: `${match[1]}.${match[2]}`,
|
|
313
|
+
message: '',
|
|
314
|
+
stackTrace: ''
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return failures;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ==========================================
|
|
322
|
+
// CODE ANALYSIS
|
|
323
|
+
// ==========================================
|
|
324
|
+
|
|
325
|
+
getFileExtensions() {
|
|
326
|
+
return ['.java', '.kt']; // Suporte a Kotlin também
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async findIntegrations(workspacePath) {
|
|
330
|
+
const { JavaIntegrationAnalyzer } = await import('./java-integrations.js');
|
|
331
|
+
const analyzer = new JavaIntegrationAnalyzer(workspacePath);
|
|
332
|
+
return analyzer.findAll();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async analyzeImports(filePath, content) {
|
|
336
|
+
// Mesma lógica do Maven
|
|
337
|
+
const imports = [];
|
|
338
|
+
const importRegex = /import\s+(static\s+)?([a-zA-Z0-9_.]+)(?:\.\*)?;/g;
|
|
339
|
+
|
|
340
|
+
let match;
|
|
341
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
342
|
+
imports.push({
|
|
343
|
+
name: match[2],
|
|
344
|
+
type: match[1] ? 'static-import' : 'import',
|
|
345
|
+
isExternal: !match[2].startsWith('com.')
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return imports;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ==========================================
|
|
353
|
+
// PATCH
|
|
354
|
+
// ==========================================
|
|
355
|
+
|
|
356
|
+
async validatePatch(workspacePath, diff) {
|
|
357
|
+
const errors = [];
|
|
358
|
+
|
|
359
|
+
const fileRegex = /^[\+\-]{3}\s+[ab]\/(.+)$/gm;
|
|
360
|
+
let match;
|
|
361
|
+
|
|
362
|
+
while ((match = fileRegex.exec(diff)) !== null) {
|
|
363
|
+
const filePath = path.join(workspacePath, match[1]);
|
|
364
|
+
if (match[0].startsWith('---') && !(await exists(filePath))) {
|
|
365
|
+
if (!diff.includes('+++ /dev/null')) {
|
|
366
|
+
errors.push(`File not found: ${match[1]}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
valid: errors.length === 0,
|
|
373
|
+
errors
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export default GradleRuntime;
|