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,528 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
// 🔹 Caminho global do workspace (inicializado com fallback padrão)
|
|
9
|
+
let workspacePath =
|
|
10
|
+
process.env.WORKSPACE_BASE ||
|
|
11
|
+
path.join(os.homedir(), "Documents", "Projects");
|
|
12
|
+
|
|
13
|
+
// --------------------------------------------------
|
|
14
|
+
// 🧩 1️⃣ GET /workspace/info
|
|
15
|
+
// Retorna informações sobre o workspace atual
|
|
16
|
+
// --------------------------------------------------
|
|
17
|
+
router.get("/info", async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
return res.json({
|
|
20
|
+
workspacePath,
|
|
21
|
+
exists: fs.existsSync(workspacePath),
|
|
22
|
+
isDirectory: fs.existsSync(workspacePath)
|
|
23
|
+
? fs.lstatSync(workspacePath).isDirectory()
|
|
24
|
+
: false,
|
|
25
|
+
});
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error("❌ Error reading workspace info:", err);
|
|
28
|
+
return res.status(500).json({ error: err.message });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// --------------------------------------------------
|
|
33
|
+
// 🧩 2️⃣ POST /workspace
|
|
34
|
+
// Atualiza o diretório de workspace selecionado pelo usuário
|
|
35
|
+
// --------------------------------------------------
|
|
36
|
+
router.post("/", async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
console.log(workspacePath)
|
|
39
|
+
const { workspacePath: newPath } = req.body;
|
|
40
|
+
if (!newPath) {
|
|
41
|
+
return res.status(400).json({ error: "Missing workspacePath" });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 🔹 Resolve o caminho completo (garante formato absoluto)
|
|
45
|
+
const resolvedPath = path.resolve(newPath);
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(resolvedPath) || !fs.lstatSync(resolvedPath).isDirectory()) {
|
|
48
|
+
return res
|
|
49
|
+
.status(400)
|
|
50
|
+
.json({ error: `Invalid directory path: ${resolvedPath}` });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
workspacePath = resolvedPath;
|
|
54
|
+
console.log(`📂 Workspace updated to: ${workspacePath}`);
|
|
55
|
+
|
|
56
|
+
return res.json({ success: true, workspacePath });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error("❌ Error updating workspace:", err);
|
|
59
|
+
return res.status(500).json({ error: err.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// --------------------------------------------------
|
|
64
|
+
// 🧩 3️⃣ GET /workspace/files
|
|
65
|
+
// Lista arquivos .java (até 3 níveis de profundidade)
|
|
66
|
+
// --------------------------------------------------
|
|
67
|
+
router.get("/files", async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
if (!workspacePath || !fs.existsSync(workspacePath)) {
|
|
70
|
+
return res.status(400).json({ error: "Workspace not configured or invalid" });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const files = [];
|
|
74
|
+
|
|
75
|
+
function walk(dir, depth = 0) {
|
|
76
|
+
if (depth > 3) return;
|
|
77
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
const fullPath = path.join(dir, entry.name);
|
|
80
|
+
if (entry.isDirectory()) {
|
|
81
|
+
walk(fullPath, depth + 1);
|
|
82
|
+
} else if (entry.isFile() && entry.name.endsWith(".java")) {
|
|
83
|
+
files.push(fullPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
walk(workspacePath);
|
|
89
|
+
return res.json({ workspacePath, files });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
console.error("❌ Error listing files:", err);
|
|
92
|
+
return res.status(500).json({ error: err.message });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// --------------------------------------------------
|
|
97
|
+
// 🧩 4️⃣ GET /workspace/file
|
|
98
|
+
// Lê o conteúdo de um arquivo específico dentro do workspace
|
|
99
|
+
// --------------------------------------------------
|
|
100
|
+
router.get("/file", async (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const { path: requestedPath } = req.query;
|
|
103
|
+
|
|
104
|
+
if (!requestedPath) {
|
|
105
|
+
return res.status(400).json({ error: "Missing 'path' query parameter" });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 🔹 Corrige o caminho caso venha relativo (ex: "src/...") ou com prefixo incorreto
|
|
109
|
+
let fullPath = requestedPath;
|
|
110
|
+
|
|
111
|
+
if (!path.isAbsolute(requestedPath) || !requestedPath.startsWith(workspacePath)) {
|
|
112
|
+
fullPath = path.join(workspacePath, requestedPath.replace(/^\//, ""));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fullPath = path.resolve(fullPath);
|
|
116
|
+
|
|
117
|
+
console.log(`📄 Attempting to read: ${fullPath}`);
|
|
118
|
+
|
|
119
|
+
if (!fs.existsSync(fullPath)) {
|
|
120
|
+
return res.status(404).json({
|
|
121
|
+
error: "File not found",
|
|
122
|
+
receivedPath: requestedPath,
|
|
123
|
+
resolvedPath: fullPath,
|
|
124
|
+
workspacePath,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
129
|
+
const stats = fs.statSync(fullPath);
|
|
130
|
+
|
|
131
|
+
return res.json({
|
|
132
|
+
workspacePath,
|
|
133
|
+
fullPath,
|
|
134
|
+
relativePath: path.relative(workspacePath, fullPath),
|
|
135
|
+
size: stats.size,
|
|
136
|
+
modified: stats.mtime,
|
|
137
|
+
content,
|
|
138
|
+
});
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error("❌ Error reading file:", err);
|
|
141
|
+
return res.status(500).json({ error: err.message });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// --------------------------------------------------
|
|
146
|
+
// 🧩 5️⃣ POST /workspace/resolve
|
|
147
|
+
// Resolve caminho absoluto do projeto pelo nome da pasta
|
|
148
|
+
// (usado quando o dev escolhe o workspace no Setup Wizard)
|
|
149
|
+
// --------------------------------------------------
|
|
150
|
+
router.post("/resolve", async (req, res) => {
|
|
151
|
+
try {
|
|
152
|
+
const { folderName } = req.body;
|
|
153
|
+
if (!folderName) {
|
|
154
|
+
return res.status(400).json({ error: "Missing folderName" });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const base = path.join(os.homedir(), "Documents", "Projects");
|
|
158
|
+
const resolved = path.resolve(base, folderName);
|
|
159
|
+
|
|
160
|
+
if (!fs.existsSync(resolved)) {
|
|
161
|
+
return res.status(400).json({ error: `Folder not found: ${resolved}` });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
workspacePath = resolved;
|
|
165
|
+
console.log(`🧩 Resolved workspace path: ${workspacePath}`);
|
|
166
|
+
|
|
167
|
+
return res.json({ absolutePath: workspacePath });
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error("❌ Error resolving absolute path:", err);
|
|
170
|
+
return res.status(500).json({ error: err.message });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// --------------------------------------------------
|
|
175
|
+
// 🧩 6️⃣ GET /workspace/detect
|
|
176
|
+
// Detecta tipo de projeto (Maven / Gradle / Outro)
|
|
177
|
+
// --------------------------------------------------
|
|
178
|
+
router.get("/detect", async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
if (!workspacePath || !fs.existsSync(workspacePath)) {
|
|
181
|
+
return res.status(400).json({ error: "Workspace not configured" });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const files = fs.readdirSync(workspacePath);
|
|
185
|
+
const type = files.includes("pom.xml")
|
|
186
|
+
? "maven"
|
|
187
|
+
: files.includes("build.gradle")
|
|
188
|
+
? "gradle"
|
|
189
|
+
: "unknown";
|
|
190
|
+
|
|
191
|
+
return res.json({ type, workspacePath });
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.error("❌ Error detecting project type:", err);
|
|
194
|
+
return res.status(500).json({ error: err.message });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ==========================================
|
|
199
|
+
// ✅ NOVOS ENDPOINTS - Investigation Mode (Sprint 1.1.5)
|
|
200
|
+
// ==========================================
|
|
201
|
+
|
|
202
|
+
// --------------------------------------------------
|
|
203
|
+
// 🧩 7️⃣ POST /workspace/search-file
|
|
204
|
+
// Busca recursiva por nome de arquivo no workspace
|
|
205
|
+
// Usado pelo InvestigationModeService para encontrar arquivos por hint
|
|
206
|
+
// --------------------------------------------------
|
|
207
|
+
router.post("/search-file", async (req, res) => {
|
|
208
|
+
try {
|
|
209
|
+
const { fileName } = req.body;
|
|
210
|
+
|
|
211
|
+
if (!fileName) {
|
|
212
|
+
return res.status(400).json({ error: "Missing fileName" });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!workspacePath || !fs.existsSync(workspacePath)) {
|
|
216
|
+
return res.status(400).json({ error: "Workspace not configured or invalid" });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log(`🔍 [search-file] Searching for: ${fileName} in ${workspacePath}`);
|
|
220
|
+
|
|
221
|
+
let foundPath = null;
|
|
222
|
+
const matches = [];
|
|
223
|
+
|
|
224
|
+
// Função de busca recursiva
|
|
225
|
+
function searchRecursive(dir, depth = 0) {
|
|
226
|
+
if (depth > 10 || foundPath) return; // Limitar profundidade
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
230
|
+
|
|
231
|
+
for (const entry of entries) {
|
|
232
|
+
// Ignorar diretórios comuns
|
|
233
|
+
if (entry.name.startsWith('.') ||
|
|
234
|
+
entry.name === 'node_modules' ||
|
|
235
|
+
entry.name === 'target' ||
|
|
236
|
+
entry.name === 'build' ||
|
|
237
|
+
entry.name === '.git') {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const fullPath = path.join(dir, entry.name);
|
|
242
|
+
|
|
243
|
+
if (entry.isDirectory()) {
|
|
244
|
+
searchRecursive(fullPath, depth + 1);
|
|
245
|
+
} else if (entry.isFile()) {
|
|
246
|
+
// Match exato ou parcial
|
|
247
|
+
if (entry.name === fileName ||
|
|
248
|
+
entry.name.toLowerCase() === fileName.toLowerCase()) {
|
|
249
|
+
foundPath = path.relative(workspacePath, fullPath);
|
|
250
|
+
matches.push({
|
|
251
|
+
path: foundPath,
|
|
252
|
+
matchType: 'exact'
|
|
253
|
+
});
|
|
254
|
+
} else if (entry.name.includes(fileName.replace('.java', ''))) {
|
|
255
|
+
matches.push({
|
|
256
|
+
path: path.relative(workspacePath, fullPath),
|
|
257
|
+
matchType: 'partial'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
// Ignorar erros de permissão
|
|
264
|
+
console.warn(`⚠️ Cannot read directory: ${dir}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
searchRecursive(workspacePath);
|
|
269
|
+
|
|
270
|
+
if (foundPath) {
|
|
271
|
+
console.log(`✅ [search-file] Found: ${foundPath}`);
|
|
272
|
+
return res.json({
|
|
273
|
+
found: true,
|
|
274
|
+
path: foundPath,
|
|
275
|
+
matches: matches.slice(0, 10) // Limitar matches
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Se não encontrou match exato, retornar matches parciais
|
|
280
|
+
if (matches.length > 0) {
|
|
281
|
+
console.log(`🔶 [search-file] Partial matches found: ${matches.length}`);
|
|
282
|
+
return res.json({
|
|
283
|
+
found: true,
|
|
284
|
+
path: matches[0].path,
|
|
285
|
+
matches: matches.slice(0, 10)
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(`❌ [search-file] Not found: ${fileName}`);
|
|
290
|
+
return res.json({ found: false, matches: [] });
|
|
291
|
+
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error("❌ Error searching file:", err);
|
|
294
|
+
return res.status(500).json({ error: err.message });
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// --------------------------------------------------
|
|
299
|
+
// 🧩 8️⃣ GET /workspace/recent-files
|
|
300
|
+
// Lista arquivos recentemente modificados
|
|
301
|
+
// Usado pelo InvestigationModeService como última estratégia
|
|
302
|
+
// --------------------------------------------------
|
|
303
|
+
router.get("/recent-files", async (req, res) => {
|
|
304
|
+
try {
|
|
305
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
306
|
+
|
|
307
|
+
if (!workspacePath || !fs.existsSync(workspacePath)) {
|
|
308
|
+
return res.status(400).json({ error: "Workspace not configured or invalid" });
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(`🕐 [recent-files] Getting ${limit} recent files from ${workspacePath}`);
|
|
312
|
+
|
|
313
|
+
const allFiles = [];
|
|
314
|
+
|
|
315
|
+
// Coletar todos os arquivos com data de modificação
|
|
316
|
+
function collectFiles(dir, depth = 0) {
|
|
317
|
+
if (depth > 5) return; // Limitar profundidade
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
321
|
+
|
|
322
|
+
for (const entry of entries) {
|
|
323
|
+
// Ignorar diretórios comuns
|
|
324
|
+
if (entry.name.startsWith('.') ||
|
|
325
|
+
entry.name === 'node_modules' ||
|
|
326
|
+
entry.name === 'target' ||
|
|
327
|
+
entry.name === 'build' ||
|
|
328
|
+
entry.name === '.git') {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const fullPath = path.join(dir, entry.name);
|
|
333
|
+
|
|
334
|
+
if (entry.isDirectory()) {
|
|
335
|
+
collectFiles(fullPath, depth + 1);
|
|
336
|
+
} else if (entry.isFile() &&
|
|
337
|
+
(entry.name.endsWith('.java') ||
|
|
338
|
+
entry.name.endsWith('.js') ||
|
|
339
|
+
entry.name.endsWith('.ts'))) {
|
|
340
|
+
try {
|
|
341
|
+
const stats = fs.statSync(fullPath);
|
|
342
|
+
allFiles.push({
|
|
343
|
+
path: path.relative(workspacePath, fullPath),
|
|
344
|
+
name: entry.name,
|
|
345
|
+
modified: stats.mtime,
|
|
346
|
+
size: stats.size
|
|
347
|
+
});
|
|
348
|
+
} catch (e) {
|
|
349
|
+
// Ignorar arquivos que não podem ser lidos
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch (err) {
|
|
354
|
+
// Ignorar erros de permissão
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
collectFiles(workspacePath);
|
|
359
|
+
|
|
360
|
+
// Ordenar por data de modificação (mais recente primeiro)
|
|
361
|
+
allFiles.sort((a, b) => b.modified - a.modified);
|
|
362
|
+
|
|
363
|
+
// Retornar apenas os N mais recentes
|
|
364
|
+
const recentFiles = allFiles.slice(0, limit);
|
|
365
|
+
|
|
366
|
+
console.log(`✅ [recent-files] Returning ${recentFiles.length} files`);
|
|
367
|
+
|
|
368
|
+
return res.json({
|
|
369
|
+
workspacePath,
|
|
370
|
+
total: allFiles.length,
|
|
371
|
+
files: recentFiles
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
} catch (err) {
|
|
375
|
+
console.error("❌ Error getting recent files:", err);
|
|
376
|
+
return res.status(500).json({ error: err.message });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// --------------------------------------------------
|
|
381
|
+
// 🧩 9️⃣ GET /workspace/scan
|
|
382
|
+
// Escaneia workspace completo com metadados
|
|
383
|
+
// Usado pelo SmartFileDetectionService para análise com AI
|
|
384
|
+
// --------------------------------------------------
|
|
385
|
+
router.get("/scan", async (req, res) => {
|
|
386
|
+
try {
|
|
387
|
+
if (!workspacePath || !fs.existsSync(workspacePath)) {
|
|
388
|
+
return res.status(400).json({ error: "Workspace not configured or invalid" });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
console.log(`📂 [scan] Scanning workspace: ${workspacePath}`);
|
|
392
|
+
|
|
393
|
+
const files = [];
|
|
394
|
+
const languages = new Set();
|
|
395
|
+
let totalFiles = 0;
|
|
396
|
+
|
|
397
|
+
// Mapeamento de extensão para linguagem
|
|
398
|
+
const extensionMap = {
|
|
399
|
+
'.java': 'Java',
|
|
400
|
+
'.js': 'JavaScript',
|
|
401
|
+
'.ts': 'TypeScript',
|
|
402
|
+
'.py': 'Python',
|
|
403
|
+
'.go': 'Go',
|
|
404
|
+
'.rs': 'Rust',
|
|
405
|
+
'.kt': 'Kotlin',
|
|
406
|
+
'.scala': 'Scala',
|
|
407
|
+
'.rb': 'Ruby',
|
|
408
|
+
'.php': 'PHP',
|
|
409
|
+
'.cs': 'C#',
|
|
410
|
+
'.cpp': 'C++',
|
|
411
|
+
'.c': 'C'
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
function scanRecursive(dir, depth = 0) {
|
|
415
|
+
if (depth > 6) return; // Limitar profundidade
|
|
416
|
+
|
|
417
|
+
try {
|
|
418
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
419
|
+
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
// Ignorar diretórios comuns
|
|
422
|
+
if (entry.name.startsWith('.') ||
|
|
423
|
+
entry.name === 'node_modules' ||
|
|
424
|
+
entry.name === 'target' ||
|
|
425
|
+
entry.name === 'build' ||
|
|
426
|
+
entry.name === '.git' ||
|
|
427
|
+
entry.name === '__pycache__') {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const fullPath = path.join(dir, entry.name);
|
|
432
|
+
|
|
433
|
+
if (entry.isDirectory()) {
|
|
434
|
+
scanRecursive(fullPath, depth + 1);
|
|
435
|
+
} else if (entry.isFile()) {
|
|
436
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
437
|
+
|
|
438
|
+
// Verificar se é arquivo de código
|
|
439
|
+
if (extensionMap[ext]) {
|
|
440
|
+
languages.add(extensionMap[ext]);
|
|
441
|
+
totalFiles++;
|
|
442
|
+
|
|
443
|
+
// Limitar arquivos retornados para não estourar memória
|
|
444
|
+
if (files.length < 200) {
|
|
445
|
+
try {
|
|
446
|
+
const stats = fs.statSync(fullPath);
|
|
447
|
+
files.push({
|
|
448
|
+
path: path.relative(workspacePath, fullPath),
|
|
449
|
+
name: entry.name,
|
|
450
|
+
language: extensionMap[ext],
|
|
451
|
+
size: stats.size,
|
|
452
|
+
modified: stats.mtime
|
|
453
|
+
});
|
|
454
|
+
} catch (e) {
|
|
455
|
+
// Ignorar
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
} catch (err) {
|
|
462
|
+
// Ignorar erros de permissão
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
scanRecursive(workspacePath);
|
|
467
|
+
|
|
468
|
+
console.log(`✅ [scan] Found ${totalFiles} files in ${languages.size} languages`);
|
|
469
|
+
|
|
470
|
+
return res.json({
|
|
471
|
+
workspacePath,
|
|
472
|
+
files,
|
|
473
|
+
metadata: {
|
|
474
|
+
totalFiles,
|
|
475
|
+
languages: Array.from(languages),
|
|
476
|
+
scannedAt: new Date().toISOString()
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.error("❌ Error scanning workspace:", err);
|
|
482
|
+
return res.status(500).json({ error: err.message });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// --------------------------------------------------
|
|
487
|
+
// 🧩 🔟 GET /workspace/file-content
|
|
488
|
+
// Alias para /workspace/file (compatibilidade com código Java)
|
|
489
|
+
// --------------------------------------------------
|
|
490
|
+
router.get("/file-content", async (req, res) => {
|
|
491
|
+
try {
|
|
492
|
+
const { path: requestedPath } = req.query;
|
|
493
|
+
|
|
494
|
+
if (!requestedPath) {
|
|
495
|
+
return res.status(400).json({ error: "Missing 'path' query parameter" });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let fullPath = requestedPath;
|
|
499
|
+
|
|
500
|
+
if (!path.isAbsolute(requestedPath) || !requestedPath.startsWith(workspacePath)) {
|
|
501
|
+
fullPath = path.join(workspacePath, requestedPath.replace(/^\//, ""));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
fullPath = path.resolve(fullPath);
|
|
505
|
+
|
|
506
|
+
console.log(`📄 [file-content] Reading: ${fullPath}`);
|
|
507
|
+
|
|
508
|
+
if (!fs.existsSync(fullPath)) {
|
|
509
|
+
return res.status(404).json({
|
|
510
|
+
error: "File not found",
|
|
511
|
+
path: requestedPath
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
516
|
+
|
|
517
|
+
return res.json({
|
|
518
|
+
content,
|
|
519
|
+
path: path.relative(workspacePath, fullPath)
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
} catch (err) {
|
|
523
|
+
console.error("❌ Error reading file content:", err);
|
|
524
|
+
return res.status(500).json({ error: err.message });
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
export default router;
|