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.
Files changed (50) hide show
  1. package/.dockerignore +24 -0
  2. package/.idea/deepdebug-local-agent.iml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/Dockerfile +46 -0
  6. package/cloudbuild.yaml +42 -0
  7. package/index.js +42 -0
  8. package/mcp-server.js +533 -0
  9. package/package.json +22 -0
  10. package/src/ai-engine.js +861 -0
  11. package/src/analyzers/config-analyzer.js +446 -0
  12. package/src/analyzers/controller-analyzer.js +429 -0
  13. package/src/analyzers/dto-analyzer.js +455 -0
  14. package/src/detectors/build-tool-detector.js +0 -0
  15. package/src/detectors/framework-detector.js +91 -0
  16. package/src/detectors/language-detector.js +89 -0
  17. package/src/detectors/multi-project-detector.js +191 -0
  18. package/src/detectors/service-detector.js +244 -0
  19. package/src/detectors.js +30 -0
  20. package/src/exec-utils.js +215 -0
  21. package/src/fs-utils.js +34 -0
  22. package/src/git/base-git-provider.js +384 -0
  23. package/src/git/git-provider-registry.js +110 -0
  24. package/src/git/github-provider.js +502 -0
  25. package/src/mcp-http-server.js +313 -0
  26. package/src/patch/patch-engine.js +339 -0
  27. package/src/patch-manager.js +816 -0
  28. package/src/patch.js +607 -0
  29. package/src/patch_bkp.js +154 -0
  30. package/src/ports.js +69 -0
  31. package/src/routes/workspace.route.js +528 -0
  32. package/src/runtimes/base-runtime.js +290 -0
  33. package/src/runtimes/java/gradle-runtime.js +378 -0
  34. package/src/runtimes/java/java-integrations.js +339 -0
  35. package/src/runtimes/java/maven-runtime.js +418 -0
  36. package/src/runtimes/node/node-integrations.js +247 -0
  37. package/src/runtimes/node/npm-runtime.js +466 -0
  38. package/src/runtimes/node/yarn-runtime.js +354 -0
  39. package/src/runtimes/runtime-registry.js +256 -0
  40. package/src/server-local.js +576 -0
  41. package/src/server.js +4565 -0
  42. package/src/utils/environment-diagnostics.js +666 -0
  43. package/src/utils/exec-utils.js +264 -0
  44. package/src/utils/fs-utils.js +218 -0
  45. package/src/workspace/detect-port.js +176 -0
  46. package/src/workspace/file-reader.js +54 -0
  47. package/src/workspace/git-client.js +0 -0
  48. package/src/workspace/process-manager.js +619 -0
  49. package/src/workspace/scanner.js +72 -0
  50. package/src/workspace-manager.js +172 -0
