ai-first-cli 1.3.5 → 1.3.6
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/CHANGELOG.md +63 -0
- package/README.es.md +55 -0
- package/README.md +40 -15
- package/ai/graph/knowledge-graph.json +1 -1
- package/dist/analyzers/architecture.d.ts.map +1 -1
- package/dist/analyzers/architecture.js +72 -5
- package/dist/analyzers/architecture.js.map +1 -1
- package/dist/analyzers/entrypoints.d.ts.map +1 -1
- package/dist/analyzers/entrypoints.js +253 -0
- package/dist/analyzers/entrypoints.js.map +1 -1
- package/dist/analyzers/symbols.d.ts.map +1 -1
- package/dist/analyzers/symbols.js +47 -2
- package/dist/analyzers/symbols.js.map +1 -1
- package/dist/analyzers/techStack.d.ts.map +1 -1
- package/dist/analyzers/techStack.js +43 -0
- package/dist/analyzers/techStack.js.map +1 -1
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +5 -0
- package/dist/utils/fileUtils.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzers/architecture.ts +75 -6
- package/src/analyzers/entrypoints.ts +285 -0
- package/src/analyzers/symbols.ts +52 -2
- package/src/analyzers/techStack.ts +44 -0
- package/src/utils/fileUtils.ts +5 -0
- package/tests/entrypoints-languages.test.ts +373 -0
- package/tests/framework-detection.test.ts +296 -0
- package/BETA_EVALUATION_REPORT.md +0 -151
- package/ai-context/context/flows/App.json +0 -17
- package/ai-context/context/flows/DashboardPage.json +0 -14
- package/ai-context/context/flows/LoginPage.json +0 -14
- package/ai-context/context/flows/admin.json +0 -10
- package/ai-context/context/flows/androidresources.json +0 -11
- package/ai-context/context/flows/authController.json +0 -14
- package/ai-context/context/flows/entrypoints.json +0 -9
- package/ai-context/context/flows/fastapiAdapter.json +0 -14
- package/ai-context/context/flows/fastapiadapter.json +0 -11
- package/ai-context/context/flows/index.json +0 -19
- package/ai-context/context/flows/indexer.json +0 -9
- package/ai-context/context/flows/indexstate.json +0 -9
- package/ai-context/context/flows/init.json +0 -22
- package/ai-context/context/flows/main.json +0 -18
- package/ai-context/context/flows/mainactivity.json +0 -9
- package/ai-context/context/flows/models.json +0 -15
- package/ai-context/context/flows/posts.json +0 -15
- package/ai-context/context/flows/repoMapper.json +0 -20
- package/ai-context/context/flows/repomapper.json +0 -11
- package/ai-context/context/flows/serializers.json +0 -10
- package/dist/scripts/ai-context-evaluator.js +0 -367
- package/quick-evaluation-report-1774396002305.md +0 -64
- package/quick-evaluator.ts +0 -200
|
@@ -96,6 +96,33 @@ export function discoverEntrypoints(files: FileInfo[], rootDir: string): Entrypo
|
|
|
96
96
|
} catch {}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Detect Go entrypoints
|
|
100
|
+
const goFiles = files.filter(f => f.extension === "go");
|
|
101
|
+
if (goFiles.length > 0) {
|
|
102
|
+
try {
|
|
103
|
+
const goEntrypoints = discoverGoEntrypoints(goFiles, rootDir);
|
|
104
|
+
entrypoints.push(...goEntrypoints);
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Detect Rust entrypoints
|
|
109
|
+
const rustFiles = files.filter(f => f.extension === "rs");
|
|
110
|
+
if (rustFiles.length > 0) {
|
|
111
|
+
try {
|
|
112
|
+
const rustEntrypoints = discoverRustEntrypoints(rustFiles, rootDir);
|
|
113
|
+
entrypoints.push(...rustEntrypoints);
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Detect PHP entrypoints
|
|
118
|
+
const phpFiles = files.filter(f => f.extension === "php");
|
|
119
|
+
if (phpFiles.length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
const phpEntrypoints = discoverPHPEntrypoints(phpFiles, rootDir);
|
|
122
|
+
entrypoints.push(...phpEntrypoints);
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
|
|
99
126
|
return entrypoints;
|
|
100
127
|
}
|
|
101
128
|
|
|
@@ -276,6 +303,264 @@ function getScriptType(name: string): Entrypoint["type"] | null {
|
|
|
276
303
|
return null;
|
|
277
304
|
}
|
|
278
305
|
|
|
306
|
+
function discoverGoEntrypoints(goFiles: FileInfo[], rootDir: string): Entrypoint[] {
|
|
307
|
+
const entrypoints: Entrypoint[] = [];
|
|
308
|
+
|
|
309
|
+
for (const file of goFiles) {
|
|
310
|
+
try {
|
|
311
|
+
const content = readFile(path.join(rootDir, file.relativePath));
|
|
312
|
+
const fileName = file.name;
|
|
313
|
+
|
|
314
|
+
if (fileName === "main.go") {
|
|
315
|
+
const hasMain = content.match(/func\s+main\s*\(\s*\)/);
|
|
316
|
+
const packageMatch = content.match(/package\s+(\w+)/);
|
|
317
|
+
const packageName = packageMatch ? packageMatch[1] : "main";
|
|
318
|
+
|
|
319
|
+
const handlers: string[] = [];
|
|
320
|
+
const handlerMatches = content.matchAll(/http\.HandleFunc\s*\(\s*["']([^"']+)["']/g);
|
|
321
|
+
for (const match of handlerMatches) {
|
|
322
|
+
handlers.push(match[1]);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const portMatches = content.matchAll(/:\s*(\d{2,5})/g);
|
|
326
|
+
const ports: string[] = [];
|
|
327
|
+
for (const match of portMatches) {
|
|
328
|
+
ports.push(match[1]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let description = `Go main package (${packageName})`;
|
|
332
|
+
if (handlers.length > 0) {
|
|
333
|
+
description += ` with HTTP handlers: ${handlers.join(", ")}`;
|
|
334
|
+
}
|
|
335
|
+
if (ports.length > 0) {
|
|
336
|
+
description += ` on port${ports.length > 1 ? "s" : ""} :${ports.join(", :")}`;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
entrypoints.push({
|
|
340
|
+
name: "main.go",
|
|
341
|
+
path: file.relativePath,
|
|
342
|
+
type: hasMain ? "server" : "library",
|
|
343
|
+
description,
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
const structMatches = content.matchAll(/type\s+(\w+)\s+struct/g);
|
|
347
|
+
const structs: string[] = [];
|
|
348
|
+
for (const match of structMatches) {
|
|
349
|
+
structs.push(match[1]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const methodMatches = content.matchAll(/func\s*\(?\s*\*?\s*(\w+)\s*\)?\s*(\w+)\s*\(/g);
|
|
353
|
+
const methods: string[] = [];
|
|
354
|
+
for (const match of methodMatches) {
|
|
355
|
+
methods.push(match[2]);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (structs.length > 0 || methods.length > 0) {
|
|
359
|
+
let description = "Go module";
|
|
360
|
+
if (structs.length > 0) {
|
|
361
|
+
description += ` with structs: ${structs.slice(0, 3).join(", ")}`;
|
|
362
|
+
}
|
|
363
|
+
if (methods.length > 0) {
|
|
364
|
+
description += `, methods: ${methods.slice(0, 3).join(", ")}`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
entrypoints.push({
|
|
368
|
+
name: fileName,
|
|
369
|
+
path: file.relativePath,
|
|
370
|
+
type: "library",
|
|
371
|
+
description,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} catch {}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const goModPath = path.join(rootDir, "go.mod");
|
|
379
|
+
try {
|
|
380
|
+
const goMod = readFile(goModPath);
|
|
381
|
+
const moduleMatch = goMod.match(/module\s+(\S+)/);
|
|
382
|
+
if (moduleMatch) {
|
|
383
|
+
entrypoints.push({
|
|
384
|
+
name: "go.mod",
|
|
385
|
+
path: "go.mod",
|
|
386
|
+
type: "config",
|
|
387
|
+
description: `Go module: ${moduleMatch[1]}`,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
} catch {}
|
|
391
|
+
|
|
392
|
+
return entrypoints;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function discoverRustEntrypoints(rustFiles: FileInfo[], rootDir: string): Entrypoint[] {
|
|
396
|
+
const entrypoints: Entrypoint[] = [];
|
|
397
|
+
|
|
398
|
+
for (const file of rustFiles) {
|
|
399
|
+
try {
|
|
400
|
+
const content = readFile(path.join(rootDir, file.relativePath));
|
|
401
|
+
const fileName = file.name;
|
|
402
|
+
|
|
403
|
+
if (fileName === "main.rs") {
|
|
404
|
+
const hasMain = content.match(/fn\s+main\s*\(\s*\)/);
|
|
405
|
+
|
|
406
|
+
const structMatches = content.matchAll(/struct\s+(\w+)/g);
|
|
407
|
+
const structs: string[] = [];
|
|
408
|
+
for (const match of structMatches) {
|
|
409
|
+
structs.push(match[1]);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const implMatches = content.matchAll(/impl\s+(?:\w+\s+for\s+)?(\w+)/g);
|
|
413
|
+
const implementations: string[] = [];
|
|
414
|
+
for (const match of implMatches) {
|
|
415
|
+
implementations.push(match[1]);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
let description = "Rust main";
|
|
419
|
+
if (structs.length > 0) {
|
|
420
|
+
description += ` with structs: ${structs.slice(0, 3).join(", ")}`;
|
|
421
|
+
}
|
|
422
|
+
if (implementations.length > 0) {
|
|
423
|
+
description += `, implementations: ${implementations.slice(0, 3).join(", ")}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
entrypoints.push({
|
|
427
|
+
name: "main.rs",
|
|
428
|
+
path: file.relativePath,
|
|
429
|
+
type: hasMain ? "cli" : "library",
|
|
430
|
+
description,
|
|
431
|
+
});
|
|
432
|
+
} else if (fileName === "lib.rs") {
|
|
433
|
+
const pubFnMatches = content.matchAll(/pub\s+fn\s+(\w+)/g);
|
|
434
|
+
const publicFns: string[] = [];
|
|
435
|
+
for (const match of pubFnMatches) {
|
|
436
|
+
publicFns.push(match[1]);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let description = "Rust library";
|
|
440
|
+
if (publicFns.length > 0) {
|
|
441
|
+
description += ` with public functions: ${publicFns.slice(0, 3).join(", ")}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
entrypoints.push({
|
|
445
|
+
name: "lib.rs",
|
|
446
|
+
path: file.relativePath,
|
|
447
|
+
type: "library",
|
|
448
|
+
description,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
} catch {}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const cargoPath = path.join(rootDir, "Cargo.toml");
|
|
455
|
+
try {
|
|
456
|
+
const cargoContent = readFile(cargoPath);
|
|
457
|
+
const nameMatch = cargoContent.match(/name\s*=\s*"([^"]+)"/);
|
|
458
|
+
const versionMatch = cargoContent.match(/version\s*=\s*"([^"]+)"/);
|
|
459
|
+
|
|
460
|
+
let description = "Rust project";
|
|
461
|
+
if (nameMatch) {
|
|
462
|
+
description += `: ${nameMatch[1]}`;
|
|
463
|
+
}
|
|
464
|
+
if (versionMatch) {
|
|
465
|
+
description += ` v${versionMatch[1]}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const binMatch = cargoContent.match(/\[\[bin\]\]/);
|
|
469
|
+
if (binMatch) {
|
|
470
|
+
description += " (has binaries)";
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
entrypoints.push({
|
|
474
|
+
name: "Cargo.toml",
|
|
475
|
+
path: "Cargo.toml",
|
|
476
|
+
type: "config",
|
|
477
|
+
description,
|
|
478
|
+
});
|
|
479
|
+
} catch {}
|
|
480
|
+
|
|
481
|
+
return entrypoints;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function discoverPHPEntrypoints(phpFiles: FileInfo[], rootDir: string): Entrypoint[] {
|
|
485
|
+
const entrypoints: Entrypoint[] = [];
|
|
486
|
+
|
|
487
|
+
const hasIndexPhp = phpFiles.some(f => f.name === "index.php");
|
|
488
|
+
const hasPublicIndex = phpFiles.some(f => f.relativePath.includes("public/index.php"));
|
|
489
|
+
|
|
490
|
+
if (hasIndexPhp) {
|
|
491
|
+
const indexFile = phpFiles.find(f => f.name === "index.php")!;
|
|
492
|
+
try {
|
|
493
|
+
const content = readFile(path.join(rootDir, indexFile.relativePath));
|
|
494
|
+
|
|
495
|
+
const classMatches = content.matchAll(/class\s+(\w+)/g);
|
|
496
|
+
const classes: string[] = [];
|
|
497
|
+
for (const match of classMatches) {
|
|
498
|
+
classes.push(match[1]);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const routes: string[] = [];
|
|
502
|
+
|
|
503
|
+
// Match $router->get('/path') or $app->post('/path') patterns
|
|
504
|
+
const httpMethodMatches = content.matchAll(/->\s*(?:get|post|put|delete|patch)\s*\(\s*["']([^"']+)["']/g);
|
|
505
|
+
for (const match of httpMethodMatches) {
|
|
506
|
+
routes.push(match[1]);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Match $router->add('METHOD', '/path') patterns (second argument is the path)
|
|
510
|
+
const addMethodMatches = content.matchAll(/->\s*add\s*\(\s*["'][^"']+["']\s*,\s*["']([^"']+)["']/g);
|
|
511
|
+
for (const match of addMethodMatches) {
|
|
512
|
+
routes.push(match[1]);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let description = "PHP entry point";
|
|
516
|
+
if (classes.length > 0) {
|
|
517
|
+
description += ` with classes: ${classes.slice(0, 3).join(", ")}`;
|
|
518
|
+
}
|
|
519
|
+
if (routes.length > 0) {
|
|
520
|
+
description += `, routes: ${routes.slice(0, 3).join(", ")}`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
entrypoints.push({
|
|
524
|
+
name: "index.php",
|
|
525
|
+
path: indexFile.relativePath,
|
|
526
|
+
type: hasPublicIndex ? "server" : "api",
|
|
527
|
+
description,
|
|
528
|
+
});
|
|
529
|
+
} catch {}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const composerPath = path.join(rootDir, "composer.json");
|
|
533
|
+
try {
|
|
534
|
+
const composer = readJsonFile(composerPath) as { name?: string; description?: string; require?: Record<string, string> };
|
|
535
|
+
|
|
536
|
+
let description = "PHP project";
|
|
537
|
+
if (composer.name) {
|
|
538
|
+
description += `: ${composer.name}`;
|
|
539
|
+
}
|
|
540
|
+
if (composer.description) {
|
|
541
|
+
description += ` - ${composer.description}`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const hasLaravel = composer.require && (composer.require["laravel/framework"] || composer.require["illuminate/support"]);
|
|
545
|
+
const hasSymfony = composer.require && (composer.require["symfony/framework-bundle"] || composer.require["symfony/symfony"]);
|
|
546
|
+
|
|
547
|
+
if (hasLaravel) {
|
|
548
|
+
description += " (Laravel)";
|
|
549
|
+
} else if (hasSymfony) {
|
|
550
|
+
description += " (Symfony)";
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
entrypoints.push({
|
|
554
|
+
name: "composer.json",
|
|
555
|
+
path: "composer.json",
|
|
556
|
+
type: "config",
|
|
557
|
+
description,
|
|
558
|
+
});
|
|
559
|
+
} catch {}
|
|
560
|
+
|
|
561
|
+
return entrypoints;
|
|
562
|
+
}
|
|
563
|
+
|
|
279
564
|
export function generateEntrypointsFile(entrypoints: Entrypoint[]): string {
|
|
280
565
|
const grouped = new Map<string, Entrypoint[]>();
|
|
281
566
|
for (const ep of entrypoints) {
|
package/src/analyzers/symbols.ts
CHANGED
|
@@ -533,8 +533,24 @@ function parseSwift(file: FileInfo, content: string, lines: string[], symbols: S
|
|
|
533
533
|
* Parse Apex (Salesforce) files
|
|
534
534
|
*/
|
|
535
535
|
function parseApex(file: FileInfo, content: string, lines: string[], symbols: Symbol[]): void {
|
|
536
|
+
// Track annotations across lines
|
|
537
|
+
let pendingAnnotations: string[] = [];
|
|
538
|
+
|
|
536
539
|
for (let i = 0; i < lines.length; i++) {
|
|
537
540
|
const line = lines[i].trim();
|
|
541
|
+
|
|
542
|
+
// Skip empty lines
|
|
543
|
+
if (!line) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Collect annotations (@AuraEnabled, @IsTest, etc.) - handles both single-line and multi-line
|
|
548
|
+
// Match patterns like @AuraEnabled, @AuraEnabled(cacheable=true), @IsTest
|
|
549
|
+
const annotationMatch = line.match(/^@(\w+)(?:\s*\([^)]*\))?\s*$/);
|
|
550
|
+
if (annotationMatch) {
|
|
551
|
+
pendingAnnotations.push(annotationMatch[1]);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
538
554
|
|
|
539
555
|
// Classes: public with sharing class ClassName, public class ClassName, etc.
|
|
540
556
|
const classMatch = line.match(/^(?:\s*(?:public|private|global)(?:\s+(?:with|without|inherited)\s+sharing)?\s+)?class\s+(\w+)/);
|
|
@@ -547,6 +563,8 @@ function parseApex(file: FileInfo, content: string, lines: string[], symbols: Sy
|
|
|
547
563
|
line: i + 1,
|
|
548
564
|
export: true,
|
|
549
565
|
});
|
|
566
|
+
pendingAnnotations = [];
|
|
567
|
+
continue;
|
|
550
568
|
}
|
|
551
569
|
|
|
552
570
|
// Interfaces
|
|
@@ -560,11 +578,16 @@ function parseApex(file: FileInfo, content: string, lines: string[], symbols: Sy
|
|
|
560
578
|
line: i + 1,
|
|
561
579
|
export: true,
|
|
562
580
|
});
|
|
581
|
+
pendingAnnotations = [];
|
|
582
|
+
continue;
|
|
563
583
|
}
|
|
564
584
|
|
|
565
585
|
// Methods: public static ReturnType methodName(
|
|
566
586
|
// Also handles @AuraEnabled public static ReturnType methodName(
|
|
567
|
-
|
|
587
|
+
// Also handles @AuraEnabled(cacheable=true) on separate line
|
|
588
|
+
// Handles generic return types like List<Account>, Map<String, Object>
|
|
589
|
+
// Also handles webservice methods
|
|
590
|
+
const methodMatch = line.match(/^(?:@\w+(?:\s*\([^)]*\))?\s+)?(?:public|private|protected|global|webservice)\s+(?:static\s+)?(?:[\w<>,\s]+?)\s+(\w+)\s*\(/);
|
|
568
591
|
if (methodMatch && !["if", "for", "while", "switch"].includes(methodMatch[1])) {
|
|
569
592
|
symbols.push({
|
|
570
593
|
id: generateSymbolId(file.relativePath, methodMatch[1]),
|
|
@@ -572,8 +595,28 @@ function parseApex(file: FileInfo, content: string, lines: string[], symbols: Sy
|
|
|
572
595
|
type: "function",
|
|
573
596
|
file: file.relativePath,
|
|
574
597
|
line: i + 1,
|
|
575
|
-
export: line.includes("public") || line.includes("global"),
|
|
598
|
+
export: line.includes("public") || line.includes("global") || line.includes("webservice"),
|
|
576
599
|
});
|
|
600
|
+
pendingAnnotations = [];
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Alternative: Method with annotations on previous lines
|
|
605
|
+
// Check if we have pending annotations and current line looks like a method
|
|
606
|
+
if (pendingAnnotations.length > 0) {
|
|
607
|
+
const methodWithAnnotationMatch = line.match(/^(?:public|private|protected|global|webservice)\s+(?:static\s+)?(?:[\w<>,\s]+?)\s+(\w+)\s*\(/);
|
|
608
|
+
if (methodWithAnnotationMatch && !["if", "for", "while", "switch"].includes(methodWithAnnotationMatch[1])) {
|
|
609
|
+
symbols.push({
|
|
610
|
+
id: generateSymbolId(file.relativePath, methodWithAnnotationMatch[1]),
|
|
611
|
+
name: methodWithAnnotationMatch[1],
|
|
612
|
+
type: "function",
|
|
613
|
+
file: file.relativePath,
|
|
614
|
+
line: i + 1,
|
|
615
|
+
export: line.includes("public") || line.includes("global") || line.includes("webservice"),
|
|
616
|
+
});
|
|
617
|
+
pendingAnnotations = [];
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
577
620
|
}
|
|
578
621
|
|
|
579
622
|
// Triggers: trigger TriggerName on ObjectName
|
|
@@ -587,6 +630,13 @@ function parseApex(file: FileInfo, content: string, lines: string[], symbols: Sy
|
|
|
587
630
|
line: i + 1,
|
|
588
631
|
export: true,
|
|
589
632
|
});
|
|
633
|
+
pendingAnnotations = [];
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Reset pending annotations if we encounter non-annotation, non-method line
|
|
638
|
+
if (!line.startsWith("@")) {
|
|
639
|
+
pendingAnnotations = [];
|
|
590
640
|
}
|
|
591
641
|
}
|
|
592
642
|
}
|
|
@@ -134,6 +134,18 @@ function detectFrameworks(files: FileInfo[], fileNames: Set<string>, rootDir: st
|
|
|
134
134
|
frameworks.push(...names);
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
// Check for @nestjs/* packages (NestJS framework)
|
|
139
|
+
const hasNestJs = Object.keys(deps).some(dep => dep.startsWith("@nestjs/"));
|
|
140
|
+
if (hasNestJs && !frameworks.includes("NestJS")) {
|
|
141
|
+
frameworks.push("NestJS");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check for Spring Boot in dependencies
|
|
145
|
+
const hasSpringBoot = Object.keys(deps).some(dep => dep.startsWith("@spring.io/") || dep.includes("spring-boot"));
|
|
146
|
+
if (hasSpringBoot && !frameworks.includes("Spring Boot")) {
|
|
147
|
+
frameworks.push("Spring Boot");
|
|
148
|
+
}
|
|
137
149
|
} catch {}
|
|
138
150
|
|
|
139
151
|
const frameworkIndicators: Record<string, string> = {
|
|
@@ -165,6 +177,38 @@ function detectFrameworks(files: FileInfo[], fileNames: Set<string>, rootDir: st
|
|
|
165
177
|
}
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
// Detect Spring Boot from pom.xml
|
|
181
|
+
try {
|
|
182
|
+
const pomPath = path.join(rootDir, "pom.xml");
|
|
183
|
+
if (fs.existsSync(pomPath)) {
|
|
184
|
+
const pomContent = readFile(pomPath);
|
|
185
|
+
if (pomContent.includes("spring-boot") && !frameworks.includes("Spring Boot")) {
|
|
186
|
+
frameworks.push("Spring Boot");
|
|
187
|
+
}
|
|
188
|
+
if (pomContent.includes("spring-boot-starter-parent") && !frameworks.includes("Spring Boot")) {
|
|
189
|
+
frameworks.push("Spring Boot");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch {}
|
|
193
|
+
|
|
194
|
+
// Detect Spring Boot from build.gradle
|
|
195
|
+
try {
|
|
196
|
+
const gradlePath = path.join(rootDir, "build.gradle");
|
|
197
|
+
const gradleKtsPath = path.join(rootDir, "build.gradle.kts");
|
|
198
|
+
let gradleContent = "";
|
|
199
|
+
if (fs.existsSync(gradlePath)) {
|
|
200
|
+
gradleContent = readFile(gradlePath);
|
|
201
|
+
} else if (fs.existsSync(gradleKtsPath)) {
|
|
202
|
+
gradleContent = readFile(gradleKtsPath);
|
|
203
|
+
}
|
|
204
|
+
if (gradleContent.includes("spring-boot") && !frameworks.includes("Spring Boot")) {
|
|
205
|
+
frameworks.push("Spring Boot");
|
|
206
|
+
}
|
|
207
|
+
if (gradleContent.includes("org.springframework.boot") && !frameworks.includes("Spring Boot")) {
|
|
208
|
+
frameworks.push("Spring Boot");
|
|
209
|
+
}
|
|
210
|
+
} catch {}
|
|
211
|
+
|
|
168
212
|
// Detect SwiftUI from Swift files
|
|
169
213
|
const swiftFiles = files.filter(f => f.extension === "swift");
|
|
170
214
|
for (const swiftFile of swiftFiles) {
|