deepdebug-local-agent 0.3.7 → 0.3.9
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/analyzers/config-analyzer.js +446 -0
- package/analyzers/controller-analyzer.js +429 -0
- package/analyzers/dto-analyzer.js +455 -0
- package/detectors/build-tool-detector.js +0 -0
- package/detectors/framework-detector.js +91 -0
- package/detectors/language-detector.js +89 -0
- package/detectors/multi-project-detector.js +191 -0
- package/detectors/service-detector.js +244 -0
- package/detectors.js +30 -0
- package/exec-utils.js +215 -0
- package/fs-utils.js +34 -0
- package/mcp-http-server.js +313 -0
- package/package.json +1 -1
- package/patch.js +607 -0
- package/ports.js +69 -0
- package/server.js +1 -138
- package/workspace/detect-port.js +176 -0
- package/workspace/file-reader.js +54 -0
- package/workspace/git-client.js +0 -0
- package/workspace/process-manager.js +619 -0
- package/workspace/scanner.js +72 -0
- package/workspace-manager.js +172 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { exists, readdir, stat } from "../fs-utils.js";
|
|
3
|
+
import { ServiceDetector } from "./service-detector.js";
|
|
4
|
+
import { LanguageDetector } from "./language-detector.js";
|
|
5
|
+
import { FrameworkDetector } from "./framework-detector.js";
|
|
6
|
+
import { WorkspaceScanner } from "../workspace/scanner.js";
|
|
7
|
+
import { FileReader } from "../workspace/file-reader.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MultiProjectDetector
|
|
11
|
+
*
|
|
12
|
+
* Detecta múltiplos projetos dentro de um workspace
|
|
13
|
+
* Escaneia subpastas em busca de projetos independentes (pom.xml, package.json, etc)
|
|
14
|
+
*/
|
|
15
|
+
export class MultiProjectDetector {
|
|
16
|
+
constructor(workspaceRoot) {
|
|
17
|
+
this.workspaceRoot = workspaceRoot;
|
|
18
|
+
this.maxDepth = 2; // Escanear até 2 níveis de profundidade
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Detecta todos os projetos no workspace
|
|
23
|
+
*/
|
|
24
|
+
async detectProjects() {
|
|
25
|
+
console.log(`🔍 Scanning workspace for multiple projects: ${this.workspaceRoot}`);
|
|
26
|
+
|
|
27
|
+
const projects = [];
|
|
28
|
+
|
|
29
|
+
// 1️⃣ Verificar se o próprio workspace root é um projeto
|
|
30
|
+
if (await this.isProject(this.workspaceRoot)) {
|
|
31
|
+
console.log(`✅ Root is a project`);
|
|
32
|
+
projects.push({
|
|
33
|
+
name: path.basename(this.workspaceRoot),
|
|
34
|
+
path: this.workspaceRoot,
|
|
35
|
+
isRoot: true
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2️⃣ Escanear subpastas em busca de projetos
|
|
40
|
+
const subProjects = await this.scanSubdirectories(this.workspaceRoot, 0);
|
|
41
|
+
projects.push(...subProjects);
|
|
42
|
+
|
|
43
|
+
console.log(`✅ Found ${projects.length} project(s) in workspace`);
|
|
44
|
+
|
|
45
|
+
// 3️⃣ Detectar serviços para cada projeto
|
|
46
|
+
const allServices = [];
|
|
47
|
+
for (const project of projects) {
|
|
48
|
+
console.log(`🔎 Detecting services in: ${project.name}`);
|
|
49
|
+
const services = await this.detectServicesInProject(project);
|
|
50
|
+
allServices.push(...services);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(`✅ Total services detected: ${allServices.length}`);
|
|
54
|
+
return allServices;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Verifica se um diretório é um projeto
|
|
59
|
+
*/
|
|
60
|
+
async isProject(dir) {
|
|
61
|
+
const markers = [
|
|
62
|
+
'pom.xml', // Maven
|
|
63
|
+
'build.gradle', // Gradle
|
|
64
|
+
'build.gradle.kts', // Gradle Kotlin
|
|
65
|
+
'package.json', // Node.js
|
|
66
|
+
'requirements.txt', // Python
|
|
67
|
+
'pyproject.toml', // Python Poetry
|
|
68
|
+
'go.mod', // Go
|
|
69
|
+
'Cargo.toml', // Rust
|
|
70
|
+
'.csproj', // .NET (qualquer arquivo .csproj)
|
|
71
|
+
'.sln' // .NET solution
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const marker of markers) {
|
|
75
|
+
// Para .csproj e .sln, procurar qualquer arquivo com extensão
|
|
76
|
+
if (marker.startsWith('.')) {
|
|
77
|
+
try {
|
|
78
|
+
const files = await readdir(dir);
|
|
79
|
+
if (files.some(f => f.endsWith(marker))) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// Para markers específicos, verificar se existe
|
|
87
|
+
if (await exists(path.join(dir, marker))) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Escaneia subdiretórios recursivamente
|
|
98
|
+
*/
|
|
99
|
+
async scanSubdirectories(dir, depth) {
|
|
100
|
+
if (depth >= this.maxDepth) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const projects = [];
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
108
|
+
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (!entry.isDirectory()) continue;
|
|
111
|
+
|
|
112
|
+
// Ignorar pastas comuns que não são projetos
|
|
113
|
+
const ignoreDirs = [
|
|
114
|
+
'node_modules', 'target', 'build', 'dist', 'out',
|
|
115
|
+
'.git', '.idea', '.vscode', '__pycache__',
|
|
116
|
+
'.next', 'coverage', '.gradle', 'vendor',
|
|
117
|
+
'venv', 'env', '.env'
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
if (ignoreDirs.includes(entry.name)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const fullPath = path.join(dir, entry.name);
|
|
125
|
+
|
|
126
|
+
// Verificar se é um projeto
|
|
127
|
+
if (await this.isProject(fullPath)) {
|
|
128
|
+
console.log(`✅ Found project: ${entry.name}`);
|
|
129
|
+
projects.push({
|
|
130
|
+
name: entry.name,
|
|
131
|
+
path: fullPath,
|
|
132
|
+
isRoot: false
|
|
133
|
+
});
|
|
134
|
+
// Não escanear dentro de projetos encontrados
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Continuar escaneando recursivamente
|
|
139
|
+
const subProjects = await this.scanSubdirectories(fullPath, depth + 1);
|
|
140
|
+
projects.push(...subProjects);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.warn(`⚠️ Could not scan directory ${dir}: ${err.message}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return projects;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Detecta serviços em um projeto específico
|
|
151
|
+
*/
|
|
152
|
+
async detectServicesInProject(project) {
|
|
153
|
+
try {
|
|
154
|
+
// Usar o scanner para analisar o projeto
|
|
155
|
+
const scanner = new WorkspaceScanner(project.path);
|
|
156
|
+
const structure = await scanner.scan();
|
|
157
|
+
|
|
158
|
+
// Detectar linguagem
|
|
159
|
+
const languageDetector = new LanguageDetector(structure.files);
|
|
160
|
+
const languageInfo = languageDetector.detect();
|
|
161
|
+
|
|
162
|
+
// Detectar framework
|
|
163
|
+
const fileReader = new FileReader(project.path);
|
|
164
|
+
const frameworkDetector = new FrameworkDetector(
|
|
165
|
+
languageInfo.primary,
|
|
166
|
+
structure.files,
|
|
167
|
+
fileReader
|
|
168
|
+
);
|
|
169
|
+
const frameworkInfo = await frameworkDetector.detect();
|
|
170
|
+
|
|
171
|
+
// Detectar serviços
|
|
172
|
+
const serviceDetector = new ServiceDetector(
|
|
173
|
+
project.path,
|
|
174
|
+
languageInfo,
|
|
175
|
+
frameworkInfo
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const services = await serviceDetector.detect();
|
|
179
|
+
|
|
180
|
+
// Adicionar nome do workspace a cada serviço
|
|
181
|
+
return services.map(service => ({
|
|
182
|
+
...service,
|
|
183
|
+
workspaceName: project.name,
|
|
184
|
+
workspacePath: this.workspaceRoot
|
|
185
|
+
}));
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error(`❌ Error detecting services in ${project.name}: ${err.message}`);
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { exists, readFile } from "../fs-utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ServiceDetector
|
|
6
|
+
*
|
|
7
|
+
* Detecta serviços/microservices em um workspace
|
|
8
|
+
* Identifica: nome, porta, tipo, comando de start
|
|
9
|
+
*/
|
|
10
|
+
export class ServiceDetector {
|
|
11
|
+
constructor(root, languageInfo, frameworkInfo) {
|
|
12
|
+
this.root = root;
|
|
13
|
+
this.language = languageInfo.primary;
|
|
14
|
+
this.framework = frameworkInfo.framework;
|
|
15
|
+
this.buildTool = frameworkInfo.buildTool;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Detecta todos os serviços no workspace
|
|
20
|
+
*/
|
|
21
|
+
async detect() {
|
|
22
|
+
const services = [];
|
|
23
|
+
|
|
24
|
+
// Detectar por tipo de projeto
|
|
25
|
+
if (this.language === "java" && this.framework === "spring-boot") {
|
|
26
|
+
const service = await this.detectSpringBootService();
|
|
27
|
+
if (service) services.push(service);
|
|
28
|
+
} else if (this.language === "node") {
|
|
29
|
+
const service = await this.detectNodeService();
|
|
30
|
+
if (service) services.push(service);
|
|
31
|
+
} else if (this.language === "python") {
|
|
32
|
+
const service = await this.detectPythonService();
|
|
33
|
+
if (service) services.push(service);
|
|
34
|
+
} else if (this.language === "dotnet") {
|
|
35
|
+
services.push(this.detectDotNetService());
|
|
36
|
+
} else if (this.language === "go") {
|
|
37
|
+
services.push(this.detectGoService());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return services;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detecta serviço Spring Boot
|
|
45
|
+
*/
|
|
46
|
+
async detectSpringBootService() {
|
|
47
|
+
const pomPath = path.join(this.root, "pom.xml");
|
|
48
|
+
const gradlePath = path.join(this.root, "build.gradle");
|
|
49
|
+
|
|
50
|
+
let serviceName = path.basename(this.root); // Fallback: nome da pasta
|
|
51
|
+
let port = 8080;
|
|
52
|
+
|
|
53
|
+
// Tentar extrair nome do pom.xml (ignorando o parent)
|
|
54
|
+
if (await exists(pomPath)) {
|
|
55
|
+
const content = await readFile(pomPath, "utf8");
|
|
56
|
+
|
|
57
|
+
// Remove o bloco <parent>...</parent> para não pegar o artifactId errado
|
|
58
|
+
const contentWithoutParent = content.replace(/<parent>[\s\S]*?<\/parent>/gi, '');
|
|
59
|
+
const artifactIdMatch = contentWithoutParent.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
60
|
+
|
|
61
|
+
if (artifactIdMatch) {
|
|
62
|
+
serviceName = artifactIdMatch[1].trim();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Tentar extrair do build.gradle
|
|
67
|
+
if (await exists(gradlePath)) {
|
|
68
|
+
const content = await readFile(gradlePath, "utf8");
|
|
69
|
+
const nameMatch = content.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
|
|
70
|
+
if (nameMatch) {
|
|
71
|
+
serviceName = nameMatch[1];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Tentar detectar porta do application.yml/properties
|
|
76
|
+
port = await this.detectSpringBootPort();
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: serviceName,
|
|
80
|
+
name: serviceName,
|
|
81
|
+
type: "spring-boot",
|
|
82
|
+
language: "java",
|
|
83
|
+
framework: "spring-boot",
|
|
84
|
+
buildTool: this.buildTool,
|
|
85
|
+
port,
|
|
86
|
+
status: "stopped",
|
|
87
|
+
path: this.root
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Detecta porta do Spring Boot
|
|
93
|
+
*/
|
|
94
|
+
async detectSpringBootPort() {
|
|
95
|
+
const candidates = [
|
|
96
|
+
"src/main/resources/application.yml",
|
|
97
|
+
"src/main/resources/application.yaml",
|
|
98
|
+
"src/main/resources/application.properties"
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
for (const rel of candidates) {
|
|
102
|
+
const fullPath = path.join(this.root, rel);
|
|
103
|
+
if (await exists(fullPath)) {
|
|
104
|
+
const content = await readFile(fullPath, "utf8");
|
|
105
|
+
|
|
106
|
+
// YAML - múltiplos padrões
|
|
107
|
+
if (rel.endsWith(".yml") || rel.endsWith(".yaml")) {
|
|
108
|
+
// Padrão 1: server:\n port: 8080
|
|
109
|
+
let match = content.match(/server:\s*\n\s*port:\s*(\d+)/);
|
|
110
|
+
if (match) return parseInt(match[1]);
|
|
111
|
+
|
|
112
|
+
// Padrão 2: server.port: 8080
|
|
113
|
+
match = content.match(/server\.port:\s*(\d+)/);
|
|
114
|
+
if (match) return parseInt(match[1]);
|
|
115
|
+
|
|
116
|
+
// Padrão 3: port: ${PORT:8080}
|
|
117
|
+
match = content.match(/port:\s*\$\{[^}]*:(\d+)\}/);
|
|
118
|
+
if (match) return parseInt(match[1]);
|
|
119
|
+
|
|
120
|
+
// Padrão 4: server:\n port: 8080 (com mais espaços)
|
|
121
|
+
match = content.match(/server:\s*[\r\n]+\s*port:\s*(\d+)/);
|
|
122
|
+
if (match) return parseInt(match[1]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Properties
|
|
126
|
+
if (rel.endsWith(".properties")) {
|
|
127
|
+
const match = content.match(/server\.port\s*=\s*(\d+)/);
|
|
128
|
+
if (match) return parseInt(match[1]);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return 8080; // Default
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Detecta serviço Node.js
|
|
138
|
+
*/
|
|
139
|
+
async detectNodeService() {
|
|
140
|
+
const pkgPath = path.join(this.root, "package.json");
|
|
141
|
+
if (!await exists(pkgPath)) return null;
|
|
142
|
+
|
|
143
|
+
const content = await readFile(pkgPath, "utf8");
|
|
144
|
+
const pkg = JSON.parse(content);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
id: pkg.name || path.basename(this.root),
|
|
148
|
+
name: pkg.name || path.basename(this.root),
|
|
149
|
+
type: this.framework,
|
|
150
|
+
language: "node",
|
|
151
|
+
framework: this.framework,
|
|
152
|
+
buildTool: "npm",
|
|
153
|
+
port: await this.detectNodePort(),
|
|
154
|
+
status: "stopped",
|
|
155
|
+
path: this.root
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Detecta porta do Node.js
|
|
161
|
+
*/
|
|
162
|
+
async detectNodePort() {
|
|
163
|
+
// Tentar .env
|
|
164
|
+
const envPath = path.join(this.root, ".env");
|
|
165
|
+
if (await exists(envPath)) {
|
|
166
|
+
const content = await readFile(envPath, "utf8");
|
|
167
|
+
const match = content.match(/PORT\s*=\s*(\d+)/i);
|
|
168
|
+
if (match) return parseInt(match[1]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Defaults por framework
|
|
172
|
+
const defaults = {
|
|
173
|
+
"next": 3000,
|
|
174
|
+
"express": 3000,
|
|
175
|
+
"nestjs": 3000
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return defaults[this.framework] || 3000;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Detecta serviço Python
|
|
183
|
+
*/
|
|
184
|
+
async detectPythonService() {
|
|
185
|
+
return {
|
|
186
|
+
id: path.basename(this.root),
|
|
187
|
+
name: path.basename(this.root),
|
|
188
|
+
type: this.framework,
|
|
189
|
+
language: "python",
|
|
190
|
+
framework: this.framework,
|
|
191
|
+
buildTool: "pip",
|
|
192
|
+
port: await this.detectPythonPort(),
|
|
193
|
+
status: "stopped",
|
|
194
|
+
path: this.root
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Detecta porta do Python
|
|
200
|
+
*/
|
|
201
|
+
async detectPythonPort() {
|
|
202
|
+
const defaults = {
|
|
203
|
+
"django": 8000,
|
|
204
|
+
"flask": 5000,
|
|
205
|
+
"fastapi": 8000
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return defaults[this.framework] || 8000;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Detecta serviço .NET
|
|
213
|
+
*/
|
|
214
|
+
detectDotNetService() {
|
|
215
|
+
return {
|
|
216
|
+
id: path.basename(this.root),
|
|
217
|
+
name: path.basename(this.root),
|
|
218
|
+
type: "dotnet",
|
|
219
|
+
language: "dotnet",
|
|
220
|
+
framework: "dotnet",
|
|
221
|
+
buildTool: "dotnet",
|
|
222
|
+
port: 5000,
|
|
223
|
+
status: "stopped",
|
|
224
|
+
path: this.root
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Detecta serviço Go
|
|
230
|
+
*/
|
|
231
|
+
detectGoService() {
|
|
232
|
+
return {
|
|
233
|
+
id: path.basename(this.root),
|
|
234
|
+
name: path.basename(this.root),
|
|
235
|
+
type: "go",
|
|
236
|
+
language: "go",
|
|
237
|
+
framework: "go",
|
|
238
|
+
buildTool: "go",
|
|
239
|
+
port: 8080,
|
|
240
|
+
status: "stopped",
|
|
241
|
+
path: this.root
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
package/detectors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { exists, readFile } from "./fs-utils.js";
|
|
3
|
+
|
|
4
|
+
export async function detectProject(root) {
|
|
5
|
+
const markers = [
|
|
6
|
+
{ file: "pom.xml", lang: "java", buildTool: "maven" },
|
|
7
|
+
{ file: "build.gradle", lang: "java", buildTool: "gradle" },
|
|
8
|
+
{ file: "package.json", lang: "node", buildTool: "npm" },
|
|
9
|
+
{ file: "pyproject.toml", lang: "python", buildTool: "poetry" },
|
|
10
|
+
{ file: "requirements.txt", lang: "python", buildTool: "pip" },
|
|
11
|
+
{ file: "go.mod", lang: "go", buildTool: "go" }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
for (const m of markers) {
|
|
15
|
+
if (await exists(path.join(root, m.file))) {
|
|
16
|
+
return { language: m.lang, buildTool: m.buildTool, marker: m.file };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// .NET: procura primeiro nível
|
|
21
|
+
const dotnet = (await (await import("fs")).promises.readdir(root))
|
|
22
|
+
.find(f => f.endsWith(".csproj") || f.endsWith(".sln"));
|
|
23
|
+
if (dotnet) return { language: ".net", buildTool: "dotnet", marker: dotnet };
|
|
24
|
+
|
|
25
|
+
return { language: "unknown", buildTool: "unknown", marker: null };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function readText(root, relPath) {
|
|
29
|
+
return readFile(path.join(root, relPath), "utf8");
|
|
30
|
+
}
|
package/exec-utils.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import stripAnsi from "strip-ansi";
|
|
3
|
+
|
|
4
|
+
export function run(cmd, args, cwd, timeoutMs = 10 * 60 * 1000) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const start = Date.now();
|
|
7
|
+
const child = spawn(cmd, args, { cwd, shell: true });
|
|
8
|
+
let stdout = "";
|
|
9
|
+
let stderr = "";
|
|
10
|
+
const timer = setTimeout(() => {
|
|
11
|
+
try { child.kill("SIGKILL"); } catch {}
|
|
12
|
+
}, timeoutMs);
|
|
13
|
+
|
|
14
|
+
child.stdout.on("data", d => stdout += d.toString());
|
|
15
|
+
child.stderr.on("data", d => stderr += d.toString());
|
|
16
|
+
|
|
17
|
+
child.on("close", code => {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
resolve({
|
|
20
|
+
code,
|
|
21
|
+
stdout: stripAnsi(stdout),
|
|
22
|
+
stderr: stripAnsi(stderr),
|
|
23
|
+
duration: Date.now() - start
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on("error", (err) => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
resolve({
|
|
30
|
+
code: 1,
|
|
31
|
+
stdout: "",
|
|
32
|
+
stderr: err.message,
|
|
33
|
+
duration: Date.now() - start
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compile and optionally test a project
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} options
|
|
43
|
+
* @param {string} options.language - Programming language (java, node, python, go, .net)
|
|
44
|
+
* @param {string} options.buildTool - Build tool (maven, gradle, npm, yarn)
|
|
45
|
+
* @param {string} options.cwd - Working directory
|
|
46
|
+
* @param {boolean} options.skipTests - Skip running tests (default: false)
|
|
47
|
+
* @returns {Promise<{code: number, stdout: string, stderr: string, duration: number}>}
|
|
48
|
+
*/
|
|
49
|
+
export async function compileAndTest({ language, buildTool, cwd, skipTests = false }) {
|
|
50
|
+
console.log(`🔨 [COMPILE] Language: ${language}, BuildTool: ${buildTool}, SkipTests: ${skipTests}`);
|
|
51
|
+
|
|
52
|
+
let result;
|
|
53
|
+
const start = Date.now();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
if (language === "java" && buildTool === "maven") {
|
|
57
|
+
// Maven: clean install (skip tests if requested)
|
|
58
|
+
const args = skipTests
|
|
59
|
+
? ["clean", "install", "-DskipTests", "-q"]
|
|
60
|
+
: ["clean", "install", "-q"];
|
|
61
|
+
|
|
62
|
+
console.log(`🔨 [COMPILE] Running: mvn ${args.join(" ")}`);
|
|
63
|
+
result = await run("mvn", args, cwd);
|
|
64
|
+
}
|
|
65
|
+
else if (language === "java" && buildTool === "gradle") {
|
|
66
|
+
// Gradle: clean build
|
|
67
|
+
const args = skipTests
|
|
68
|
+
? ["clean", "build", "-x", "test"]
|
|
69
|
+
: ["clean", "build"];
|
|
70
|
+
|
|
71
|
+
console.log(`🔨 [COMPILE] Running: ./gradlew ${args.join(" ")}`);
|
|
72
|
+
result = await run("./gradlew", args, cwd);
|
|
73
|
+
}
|
|
74
|
+
else if (language === "node" || buildTool === "npm") {
|
|
75
|
+
// Node/npm: install and optionally test
|
|
76
|
+
console.log(`🔨 [COMPILE] Running: npm install`);
|
|
77
|
+
const installResult = await run("npm", ["install", "--silent"], cwd);
|
|
78
|
+
|
|
79
|
+
if (installResult.code !== 0) {
|
|
80
|
+
return installResult;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!skipTests) {
|
|
84
|
+
console.log(`🔨 [COMPILE] Running: npm test`);
|
|
85
|
+
result = await run("npm", ["test", "--silent"], cwd);
|
|
86
|
+
} else {
|
|
87
|
+
result = installResult;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (language === "node" || buildTool === "yarn") {
|
|
91
|
+
// Yarn
|
|
92
|
+
console.log(`🔨 [COMPILE] Running: yarn install`);
|
|
93
|
+
const installResult = await run("yarn", ["install", "--silent"], cwd);
|
|
94
|
+
|
|
95
|
+
if (installResult.code !== 0) {
|
|
96
|
+
return installResult;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!skipTests) {
|
|
100
|
+
console.log(`🔨 [COMPILE] Running: yarn test`);
|
|
101
|
+
result = await run("yarn", ["test", "--silent"], cwd);
|
|
102
|
+
} else {
|
|
103
|
+
result = installResult;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (language === "python") {
|
|
107
|
+
// Python: pytest
|
|
108
|
+
if (!skipTests) {
|
|
109
|
+
console.log(`🔨 [COMPILE] Running: pytest`);
|
|
110
|
+
result = await run("pytest", [], cwd);
|
|
111
|
+
} else {
|
|
112
|
+
// Python doesn't have a compile step, just return success
|
|
113
|
+
result = { code: 0, stdout: "Python: No compilation needed", stderr: "", duration: 0 };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (language === "go") {
|
|
117
|
+
// Go: build and optionally test
|
|
118
|
+
console.log(`🔨 [COMPILE] Running: go build ./...`);
|
|
119
|
+
const buildResult = await run("go", ["build", "./..."], cwd);
|
|
120
|
+
|
|
121
|
+
if (buildResult.code !== 0) {
|
|
122
|
+
return buildResult;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!skipTests) {
|
|
126
|
+
console.log(`🔨 [COMPILE] Running: go test ./...`);
|
|
127
|
+
result = await run("go", ["test", "./..."], cwd);
|
|
128
|
+
} else {
|
|
129
|
+
result = buildResult;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (language === ".net" || language === "dotnet") {
|
|
133
|
+
// .NET: build and optionally test
|
|
134
|
+
console.log(`🔨 [COMPILE] Running: dotnet build`);
|
|
135
|
+
const buildResult = await run("dotnet", ["build"], cwd);
|
|
136
|
+
|
|
137
|
+
if (buildResult.code !== 0) {
|
|
138
|
+
return buildResult;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!skipTests) {
|
|
142
|
+
console.log(`🔨 [COMPILE] Running: dotnet test`);
|
|
143
|
+
result = await run("dotnet", ["test"], cwd);
|
|
144
|
+
} else {
|
|
145
|
+
result = buildResult;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error(`❌ [COMPILE] Unsupported: ${language}/${buildTool}`);
|
|
150
|
+
return {
|
|
151
|
+
code: 1,
|
|
152
|
+
stdout: "",
|
|
153
|
+
stderr: `Unsupported language/build tool: ${language}/${buildTool}`,
|
|
154
|
+
duration: Date.now() - start
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Log result
|
|
159
|
+
if (result.code === 0) {
|
|
160
|
+
console.log(`✅ [COMPILE] Success in ${result.duration}ms`);
|
|
161
|
+
} else {
|
|
162
|
+
console.error(`❌ [COMPILE] Failed with code ${result.code}`);
|
|
163
|
+
if (result.stderr) {
|
|
164
|
+
console.error(`❌ [COMPILE] Error: ${result.stderr.substring(0, 500)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`❌ [COMPILE] Exception: ${err.message}`);
|
|
172
|
+
return {
|
|
173
|
+
code: 1,
|
|
174
|
+
stdout: "",
|
|
175
|
+
stderr: err.message,
|
|
176
|
+
duration: Date.now() - start
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Legacy function for backward compatibility
|
|
183
|
+
* Returns { steps: [...] } format
|
|
184
|
+
*/
|
|
185
|
+
export async function compileAndTestLegacy({ language, buildTool, cwd }) {
|
|
186
|
+
if (language === "java" && buildTool === "maven") {
|
|
187
|
+
const c1 = await run("mvn", "clean -q".split(" "), cwd);
|
|
188
|
+
const c2 = await run("mvn", "test -q".split(" "), cwd);
|
|
189
|
+
return { steps: [c1, c2] };
|
|
190
|
+
}
|
|
191
|
+
if (language === "java" && buildTool === "gradle") {
|
|
192
|
+
const c1 = await run("./gradlew", ["clean"], cwd);
|
|
193
|
+
const c2 = await run("./gradlew", ["test"], cwd);
|
|
194
|
+
return { steps: [c1, c2] };
|
|
195
|
+
}
|
|
196
|
+
if (language === "node") {
|
|
197
|
+
const c1 = await run("npm", ["install", "--silent"], cwd);
|
|
198
|
+
const c2 = await run("npm", ["test", "--silent"], cwd);
|
|
199
|
+
return { steps: [c1, c2] };
|
|
200
|
+
}
|
|
201
|
+
if (language === "python") {
|
|
202
|
+
const c1 = await run("pytest", [], cwd);
|
|
203
|
+
return { steps: [c1] };
|
|
204
|
+
}
|
|
205
|
+
if (language === "go") {
|
|
206
|
+
const c1 = await run("go", ["test", "./..."], cwd);
|
|
207
|
+
return { steps: [c1] };
|
|
208
|
+
}
|
|
209
|
+
if (language === ".net") {
|
|
210
|
+
const c1 = await run("dotnet", ["build"], cwd);
|
|
211
|
+
const c2 = await run("dotnet", ["test"], cwd);
|
|
212
|
+
return { steps: [c1, c2] };
|
|
213
|
+
}
|
|
214
|
+
return { steps: [{ code: 1, stdout: "", stderr: "Unsupported language/build tool" }] };
|
|
215
|
+
}
|