deepdebug-local-agent 0.3.13 → 0.3.14
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/index.js +0 -0
- package/package.json +7 -7
- package/src/server.js +118 -0
- package/analyzers/config-analyzer.js +0 -446
- package/analyzers/controller-analyzer.js +0 -429
- package/analyzers/dto-analyzer.js +0 -455
- package/detectors/build-tool-detector.js +0 -0
- package/detectors/framework-detector.js +0 -91
- package/detectors/language-detector.js +0 -89
- package/detectors/multi-project-detector.js +0 -191
- package/detectors/service-detector.js +0 -244
- package/detectors.js +0 -30
- package/exec-utils.js +0 -215
- package/fs-utils.js +0 -34
- package/mcp-http-server.js +0 -313
- package/patch.js +0 -607
- package/ports.js +0 -69
- package/server.js +0 -5261
- package/workspace/detect-port.js +0 -176
- package/workspace/file-reader.js +0 -54
- package/workspace/git-client.js +0 -0
- package/workspace/process-manager.js +0 -619
- package/workspace/scanner.js +0 -72
- package/workspace-manager.js +0 -172
package/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepdebug-local-agent",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Insptech AI — DeepDebug Local Agent. Autonomous code debugging agent for production environments.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "server.js",
|
|
6
|
+
"main": "src/server.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"deepdebug-local-agent": "
|
|
8
|
+
"deepdebug-local-agent": "index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node server.js",
|
|
12
|
-
"dev": "NODE_ENV=development node server.js",
|
|
13
|
-
"mcp": "node mcp-server.js"
|
|
11
|
+
"start": "node src/server.js",
|
|
12
|
+
"dev": "NODE_ENV=development node src/server.js",
|
|
13
|
+
"mcp": "node src/mcp-server.js"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"deepdebug",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"properties-reader": "^2.3.0",
|
|
37
37
|
"strip-ansi": "^7.1.0",
|
|
38
38
|
"unidiff": "^1.0.2",
|
|
39
|
-
"ws": "^8.
|
|
39
|
+
"ws": "^8.18.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/server.js
CHANGED
|
@@ -557,7 +557,10 @@ app.use(cors({
|
|
|
557
557
|
// Cloud Run URLs (regex patterns)
|
|
558
558
|
/https:\/\/.*\.run\.app$/,
|
|
559
559
|
/https:\/\/.*\.web\.app$/,
|
|
560
|
+
<<<<<<< HEAD
|
|
560
561
|
// Production frontend
|
|
562
|
+
=======
|
|
563
|
+
>>>>>>> dd7d3ab (fix: add deepdebug.ai to CORS origins)
|
|
561
564
|
"https://deepdebug.ai",
|
|
562
565
|
"https://www.deepdebug.ai"
|
|
563
566
|
],
|
|
@@ -5201,6 +5204,121 @@ async function startWebSocketTunnel(port, gatewayUrl, apiKey, tenantId) {
|
|
|
5201
5204
|
return await res.json();
|
|
5202
5205
|
}
|
|
5203
5206
|
|
|
5207
|
+
// ============================================
|
|
5208
|
+
// MCP TOOLS — routed via MCP HTTP Bridge (port 5056)
|
|
5209
|
+
// ============================================
|
|
5210
|
+
case 'mcp.read-file': {
|
|
5211
|
+
const mcpBase = `http://localhost:5056`;
|
|
5212
|
+
const res = await fetch(`${mcpBase}/mcp/read-file`, {
|
|
5213
|
+
method: 'POST',
|
|
5214
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5215
|
+
body: JSON.stringify({
|
|
5216
|
+
workspaceId: params.workspaceId || 'default',
|
|
5217
|
+
path: params.path
|
|
5218
|
+
})
|
|
5219
|
+
});
|
|
5220
|
+
return await res.json();
|
|
5221
|
+
}
|
|
5222
|
+
|
|
5223
|
+
case 'mcp.read-files': {
|
|
5224
|
+
const mcpBase = `http://localhost:5056`;
|
|
5225
|
+
const res = await fetch(`${mcpBase}/mcp/read-files`, {
|
|
5226
|
+
method: 'POST',
|
|
5227
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5228
|
+
body: JSON.stringify({
|
|
5229
|
+
workspaceId: params.workspaceId || 'default',
|
|
5230
|
+
paths: params.paths
|
|
5231
|
+
})
|
|
5232
|
+
});
|
|
5233
|
+
return await res.json();
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
case 'mcp.list-directory': {
|
|
5237
|
+
const mcpBase = `http://localhost:5056`;
|
|
5238
|
+
const res = await fetch(`${mcpBase}/mcp/list-directory`, {
|
|
5239
|
+
method: 'POST',
|
|
5240
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5241
|
+
body: JSON.stringify({
|
|
5242
|
+
workspaceId: params.workspaceId || 'default',
|
|
5243
|
+
path: params.path || '.',
|
|
5244
|
+
maxFiles: params.maxFiles || 500
|
|
5245
|
+
})
|
|
5246
|
+
});
|
|
5247
|
+
return await res.json();
|
|
5248
|
+
}
|
|
5249
|
+
|
|
5250
|
+
case 'mcp.search-code': {
|
|
5251
|
+
const mcpBase = `http://localhost:5056`;
|
|
5252
|
+
const res = await fetch(`${mcpBase}/mcp/search-code`, {
|
|
5253
|
+
method: 'POST',
|
|
5254
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5255
|
+
body: JSON.stringify({
|
|
5256
|
+
workspaceId: params.workspaceId || 'default',
|
|
5257
|
+
query: params.query,
|
|
5258
|
+
filePattern: params.filePattern || '*',
|
|
5259
|
+
maxResults: params.maxResults || 50
|
|
5260
|
+
})
|
|
5261
|
+
});
|
|
5262
|
+
return await res.json();
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5265
|
+
case 'mcp.execute-command': {
|
|
5266
|
+
const mcpBase = `http://localhost:5056`;
|
|
5267
|
+
const res = await fetch(`${mcpBase}/mcp/execute-command`, {
|
|
5268
|
+
method: 'POST',
|
|
5269
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5270
|
+
body: JSON.stringify({
|
|
5271
|
+
workspaceId: params.workspaceId || 'default',
|
|
5272
|
+
command: params.command,
|
|
5273
|
+
timeoutMs: params.timeoutMs || 120000
|
|
5274
|
+
})
|
|
5275
|
+
});
|
|
5276
|
+
return await res.json();
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
case 'mcp.project-info': {
|
|
5280
|
+
const mcpBase = `http://localhost:5056`;
|
|
5281
|
+
const res = await fetch(`${mcpBase}/mcp/project-info`, {
|
|
5282
|
+
method: 'POST',
|
|
5283
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5284
|
+
body: JSON.stringify({
|
|
5285
|
+
workspaceId: params.workspaceId || 'default'
|
|
5286
|
+
})
|
|
5287
|
+
});
|
|
5288
|
+
return await res.json();
|
|
5289
|
+
}
|
|
5290
|
+
|
|
5291
|
+
case 'mcp.apply-patch': {
|
|
5292
|
+
const mcpBase = `http://localhost:5056`;
|
|
5293
|
+
const res = await fetch(`${mcpBase}/mcp/apply-patch`, {
|
|
5294
|
+
method: 'POST',
|
|
5295
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5296
|
+
body: JSON.stringify({
|
|
5297
|
+
workspaceId: params.workspaceId || 'default',
|
|
5298
|
+
diff: params.diff
|
|
5299
|
+
})
|
|
5300
|
+
});
|
|
5301
|
+
return await res.json();
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5304
|
+
case 'mcp.open': {
|
|
5305
|
+
const mcpBase = `http://localhost:5056`;
|
|
5306
|
+
// Also open in wsManager for backward compat
|
|
5307
|
+
if (params.root) {
|
|
5308
|
+
await wsManager.open(params.workspaceId || 'default', params.root);
|
|
5309
|
+
WORKSPACE_ROOT = params.root;
|
|
5310
|
+
}
|
|
5311
|
+
const res = await fetch(`${mcpBase}/mcp/open`, {
|
|
5312
|
+
method: 'POST',
|
|
5313
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5314
|
+
body: JSON.stringify({
|
|
5315
|
+
workspaceId: params.workspaceId || 'default',
|
|
5316
|
+
root: params.root
|
|
5317
|
+
})
|
|
5318
|
+
});
|
|
5319
|
+
return await res.json();
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5204
5322
|
default:
|
|
5205
5323
|
return { error: `Unknown command: ${command}` };
|
|
5206
5324
|
}
|
|
@@ -1,446 +0,0 @@
|
|
|
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;
|