deepdebug-local-agent 0.3.8 → 0.3.10
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 +3 -2
- package/patch.js +607 -0
- package/ports.js +69 -0
- 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,446 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { readFile, exists } from "../fs-utils.js";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ConfigAnalyzer
|
|
7
|
+
*
|
|
8
|
+
* Analyzes application configuration to extract:
|
|
9
|
+
* - Server port
|
|
10
|
+
* - Environment variables
|
|
11
|
+
* - Database connections
|
|
12
|
+
* - API keys and secrets (obfuscated)
|
|
13
|
+
* - Spring profiles
|
|
14
|
+
*/
|
|
15
|
+
export class ConfigAnalyzer {
|
|
16
|
+
constructor(workspaceRoot) {
|
|
17
|
+
this.workspaceRoot = workspaceRoot;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Find all configuration files
|
|
22
|
+
*/
|
|
23
|
+
async findConfigFiles() {
|
|
24
|
+
const configPatterns = [
|
|
25
|
+
"application.yml",
|
|
26
|
+
"application.yaml",
|
|
27
|
+
"application.properties",
|
|
28
|
+
"application-dev.yml",
|
|
29
|
+
"application-dev.yaml",
|
|
30
|
+
"application-dev.properties",
|
|
31
|
+
"application-local.yml",
|
|
32
|
+
"application-local.yaml",
|
|
33
|
+
"application-local.properties",
|
|
34
|
+
"application-prod.yml",
|
|
35
|
+
"application-prod.yaml",
|
|
36
|
+
"application-prod.properties",
|
|
37
|
+
"bootstrap.yml",
|
|
38
|
+
"bootstrap.yaml",
|
|
39
|
+
".env",
|
|
40
|
+
".env.local",
|
|
41
|
+
".env.development",
|
|
42
|
+
"config/application.yml",
|
|
43
|
+
"config/application.yaml"
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const srcPaths = [
|
|
47
|
+
"src/main/resources/",
|
|
48
|
+
"src/main/java/resources/",
|
|
49
|
+
""
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const foundConfigs = [];
|
|
53
|
+
|
|
54
|
+
for (const srcPath of srcPaths) {
|
|
55
|
+
for (const configPattern of configPatterns) {
|
|
56
|
+
const fullPath = path.join(this.workspaceRoot, srcPath, configPattern);
|
|
57
|
+
if (await exists(fullPath)) {
|
|
58
|
+
foundConfigs.push(path.join(srcPath, configPattern));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return foundConfigs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Analyze main configuration
|
|
68
|
+
*/
|
|
69
|
+
async analyze() {
|
|
70
|
+
const configFiles = await this.findConfigFiles();
|
|
71
|
+
const configs = [];
|
|
72
|
+
|
|
73
|
+
for (const configFile of configFiles) {
|
|
74
|
+
const config = await this.analyzeConfigFile(configFile);
|
|
75
|
+
if (config) {
|
|
76
|
+
configs.push(config);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Merge all configs into a unified view
|
|
81
|
+
const merged = this.mergeConfigs(configs);
|
|
82
|
+
|
|
83
|
+
// Extract server config and add available profiles
|
|
84
|
+
const serverConfig = this.extractServerConfig(merged);
|
|
85
|
+
serverConfig.availableProfiles = this.detectProfiles(configFiles);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
files: configFiles,
|
|
89
|
+
configs: configs,
|
|
90
|
+
merged: merged,
|
|
91
|
+
server: serverConfig,
|
|
92
|
+
database: this.extractDatabaseConfig(merged),
|
|
93
|
+
security: this.extractSecurityConfig(merged),
|
|
94
|
+
envVars: this.extractEnvVars(configs)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Analyze a single config file
|
|
100
|
+
*/
|
|
101
|
+
async analyzeConfigFile(configPath) {
|
|
102
|
+
try {
|
|
103
|
+
const fullPath = path.join(this.workspaceRoot, configPath);
|
|
104
|
+
const content = await readFile(fullPath, "utf8");
|
|
105
|
+
const fileName = path.basename(configPath);
|
|
106
|
+
|
|
107
|
+
if (fileName.endsWith(".yml") || fileName.endsWith(".yaml")) {
|
|
108
|
+
return {
|
|
109
|
+
file: configPath,
|
|
110
|
+
type: "yaml",
|
|
111
|
+
content: yaml.load(content) || {}
|
|
112
|
+
};
|
|
113
|
+
} else if (fileName.endsWith(".properties")) {
|
|
114
|
+
return {
|
|
115
|
+
file: configPath,
|
|
116
|
+
type: "properties",
|
|
117
|
+
content: this.parseProperties(content)
|
|
118
|
+
};
|
|
119
|
+
} else if (fileName.startsWith(".env")) {
|
|
120
|
+
return {
|
|
121
|
+
file: configPath,
|
|
122
|
+
type: "env",
|
|
123
|
+
content: this.parseEnvFile(content)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return null;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error(`Failed to analyze config ${configPath}:`, err.message);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse .properties file
|
|
136
|
+
*/
|
|
137
|
+
parseProperties(content) {
|
|
138
|
+
const result = {};
|
|
139
|
+
const lines = content.split("\n");
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
144
|
+
|
|
145
|
+
const eqIndex = trimmed.indexOf("=");
|
|
146
|
+
if (eqIndex > 0) {
|
|
147
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
148
|
+
const value = trimmed.substring(eqIndex + 1).trim();
|
|
149
|
+
this.setNestedValue(result, key, value);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Parse .env file
|
|
158
|
+
*/
|
|
159
|
+
parseEnvFile(content) {
|
|
160
|
+
const result = {};
|
|
161
|
+
const lines = content.split("\n");
|
|
162
|
+
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
const trimmed = line.trim();
|
|
165
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
166
|
+
|
|
167
|
+
const eqIndex = trimmed.indexOf("=");
|
|
168
|
+
if (eqIndex > 0) {
|
|
169
|
+
const key = trimmed.substring(0, eqIndex).trim();
|
|
170
|
+
let value = trimmed.substring(eqIndex + 1).trim();
|
|
171
|
+
// Remove quotes
|
|
172
|
+
value = value.replace(/^["']|["']$/g, "");
|
|
173
|
+
result[key] = this.obfuscateSensitive(key, value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Set nested value from dot notation key
|
|
182
|
+
*/
|
|
183
|
+
setNestedValue(obj, key, value) {
|
|
184
|
+
const parts = key.split(".");
|
|
185
|
+
let current = obj;
|
|
186
|
+
|
|
187
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
188
|
+
const part = parts[i];
|
|
189
|
+
if (!current[part]) {
|
|
190
|
+
current[part] = {};
|
|
191
|
+
}
|
|
192
|
+
current = current[part];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
current[parts[parts.length - 1]] = value;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Merge multiple config files
|
|
200
|
+
*/
|
|
201
|
+
mergeConfigs(configs) {
|
|
202
|
+
const merged = {};
|
|
203
|
+
|
|
204
|
+
for (const config of configs) {
|
|
205
|
+
if (config && config.content) {
|
|
206
|
+
this.deepMerge(merged, config.content);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return merged;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Deep merge two objects
|
|
215
|
+
*/
|
|
216
|
+
deepMerge(target, source) {
|
|
217
|
+
for (const key in source) {
|
|
218
|
+
if (source[key] instanceof Object && key in target) {
|
|
219
|
+
this.deepMerge(target[key], source[key]);
|
|
220
|
+
} else {
|
|
221
|
+
target[key] = source[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return target;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Extract server configuration
|
|
229
|
+
*/
|
|
230
|
+
extractServerConfig(config) {
|
|
231
|
+
const server = {
|
|
232
|
+
port: 8080, // default
|
|
233
|
+
contextPath: "/",
|
|
234
|
+
ssl: false,
|
|
235
|
+
activeProfile: "dev" // default profile
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Spring Boot server config
|
|
239
|
+
if (config.server) {
|
|
240
|
+
server.port = config.server.port || server.port;
|
|
241
|
+
server.contextPath = config.server.servlet?.["context-path"] ||
|
|
242
|
+
config.server["servlet.context-path"] ||
|
|
243
|
+
config.server.contextPath ||
|
|
244
|
+
server.contextPath;
|
|
245
|
+
server.ssl = !!config.server.ssl;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Also check for PORT env var placeholder
|
|
249
|
+
if (typeof server.port === "string" && server.port.includes("${")) {
|
|
250
|
+
const match = server.port.match(/\$\{([^:}]+)(?::(\d+))?\}/);
|
|
251
|
+
if (match) {
|
|
252
|
+
server.portEnvVar = match[1];
|
|
253
|
+
server.port = parseInt(match[2]) || 8080;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Extract active profile from spring.profiles.active
|
|
258
|
+
if (config.spring?.profiles?.active) {
|
|
259
|
+
server.activeProfile = config.spring.profiles.active;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return server;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Extract database configuration
|
|
267
|
+
*/
|
|
268
|
+
extractDatabaseConfig(config) {
|
|
269
|
+
const database = {
|
|
270
|
+
type: null,
|
|
271
|
+
host: null,
|
|
272
|
+
port: null,
|
|
273
|
+
name: null,
|
|
274
|
+
configured: false
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// Spring Boot data source
|
|
278
|
+
const datasource = config.spring?.datasource;
|
|
279
|
+
if (datasource) {
|
|
280
|
+
database.configured = true;
|
|
281
|
+
database.url = this.obfuscateSensitive("url", datasource.url);
|
|
282
|
+
|
|
283
|
+
// Parse URL to extract type
|
|
284
|
+
if (datasource.url) {
|
|
285
|
+
if (datasource.url.includes("mysql")) database.type = "mysql";
|
|
286
|
+
else if (datasource.url.includes("postgresql") || datasource.url.includes("postgres")) database.type = "postgresql";
|
|
287
|
+
else if (datasource.url.includes("mongodb")) database.type = "mongodb";
|
|
288
|
+
else if (datasource.url.includes("h2")) database.type = "h2";
|
|
289
|
+
else if (datasource.url.includes("oracle")) database.type = "oracle";
|
|
290
|
+
else if (datasource.url.includes("sqlserver")) database.type = "sqlserver";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// MongoDB
|
|
295
|
+
const mongodb = config.spring?.data?.mongodb;
|
|
296
|
+
if (mongodb) {
|
|
297
|
+
database.configured = true;
|
|
298
|
+
database.type = "mongodb";
|
|
299
|
+
database.uri = this.obfuscateSensitive("uri", mongodb.uri);
|
|
300
|
+
database.database = mongodb.database;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Redis
|
|
304
|
+
const redis = config.spring?.data?.redis || config.spring?.redis;
|
|
305
|
+
if (redis) {
|
|
306
|
+
database.redis = {
|
|
307
|
+
configured: true,
|
|
308
|
+
host: redis.host || "localhost",
|
|
309
|
+
port: redis.port || 6379
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return database;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Extract security configuration
|
|
318
|
+
*/
|
|
319
|
+
extractSecurityConfig(config) {
|
|
320
|
+
const security = {
|
|
321
|
+
hasAuth: false,
|
|
322
|
+
type: null,
|
|
323
|
+
cors: null
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Check for Spring Security
|
|
327
|
+
if (config.spring?.security) {
|
|
328
|
+
security.hasAuth = true;
|
|
329
|
+
|
|
330
|
+
if (config.spring.security.oauth2) {
|
|
331
|
+
security.type = "oauth2";
|
|
332
|
+
} else if (config.spring.security.jwt) {
|
|
333
|
+
security.type = "jwt";
|
|
334
|
+
} else {
|
|
335
|
+
security.type = "basic";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Check for JWT config
|
|
340
|
+
if (config.jwt || config.security?.jwt) {
|
|
341
|
+
security.hasAuth = true;
|
|
342
|
+
security.type = "jwt";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return security;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Extract environment variables referenced in configs
|
|
350
|
+
*/
|
|
351
|
+
extractEnvVars(configs) {
|
|
352
|
+
const envVars = new Set();
|
|
353
|
+
|
|
354
|
+
const extractFromObject = (obj) => {
|
|
355
|
+
if (typeof obj === "string") {
|
|
356
|
+
const matches = obj.matchAll(/\$\{([^:}]+)(?::[^}]*)?\}/g);
|
|
357
|
+
for (const match of matches) {
|
|
358
|
+
envVars.add(match[1]);
|
|
359
|
+
}
|
|
360
|
+
} else if (obj && typeof obj === "object") {
|
|
361
|
+
for (const value of Object.values(obj)) {
|
|
362
|
+
extractFromObject(value);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
for (const config of configs) {
|
|
368
|
+
if (config && config.content) {
|
|
369
|
+
extractFromObject(config.content);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return Array.from(envVars);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Obfuscate sensitive values
|
|
378
|
+
*/
|
|
379
|
+
obfuscateSensitive(key, value) {
|
|
380
|
+
if (!value || typeof value !== "string") return value;
|
|
381
|
+
|
|
382
|
+
const sensitivePatterns = [
|
|
383
|
+
"password", "secret", "key", "token", "credential",
|
|
384
|
+
"api_key", "apikey", "auth", "jwt"
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
const keyLower = key.toLowerCase();
|
|
388
|
+
for (const pattern of sensitivePatterns) {
|
|
389
|
+
if (keyLower.includes(pattern)) {
|
|
390
|
+
if (value.length > 4) {
|
|
391
|
+
return value.substring(0, 2) + "****" + value.substring(value.length - 2);
|
|
392
|
+
}
|
|
393
|
+
return "****";
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return value;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get runtime configuration for starting the server
|
|
402
|
+
*/
|
|
403
|
+
async getRuntimeConfig() {
|
|
404
|
+
const analysis = await this.analyze();
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
port: analysis.server.port,
|
|
408
|
+
contextPath: analysis.server.contextPath,
|
|
409
|
+
envVars: analysis.envVars,
|
|
410
|
+
profiles: this.detectProfiles(analysis.files),
|
|
411
|
+
jvmArgs: this.buildJvmArgs(analysis)
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Detect available Spring profiles
|
|
417
|
+
*/
|
|
418
|
+
detectProfiles(configFiles) {
|
|
419
|
+
const profiles = ["default"];
|
|
420
|
+
|
|
421
|
+
for (const file of configFiles) {
|
|
422
|
+
const match = file.match(/application-(\w+)\./);
|
|
423
|
+
if (match && match[1] !== "test") {
|
|
424
|
+
profiles.push(match[1]);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return [...new Set(profiles)];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Build JVM arguments for running the application
|
|
433
|
+
*/
|
|
434
|
+
buildJvmArgs(analysis) {
|
|
435
|
+
const args = [];
|
|
436
|
+
|
|
437
|
+
// Add profile if dev or local exists
|
|
438
|
+
if (analysis.files.some(f => f.includes("-dev") || f.includes("-local"))) {
|
|
439
|
+
args.push("-Dspring.profiles.active=dev,local");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return args;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export default ConfigAnalyzer;
|