@@ -0,0 +1,418 @@
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
+ * MavenRuntime
8
+ *
9
+ * Runtime para projetos Java com Maven.
10
+ * Suporta Spring Boot, Quarkus, e projetos Maven genéricos.
11
+ */
12
+ export class MavenRuntime extends BaseRuntime {
13
+
14
+ constructor() {
15
+ super();
16
+ this.framework = null; // Detectado dinamicamente
17
+ }
18
+
19
+ // ==========================================
20
+ // IDENTIFICATION
21
+ // ==========================================
22
+
23
+ getId() {
24
+ return 'java-maven';
25
+ }
26
+
27
+ getName() {
28
+ return 'Java (Maven)';
29
+ }
30
+
31
+ getLanguage() {
32
+ return 'java';
33
+ }
34
+
35
+ getBuildTool() {
36
+ return 'maven';
37
+ }
38
+
39
+ // ==========================================
40
+ // DETECTION
41
+ // ==========================================
42
+
43
+ getMarkerFiles() {
44
+ return ['pom.xml'];
45
+ }
46
+
47
+ async isCompatible(workspacePath) {
48
+ const pomPath = path.join(workspacePath, 'pom.xml');
49
+ return exists(pomPath);
50
+ }
51
+
52
+ async detectProject(workspacePath) {
53
+ const pomPath = path.join(workspacePath, 'pom.xml');
54
+ const pomContent = await readFile(pomPath, 'utf8');
55
+
56
+ // Parse básico do pom.xml
57
+ const name = this.extractXmlValue(pomContent, 'artifactId') ||
58
+ path.basename(workspacePath);
59
+ const version = this.extractXmlValue(pomContent, 'version') || '0.0.1';
60
+
61
+ // Detectar framework
62
+ const framework = this.detectFramework(pomContent);
63
+ this.framework = framework;
64
+
65
+ // Detectar porta
66
+ const port = await this.detectPort(workspacePath);
67
+
68
+ return {
69
+ name,
70
+ version,
71
+ language: 'java',
72
+ framework,
73
+ buildTool: 'maven',
74
+ port
75
+ };
76
+ }
77
+
78
+ detectFramework(pomContent) {
79
+ if (pomContent.includes('spring-boot-starter') ||
80
+ pomContent.includes('org.springframework.boot')) {
81
+ return 'spring-boot';
82
+ }
83
+ if (pomContent.includes('quarkus')) {
84
+ return 'quarkus';
85
+ }
86
+ if (pomContent.includes('micronaut')) {
87
+ return 'micronaut';
88
+ }
89
+ if (pomContent.includes('jakarta.') || pomContent.includes('javax.')) {
90
+ return 'jakarta-ee';
91
+ }
92
+ return 'java-plain';
93
+ }
94
+
95
+ /**
96
+ * Extrai valor XML ignorando o bloco <parent>
97
+ * Isso é necessário porque o <parent> tem seu próprio artifactId/version
98
+ */
99
+ extractXmlValue(xml, tag) {
100
+ // Remove o bloco <parent>...</parent> para não pegar valores do parent
101
+ const xmlWithoutParent = xml.replace(/<parent>[\s\S]*?<\/parent>/gi, '');
102
+
103
+ const regex = new RegExp(`<${tag}>([^<]+)</${tag}>`);
104
+ const match = xmlWithoutParent.match(regex);
105
+ return match ? match[1].trim() : null;
106
+ }
107
+
108
+ // ==========================================
109
+ // BUILD & RUN
110
+ // ==========================================
111
+
112
+ async install(workspacePath, options = {}) {
113
+ const startTime = Date.now();
114
+ const result = await run('mvn', ['dependency:resolve', '-q'], workspacePath);
115
+
116
+ return {
117
+ ...result,
118
+ duration: Date.now() - startTime
119
+ };
120
+ }
121
+
122
+ async build(workspacePath, options = {}) {
123
+ const startTime = Date.now();
124
+ const skipTests = options.skipTests ? '-DskipTests' : '';
125
+ const args = ['compile', '-q'];
126
+ if (skipTests) args.push(skipTests);
127
+
128
+ const result = await run('mvn', args, workspacePath);
129
+
130
+ return {
131
+ ...result,
132
+ duration: Date.now() - startTime
133
+ };
134
+ }
135
+
136
+ getStartCommand(options = {}) {
137
+ const framework = options.framework || this.framework || 'spring-boot';
138
+ const port = options.port; // Pode ser undefined se não precisa sobrescrever
139
+ const profile = options.profile || 'default';
140
+
141
+ if (framework === 'spring-boot') {
142
+ const args = [
143
+ 'spring-boot:run',
144
+ '-Dspring-boot.run.fork=false' // Mantém Java no mesmo processo
145
+ ];
146
+
147
+ // Só adiciona porta se foi explicitamente fornecida
148
+ // Se o projeto já tem porta no application.yml, não precisa
149
+ if (port) {
150
+ args.push(`-Dspring-boot.run.arguments=--server.port=${port}`);
151
+ }
152
+
153
+ // Perfil ativo
154
+ if (profile !== 'default') {
155
+ args.push(`-Dspring.profiles.active=${profile}`);
156
+ }
157
+
158
+ return {
159
+ cmd: 'mvn',
160
+ args,
161
+ env: port ? { SERVER_PORT: String(port) } : {}
162
+ };
163
+ }
164
+
165
+ if (framework === 'quarkus') {
166
+ return {
167
+ cmd: 'mvn',
168
+ args: [
169
+ 'quarkus:dev',
170
+ `-Dquarkus.http.port=${port || 8080}`,
171
+ '-q'
172
+ ],
173
+ env: {
174
+ QUARKUS_HTTP_PORT: String(port || 8080)
175
+ }
176
+ };
177
+ }
178
+
179
+ // Fallback genérico
180
+ return {
181
+ cmd: 'mvn',
182
+ args: ['exec:java', '-q'],
183
+ env: {}
184
+ };
185
+ }
186
+
187
+ getReadyPattern() {
188
+ if (this.framework === 'spring-boot') {
189
+ return /Started \w+ in \d+\.?\d* seconds/i;
190
+ }
191
+ if (this.framework === 'quarkus') {
192
+ return /Quarkus .* started in/i;
193
+ }
194
+ return /started|listening|ready/i;
195
+ }
196
+
197
+ getDefaultPort() {
198
+ if (this.framework === 'quarkus') {
199
+ return 8080;
200
+ }
201
+ return 8080;
202
+ }
203
+
204
+ async detectPort(workspacePath) {
205
+ // Tentar application.properties
206
+ const propsPath = path.join(workspacePath, 'src/main/resources/application.properties');
207
+ if (await exists(propsPath)) {
208
+ const content = await readFile(propsPath, 'utf8');
209
+ const port = this.extractPortFromProperties(content);
210
+ if (port) return port;
211
+ }
212
+
213
+ // Tentar application.yml
214
+ const ymlPath = path.join(workspacePath, 'src/main/resources/application.yml');
215
+ if (await exists(ymlPath)) {
216
+ const content = await readFile(ymlPath, 'utf8');
217
+ const port = this.extractPortFromYaml(content);
218
+ if (port) return port;
219
+ }
220
+
221
+ // Tentar application.yaml
222
+ const yamlPath = path.join(workspacePath, 'src/main/resources/application.yaml');
223
+ if (await exists(yamlPath)) {
224
+ const content = await readFile(yamlPath, 'utf8');
225
+ const port = this.extractPortFromYaml(content);
226
+ if (port) return port;
227
+ }
228
+
229
+ return this.getDefaultPort();
230
+ }
231
+
232
+ /**
233
+ * Extrai porta de application.properties
234
+ * Suporta: server.port=8090 ou server.port=${PORT:8090}
235
+ */
236
+ extractPortFromProperties(content) {
237
+ // Primeiro tenta: server.port=8090
238
+ let match = content.match(/server\.port\s*=\s*(\d+)/);
239
+ if (match) return parseInt(match[1]);
240
+
241
+ // Depois tenta: server.port=${PORT:8090} ou ${SERVER_PORT:8090}
242
+ match = content.match(/server\.port\s*=\s*\$\{[^:}]+:(\d+)\}/);
243
+ if (match) return parseInt(match[1]);
244
+
245
+ return null;
246
+ }
247
+
248
+ /**
249
+ * Extrai porta de application.yml/yaml
250
+ * Suporta: port: 8090 ou port: ${PORT:8090}
251
+ */
252
+ extractPortFromYaml(content) {
253
+ // Primeiro tenta: port: 8090
254
+ let match = content.match(/port:\s*(\d+)/);
255
+ if (match) return parseInt(match[1]);
256
+
257
+ // Depois tenta: port: ${PORT:8090} ou port: ${SERVER_PORT:8090}
258
+ match = content.match(/port:\s*\$\{[^:}]+:(\d+)\}/);
259
+ if (match) return parseInt(match[1]);
260
+
261
+ return null;
262
+ }
263
+
264
+ // ==========================================
265
+ // TEST
266
+ // ==========================================
267
+
268
+ async test(workspacePath, options = {}) {
269
+ const startTime = Date.now();
270
+
271
+ // Clean primeiro
272
+ await run('mvn', ['clean', '-q'], workspacePath);
273
+
274
+ // Rodar testes
275
+ const result = await run('mvn', ['test', '-q'], workspacePath);
276
+
277
+ const testResult = this.parseTestOutput(result);
278
+ testResult.duration = Date.now() - startTime;
279
+
280
+ return testResult;
281
+ }
282
+
283
+ async testSingle(workspacePath, testFile) {
284
+ const startTime = Date.now();
285
+
286
+ // Extrair nome da classe do caminho
287
+ const className = path.basename(testFile, '.java');
288
+
289
+ const result = await run('mvn', [
290
+ 'test',
291
+ `-Dtest=${className}`,
292
+ '-q'
293
+ ], workspacePath);
294
+
295
+ const testResult = this.parseTestOutput(result);
296
+ testResult.duration = Date.now() - startTime;
297
+
298
+ return testResult;
299
+ }
300
+
301
+ parseTestOutput(result) {
302
+ const output = result.stdout + result.stderr;
303
+
304
+ // Tentar parsear output do Surefire/JUnit
305
+ const testsRunMatch = output.match(/Tests run: (\d+)/);
306
+ const failuresMatch = output.match(/Failures: (\d+)/);
307
+ const errorsMatch = output.match(/Errors: (\d+)/);
308
+ const skippedMatch = output.match(/Skipped: (\d+)/);
309
+
310
+ const total = testsRunMatch ? parseInt(testsRunMatch[1]) : 0;
311
+ const failures = failuresMatch ? parseInt(failuresMatch[1]) : 0;
312
+ const errors = errorsMatch ? parseInt(errorsMatch[1]) : 0;
313
+ const skipped = skippedMatch ? parseInt(skippedMatch[1]) : 0;
314
+ const passed = total - failures - errors - skipped;
315
+
316
+ return {
317
+ success: result.code === 0 && failures === 0 && errors === 0,
318
+ passed,
319
+ failed: failures + errors,
320
+ skipped,
321
+ duration: 0,
322
+ output,
323
+ failures: this.extractFailures(output)
324
+ };
325
+ }
326
+
327
+ extractFailures(output) {
328
+ const failures = [];
329
+ const failureRegex = /Failed tests?:\s*([\s\S]*?)(?=Tests run:|$)/gi;
330
+ const match = failureRegex.exec(output);
331
+
332
+ if (match) {
333
+ const lines = match[1].split('\n').filter(l => l.trim());
334
+ for (const line of lines) {
335
+ failures.push({
336
+ testName: line.trim(),
337
+ message: '',
338
+ stackTrace: ''
339
+ });
340
+ }
341
+ }
342
+
343
+ return failures;
344
+ }
345
+
346
+ // ==========================================
347
+ // CODE ANALYSIS
348
+ // ==========================================
349
+
350
+ getFileExtensions() {
351
+ return ['.java'];
352
+ }
353
+
354
+ async findIntegrations(workspacePath) {
355
+ const integrations = [];
356
+ const { JavaIntegrationAnalyzer } = await import('./java-integrations.js');
357
+ const analyzer = new JavaIntegrationAnalyzer(workspacePath);
358
+
359
+ return analyzer.findAll();
360
+ }
361
+
362
+ async analyzeImports(filePath, content) {
363
+ const imports = [];
364
+ const importRegex = /import\s+(static\s+)?([a-zA-Z0-9_.]+)(?:\.\*)?;/g;
365
+
366
+ let match;
367
+ while ((match = importRegex.exec(content)) !== null) {
368
+ const fullImport = match[2];
369
+ const isStatic = !!match[1];
370
+
371
+ imports.push({
372
+ name: fullImport,
373
+ type: isStatic ? 'static-import' : 'import',
374
+ isExternal: !fullImport.startsWith('com.') ||
375
+ fullImport.includes('springframework') ||
376
+ fullImport.includes('jakarta') ||
377
+ fullImport.includes('javax')
378
+ });
379
+ }
380
+
381
+ return imports;
382
+ }
383
+
384
+ // ==========================================
385
+ // PATCH
386
+ // ==========================================
387
+
388
+ async validatePatch(workspacePath, diff) {
389
+ // Validar se os arquivos existem
390
+ const errors = [];
391
+
392
+ // Extrair arquivos do diff
393
+ const fileRegex = /^[\+\-]{3}\s+[ab]\/(.+)$/gm;
394
+ let match;
395
+
396
+ while ((match = fileRegex.exec(diff)) !== null) {
397
+ const filePath = path.join(workspacePath, match[1]);
398
+ if (match[0].startsWith('---') && !(await exists(filePath))) {
399
+ // Arquivo sendo modificado não existe
400
+ if (!diff.includes('+++ /dev/null')) {
401
+ errors.push(`File not found: ${match[1]}`);
402
+ }
403
+ }
404
+ }
405
+
406
+ return {
407
+ valid: errors.length === 0,
408
+ errors
409
+ };
410
+ }
411
+
412
+ async postPatchActions(workspacePath, modifiedFiles) {
413
+ // Opcional: rodar formatter após patch
414
+ // await run('mvn', ['formatter:format', '-q'], workspacePath);
415
+ }
416
+ }
417
+
418
+ export default MavenRuntime;
@@ -0,0 +1,247 @@
1
+ import path from 'path';
2
+ import { readFile, readdir } from '../../utils/fs-utils.js';
3
+
4
+ /**
5
+ * NodeIntegrationAnalyzer
6
+ *
7
+ * Analisa código Node.js/TypeScript para encontrar integrações HTTP:
8
+ * - axios
9
+ * - fetch / node-fetch
10
+ * - got
11
+ * - HttpService (NestJS)
12
+ */
13
+ export class NodeIntegrationAnalyzer {
14
+
15
+ constructor(workspacePath) {
16
+ this.workspacePath = workspacePath;
17
+ this.integrations = [];
18
+ }
19
+
20
+ async findAll() {
21
+ this.integrations = [];
22
+
23
+ const srcPath = path.join(this.workspacePath, 'src');
24
+ await this.scanDirectory(srcPath);
25
+ await this.scanDirectory(this.workspacePath, 1);
26
+
27
+ return this.integrations;
28
+ }
29
+
30
+ async scanDirectory(dir, maxDepth = 10, currentDepth = 0) {
31
+ if (currentDepth > maxDepth) return;
32
+
33
+ try {
34
+ const entries = await readdir(dir, { withFileTypes: true });
35
+
36
+ for (const entry of entries) {
37
+ if (['node_modules', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
38
+ continue;
39
+ }
40
+
41
+ const fullPath = path.join(dir, entry.name);
42
+
43
+ if (entry.isDirectory()) {
44
+ await this.scanDirectory(fullPath, maxDepth, currentDepth + 1);
45
+ } else if (/\.(js|ts|jsx|tsx|mjs|cjs)$/.test(entry.name)) {
46
+ await this.analyzeFile(fullPath);
47
+ }
48
+ }
49
+ } catch (e) {
50
+ // Directory not found
51
+ }
52
+ }
53
+
54
+ async analyzeFile(filePath) {
55
+ try {
56
+ const content = await readFile(filePath, 'utf8');
57
+ const relativePath = path.relative(this.workspacePath, filePath);
58
+ const lines = content.split('\n');
59
+
60
+ this.findAxios(content, relativePath, lines);
61
+ this.findFetch(content, relativePath, lines);
62
+ this.findGot(content, relativePath, lines);
63
+ this.findNestHttpService(content, relativePath, lines);
64
+ this.findKafka(content, relativePath, lines);
65
+ this.findAmqp(content, relativePath, lines);
66
+
67
+ } catch (e) {
68
+ // Error reading file
69
+ }
70
+ }
71
+
72
+ findAxios(content, filePath, lines) {
73
+ const patterns = [
74
+ { regex: /axios\.(get|post|put|patch|delete|head|options)\s*\(/gi, type: 'call' },
75
+ { regex: /axios\s*\(\s*\{/g, type: 'config-call' },
76
+ { regex: /axios\.create\s*\(/g, type: 'instance' }
77
+ ];
78
+
79
+ for (const pattern of patterns) {
80
+ let match;
81
+ while ((match = pattern.regex.exec(content)) !== null) {
82
+ const line = this.getLineNumber(content, match.index);
83
+ const url = this.extractUrl(content, match.index);
84
+
85
+ this.integrations.push({
86
+ type: 'axios',
87
+ subType: pattern.type,
88
+ file: filePath,
89
+ line,
90
+ targetUrl: url,
91
+ method: match[1]?.toUpperCase() || null,
92
+ snippet: lines[line - 1]?.trim() || match[0]
93
+ });
94
+ }
95
+ }
96
+ }
97
+
98
+ findFetch(content, filePath, lines) {
99
+ const patterns = [
100
+ { regex: /fetch\s*\(\s*['"`]/g, type: 'call' },
101
+ { regex: /fetch\s*\(\s*\$\{/g, type: 'template-call' }
102
+ ];
103
+
104
+ for (const pattern of patterns) {
105
+ let match;
106
+ while ((match = pattern.regex.exec(content)) !== null) {
107
+ const line = this.getLineNumber(content, match.index);
108
+ const url = this.extractUrl(content, match.index);
109
+ const method = this.extractFetchMethod(content, match.index);
110
+
111
+ this.integrations.push({
112
+ type: 'fetch',
113
+ subType: pattern.type,
114
+ file: filePath,
115
+ line,
116
+ targetUrl: url,
117
+ method,
118
+ snippet: lines[line - 1]?.trim() || match[0]
119
+ });
120
+ }
121
+ }
122
+ }
123
+
124
+ findGot(content, filePath, lines) {
125
+ const patterns = [
126
+ { regex: /got\.(get|post|put|patch|delete|head)\s*\(/gi, type: 'call' },
127
+ { regex: /got\s*\(\s*['"`]/g, type: 'call' }
128
+ ];
129
+
130
+ for (const pattern of patterns) {
131
+ let match;
132
+ while ((match = pattern.regex.exec(content)) !== null) {
133
+ const line = this.getLineNumber(content, match.index);
134
+ const url = this.extractUrl(content, match.index);
135
+
136
+ this.integrations.push({
137
+ type: 'got',
138
+ subType: pattern.type,
139
+ file: filePath,
140
+ line,
141
+ targetUrl: url,
142
+ method: match[1]?.toUpperCase() || 'GET',
143
+ snippet: lines[line - 1]?.trim() || match[0]
144
+ });
145
+ }
146
+ }
147
+ }
148
+
149
+ findNestHttpService(content, filePath, lines) {
150
+ const patterns = [
151
+ { regex: /this\.httpService\.(get|post|put|patch|delete|head)\s*\(/gi, type: 'call' }
152
+ ];
153
+
154
+ for (const pattern of patterns) {
155
+ let match;
156
+ while ((match = pattern.regex.exec(content)) !== null) {
157
+ const line = this.getLineNumber(content, match.index);
158
+ const url = this.extractUrl(content, match.index);
159
+
160
+ this.integrations.push({
161
+ type: 'nest-http-service',
162
+ subType: pattern.type,
163
+ file: filePath,
164
+ line,
165
+ targetUrl: url,
166
+ method: match[1]?.toUpperCase() || null,
167
+ snippet: lines[line - 1]?.trim() || match[0]
168
+ });
169
+ }
170
+ }
171
+ }
172
+
173
+ findKafka(content, filePath, lines) {
174
+ const patterns = [
175
+ { regex: /\.send\s*\(\s*\{\s*topic\s*:\s*['"]([^'"]+)['"]/g, type: 'producer' },
176
+ { regex: /\.subscribe\s*\(\s*\{\s*topic\s*:\s*['"]([^'"]+)['"]/g, type: 'consumer' }
177
+ ];
178
+
179
+ for (const pattern of patterns) {
180
+ let match;
181
+ while ((match = pattern.regex.exec(content)) !== null) {
182
+ const line = this.getLineNumber(content, match.index);
183
+
184
+ this.integrations.push({
185
+ type: 'kafka',
186
+ subType: pattern.type,
187
+ file: filePath,
188
+ line,
189
+ topic: match[1] || null,
190
+ snippet: lines[line - 1]?.trim() || match[0]
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ findAmqp(content, filePath, lines) {
197
+ const patterns = [
198
+ { regex: /\.sendToQueue\s*\(\s*['"]([^'"]+)['"]/g, type: 'producer' },
199
+ { regex: /\.consume\s*\(\s*['"]([^'"]+)['"]/g, type: 'consumer' }
200
+ ];
201
+
202
+ for (const pattern of patterns) {
203
+ let match;
204
+ while ((match = pattern.regex.exec(content)) !== null) {
205
+ const line = this.getLineNumber(content, match.index);
206
+
207
+ this.integrations.push({
208
+ type: 'amqp',
209
+ subType: pattern.type,
210
+ file: filePath,
211
+ line,
212
+ queue: match[1] || null,
213
+ snippet: lines[line - 1]?.trim() || match[0]
214
+ });
215
+ }
216
+ }
217
+ }
218
+
219
+ getLineNumber(content, index) {
220
+ return content.substring(0, index).split('\n').length;
221
+ }
222
+
223
+ extractUrl(content, startIndex) {
224
+ const context = content.substring(startIndex, startIndex + 500);
225
+
226
+ const urlPatterns = [
227
+ /['"`](https?:\/\/[^'"`]+)['"`]/,
228
+ /['"`]([^'"`]*\/api\/[^'"`]+)['"`]/,
229
+ /['"`](\/[a-zA-Z][a-zA-Z0-9/\-_{}:]+)['"`]/
230
+ ];
231
+
232
+ for (const pattern of urlPatterns) {
233
+ const match = context.match(pattern);
234
+ if (match) return match[1];
235
+ }
236
+
237
+ return null;
238
+ }
239
+
240
+ extractFetchMethod(content, startIndex) {
241
+ const context = content.substring(startIndex, startIndex + 300);
242
+ const methodMatch = context.match(/method\s*:\s*['"](\w+)['"]/i);
243
+ return methodMatch ? methodMatch[1].toUpperCase() : 'GET';
244
+ }
245
+ }
246
+
247
+ export default NodeIntegrationAnalyzer;