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,466 @@
|
|
|
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
|
+
* NpmRuntime
|
|
8
|
+
*
|
|
9
|
+
* Runtime para projetos Node.js com npm.
|
|
10
|
+
* Suporta Express, NestJS, Next.js, React, Vue, etc.
|
|
11
|
+
*/
|
|
12
|
+
export class NpmRuntime extends BaseRuntime {
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.framework = null;
|
|
17
|
+
this.packageJson = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ==========================================
|
|
21
|
+
// IDENTIFICATION
|
|
22
|
+
// ==========================================
|
|
23
|
+
|
|
24
|
+
getId() {
|
|
25
|
+
return 'node-npm';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getName() {
|
|
29
|
+
return 'Node.js (npm)';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getLanguage() {
|
|
33
|
+
return 'node';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
getBuildTool() {
|
|
37
|
+
return 'npm';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ==========================================
|
|
41
|
+
// DETECTION
|
|
42
|
+
// ==========================================
|
|
43
|
+
|
|
44
|
+
getMarkerFiles() {
|
|
45
|
+
return ['package.json'];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async isCompatible(workspacePath) {
|
|
49
|
+
const pkgPath = path.join(workspacePath, 'package.json');
|
|
50
|
+
const yarnLock = path.join(workspacePath, 'yarn.lock');
|
|
51
|
+
|
|
52
|
+
// Se tem yarn.lock, prefere YarnRuntime
|
|
53
|
+
if (await exists(yarnLock)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return exists(pkgPath);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async detectProject(workspacePath) {
|
|
61
|
+
const pkgPath = path.join(workspacePath, 'package.json');
|
|
62
|
+
const content = await readFile(pkgPath, 'utf8');
|
|
63
|
+
this.packageJson = JSON.parse(content);
|
|
64
|
+
|
|
65
|
+
const name = this.packageJson.name || path.basename(workspacePath);
|
|
66
|
+
const version = this.packageJson.version || '0.0.1';
|
|
67
|
+
|
|
68
|
+
// Detectar framework
|
|
69
|
+
const framework = this.detectFramework(this.packageJson);
|
|
70
|
+
this.framework = framework;
|
|
71
|
+
|
|
72
|
+
// Detectar porta
|
|
73
|
+
const port = await this.detectPort(workspacePath);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
version,
|
|
78
|
+
language: 'node',
|
|
79
|
+
framework,
|
|
80
|
+
buildTool: 'npm',
|
|
81
|
+
port
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
detectFramework(pkg) {
|
|
86
|
+
const deps = {
|
|
87
|
+
...pkg.dependencies,
|
|
88
|
+
...pkg.devDependencies
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Frontend frameworks
|
|
92
|
+
if (deps['next']) return 'next';
|
|
93
|
+
if (deps['nuxt']) return 'nuxt';
|
|
94
|
+
if (deps['@angular/core']) return 'angular';
|
|
95
|
+
if (deps['vue']) return deps['nuxt'] ? 'nuxt' : 'vue';
|
|
96
|
+
if (deps['react']) return deps['next'] ? 'next' : 'react';
|
|
97
|
+
if (deps['svelte']) return 'svelte';
|
|
98
|
+
|
|
99
|
+
// Backend frameworks
|
|
100
|
+
if (deps['@nestjs/core']) return 'nestjs';
|
|
101
|
+
if (deps['express']) return 'express';
|
|
102
|
+
if (deps['fastify']) return 'fastify';
|
|
103
|
+
if (deps['koa']) return 'koa';
|
|
104
|
+
if (deps['hapi'] || deps['@hapi/hapi']) return 'hapi';
|
|
105
|
+
|
|
106
|
+
// Outras ferramentas
|
|
107
|
+
if (deps['electron']) return 'electron';
|
|
108
|
+
|
|
109
|
+
return 'node-plain';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ==========================================
|
|
113
|
+
// BUILD & RUN
|
|
114
|
+
// ==========================================
|
|
115
|
+
|
|
116
|
+
async install(workspacePath, options = {}) {
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
const args = ['install'];
|
|
119
|
+
|
|
120
|
+
if (options.production) {
|
|
121
|
+
args.push('--production');
|
|
122
|
+
}
|
|
123
|
+
if (options.silent) {
|
|
124
|
+
args.push('--silent');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = await run('npm', args, workspacePath);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
...result,
|
|
131
|
+
duration: Date.now() - startTime
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async build(workspacePath, options = {}) {
|
|
136
|
+
const startTime = Date.now();
|
|
137
|
+
|
|
138
|
+
// Verificar se existe script de build
|
|
139
|
+
if (!this.packageJson?.scripts?.build) {
|
|
140
|
+
return {
|
|
141
|
+
code: 0,
|
|
142
|
+
stdout: 'No build script defined',
|
|
143
|
+
stderr: '',
|
|
144
|
+
duration: Date.now() - startTime
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await run('npm', ['run', 'build'], workspacePath);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...result,
|
|
152
|
+
duration: Date.now() - startTime
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getStartCommand(options = {}) {
|
|
157
|
+
const framework = options.framework || this.framework || 'node-plain';
|
|
158
|
+
const port = options.port || 3000;
|
|
159
|
+
|
|
160
|
+
// Comandos específicos por framework
|
|
161
|
+
const commands = {
|
|
162
|
+
'next': {
|
|
163
|
+
cmd: 'npm',
|
|
164
|
+
args: ['run', 'dev'],
|
|
165
|
+
env: { PORT: String(port) }
|
|
166
|
+
},
|
|
167
|
+
'nuxt': {
|
|
168
|
+
cmd: 'npm',
|
|
169
|
+
args: ['run', 'dev'],
|
|
170
|
+
env: { PORT: String(port) }
|
|
171
|
+
},
|
|
172
|
+
'nestjs': {
|
|
173
|
+
cmd: 'npm',
|
|
174
|
+
args: ['run', 'start:dev'],
|
|
175
|
+
env: { PORT: String(port) }
|
|
176
|
+
},
|
|
177
|
+
'express': {
|
|
178
|
+
cmd: 'npm',
|
|
179
|
+
args: ['start'],
|
|
180
|
+
env: { PORT: String(port) }
|
|
181
|
+
},
|
|
182
|
+
'fastify': {
|
|
183
|
+
cmd: 'npm',
|
|
184
|
+
args: ['start'],
|
|
185
|
+
env: { PORT: String(port) }
|
|
186
|
+
},
|
|
187
|
+
'react': {
|
|
188
|
+
cmd: 'npm',
|
|
189
|
+
args: ['start'],
|
|
190
|
+
env: { PORT: String(port) }
|
|
191
|
+
},
|
|
192
|
+
'vue': {
|
|
193
|
+
cmd: 'npm',
|
|
194
|
+
args: ['run', 'serve'],
|
|
195
|
+
env: { PORT: String(port) }
|
|
196
|
+
},
|
|
197
|
+
'angular': {
|
|
198
|
+
cmd: 'npm',
|
|
199
|
+
args: ['start', '--', '--port', String(port)],
|
|
200
|
+
env: {}
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return commands[framework] || {
|
|
205
|
+
cmd: 'npm',
|
|
206
|
+
args: ['start'],
|
|
207
|
+
env: { PORT: String(port) }
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getReadyPattern() {
|
|
212
|
+
const patterns = {
|
|
213
|
+
'next': /ready.*started server on/i,
|
|
214
|
+
'nuxt': /Listening on/i,
|
|
215
|
+
'nestjs': /Nest application successfully started/i,
|
|
216
|
+
'express': /listening on port|server (is )?running/i,
|
|
217
|
+
'fastify': /Server listening/i,
|
|
218
|
+
'react': /compiled successfully|You can now view/i,
|
|
219
|
+
'vue': /App running at/i,
|
|
220
|
+
'angular': /Compiled successfully|Angular Live/i
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return patterns[this.framework] || /listening|started|ready|running/i;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
getDefaultPort() {
|
|
227
|
+
const defaults = {
|
|
228
|
+
'next': 3000,
|
|
229
|
+
'nuxt': 3000,
|
|
230
|
+
'nestjs': 3000,
|
|
231
|
+
'express': 3000,
|
|
232
|
+
'fastify': 3000,
|
|
233
|
+
'react': 3000,
|
|
234
|
+
'vue': 8080,
|
|
235
|
+
'angular': 4200
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return defaults[this.framework] || 3000;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async detectPort(workspacePath) {
|
|
242
|
+
// Tentar .env
|
|
243
|
+
const envPath = path.join(workspacePath, '.env');
|
|
244
|
+
if (await exists(envPath)) {
|
|
245
|
+
const content = await readFile(envPath, 'utf8');
|
|
246
|
+
const match = content.match(/^PORT\s*=\s*(\d+)/m);
|
|
247
|
+
if (match) return parseInt(match[1]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Tentar .env.local
|
|
251
|
+
const envLocalPath = path.join(workspacePath, '.env.local');
|
|
252
|
+
if (await exists(envLocalPath)) {
|
|
253
|
+
const content = await readFile(envLocalPath, 'utf8');
|
|
254
|
+
const match = content.match(/^PORT\s*=\s*(\d+)/m);
|
|
255
|
+
if (match) return parseInt(match[1]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Para Next.js, verificar next.config.js
|
|
259
|
+
if (this.framework === 'next') {
|
|
260
|
+
const nextConfigPath = path.join(workspacePath, 'next.config.js');
|
|
261
|
+
if (await exists(nextConfigPath)) {
|
|
262
|
+
const content = await readFile(nextConfigPath, 'utf8');
|
|
263
|
+
const match = content.match(/port:\s*(\d+)/);
|
|
264
|
+
if (match) return parseInt(match[1]);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return this.getDefaultPort();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ==========================================
|
|
272
|
+
// TEST
|
|
273
|
+
// ==========================================
|
|
274
|
+
|
|
275
|
+
async test(workspacePath, options = {}) {
|
|
276
|
+
const startTime = Date.now();
|
|
277
|
+
|
|
278
|
+
// Verificar se existe script de test
|
|
279
|
+
if (!this.packageJson?.scripts?.test) {
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
passed: 0,
|
|
283
|
+
failed: 0,
|
|
284
|
+
skipped: 0,
|
|
285
|
+
duration: Date.now() - startTime,
|
|
286
|
+
output: 'No test script defined',
|
|
287
|
+
failures: []
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const result = await run('npm', ['test', '--', '--passWithNoTests'], workspacePath);
|
|
292
|
+
|
|
293
|
+
const testResult = this.parseTestOutput(result);
|
|
294
|
+
testResult.duration = Date.now() - startTime;
|
|
295
|
+
|
|
296
|
+
return testResult;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async testSingle(workspacePath, testFile) {
|
|
300
|
+
const startTime = Date.now();
|
|
301
|
+
|
|
302
|
+
// Detectar test runner
|
|
303
|
+
const deps = {
|
|
304
|
+
...this.packageJson?.dependencies,
|
|
305
|
+
...this.packageJson?.devDependencies
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
let args = ['test', '--'];
|
|
309
|
+
|
|
310
|
+
if (deps['jest']) {
|
|
311
|
+
args.push(testFile);
|
|
312
|
+
} else if (deps['mocha']) {
|
|
313
|
+
args.push(testFile);
|
|
314
|
+
} else if (deps['vitest']) {
|
|
315
|
+
args = ['run', 'vitest', testFile];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const result = await run('npm', args, workspacePath);
|
|
319
|
+
|
|
320
|
+
const testResult = this.parseTestOutput(result);
|
|
321
|
+
testResult.duration = Date.now() - startTime;
|
|
322
|
+
|
|
323
|
+
return testResult;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
parseTestOutput(result) {
|
|
327
|
+
const output = result.stdout + result.stderr;
|
|
328
|
+
|
|
329
|
+
// Jest output format
|
|
330
|
+
const jestMatch = output.match(/Tests:\s+(\d+)\s+passed,?\s*(\d+)?\s*failed?,?\s*(\d+)?\s*skipped?/i);
|
|
331
|
+
if (jestMatch) {
|
|
332
|
+
return {
|
|
333
|
+
success: result.code === 0,
|
|
334
|
+
passed: parseInt(jestMatch[1]) || 0,
|
|
335
|
+
failed: parseInt(jestMatch[2]) || 0,
|
|
336
|
+
skipped: parseInt(jestMatch[3]) || 0,
|
|
337
|
+
duration: 0,
|
|
338
|
+
output,
|
|
339
|
+
failures: this.extractJestFailures(output)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Mocha output format
|
|
344
|
+
const mochaMatch = output.match(/(\d+)\s+passing.*?(\d+)?\s*failing/s);
|
|
345
|
+
if (mochaMatch) {
|
|
346
|
+
return {
|
|
347
|
+
success: result.code === 0,
|
|
348
|
+
passed: parseInt(mochaMatch[1]) || 0,
|
|
349
|
+
failed: parseInt(mochaMatch[2]) || 0,
|
|
350
|
+
skipped: 0,
|
|
351
|
+
duration: 0,
|
|
352
|
+
output,
|
|
353
|
+
failures: []
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Fallback
|
|
358
|
+
return {
|
|
359
|
+
success: result.code === 0,
|
|
360
|
+
passed: result.code === 0 ? 1 : 0,
|
|
361
|
+
failed: result.code === 0 ? 0 : 1,
|
|
362
|
+
skipped: 0,
|
|
363
|
+
duration: 0,
|
|
364
|
+
output,
|
|
365
|
+
failures: []
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
extractJestFailures(output) {
|
|
370
|
+
const failures = [];
|
|
371
|
+
const failureRegex = /● (.+)/g;
|
|
372
|
+
|
|
373
|
+
let match;
|
|
374
|
+
while ((match = failureRegex.exec(output)) !== null) {
|
|
375
|
+
failures.push({
|
|
376
|
+
testName: match[1],
|
|
377
|
+
message: '',
|
|
378
|
+
stackTrace: ''
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return failures;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ==========================================
|
|
386
|
+
// CODE ANALYSIS
|
|
387
|
+
// ==========================================
|
|
388
|
+
|
|
389
|
+
getFileExtensions() {
|
|
390
|
+
return ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async findIntegrations(workspacePath) {
|
|
394
|
+
const { NodeIntegrationAnalyzer } = await import('./node-integrations.js');
|
|
395
|
+
const analyzer = new NodeIntegrationAnalyzer(workspacePath);
|
|
396
|
+
return analyzer.findAll();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async analyzeImports(filePath, content) {
|
|
400
|
+
const imports = [];
|
|
401
|
+
|
|
402
|
+
// ES6 imports
|
|
403
|
+
const es6Regex = /import\s+(?:{[^}]+}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
404
|
+
let match;
|
|
405
|
+
while ((match = es6Regex.exec(content)) !== null) {
|
|
406
|
+
imports.push({
|
|
407
|
+
name: match[1],
|
|
408
|
+
type: 'import',
|
|
409
|
+
isExternal: !match[1].startsWith('.') && !match[1].startsWith('/')
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// CommonJS requires
|
|
414
|
+
const cjsRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
415
|
+
while ((match = cjsRegex.exec(content)) !== null) {
|
|
416
|
+
imports.push({
|
|
417
|
+
name: match[1],
|
|
418
|
+
type: 'require',
|
|
419
|
+
isExternal: !match[1].startsWith('.') && !match[1].startsWith('/')
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return imports;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ==========================================
|
|
427
|
+
// PATCH
|
|
428
|
+
// ==========================================
|
|
429
|
+
|
|
430
|
+
async validatePatch(workspacePath, diff) {
|
|
431
|
+
const errors = [];
|
|
432
|
+
|
|
433
|
+
const fileRegex = /^[\+\-]{3}\s+[ab]\/(.+)$/gm;
|
|
434
|
+
let match;
|
|
435
|
+
|
|
436
|
+
while ((match = fileRegex.exec(diff)) !== null) {
|
|
437
|
+
const filePath = path.join(workspacePath, match[1]);
|
|
438
|
+
if (match[0].startsWith('---') && !(await exists(filePath))) {
|
|
439
|
+
if (!diff.includes('+++ /dev/null')) {
|
|
440
|
+
errors.push(`File not found: ${match[1]}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
valid: errors.length === 0,
|
|
447
|
+
errors
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async postPatchActions(workspacePath, modifiedFiles) {
|
|
452
|
+
// Rodar prettier se disponível
|
|
453
|
+
const deps = {
|
|
454
|
+
...this.packageJson?.dependencies,
|
|
455
|
+
...this.packageJson?.devDependencies
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
if (deps['prettier']) {
|
|
459
|
+
for (const file of modifiedFiles) {
|
|
460
|
+
await run('npx', ['prettier', '--write', file], workspacePath);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export default NpmRuntime;
|