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,619 @@
1
+ import { spawn } from "child_process";
2
+ import { EventEmitter } from "events";
3
+ import stripAnsi from "strip-ansi";
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ /**
8
+ * ProcessManager
9
+ *
10
+ * Gerencia ciclo de vida de processos (start, stop, restart)
11
+ * Monitora CPU, Memory, Uptime
12
+ * Captura logs em tempo real
13
+ *
14
+ * UNIFIED VERSION: Aceita AMBOS os formatos:
15
+ * 1. command/args direto (novo)
16
+ * 2. language/framework/buildTool (legado)
17
+ */
18
+ export class ProcessManager extends EventEmitter {
19
+ constructor() {
20
+ super();
21
+ this.processes = new Map(); // serviceId -> { process, startTime, logs, stats }
22
+ }
23
+
24
+ /**
25
+ * Verifica se um serviço está rodando
26
+ */
27
+ isRunning(serviceId) {
28
+ const processData = this.processes.get(serviceId);
29
+ return processData && processData.process && !processData.process.killed;
30
+ }
31
+
32
+ /**
33
+ * Inicia um serviço
34
+ *
35
+ * ACEITA DOIS FORMATOS:
36
+ * 1. { command, args, cwd, port, env } - Novo formato direto
37
+ * 2. { language, framework, buildTool, cwd, port, profile } - Formato legado
38
+ */
39
+ async start(serviceId, config) {
40
+ const {
41
+ // Novo formato
42
+ command,
43
+ args,
44
+ // Formato legado
45
+ language,
46
+ framework,
47
+ buildTool,
48
+ // Comum
49
+ cwd,
50
+ port,
51
+ profile,
52
+ env,
53
+ envVars = {}
54
+ } = config;
55
+
56
+ // Se já estiver rodando, parar primeiro
57
+ if (this.processes.has(serviceId)) {
58
+ console.log(`⚠️ Service ${serviceId} already running, stopping first...`);
59
+ await this.stop(serviceId);
60
+ await new Promise(resolve => setTimeout(resolve, 1000));
61
+ }
62
+
63
+ // Determinar comando a usar
64
+ let cmd, cmdArgs;
65
+
66
+ if (command && args) {
67
+ // NOVO FORMATO: command/args passados diretamente
68
+ cmd = command;
69
+ cmdArgs = args;
70
+ console.log(`🚀 Starting ${serviceId} (direct): ${cmd} ${cmdArgs.join(' ')}`);
71
+ } else if (language) {
72
+ // FORMATO LEGADO: derivar de language/framework/buildTool
73
+ const startCommand = await this.getStartCommand(language, framework, buildTool, cwd, profile || 'dev');
74
+ cmd = startCommand.cmd;
75
+ cmdArgs = startCommand.args;
76
+ console.log(`🚀 Starting ${serviceId} (derived): ${cmd} ${cmdArgs.join(' ')}`);
77
+ } else {
78
+ throw new Error(`Either 'command/args' or 'language' must be provided to start ${serviceId}`);
79
+ }
80
+
81
+ console.log(`⚙️ Config: port=${port}, profile=${profile || 'none'}`);
82
+ console.log(`📁 Working directory: ${cwd}`);
83
+
84
+ // ============================================
85
+ // BUILD ENVIRONMENT
86
+ // ============================================
87
+ // Two strategies:
88
+ // DIRECT MODE (command/args provided): env is used AS-IS if provided.
89
+ // The caller (test-local/start) already builds a clean env without
90
+ // SPRING_* variables so the app uses only application.yml defaults.
91
+ // LEGACY MODE (language/framework): merge process.env with overrides.
92
+ // ============================================
93
+ let processEnv;
94
+
95
+ if (command && args && env) {
96
+ // ✅ DIRECT MODE: Use the caller's env as the base (it's already clean)
97
+ processEnv = {
98
+ ...env,
99
+ ...envVars
100
+ };
101
+ // Only set port vars if not already present
102
+ if (!processEnv.SERVER_PORT) processEnv.SERVER_PORT = String(port || 8080);
103
+ if (!processEnv.PORT) processEnv.PORT = String(port || 8080);
104
+ // DO NOT set SPRING_PROFILES_ACTIVE — let application.yml decide
105
+ console.log(` 📦 Using caller-provided env (clean, ${Object.keys(processEnv).length} vars)`);
106
+ } else {
107
+ // 🔄 LEGACY MODE: Merge with process.env and apply profile
108
+ processEnv = {
109
+ ...process.env,
110
+ ...(env || {}),
111
+ ...envVars,
112
+ SERVER_PORT: String(port || 8080),
113
+ PORT: String(port || 8080),
114
+ NODE_ENV: profile === 'prod' ? 'production' : 'development',
115
+ FLASK_ENV: profile === 'prod' ? 'production' : 'development',
116
+ };
117
+ // Only set profile if explicitly provided
118
+ if (profile) {
119
+ processEnv.SPRING_PROFILES_ACTIVE = profile;
120
+ }
121
+ console.log(` 📦 Using merged env (legacy, profile=${profile || 'default'})`);
122
+ }
123
+
124
+ const child = spawn(cmd, cmdArgs, {
125
+ cwd,
126
+ shell: true,
127
+ env: processEnv,
128
+ stdio: ['pipe', 'pipe', 'pipe']
129
+ });
130
+
131
+ if (!child.pid) {
132
+ throw new Error(`Failed to spawn process for ${serviceId}`);
133
+ }
134
+
135
+ console.log(`✅ Process spawned with PID: ${child.pid}`);
136
+
137
+ const processData = {
138
+ process: child,
139
+ pid: child.pid,
140
+ command: cmd,
141
+ args: cmdArgs,
142
+ cwd,
143
+ startTime: Date.now(),
144
+ logs: [],
145
+ port,
146
+ status: "starting",
147
+ cpu: 0,
148
+ memory: 0
149
+ };
150
+
151
+ this.processes.set(serviceId, processData);
152
+
153
+ // Capturar stdout
154
+ child.stdout.on("data", (data) => {
155
+ const text = data.toString();
156
+ const lines = text.split('\n').filter(l => l.trim());
157
+
158
+ for (const line of lines) {
159
+ const log = stripAnsi(line).trim();
160
+ if (!log) continue;
161
+
162
+ processData.logs.push({
163
+ timestamp: Date.now(),
164
+ type: "stdout",
165
+ message: log
166
+ });
167
+
168
+ this.emit("log", { serviceId, message: log, type: "stdout" });
169
+
170
+ // Detectar quando serviço está pronto
171
+ if (this.isServiceReady(log, language || 'java')) {
172
+ if (processData.status !== "running") {
173
+ processData.status = "running";
174
+ console.log(`✅ Service ${serviceId} is READY!`);
175
+ this.emit("started", { serviceId, port });
176
+ }
177
+ }
178
+ }
179
+ });
180
+
181
+ // Capturar stderr (Spring Boot também loga aqui)
182
+ child.stderr.on("data", (data) => {
183
+ const text = data.toString();
184
+ const lines = text.split('\n').filter(l => l.trim());
185
+
186
+ for (const line of lines) {
187
+ const log = stripAnsi(line).trim();
188
+ if (!log) continue;
189
+
190
+ processData.logs.push({
191
+ timestamp: Date.now(),
192
+ type: "stderr",
193
+ message: log
194
+ });
195
+
196
+ this.emit("log", { serviceId, message: log, type: "stderr" });
197
+
198
+ // Spring Boot pode logar "Started" no stderr também
199
+ if (this.isServiceReady(log, language || 'java')) {
200
+ if (processData.status !== "running") {
201
+ processData.status = "running";
202
+ console.log(`✅ Service ${serviceId} is READY!`);
203
+ this.emit("started", { serviceId, port });
204
+ }
205
+ }
206
+ }
207
+ });
208
+
209
+ // Capturar erro de inicialização
210
+ child.on("error", (err) => {
211
+ console.error(`❌ Failed to start ${serviceId}:`, err.message);
212
+ processData.status = "failed";
213
+ this.emit("error", { serviceId, error: err.message });
214
+ });
215
+
216
+ // Capturar encerramento
217
+ child.on("close", (code, signal) => {
218
+ console.log(`⏹️ Service ${serviceId} exited with code ${code}, signal ${signal}`);
219
+ this.processes.delete(serviceId);
220
+ this.emit("stopped", { serviceId, code, signal });
221
+ });
222
+
223
+ // Iniciar monitoramento de recursos
224
+ this.startResourceMonitoring(serviceId);
225
+
226
+ return {
227
+ serviceId,
228
+ pid: child.pid,
229
+ status: "starting",
230
+ port,
231
+ command: `${cmd} ${cmdArgs.join(' ')}`,
232
+ startedAt: new Date().toISOString()
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Para um serviço
238
+ */
239
+ async stop(serviceId) {
240
+ const processData = this.processes.get(serviceId);
241
+ if (!processData) {
242
+ console.log(`⚠️ Service ${serviceId} is not running (already stopped)`);
243
+ return {
244
+ serviceId,
245
+ status: 'stopped',
246
+ stoppedAt: new Date().toISOString(),
247
+ alreadyStopped: true
248
+ };
249
+ }
250
+
251
+ console.log(`⏹️ Stopping ${serviceId} (PID: ${processData.pid})...`);
252
+
253
+ return new Promise((resolve) => {
254
+ const child = processData.process;
255
+ let resolved = false;
256
+
257
+ const onExit = (code) => {
258
+ if (!resolved) {
259
+ resolved = true;
260
+ this.processes.delete(serviceId);
261
+ console.log(`✅ Service ${serviceId} stopped (exit code: ${code})`);
262
+ this.emit("log", { serviceId, message: `Service stopped (exit code: ${code})`, type: "info" });
263
+ resolve({
264
+ serviceId,
265
+ status: 'stopped',
266
+ exitCode: code,
267
+ stoppedAt: new Date().toISOString()
268
+ });
269
+ }
270
+ };
271
+
272
+ child.once('exit', onExit);
273
+ child.once('close', onExit);
274
+
275
+ // Tentar graceful shutdown
276
+ try {
277
+ child.kill('SIGTERM');
278
+ } catch (err) {
279
+ console.log(`⚠️ SIGTERM failed for ${serviceId}: ${err.message}`);
280
+ }
281
+
282
+ // Force kill após 5 segundos
283
+ setTimeout(() => {
284
+ if (!resolved && this.processes.has(serviceId)) {
285
+ console.log(`🔨 Force killing ${serviceId} (SIGKILL)`);
286
+ try {
287
+ child.kill('SIGKILL');
288
+ } catch (err) {
289
+ console.log(`⚠️ SIGKILL failed: ${err.message}`);
290
+ }
291
+ }
292
+ }, 5000);
293
+
294
+ // Timeout geral após 10 segundos
295
+ setTimeout(() => {
296
+ if (!resolved) {
297
+ resolved = true;
298
+ this.processes.delete(serviceId);
299
+ console.log(`⏰ Stop timeout for ${serviceId}, assuming stopped`);
300
+ resolve({
301
+ serviceId,
302
+ status: 'stopped',
303
+ timeout: true,
304
+ stoppedAt: new Date().toISOString()
305
+ });
306
+ }
307
+ }, 10000);
308
+ });
309
+ }
310
+
311
+ /**
312
+ * Retorna status de um serviço
313
+ */
314
+ getStatus(serviceId) {
315
+ const processData = this.processes.get(serviceId);
316
+ if (!processData) {
317
+ return { serviceId, status: "stopped" };
318
+ }
319
+
320
+ const uptime = Math.floor((Date.now() - processData.startTime) / 1000);
321
+
322
+ return {
323
+ serviceId,
324
+ status: processData.status,
325
+ pid: processData.pid,
326
+ port: processData.port,
327
+ cpu: processData.cpu,
328
+ memory: processData.memory,
329
+ uptime,
330
+ logsCount: processData.logs.length,
331
+ startedAt: new Date(processData.startTime).toISOString()
332
+ };
333
+ }
334
+
335
+ /**
336
+ * Retorna logs de um serviço
337
+ */
338
+ getLogs(serviceId, limit = 100) {
339
+ const processData = this.processes.get(serviceId);
340
+ if (!processData) {
341
+ return [];
342
+ }
343
+ return processData.logs.slice(-limit);
344
+ }
345
+
346
+ /**
347
+ * Lista todos os serviços
348
+ */
349
+ listServices() {
350
+ const services = [];
351
+ for (const [serviceId] of this.processes.entries()) {
352
+ services.push(this.getStatus(serviceId));
353
+ }
354
+ return services;
355
+ }
356
+
357
+ /**
358
+ * Alias para compatibilidade
359
+ */
360
+ listAll() {
361
+ return this.listServices();
362
+ }
363
+
364
+ /**
365
+ * Determina comando de start baseado no framework (FORMATO LEGADO)
366
+ * Usado quando server.js não passa command/args diretamente
367
+ */
368
+ async getStartCommand(language, framework, buildTool, cwd, profile = 'dev') {
369
+ console.log(`🔧 getStartCommand: language=${language}, framework=${framework}, buildTool=${buildTool}`);
370
+
371
+ // Java (qualquer framework ou sem framework)
372
+ if (language === "java") {
373
+ return await this.getJavaCommand(buildTool, cwd, profile);
374
+ }
375
+
376
+ // Node.js
377
+ if (language === "node") {
378
+ return await this.getNodeCommand(framework, cwd);
379
+ }
380
+
381
+ // Python
382
+ if (language === "python") {
383
+ return await this.getPythonCommand(framework, cwd);
384
+ }
385
+
386
+ // .NET
387
+ if (language === "dotnet" || language === ".net") {
388
+ return await this.getDotNetCommand(cwd);
389
+ }
390
+
391
+ // Go
392
+ if (language === "go") {
393
+ return await this.getGoCommand(cwd);
394
+ }
395
+
396
+ // Fallback genérico - tenta npm start
397
+ console.warn(`⚠️ Unknown language: ${language}, trying npm start`);
398
+ return { cmd: "npm", args: ["start"] };
399
+ }
400
+
401
+ /**
402
+ * Comando Java/Spring Boot
403
+ */
404
+ async getJavaCommand(buildTool, cwd, profile = null) {
405
+ const targetDir = path.join(cwd, "target");
406
+ const buildDir = path.join(cwd, "build", "libs");
407
+
408
+ console.log(`🔍 Checking for compiled JARs in: ${cwd}`);
409
+
410
+ // ✅ Helper: Build args with optional profile
411
+ const buildJavaArgs = (jarPath) => {
412
+ const javaArgs = [];
413
+ // Only add profile if explicitly provided (not null/undefined/'default')
414
+ if (profile && profile !== 'default') {
415
+ javaArgs.push(`-Dspring.profiles.active=${profile}`);
416
+ console.log(` 📋 Using Spring profile: ${profile}`);
417
+ } else {
418
+ // ✅ Check if there's a local/dev profile available
419
+ const resourcesDir = path.join(cwd, 'src/main/resources');
420
+ const localProfile = ['local', 'dev'].find(p => {
421
+ return fs.existsSync(path.join(resourcesDir, `application-${p}.yml`)) ||
422
+ fs.existsSync(path.join(resourcesDir, `application-${p}.yaml`)) ||
423
+ fs.existsSync(path.join(resourcesDir, `application-${p}.properties`));
424
+ });
425
+ if (localProfile) {
426
+ javaArgs.push(`-Dspring.profiles.active=${localProfile}`);
427
+ console.log(` 📋 Auto-detected profile: ${localProfile}`);
428
+ } else {
429
+ console.log(` 📋 No profile set — using application.yml defaults`);
430
+ }
431
+ }
432
+ javaArgs.push("-jar", jarPath);
433
+ return javaArgs;
434
+ };
435
+
436
+ // Maven: Procurar JAR em target/
437
+ if (buildTool === "maven") {
438
+ console.log(`📁 Target directory: ${targetDir} (exists: ${fs.existsSync(targetDir)})`);
439
+
440
+ if (fs.existsSync(targetDir)) {
441
+ try {
442
+ const allFiles = fs.readdirSync(targetDir);
443
+ console.log(`📦 Files in target/: ${allFiles.join(", ")}`);
444
+
445
+ const jars = allFiles
446
+ .filter(f => {
447
+ return f.endsWith(".jar") &&
448
+ !f.includes("original") &&
449
+ !f.includes("sources") &&
450
+ !f.includes("javadoc");
451
+ })
452
+ .sort((a, b) => {
453
+ const statsA = fs.statSync(path.join(targetDir, a));
454
+ const statsB = fs.statSync(path.join(targetDir, b));
455
+ return statsB.mtimeMs - statsA.mtimeMs;
456
+ });
457
+
458
+ console.log(`🎯 Filtered JARs: ${jars.join(", ")}`);
459
+
460
+ if (jars.length > 0) {
461
+ const jarPath = path.join(targetDir, jars[0]);
462
+ console.log(`✅ Found compiled JAR: ${jars[0]}`);
463
+ return {
464
+ cmd: "java",
465
+ args: buildJavaArgs(jarPath)
466
+ };
467
+ }
468
+ } catch (err) {
469
+ console.error(`❌ Error reading target: ${err.message}`);
470
+ }
471
+ }
472
+
473
+ // Sem JAR - pedir para compilar
474
+ throw new Error(`No JAR found. Please compile first: mvn clean install -DskipTests`);
475
+ }
476
+
477
+ // Gradle: Procurar JAR em build/libs/
478
+ if (buildTool === "gradle") {
479
+ console.log(`📁 Build directory: ${buildDir} (exists: ${fs.existsSync(buildDir)})`);
480
+
481
+ if (fs.existsSync(buildDir)) {
482
+ try {
483
+ const allFiles = fs.readdirSync(buildDir);
484
+ const jars = allFiles
485
+ .filter(f => f.endsWith(".jar") && !f.includes("plain") && !f.includes("sources"))
486
+ .sort((a, b) => {
487
+ const statsA = fs.statSync(path.join(buildDir, a));
488
+ const statsB = fs.statSync(path.join(buildDir, b));
489
+ return statsB.mtimeMs - statsA.mtimeMs;
490
+ });
491
+
492
+ if (jars.length > 0) {
493
+ const jarPath = path.join(buildDir, jars[0]);
494
+ console.log(`✅ Found compiled JAR: ${jars[0]}`);
495
+ return {
496
+ cmd: "java",
497
+ args: buildJavaArgs(jarPath)
498
+ };
499
+ }
500
+ } catch (err) {
501
+ console.error(`❌ Error reading build/libs: ${err.message}`);
502
+ }
503
+ }
504
+
505
+ throw new Error(`No JAR found. Please compile first: ./gradlew clean build -x test`);
506
+ }
507
+
508
+ throw new Error(`Unknown Java build tool: ${buildTool}`);
509
+ }
510
+
511
+ /**
512
+ * Comando Node.js
513
+ */
514
+ async getNodeCommand(framework, cwd) {
515
+ const nodeModules = path.join(cwd, "node_modules");
516
+ const hasNodeModules = fs.existsSync(nodeModules);
517
+
518
+ if (!hasNodeModules) {
519
+ console.log("⚠️ node_modules not found, will install first");
520
+ if (framework === "next") {
521
+ return { cmd: "sh", args: ["-c", "npm install && npm run dev"] };
522
+ }
523
+ return { cmd: "sh", args: ["-c", "npm install && npm start"] };
524
+ }
525
+
526
+ if (framework === "next") {
527
+ return { cmd: "npm", args: ["run", "dev"] };
528
+ }
529
+ return { cmd: "npm", args: ["start"] };
530
+ }
531
+
532
+ /**
533
+ * Comando Python
534
+ */
535
+ async getPythonCommand(framework, cwd) {
536
+ if (framework === "django") {
537
+ return { cmd: "python", args: ["manage.py", "runserver"] };
538
+ }
539
+ if (framework === "flask") {
540
+ return { cmd: "flask", args: ["run"] };
541
+ }
542
+ if (framework === "fastapi") {
543
+ return { cmd: "uvicorn", args: ["main:app", "--reload"] };
544
+ }
545
+ return { cmd: "python", args: ["main.py"] };
546
+ }
547
+
548
+ /**
549
+ * Comando .NET
550
+ */
551
+ async getDotNetCommand(cwd) {
552
+ return { cmd: "dotnet", args: ["run"] };
553
+ }
554
+
555
+ /**
556
+ * Comando Go
557
+ */
558
+ async getGoCommand(cwd) {
559
+ return { cmd: "go", args: ["run", "."] };
560
+ }
561
+
562
+ /**
563
+ * Detecta quando serviço está pronto
564
+ */
565
+ isServiceReady(log, language) {
566
+ if (!log) return false;
567
+
568
+ const patterns = [
569
+ // Spring Boot
570
+ /started.*in \d+(\.\d+)? seconds/i,
571
+ /tomcat started on port/i,
572
+ /started \w+application/i,
573
+ /jvm running for/i,
574
+ // Node.js
575
+ /listening on port/i,
576
+ /server running/i,
577
+ /ready on/i,
578
+ /started on/i,
579
+ // Python
580
+ /running on http/i,
581
+ /uvicorn running/i,
582
+ /development server/i,
583
+ // .NET
584
+ /now listening on/i,
585
+ // Generic
586
+ /server started/i,
587
+ /application started/i
588
+ ];
589
+
590
+ return patterns.some(pattern => pattern.test(log));
591
+ }
592
+
593
+ /**
594
+ * Monitora recursos (CPU e Memory)
595
+ */
596
+ startResourceMonitoring(serviceId) {
597
+ const interval = setInterval(async () => {
598
+ const processData = this.processes.get(serviceId);
599
+ if (!processData) {
600
+ clearInterval(interval);
601
+ return;
602
+ }
603
+
604
+ try {
605
+ const pidusage = await import('pidusage').then(m => m.default).catch(() => null);
606
+ if (pidusage && processData.pid) {
607
+ const stats = await pidusage(processData.pid);
608
+ processData.cpu = parseFloat(stats.cpu.toFixed(1));
609
+ processData.memory = Math.floor(stats.memory / 1024 / 1024);
610
+ }
611
+ } catch {
612
+ processData.cpu = 0;
613
+ processData.memory = 0;
614
+ }
615
+ }, 5000);
616
+ }
617
+ }
618
+
619
+ export default ProcessManager;
@@ -0,0 +1,72 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ export class WorkspaceScanner {
5
+ constructor(rootPath) {
6
+ this.rootPath = rootPath;
7
+ this.ignorePatterns = [
8
+ 'node_modules', 'target', 'build', 'dist',
9
+ '.git', '.idea', '.vscode', '__pycache__',
10
+ '.next', 'out', 'coverage', '.gradle'
11
+ ];
12
+ }
13
+
14
+ async scan() {
15
+ const structure = {
16
+ root: this.rootPath,
17
+ files: [],
18
+ directories: [],
19
+ metadata: {
20
+ totalFiles: 0,
21
+ totalSize: 0,
22
+ languages: new Set(),
23
+ frameworks: new Set()
24
+ }
25
+ };
26
+
27
+ await this.walkDirectory(this.rootPath, structure);
28
+
29
+ // Convert Set to Array for JSON serialization
30
+ structure.metadata.languages = Array.from(structure.metadata.languages);
31
+ structure.metadata.frameworks = Array.from(structure.metadata.frameworks);
32
+
33
+ return structure;
34
+ }
35
+
36
+ async walkDirectory(dir, structure, depth = 0) {
37
+ if (depth > 10) return; // Limite de profundidade
38
+
39
+ try {
40
+ const entries = await fs.readdir(dir, { withFileTypes: true });
41
+
42
+ for (const entry of entries) {
43
+ if (this.shouldIgnore(entry.name)) continue;
44
+
45
+ const fullPath = path.join(dir, entry.name);
46
+ const relativePath = path.relative(this.rootPath, fullPath);
47
+
48
+ if (entry.isDirectory()) {
49
+ structure.directories.push(relativePath);
50
+ await this.walkDirectory(fullPath, structure, depth + 1);
51
+ } else {
52
+ const stat = await fs.stat(fullPath);
53
+ structure.files.push({
54
+ path: relativePath,
55
+ name: entry.name,
56
+ extension: path.extname(entry.name),
57
+ size: stat.size,
58
+ modified: stat.mtime
59
+ });
60
+ structure.metadata.totalFiles++;
61
+ structure.metadata.totalSize += stat.size;
62
+ }
63
+ }
64
+ } catch (err) {
65
+ console.error(`Error reading directory ${dir}:`, err.message);
66
+ }
67
+ }
68
+
69
+ shouldIgnore(name) {
70
+ return this.ignorePatterns.some(pattern => name.includes(pattern));
71
+ }
72
+ }