botversion-sdk 1.0.3 → 1.0.5
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/bin/init.js +184 -135
- package/cli/detector.js +360 -173
- package/cli/generator.js +18 -476
- package/cli/prompts.js +40 -42
- package/cli/writer.js +118 -42
- package/client.js +214 -36
- package/index.js +74 -391
- package/interceptor.js +112 -124
- package/package.json +3 -2
- package/scanner.js +792 -27
package/cli/detector.js
CHANGED
|
@@ -356,7 +356,7 @@ function detectMonorepo(cwd) {
|
|
|
356
356
|
|
|
357
357
|
// ─── FRAMEWORK DETECTION ─────────────────────────────────────────────────────
|
|
358
358
|
|
|
359
|
-
const SUPPORTED_FRAMEWORKS = ["
|
|
359
|
+
const SUPPORTED_FRAMEWORKS = ["next", "express"];
|
|
360
360
|
const UNSUPPORTED_FRAMEWORKS = [
|
|
361
361
|
"fastify",
|
|
362
362
|
"koa",
|
|
@@ -377,6 +377,10 @@ function detectFramework(pkg) {
|
|
|
377
377
|
if (deps[fw]) return { name: fw, supported: false };
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
// If BOTH next and express exist, always pick next
|
|
381
|
+
// because express is just being used as a custom server wrapper
|
|
382
|
+
if (deps["next"]) return { name: "next", supported: true };
|
|
383
|
+
|
|
380
384
|
for (const fw of SUPPORTED_FRAMEWORKS) {
|
|
381
385
|
if (deps[fw]) return { name: fw, supported: true };
|
|
382
386
|
}
|
|
@@ -462,61 +466,311 @@ function detectNextVersion(pkg) {
|
|
|
462
466
|
return { raw: version, major };
|
|
463
467
|
}
|
|
464
468
|
|
|
465
|
-
// ───
|
|
469
|
+
// ─── SCORING FUNCTIONS ────────────────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
function scoreExpressFile(content, filePath) {
|
|
472
|
+
let score = 0;
|
|
473
|
+
const filename = path.basename(filePath);
|
|
474
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
475
|
+
|
|
476
|
+
// ── EXISTING POSITIVE SIGNALS ──────────────────────────────────
|
|
477
|
+
if (/express\s*\(\s*\)/.test(content)) score += 10;
|
|
478
|
+
if (/class\s+\w+\s+extends\s+express/.test(content)) score += 10;
|
|
479
|
+
if (/(?:function|const|let|var)\s+(create|make|build|setup)App/.test(content))
|
|
480
|
+
score += 8;
|
|
481
|
+
if (/\.listen\s*\(/.test(content)) score += 8;
|
|
482
|
+
if (/\.use\s*\(\s*['"`\/]/.test(content)) score += 5;
|
|
483
|
+
if (/app\.use\s*\(/.test(content)) score += 4;
|
|
484
|
+
if (
|
|
485
|
+
/require\s*\(\s*['"]express['"]\s*\)|from\s+['"]express['"]/.test(content)
|
|
486
|
+
)
|
|
487
|
+
score += 1;
|
|
488
|
+
|
|
489
|
+
// ── NEW POSITIVE SIGNALS — real API server ─────────────────────
|
|
490
|
+
const hasApiRoutes = /\.(get|post|put|delete|patch)\s*\(\s*['"`]\//.test(
|
|
491
|
+
content,
|
|
492
|
+
);
|
|
493
|
+
const returnsJson = /res\.json\s*\(/.test(content);
|
|
494
|
+
const hasRouterFiles = /require\s*\(['"`]\.\/routes/.test(content);
|
|
495
|
+
const hasControllers = /require\s*\(['"`]\.\/controllers/.test(content);
|
|
496
|
+
|
|
497
|
+
if (hasApiRoutes) score += 6;
|
|
498
|
+
if (returnsJson) score += 5;
|
|
499
|
+
if (hasRouterFiles) score += 4;
|
|
500
|
+
if (hasControllers) score += 3;
|
|
501
|
+
|
|
502
|
+
// ── EXISTING PENALTIES ─────────────────────────────────────────
|
|
503
|
+
if (/^(?:const|let|var)\s+router\s*=\s*express\.Router\s*\(/m.test(content))
|
|
504
|
+
score -= 8;
|
|
505
|
+
if (/(test_|_test|\.test\.|\.spec\.|conftest)/.test(filename)) score -= 10;
|
|
506
|
+
if (/dist\/|build\//.test(filePath)) score -= 10;
|
|
507
|
+
|
|
508
|
+
// ── NEW PENALTIES ──────────────────────────────────────────────
|
|
509
|
+
|
|
510
|
+
// Category 1: PHP servers
|
|
511
|
+
if (content.includes("php-express")) score -= 20;
|
|
512
|
+
if (content.includes("phpExpress")) score -= 20;
|
|
513
|
+
if (/app\.engine\s*\(\s*['"]php['"]/.test(content)) score -= 20;
|
|
514
|
+
|
|
515
|
+
// Category 2: View-only servers (any template engine, no API)
|
|
516
|
+
const hasViewEngine = /app\.set\s*\(\s*['"]view engine['"]/.test(content);
|
|
517
|
+
if (hasViewEngine && !hasApiRoutes && !returnsJson) score -= 15;
|
|
518
|
+
|
|
519
|
+
// Category 3: Static-only servers
|
|
520
|
+
const hasStatic = /express\.static\s*\(/.test(content);
|
|
521
|
+
const hasOnlyStatic = hasStatic && !hasApiRoutes && !returnsJson;
|
|
522
|
+
if (hasOnlyStatic) score -= 15;
|
|
523
|
+
|
|
524
|
+
// Category 4: Proxy servers
|
|
525
|
+
if (
|
|
526
|
+
content.includes("http-proxy-middleware") ||
|
|
527
|
+
content.includes("createProxyMiddleware")
|
|
528
|
+
)
|
|
529
|
+
score -= 15;
|
|
530
|
+
|
|
531
|
+
// Category 5: WebSocket-only servers
|
|
532
|
+
const hasSocketIO =
|
|
533
|
+
content.includes("socket.io") || content.includes("new Server(");
|
|
534
|
+
if (hasSocketIO && !hasApiRoutes) score -= 15;
|
|
535
|
+
|
|
536
|
+
// Category 6: Job/Queue servers
|
|
537
|
+
if (
|
|
538
|
+
(content.includes("require('bull')") ||
|
|
539
|
+
content.includes('require("bull")') ||
|
|
540
|
+
content.includes("bullmq") ||
|
|
541
|
+
content.includes("agenda")) &&
|
|
542
|
+
!hasApiRoutes
|
|
543
|
+
)
|
|
544
|
+
score -= 15;
|
|
545
|
+
|
|
546
|
+
// Category 7: Webhook-only servers
|
|
547
|
+
const hasWebhook = /['"`]\/webhook['"`]/.test(content);
|
|
548
|
+
const onlyWebhook = hasWebhook && !hasApiRoutes;
|
|
549
|
+
if (onlyWebhook) score -= 10;
|
|
550
|
+
|
|
551
|
+
// Category 8: Dev tooling servers
|
|
552
|
+
if (
|
|
553
|
+
content.includes("webpack-dev-middleware") ||
|
|
554
|
+
content.includes("webpackDevMiddleware") ||
|
|
555
|
+
content.includes("storybook")
|
|
556
|
+
)
|
|
557
|
+
score -= 15;
|
|
558
|
+
|
|
559
|
+
// Category 9: Test/mock servers (path-based)
|
|
560
|
+
if (/__mocks__|fixtures\/|test\/|__tests__\//.test(normalizedPath))
|
|
561
|
+
score -= 15;
|
|
562
|
+
|
|
563
|
+
// ── FILENAME BONUSES ───────────────────────────────────────────
|
|
564
|
+
if (["server.js", "server.ts", "app.js", "app.ts"].includes(filename))
|
|
565
|
+
score += 3;
|
|
566
|
+
if (["index.js", "index.ts", "main.js", "main.ts"].includes(filename))
|
|
567
|
+
score += 2;
|
|
568
|
+
|
|
569
|
+
return score;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// ─── PARSE ENTRY FROM CONFIG FILES ───────────────────────────────────────────
|
|
573
|
+
|
|
574
|
+
function parseEntryFromConfigFiles(cwd) {
|
|
575
|
+
/**
|
|
576
|
+
* Extracts likely entry point from Procfile or Dockerfile.
|
|
577
|
+
* Covers patterns like:
|
|
578
|
+
* Procfile: web: node server.js
|
|
579
|
+
* Procfile: web: nodemon src/index.js
|
|
580
|
+
* Dockerfile: CMD ["node", "server.js"]
|
|
581
|
+
* Dockerfile: CMD ["nodemon", "src/index.js"]
|
|
582
|
+
*/
|
|
583
|
+
|
|
584
|
+
function resolveFile(cwd, filePath) {
|
|
585
|
+
const full = path.join(cwd, filePath);
|
|
586
|
+
if (fs.existsSync(full)) return full;
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// ── 1. Check Procfile ─────────────────────────────────────────────────────
|
|
591
|
+
const procfilePath = path.join(cwd, "Procfile");
|
|
592
|
+
if (fs.existsSync(procfilePath)) {
|
|
593
|
+
try {
|
|
594
|
+
const lines = fs.readFileSync(procfilePath, "utf8").split("\n");
|
|
595
|
+
for (const line of lines) {
|
|
596
|
+
if (!line.trim() || line.startsWith("#")) continue;
|
|
597
|
+
|
|
598
|
+
// node/nodemon/ts-node/tsx server.js or src/index.ts
|
|
599
|
+
const match = line.match(
|
|
600
|
+
/(?:node|nodemon|ts-node|ts-node-dev|tsx)\s+([\w./\\-]+\.[jt]s)/,
|
|
601
|
+
);
|
|
602
|
+
if (match) {
|
|
603
|
+
const result = resolveFile(cwd, match[1]);
|
|
604
|
+
if (result) return result;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} catch {}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ── 2. Check Dockerfile ───────────────────────────────────────────────────
|
|
611
|
+
const dockerfilePath = path.join(cwd, "Dockerfile");
|
|
612
|
+
if (fs.existsSync(dockerfilePath)) {
|
|
613
|
+
try {
|
|
614
|
+
const lines = fs.readFileSync(dockerfilePath, "utf8").split("\n");
|
|
615
|
+
for (const line of lines) {
|
|
616
|
+
if (!line.trim() || line.startsWith("#")) continue;
|
|
617
|
+
|
|
618
|
+
// CMD ["node", "server.js"]
|
|
619
|
+
// CMD ["nodemon", "src/index.js"]
|
|
620
|
+
const cmdMatch = line.match(
|
|
621
|
+
/(?:CMD|ENTRYPOINT)\s*\[.*?"(?:node|nodemon|ts-node|tsx)",\s*"([\w./\\-]+\.[jt]s)"/,
|
|
622
|
+
);
|
|
623
|
+
if (cmdMatch) {
|
|
624
|
+
const result = resolveFile(cwd, cmdMatch[1]);
|
|
625
|
+
if (result) return result;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// CMD node server.js (shell form)
|
|
629
|
+
const shellMatch = line.match(
|
|
630
|
+
/(?:CMD|ENTRYPOINT)\s+(?:node|nodemon|ts-node|tsx)\s+([\w./\\-]+\.[jt]s)/,
|
|
631
|
+
);
|
|
632
|
+
if (shellMatch) {
|
|
633
|
+
const result = resolveFile(cwd, shellMatch[1]);
|
|
634
|
+
if (result) return result;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} catch {}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
466
642
|
|
|
467
643
|
function detectExpressEntry(cwd, pkg) {
|
|
468
|
-
|
|
644
|
+
const SKIP_ENTRY_DIRS = ["dist", "build", "out", ".next", "coverage"];
|
|
645
|
+
|
|
646
|
+
function isCompiledPath(filePath) {
|
|
647
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
648
|
+
return SKIP_ENTRY_DIRS.some(
|
|
649
|
+
(dir) =>
|
|
650
|
+
normalized.includes(`/${dir}/`) ||
|
|
651
|
+
normalized.endsWith(`/${dir}`) ||
|
|
652
|
+
normalized.startsWith(`${dir}/`),
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// ── Strategy 1: package.json "main" field ─────────────────────────────────
|
|
469
657
|
if (pkg && pkg.main) {
|
|
470
658
|
const mainPath = path.join(cwd, pkg.main);
|
|
471
|
-
if (fs.existsSync(mainPath))
|
|
659
|
+
if (fs.existsSync(mainPath) && !isCompiledPath(pkg.main)) {
|
|
660
|
+
return mainPath;
|
|
661
|
+
}
|
|
472
662
|
}
|
|
473
663
|
|
|
474
|
-
// Strategy 2: scripts.start / scripts.dev
|
|
664
|
+
// ── Strategy 2: scripts.start / scripts.dev ───────────────────────────────
|
|
475
665
|
if (pkg && pkg.scripts) {
|
|
476
|
-
const scripts = [
|
|
666
|
+
const scripts = [
|
|
667
|
+
pkg.scripts.dev,
|
|
668
|
+
pkg.scripts.serve,
|
|
669
|
+
pkg.scripts["dev-server"],
|
|
670
|
+
pkg.scripts.start,
|
|
671
|
+
];
|
|
672
|
+
for (const script of scripts) {
|
|
673
|
+
if (!script) continue;
|
|
674
|
+
const match = script.match(
|
|
675
|
+
/(?:node|nodemon|ts-node|ts-node-dev|tsx)\s+([^\s]+\.(js|ts))/,
|
|
676
|
+
);
|
|
677
|
+
if (match) {
|
|
678
|
+
const scriptPath = match[1];
|
|
679
|
+
if (isCompiledPath(scriptPath)) continue;
|
|
680
|
+
const filePath = path.join(cwd, scriptPath);
|
|
681
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Strategy 2b: compiled path fallback — find TS source equivalent
|
|
477
686
|
for (const script of scripts) {
|
|
478
687
|
if (!script) continue;
|
|
479
688
|
const match = script.match(
|
|
480
|
-
/(?:node|nodemon|ts-node|tsx)\s+([^\s]+\.(js|ts))/,
|
|
689
|
+
/(?:node|nodemon|ts-node|ts-node-dev|tsx)\s+([^\s]+\.(js|ts))/,
|
|
481
690
|
);
|
|
482
691
|
if (match) {
|
|
483
|
-
const
|
|
692
|
+
const tsEquivalent = match[1]
|
|
693
|
+
.replace(/^backend\/dist\//, "backend/")
|
|
694
|
+
.replace(/^dist\//, "")
|
|
695
|
+
.replace(/\.js$/, ".ts");
|
|
696
|
+
const filePath = path.join(cwd, tsEquivalent);
|
|
484
697
|
if (fs.existsSync(filePath)) return filePath;
|
|
485
698
|
}
|
|
486
699
|
}
|
|
487
700
|
}
|
|
488
701
|
|
|
489
|
-
// Strategy 3:
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
"
|
|
493
|
-
"
|
|
494
|
-
"
|
|
495
|
-
"
|
|
496
|
-
"
|
|
497
|
-
"
|
|
498
|
-
"
|
|
499
|
-
"
|
|
500
|
-
"
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
702
|
+
// ── Strategy 3: Scoring — scan all JS/TS files ────────────────────────────
|
|
703
|
+
const skipDirs = new Set([
|
|
704
|
+
...SKIP_DIRS,
|
|
705
|
+
"dist",
|
|
706
|
+
"build",
|
|
707
|
+
"out",
|
|
708
|
+
"coverage",
|
|
709
|
+
"tests",
|
|
710
|
+
"test",
|
|
711
|
+
"__tests__",
|
|
712
|
+
"scripts",
|
|
713
|
+
"docs",
|
|
714
|
+
]);
|
|
715
|
+
|
|
716
|
+
const scoredCandidates = [];
|
|
717
|
+
|
|
718
|
+
function walk(directory, depth) {
|
|
719
|
+
if (depth > 3) return;
|
|
720
|
+
let entries;
|
|
721
|
+
try {
|
|
722
|
+
entries = fs.readdirSync(directory);
|
|
723
|
+
} catch {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
for (const entry of entries) {
|
|
728
|
+
if (skipDirs.has(entry) || entry.startsWith(".")) continue;
|
|
729
|
+
|
|
730
|
+
const fullPath = path.join(directory, entry);
|
|
731
|
+
let stat;
|
|
732
|
+
try {
|
|
733
|
+
stat = fs.statSync(fullPath);
|
|
734
|
+
} catch {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
508
737
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
738
|
+
if (stat.isDirectory()) {
|
|
739
|
+
walk(fullPath, depth + 1);
|
|
740
|
+
} else if (/\.(js|ts)$/.test(entry) && !/\.d\.ts$/.test(entry)) {
|
|
741
|
+
try {
|
|
742
|
+
const content = fs.readFileSync(fullPath, "utf8");
|
|
743
|
+
const score = scoreExpressFile(content, fullPath);
|
|
744
|
+
if (score > 0) {
|
|
745
|
+
scoredCandidates.push({ score, filePath: fullPath });
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
515
750
|
}
|
|
516
751
|
}
|
|
517
752
|
}
|
|
518
753
|
|
|
519
|
-
|
|
754
|
+
walk(cwd, 0);
|
|
755
|
+
|
|
756
|
+
if (scoredCandidates.length > 0) {
|
|
757
|
+
scoredCandidates.sort((a, b) => b.score - a.score);
|
|
758
|
+
const best = scoredCandidates[0];
|
|
759
|
+
|
|
760
|
+
// If best score is weak, cross-check with Procfile/Dockerfile
|
|
761
|
+
if (best.score <= 3) {
|
|
762
|
+
const configEntry = parseEntryFromConfigFiles(cwd);
|
|
763
|
+
if (configEntry) return configEntry;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return best.filePath;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ── Strategy 4: Procfile/Dockerfile fallback ──────────────────────────────
|
|
770
|
+
const configEntry = parseEntryFromConfigFiles(cwd);
|
|
771
|
+
if (configEntry) return configEntry;
|
|
772
|
+
|
|
773
|
+
// ── Strategy 5: Last resort deep scan ────────────────────────────────────
|
|
520
774
|
return findFileWithContent(cwd, ".listen(", [".js", ".ts"], 2);
|
|
521
775
|
}
|
|
522
776
|
|
|
@@ -575,136 +829,6 @@ function findCreateServer(filePath) {
|
|
|
575
829
|
return null;
|
|
576
830
|
}
|
|
577
831
|
|
|
578
|
-
// ─── AUTH DETECTION ──────────────────────────────────────────────────────────
|
|
579
|
-
|
|
580
|
-
const AUTH_LIBS = [
|
|
581
|
-
{
|
|
582
|
-
name: "next-auth",
|
|
583
|
-
packages: ["next-auth"],
|
|
584
|
-
detect: (deps) => !!deps["next-auth"],
|
|
585
|
-
getVersion: (deps) => {
|
|
586
|
-
const v = deps["next-auth"] || "";
|
|
587
|
-
const major = parseInt(v.replace(/[^0-9]/, ""), 10);
|
|
588
|
-
return major >= 5 ? "v5" : "v4";
|
|
589
|
-
},
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
name: "clerk",
|
|
593
|
-
packages: ["@clerk/nextjs", "@clerk/clerk-sdk-node", "@clerk/express"],
|
|
594
|
-
detect: (deps) =>
|
|
595
|
-
!!deps["@clerk/nextjs"] ||
|
|
596
|
-
!!deps["@clerk/clerk-sdk-node"] ||
|
|
597
|
-
!!deps["@clerk/express"],
|
|
598
|
-
getPackage: (deps) =>
|
|
599
|
-
deps["@clerk/nextjs"]
|
|
600
|
-
? "@clerk/nextjs"
|
|
601
|
-
: deps["@clerk/express"]
|
|
602
|
-
? "@clerk/express"
|
|
603
|
-
: "@clerk/clerk-sdk-node",
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
name: "supabase",
|
|
607
|
-
packages: ["@supabase/supabase-js", "@supabase/ssr"],
|
|
608
|
-
detect: (deps) =>
|
|
609
|
-
!!deps["@supabase/supabase-js"] || !!deps["@supabase/ssr"],
|
|
610
|
-
},
|
|
611
|
-
{
|
|
612
|
-
name: "passport",
|
|
613
|
-
packages: ["passport"],
|
|
614
|
-
detect: (deps) => !!deps["passport"],
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
name: "lucia",
|
|
618
|
-
packages: ["lucia"],
|
|
619
|
-
detect: (deps) => !!deps["lucia"],
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
name: "auth0",
|
|
623
|
-
packages: ["@auth0/nextjs-auth0", "express-openid-connect"],
|
|
624
|
-
detect: (deps) =>
|
|
625
|
-
!!deps["@auth0/nextjs-auth0"] || !!deps["express-openid-connect"],
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
name: "firebase",
|
|
629
|
-
packages: ["firebase-admin", "firebase"],
|
|
630
|
-
detect: (deps) => !!deps["firebase-admin"] || !!deps["firebase"],
|
|
631
|
-
},
|
|
632
|
-
{
|
|
633
|
-
name: "jwt",
|
|
634
|
-
packages: ["jsonwebtoken"],
|
|
635
|
-
detect: (deps) => !!deps["jsonwebtoken"],
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: "express-session",
|
|
639
|
-
packages: ["express-session"],
|
|
640
|
-
detect: (deps) => !!deps["express-session"],
|
|
641
|
-
},
|
|
642
|
-
];
|
|
643
|
-
|
|
644
|
-
function detectAuth(pkg) {
|
|
645
|
-
if (!pkg) return { name: null, supported: false };
|
|
646
|
-
|
|
647
|
-
const deps = {
|
|
648
|
-
...(pkg.dependencies || {}),
|
|
649
|
-
...(pkg.devDependencies || {}),
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
for (const lib of AUTH_LIBS) {
|
|
653
|
-
if (lib.detect(deps)) {
|
|
654
|
-
const version = lib.getVersion ? lib.getVersion(deps) : null;
|
|
655
|
-
const pkg2 = lib.getPackage ? lib.getPackage(deps) : lib.packages[0];
|
|
656
|
-
const supported = [
|
|
657
|
-
"next-auth",
|
|
658
|
-
"clerk",
|
|
659
|
-
"supabase",
|
|
660
|
-
"passport",
|
|
661
|
-
"jwt",
|
|
662
|
-
"express-session",
|
|
663
|
-
].includes(lib.name);
|
|
664
|
-
return { name: lib.name, version, package: pkg2, supported };
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
return { name: null, supported: false };
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// ─── NEXT-AUTH CONFIG LOCATION ───────────────────────────────────────────────
|
|
672
|
-
|
|
673
|
-
function findNextAuthConfig(cwd) {
|
|
674
|
-
const candidates = [
|
|
675
|
-
"pages/api/auth/[...nextauth].js",
|
|
676
|
-
"pages/api/auth/[...nextauth].ts",
|
|
677
|
-
"app/api/auth/[...nextauth]/route.js",
|
|
678
|
-
"app/api/auth/[...nextauth]/route.ts",
|
|
679
|
-
"src/pages/api/auth/[...nextauth].js",
|
|
680
|
-
"src/pages/api/auth/[...nextauth].ts",
|
|
681
|
-
"src/app/api/auth/[...nextauth]/route.js",
|
|
682
|
-
"src/app/api/auth/[...nextauth]/route.ts",
|
|
683
|
-
"lib/auth.js",
|
|
684
|
-
"lib/auth.ts",
|
|
685
|
-
"lib/authOptions.js",
|
|
686
|
-
"lib/authOptions.ts",
|
|
687
|
-
"utils/auth.js",
|
|
688
|
-
"utils/auth.ts",
|
|
689
|
-
"auth.js",
|
|
690
|
-
"auth.ts",
|
|
691
|
-
];
|
|
692
|
-
|
|
693
|
-
for (const candidate of candidates) {
|
|
694
|
-
const fullPath = path.join(cwd, candidate);
|
|
695
|
-
if (fs.existsSync(fullPath)) {
|
|
696
|
-
return { path: fullPath, relativePath: candidate };
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
const found = findFileWithContent(cwd, "authOptions", [".js", ".ts"], 3);
|
|
701
|
-
if (found) {
|
|
702
|
-
return { path: found, relativePath: path.relative(cwd, found) };
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
return null;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
832
|
// ─── PACKAGE MANAGER DETECTION ───────────────────────────────────────────────
|
|
709
833
|
|
|
710
834
|
function detectPackageManager(cwd) {
|
|
@@ -770,13 +894,79 @@ function findFileWithContent(dir, searchString, extensions, maxDepth) {
|
|
|
770
894
|
function detectAppVarName(filePath) {
|
|
771
895
|
try {
|
|
772
896
|
const content = fs.readFileSync(filePath, "utf8");
|
|
773
|
-
|
|
774
|
-
|
|
897
|
+
|
|
898
|
+
// Pattern 1: const app = express()
|
|
899
|
+
const match1 = content.match(
|
|
900
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*express\s*\(/,
|
|
901
|
+
);
|
|
902
|
+
if (match1) return match1[1];
|
|
903
|
+
|
|
904
|
+
// Pattern 2: const app = require('express')()
|
|
905
|
+
const match2 = content.match(
|
|
906
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"]express['"]\s*\)\s*\(/,
|
|
907
|
+
);
|
|
908
|
+
if (match2) return match2[1];
|
|
909
|
+
|
|
910
|
+
// Pattern 3: factory function — createApp(), makeApp() etc.
|
|
911
|
+
// const app = createApp()
|
|
912
|
+
const match3 = content.match(
|
|
913
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(?:create|make|build|setup)App\s*\(/,
|
|
914
|
+
);
|
|
915
|
+
if (match3) return match3[1];
|
|
916
|
+
|
|
917
|
+
// Pattern 4: subclass — class MyApp extends express
|
|
918
|
+
// const app = new MyApp()
|
|
919
|
+
const subclassNames = [
|
|
920
|
+
...content.matchAll(/class\s+(\w+)\s+extends\s+express/g),
|
|
921
|
+
].map((m) => m[1]);
|
|
922
|
+
for (const name of subclassNames) {
|
|
923
|
+
const subMatch = content.match(
|
|
924
|
+
new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+${name}\\s*\\(`),
|
|
925
|
+
);
|
|
926
|
+
if (subMatch) return subMatch[1];
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Pattern 5: const server = app.listen( → return "app"
|
|
930
|
+
const match5 = content.match(
|
|
931
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(\w+)\.listen\s*\(/,
|
|
932
|
+
);
|
|
933
|
+
if (match5) return match5[2];
|
|
934
|
+
|
|
935
|
+
// Pattern 6: app.listen( or server.listen(
|
|
936
|
+
const match6 = content.match(
|
|
937
|
+
/\b(app|server|router|handler|httpServer)\s*\.listen\s*\(/,
|
|
938
|
+
);
|
|
939
|
+
if (match6) return match6[1];
|
|
940
|
+
|
|
941
|
+
return "app";
|
|
775
942
|
} catch {
|
|
776
943
|
return "app";
|
|
777
944
|
}
|
|
778
945
|
}
|
|
779
946
|
|
|
947
|
+
function detectDotenv(cwd, pkg, entryPoint) {
|
|
948
|
+
// Check if dotenv is in dependencies
|
|
949
|
+
const deps = {
|
|
950
|
+
...(pkg.dependencies || {}),
|
|
951
|
+
...(pkg.devDependencies || {}),
|
|
952
|
+
};
|
|
953
|
+
const hasDotenvPackage = !!deps["dotenv"];
|
|
954
|
+
|
|
955
|
+
// Check if dotenv is actually being called in entry file
|
|
956
|
+
let hasDotenvCall = false;
|
|
957
|
+
if (entryPoint && fs.existsSync(entryPoint)) {
|
|
958
|
+
try {
|
|
959
|
+
const content = fs.readFileSync(entryPoint, "utf8");
|
|
960
|
+
hasDotenvCall =
|
|
961
|
+
content.includes("dotenv") ||
|
|
962
|
+
content.includes("require('dotenv')") ||
|
|
963
|
+
content.includes('require("dotenv")');
|
|
964
|
+
} catch {}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return hasDotenvPackage || hasDotenvCall;
|
|
968
|
+
}
|
|
969
|
+
|
|
780
970
|
// ─── MAIN DETECT FUNCTION ────────────────────────────────────────────────────
|
|
781
971
|
|
|
782
972
|
function detect(cwd) {
|
|
@@ -843,7 +1033,6 @@ function detect(cwd) {
|
|
|
843
1033
|
const moduleSystem = detectModuleSystem(backendPkg);
|
|
844
1034
|
const isTypeScript = detectTypeScript(backendDir);
|
|
845
1035
|
const hasSrc = detectSrcDir(backendDir);
|
|
846
|
-
const auth = detectAuth(backendPkg);
|
|
847
1036
|
const generateTs = shouldGenerateTs(backendDir, isTypeScript);
|
|
848
1037
|
|
|
849
1038
|
// ── Find frontend main file ───────────────────────────────────────────────
|
|
@@ -872,7 +1061,6 @@ function detect(cwd) {
|
|
|
872
1061
|
isTypeScript,
|
|
873
1062
|
generateTs,
|
|
874
1063
|
hasSrc,
|
|
875
|
-
auth,
|
|
876
1064
|
packageManager,
|
|
877
1065
|
ext: generateTs ? ".ts" : ".js",
|
|
878
1066
|
// Frontend info
|
|
@@ -885,13 +1073,11 @@ function detect(cwd) {
|
|
|
885
1073
|
if (framework.name === "next") {
|
|
886
1074
|
result.next = detectNextRouter(backendDir);
|
|
887
1075
|
result.nextVersion = detectNextVersion(backendPkg);
|
|
888
|
-
if (auth.name === "next-auth") {
|
|
889
|
-
result.nextAuthConfig = findNextAuthConfig(backendDir);
|
|
890
|
-
}
|
|
891
1076
|
}
|
|
892
1077
|
|
|
893
1078
|
if (framework.name === "express") {
|
|
894
1079
|
result.entryPoint = detectExpressEntry(backendDir, backendPkg);
|
|
1080
|
+
result.hasDotenv = detectDotenv(backendDir, backendPkg, result.entryPoint);
|
|
895
1081
|
if (result.entryPoint) {
|
|
896
1082
|
result.appVarName = detectAppVarName(result.entryPoint); // detect FIRST
|
|
897
1083
|
result.listenCall = findListenCall(result.entryPoint, result.appVarName);
|
|
@@ -949,8 +1135,6 @@ module.exports = {
|
|
|
949
1135
|
shouldGenerateTs,
|
|
950
1136
|
detectSrcDir,
|
|
951
1137
|
detectNextRouter,
|
|
952
|
-
detectAuth,
|
|
953
|
-
findNextAuthConfig,
|
|
954
1138
|
detectPackageManager,
|
|
955
1139
|
detectExistingBotVersion,
|
|
956
1140
|
findFileWithContent,
|
|
@@ -959,4 +1143,7 @@ module.exports = {
|
|
|
959
1143
|
findListenInsideCallback,
|
|
960
1144
|
findCreateServer,
|
|
961
1145
|
detectAppVarName,
|
|
1146
|
+
detectDotenv,
|
|
1147
|
+
parseEntryFromConfigFiles,
|
|
1148
|
+
scoreExpressFile,
|
|
962
1149
|
};
|