larats 1.0.2 → 1.1.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/README.md +58 -6
- package/dist/index.js +575 -17
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import fs3 from "fs";
|
|
5
5
|
import path4 from "path";
|
|
6
|
+
import os2 from "os";
|
|
7
|
+
import { exec } from "child_process";
|
|
6
8
|
import { parseArgs } from "util";
|
|
7
9
|
import * as p from "@clack/prompts";
|
|
8
10
|
import pc from "picocolors";
|
|
@@ -69,7 +71,7 @@ async function detectProjectType(targetDir = process.cwd()) {
|
|
|
69
71
|
summary
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
|
-
if (
|
|
74
|
+
if (hasPackageJson) {
|
|
73
75
|
const pkg = readJsonSafe(packageJsonPath);
|
|
74
76
|
const allDeps = {
|
|
75
77
|
...pkg?.dependencies || {},
|
|
@@ -91,13 +93,15 @@ async function detectProjectType(targetDir = process.cwd()) {
|
|
|
91
93
|
const hasSrc = fs.existsSync(path.join(resolvedDir, "src"));
|
|
92
94
|
const srcDir = hasSrc ? "src" : ".";
|
|
93
95
|
const isModular = fs.existsSync(path.join(resolvedDir, srcDir, "modules")) || fs.existsSync(path.join(resolvedDir, "modules"));
|
|
94
|
-
const
|
|
96
|
+
const isTypeScript = hasTsConfig || "typescript" in allDeps;
|
|
97
|
+
const stackLabel = isTypeScript ? "TypeScript" : "JavaScript / Node.js";
|
|
98
|
+
const extras = [framework !== "node-vanilla" ? framework : stackLabel];
|
|
95
99
|
if (hasPrisma) extras.push("Prisma");
|
|
96
100
|
if (hasTypeORM) extras.push("TypeORM");
|
|
97
101
|
if (hasDrizzle) extras.push("Drizzle");
|
|
98
102
|
if (isMonorepo) extras.push("Monorepo");
|
|
99
103
|
if (isModular) extras.push("Modular");
|
|
100
|
-
const summary = `TypeScript (${extras.join(", ")})`;
|
|
104
|
+
const summary = isTypeScript ? `TypeScript (${extras.join(", ")})` : `JavaScript / Node.js (${extras.join(", ")})`;
|
|
101
105
|
return {
|
|
102
106
|
type: "typescript",
|
|
103
107
|
name: pkg?.name || dirName,
|
|
@@ -109,7 +113,8 @@ async function detectProjectType(targetDir = process.cwd()) {
|
|
|
109
113
|
hasDrizzle,
|
|
110
114
|
isMonorepo,
|
|
111
115
|
isModular,
|
|
112
|
-
srcDir
|
|
116
|
+
srcDir,
|
|
117
|
+
isTypeScript
|
|
113
118
|
},
|
|
114
119
|
summary
|
|
115
120
|
};
|
|
@@ -505,7 +510,7 @@ ${f}`;
|
|
|
505
510
|
}
|
|
506
511
|
function mermaidLabel(str, maxLen = 40) {
|
|
507
512
|
if (!str) return "";
|
|
508
|
-
let cleaned = str.replace(/"/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/`/g, "'").replace(/[\r\n]+/g, " ").trim();
|
|
513
|
+
let cleaned = str.replace(/"/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/`/g, "'").replace(/[\[\]\(\)\{\}]/g, "").replace(/[\r\n]+/g, " ").trim();
|
|
509
514
|
if (cleaned.length > maxLen) {
|
|
510
515
|
cleaned = cleaned.slice(0, maxLen - 3) + "...";
|
|
511
516
|
}
|
|
@@ -908,6 +913,312 @@ ${fence(directoryTree || "(Empty directory structure)", "text")}
|
|
|
908
913
|
${securityNotice}
|
|
909
914
|
`;
|
|
910
915
|
}
|
|
916
|
+
function renderTerminalFlow(scanResult) {
|
|
917
|
+
const { projectInfo, components } = scanResult;
|
|
918
|
+
const steps = [];
|
|
919
|
+
if (projectInfo.type === "laravel") {
|
|
920
|
+
steps.push("Client");
|
|
921
|
+
const routes = components["Routes"]?.count ?? 0;
|
|
922
|
+
if (routes > 0) steps.push(`Routes [${routes}]`);
|
|
923
|
+
const requests = components["Form Requests"]?.count ?? 0;
|
|
924
|
+
if (requests > 0) steps.push(`Requests [${requests}]`);
|
|
925
|
+
const controllers = components["Controllers"]?.count ?? 0;
|
|
926
|
+
if (controllers > 0) steps.push(`Controllers [${controllers}]`);
|
|
927
|
+
const services = (components["Services"]?.count ?? 0) + (components["Actions"]?.count ?? 0);
|
|
928
|
+
if (services > 0) steps.push(`Services [${services}]`);
|
|
929
|
+
const models = components["Models"]?.count ?? 0;
|
|
930
|
+
if (models > 0) steps.push(`Models [${models}]`);
|
|
931
|
+
steps.push("Database");
|
|
932
|
+
} else if (projectInfo.type === "typescript") {
|
|
933
|
+
const hasAnyLayer = ["Controllers", "Services", "Repositories", "Routes", "DTOs & Schemas", "Models & Entities", "UI Components"].some((k) => (components[k]?.count ?? 0) > 0);
|
|
934
|
+
if (hasAnyLayer) {
|
|
935
|
+
steps.push("Client");
|
|
936
|
+
const ui = components["UI Components"]?.count ?? 0;
|
|
937
|
+
if (ui > 0) steps.push(`UI [${ui}]`);
|
|
938
|
+
const routes = components["Routes"]?.count ?? 0;
|
|
939
|
+
if (routes > 0) steps.push(`Router [${routes}]`);
|
|
940
|
+
const dtos = components["DTOs & Schemas"]?.count ?? 0;
|
|
941
|
+
if (dtos > 0) steps.push(`DTOs [${dtos}]`);
|
|
942
|
+
const controllers = components["Controllers"]?.count ?? 0;
|
|
943
|
+
if (controllers > 0) steps.push(`Controllers [${controllers}]`);
|
|
944
|
+
const services = components["Services"]?.count ?? 0;
|
|
945
|
+
if (services > 0) steps.push(`Services [${services}]`);
|
|
946
|
+
const data = (components["Repositories"]?.count ?? 0) + (components["Models & Entities"]?.count ?? 0);
|
|
947
|
+
if (data > 0) steps.push(`Data Layer [${data}]`);
|
|
948
|
+
steps.push("Database");
|
|
949
|
+
} else {
|
|
950
|
+
const src = components["Source Files"]?.count ?? 0;
|
|
951
|
+
const tests = components["Test Files"]?.count ?? 0;
|
|
952
|
+
steps.push("CLI / Entrypoint");
|
|
953
|
+
if (src > 0) steps.push(`Source Modules [${src}]`);
|
|
954
|
+
if (tests > 0) steps.push(`Test Suites [${tests}]`);
|
|
955
|
+
}
|
|
956
|
+
} else {
|
|
957
|
+
const src = components["Source Files"]?.count ?? 0;
|
|
958
|
+
const tests = components["Test Files"]?.count ?? 0;
|
|
959
|
+
steps.push("Project Root");
|
|
960
|
+
if (src > 0) steps.push(`Source [${src}]`);
|
|
961
|
+
if (tests > 0) steps.push(`Tests [${tests}]`);
|
|
962
|
+
}
|
|
963
|
+
return steps.join(" \u2500\u2500> ");
|
|
964
|
+
}
|
|
965
|
+
function escapeHtml(str) {
|
|
966
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
967
|
+
}
|
|
968
|
+
function generateArchitectureHtml(scanResult) {
|
|
969
|
+
const { projectInfo, totalFilesScanned, components, modules, directoryTree } = scanResult;
|
|
970
|
+
const mermaid = generateMermaidDiagram(scanResult);
|
|
971
|
+
const componentRowsHtml = Object.entries(components).map(([, comp]) => {
|
|
972
|
+
const samples = comp.files.slice(0, 3).map((f) => `<code>${escapeHtml(f)}</code>`).join(", ");
|
|
973
|
+
const more = comp.files.length > 3 ? ` <span class="muted">(+${comp.files.length - 3} more)</span>` : "";
|
|
974
|
+
return `<tr>
|
|
975
|
+
<td><strong>${escapeHtml(comp.name)}</strong></td>
|
|
976
|
+
<td><span class="badge">${comp.count}</span></td>
|
|
977
|
+
<td>${escapeHtml(comp.description)}</td>
|
|
978
|
+
<td>${samples ? samples + more : "-"}</td>
|
|
979
|
+
</tr>`;
|
|
980
|
+
}).join("\n");
|
|
981
|
+
const modulesHtml = modules.length > 0 ? `<div class="card">
|
|
982
|
+
<h2>\u{1F4E6} Detected Modules & Domains (${modules.length})</h2>
|
|
983
|
+
<div class="modules-grid">
|
|
984
|
+
${modules.map((m) => `<div class="module-item"><code>${escapeHtml(m)}</code></div>`).join("\n")}
|
|
985
|
+
</div>
|
|
986
|
+
</div>` : "";
|
|
987
|
+
return `<!DOCTYPE html>
|
|
988
|
+
<html lang="en">
|
|
989
|
+
<head>
|
|
990
|
+
<meta charset="UTF-8">
|
|
991
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
992
|
+
<title>LaraTS Architecture: ${escapeHtml(projectInfo.name)}</title>
|
|
993
|
+
<style>
|
|
994
|
+
:root {
|
|
995
|
+
--bg: #0f172a;
|
|
996
|
+
--card-bg: #1e293b;
|
|
997
|
+
--text: #f8fafc;
|
|
998
|
+
--muted: #94a3b8;
|
|
999
|
+
--border: #334155;
|
|
1000
|
+
--primary: #38bdf8;
|
|
1001
|
+
--accent: #818cf8;
|
|
1002
|
+
}
|
|
1003
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1004
|
+
body {
|
|
1005
|
+
background: var(--bg);
|
|
1006
|
+
color: var(--text);
|
|
1007
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
1008
|
+
line-height: 1.5;
|
|
1009
|
+
padding: 2rem 1rem;
|
|
1010
|
+
}
|
|
1011
|
+
.container { max-width: 1080px; margin: 0 auto; }
|
|
1012
|
+
header { margin-bottom: 2rem; border-bottom: 1px solid var(--border); padding-bottom: 1.5rem; }
|
|
1013
|
+
.header-top { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem; }
|
|
1014
|
+
h1 { font-size: 1.8rem; font-weight: 700; color: #fff; }
|
|
1015
|
+
.pill { background: #0284c7; color: #fff; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.85rem; font-weight: 600; }
|
|
1016
|
+
.subtitle { color: var(--muted); margin-top: 0.5rem; font-size: 0.95rem; }
|
|
1017
|
+
.card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; }
|
|
1018
|
+
h2 { font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: #e2e8f0; }
|
|
1019
|
+
table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; font-size: 0.9rem; }
|
|
1020
|
+
th, td { text-align: left; padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); }
|
|
1021
|
+
th { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
1022
|
+
.badge { background: #334155; padding: 0.2rem 0.6rem; border-radius: 4px; font-weight: 600; font-size: 0.85rem; }
|
|
1023
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; background: #090d16; padding: 0.15rem 0.4rem; border-radius: 4px; font-size: 0.85rem; color: #38bdf8; }
|
|
1024
|
+
pre { background: #090d16; padding: 1rem; border-radius: 6px; overflow-x: auto; font-size: 0.85rem; border: 1px solid var(--border); line-height: 1.4; }
|
|
1025
|
+
.mermaid-box { display: flex; justify-content: center; background: #090d16; border-radius: 8px; padding: 2rem 1rem; overflow-x: auto; border: 1px solid var(--border); }
|
|
1026
|
+
.modules-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
1027
|
+
.module-item { background: #090d16; border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem 1rem; }
|
|
1028
|
+
.muted { color: var(--muted); font-size: 0.85rem; }
|
|
1029
|
+
footer { text-align: center; color: var(--muted); font-size: 0.85rem; margin-top: 3rem; }
|
|
1030
|
+
</style>
|
|
1031
|
+
</head>
|
|
1032
|
+
<body>
|
|
1033
|
+
<div class="container">
|
|
1034
|
+
<header>
|
|
1035
|
+
<div class="header-top">
|
|
1036
|
+
<div>
|
|
1037
|
+
<h1>\u{1F3DB}\uFE0F Architecture Overview: ${escapeHtml(projectInfo.name)}</h1>
|
|
1038
|
+
<p class="subtitle">Stack: <strong>${escapeHtml(projectInfo.summary)}</strong> • Total Files: <strong>${totalFilesScanned}</strong></p>
|
|
1039
|
+
</div>
|
|
1040
|
+
<span class="pill">LaraTS Architecture Map</span>
|
|
1041
|
+
</div>
|
|
1042
|
+
</header>
|
|
1043
|
+
|
|
1044
|
+
<div class="card">
|
|
1045
|
+
<h2>\u{1F5FA}\uFE0F Interactive Architecture Flowchart</h2>
|
|
1046
|
+
<div class="mermaid-box">
|
|
1047
|
+
<pre class="mermaid">${mermaid}</pre>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
|
|
1051
|
+
<div class="card">
|
|
1052
|
+
<h2>\u{1F4CA} Architectural Component Statistics</h2>
|
|
1053
|
+
<table>
|
|
1054
|
+
<thead>
|
|
1055
|
+
<tr>
|
|
1056
|
+
<th>Component Layer</th>
|
|
1057
|
+
<th>Files</th>
|
|
1058
|
+
<th>Description</th>
|
|
1059
|
+
<th>Sample Files</th>
|
|
1060
|
+
</tr>
|
|
1061
|
+
</thead>
|
|
1062
|
+
<tbody>
|
|
1063
|
+
${componentRowsHtml || '<tr><td colspan="4">No architectural components detected</td></tr>'}
|
|
1064
|
+
</tbody>
|
|
1065
|
+
</table>
|
|
1066
|
+
</div>
|
|
1067
|
+
|
|
1068
|
+
${modulesHtml}
|
|
1069
|
+
|
|
1070
|
+
<div class="card">
|
|
1071
|
+
<h2>\u{1F332} Directory Structure Tree</h2>
|
|
1072
|
+
<pre><code>${escapeHtml(directoryTree || "(Empty directory)")}</code></pre>
|
|
1073
|
+
</div>
|
|
1074
|
+
|
|
1075
|
+
<footer>
|
|
1076
|
+
Generated locally & offline with \u2764\uFE0F by <strong>LaraTS CLI</strong>
|
|
1077
|
+
</footer>
|
|
1078
|
+
</div>
|
|
1079
|
+
|
|
1080
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
1081
|
+
<script>
|
|
1082
|
+
if (typeof mermaid !== 'undefined') {
|
|
1083
|
+
mermaid.initialize({
|
|
1084
|
+
startOnLoad: true,
|
|
1085
|
+
theme: 'dark',
|
|
1086
|
+
flowchart: { curve: 'basis' }
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
</script>
|
|
1090
|
+
</body>
|
|
1091
|
+
</html>`;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/generator/ai-context.ts
|
|
1095
|
+
var AI_RULES_START_TAG = "<!-- larats-rules:start -->";
|
|
1096
|
+
var AI_RULES_END_TAG = "<!-- larats-rules:end -->";
|
|
1097
|
+
function generateAiContext(scanResult) {
|
|
1098
|
+
const { projectInfo, components, modules } = scanResult;
|
|
1099
|
+
const lines = [];
|
|
1100
|
+
lines.push("# Codebase Architecture Rules");
|
|
1101
|
+
lines.push(`> Project: ${projectInfo.name} | Stack: ${projectInfo.summary}`);
|
|
1102
|
+
lines.push("");
|
|
1103
|
+
lines.push("## Architectural Boundaries & Principles");
|
|
1104
|
+
lines.push("- Maintain strict separation of concerns across active architectural layers.");
|
|
1105
|
+
lines.push("- Keep Controllers thin: handle HTTP requests, invoke Services, and return responses.");
|
|
1106
|
+
lines.push("- Encapsulate all business rules, calculations, and mutations inside Services/Actions.");
|
|
1107
|
+
lines.push("- Isolate data storage and queries to Repositories or Model entities.");
|
|
1108
|
+
lines.push("- Validate all incoming user inputs with dedicated DTOs or Form Requests before reaching domain logic.");
|
|
1109
|
+
lines.push("");
|
|
1110
|
+
lines.push("## Project Structure & Layer Conventions");
|
|
1111
|
+
if (projectInfo.type === "laravel") {
|
|
1112
|
+
const isModular = Boolean(projectInfo.laravel?.isModular);
|
|
1113
|
+
const modDir = projectInfo.laravel?.modulesDir || "app/Modules";
|
|
1114
|
+
if (isModular) {
|
|
1115
|
+
lines.push(`- **Architecture Style**: Modular Domain Architecture`);
|
|
1116
|
+
lines.push(`- **Module Root**: \`${modDir}/<FeatureName>/\``);
|
|
1117
|
+
lines.push(`- **Controllers**: \`${modDir}/<FeatureName>/Controllers/\` (HTTP handlers)`);
|
|
1118
|
+
lines.push(`- **Services / Actions**: \`${modDir}/<FeatureName>/Services/\` or \`Actions/\` (Business logic)`);
|
|
1119
|
+
lines.push(`- **Models**: \`${modDir}/<FeatureName>/Models/\` (Eloquent entities)`);
|
|
1120
|
+
lines.push(`- **Validation**: \`${modDir}/<FeatureName>/Requests/\` (FormRequest classes)`);
|
|
1121
|
+
lines.push(`- **Routes**: \`${modDir}/<FeatureName>/routes.php\` or root \`routes/\``);
|
|
1122
|
+
} else {
|
|
1123
|
+
lines.push(`- **Architecture Style**: Standard Laravel Layered Architecture`);
|
|
1124
|
+
lines.push(`- **Controllers**: \`app/Http/Controllers/\``);
|
|
1125
|
+
lines.push(`- **Services / Actions**: \`app/Services/\` or \`app/Actions/\``);
|
|
1126
|
+
lines.push(`- **Models**: \`app/Models/\``);
|
|
1127
|
+
lines.push(`- **Validation**: \`app/Http/Requests/\``);
|
|
1128
|
+
lines.push(`- **Routes**: \`routes/web.php\`, \`routes/api.php\``);
|
|
1129
|
+
lines.push(`- **Migrations**: \`database/migrations/\``);
|
|
1130
|
+
}
|
|
1131
|
+
if (projectInfo.laravel?.hasFilament) {
|
|
1132
|
+
lines.push(`- **Admin Panel**: Filament Resources live in \`app/Filament/Resources/\``);
|
|
1133
|
+
}
|
|
1134
|
+
} else if (projectInfo.type === "typescript") {
|
|
1135
|
+
const isModular = Boolean(projectInfo.typescript?.isModular);
|
|
1136
|
+
const srcDir = projectInfo.typescript?.srcDir || "src";
|
|
1137
|
+
const orm = projectInfo.typescript?.hasPrisma ? "Prisma" : projectInfo.typescript?.hasTypeORM ? "TypeORM" : projectInfo.typescript?.hasDrizzle ? "Drizzle" : null;
|
|
1138
|
+
if (isModular) {
|
|
1139
|
+
lines.push(`- **Architecture Style**: Feature Module Architecture`);
|
|
1140
|
+
lines.push(`- **Module Root**: \`${srcDir}/modules/<feature>/\``);
|
|
1141
|
+
lines.push(`- **Controllers**: \`${srcDir}/modules/<feature>/<feature>.controller.ts\``);
|
|
1142
|
+
lines.push(`- **Services**: \`${srcDir}/modules/<feature>/<feature>.service.ts\``);
|
|
1143
|
+
lines.push(`- **Repositories**: \`${srcDir}/modules/<feature>/<feature>.repository.ts\``);
|
|
1144
|
+
lines.push(`- **DTOs / Schemas**: \`${srcDir}/modules/<feature>/<feature>.dto.ts\``);
|
|
1145
|
+
lines.push(`- **Routes**: \`${srcDir}/modules/<feature>/<feature>.routes.ts\``);
|
|
1146
|
+
lines.push(`- **Module Index**: \`${srcDir}/modules/<feature>/index.ts\` (barrel export)`);
|
|
1147
|
+
} else {
|
|
1148
|
+
lines.push(`- **Architecture Style**: Layered Architecture`);
|
|
1149
|
+
lines.push(`- **Controllers**: \`${srcDir}/controllers/\``);
|
|
1150
|
+
lines.push(`- **Services**: \`${srcDir}/services/\``);
|
|
1151
|
+
lines.push(`- **Repositories / Data**: \`${srcDir}/repositories/\``);
|
|
1152
|
+
lines.push(`- **DTOs / Schemas**: \`${srcDir}/dtos/\` or \`${srcDir}/schemas/\``);
|
|
1153
|
+
lines.push(`- **Routes**: \`${srcDir}/routes/\``);
|
|
1154
|
+
}
|
|
1155
|
+
if (orm) {
|
|
1156
|
+
lines.push(`- **Database ORM**: ${orm} is used for data persistence.`);
|
|
1157
|
+
}
|
|
1158
|
+
} else {
|
|
1159
|
+
lines.push(`- **Source Code**: \`src/\``);
|
|
1160
|
+
lines.push(`- **Automated Tests**: \`tests/\``);
|
|
1161
|
+
}
|
|
1162
|
+
if (modules.length > 0) {
|
|
1163
|
+
lines.push("");
|
|
1164
|
+
lines.push(`## Active Feature Modules (${modules.length})`);
|
|
1165
|
+
lines.push(`Existing domains: ${modules.map((m) => `\`${m}\``).join(", ")}`);
|
|
1166
|
+
lines.push("When creating new features, follow this established domain partition.");
|
|
1167
|
+
}
|
|
1168
|
+
lines.push("");
|
|
1169
|
+
lines.push("## AI Coding Guardrails");
|
|
1170
|
+
lines.push("1. NEVER place raw queries or database transactions inside Controller files.");
|
|
1171
|
+
lines.push("2. When implementing a new feature, always create cohesive layers: Route -> Controller -> Service -> Repository/Model.");
|
|
1172
|
+
lines.push("3. Keep naming conventions consistent with existing files in the repository.");
|
|
1173
|
+
lines.push("4. Write automated unit/feature tests for new domain services.");
|
|
1174
|
+
return lines.join("\n");
|
|
1175
|
+
}
|
|
1176
|
+
function mergeAiRules(existingContent, newRules) {
|
|
1177
|
+
const block = `${AI_RULES_START_TAG}
|
|
1178
|
+
${newRules}
|
|
1179
|
+
${AI_RULES_END_TAG}`;
|
|
1180
|
+
if (!existingContent || existingContent.trim() === "") {
|
|
1181
|
+
return block + "\n";
|
|
1182
|
+
}
|
|
1183
|
+
const regex = new RegExp(`${AI_RULES_START_TAG}[\\s\\S]*?${AI_RULES_END_TAG}`, "m");
|
|
1184
|
+
if (regex.test(existingContent)) {
|
|
1185
|
+
return existingContent.replace(regex, block);
|
|
1186
|
+
}
|
|
1187
|
+
return existingContent.trimEnd() + "\n\n" + block + "\n";
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/utils/clipboard.ts
|
|
1191
|
+
import { spawn } from "child_process";
|
|
1192
|
+
async function copyToClipboard(text2) {
|
|
1193
|
+
const platform = process.platform;
|
|
1194
|
+
let command;
|
|
1195
|
+
let args = [];
|
|
1196
|
+
if (platform === "win32") {
|
|
1197
|
+
command = "clip";
|
|
1198
|
+
} else if (platform === "darwin") {
|
|
1199
|
+
command = "pbcopy";
|
|
1200
|
+
} else {
|
|
1201
|
+
command = process.env.WAYLAND_DISPLAY ? "wl-copy" : "xclip";
|
|
1202
|
+
if (command === "xclip") {
|
|
1203
|
+
args = ["-selection", "clipboard"];
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return new Promise((resolve) => {
|
|
1207
|
+
try {
|
|
1208
|
+
const child = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
1209
|
+
child.on("error", () => {
|
|
1210
|
+
resolve(false);
|
|
1211
|
+
});
|
|
1212
|
+
child.on("close", (code) => {
|
|
1213
|
+
resolve(code === 0);
|
|
1214
|
+
});
|
|
1215
|
+
child.stdin.write(text2);
|
|
1216
|
+
child.stdin.end();
|
|
1217
|
+
} catch {
|
|
1218
|
+
resolve(false);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
911
1222
|
|
|
912
1223
|
// src/generator/scaffolder.ts
|
|
913
1224
|
import fs2 from "fs";
|
|
@@ -1850,7 +2161,7 @@ async function scaffoldModule(projectInfo, options) {
|
|
|
1850
2161
|
}
|
|
1851
2162
|
|
|
1852
2163
|
// src/index.ts
|
|
1853
|
-
var VERSION = true ? "1.
|
|
2164
|
+
var VERSION = true ? "1.1.1" : "1.1.1";
|
|
1854
2165
|
function showHelp() {
|
|
1855
2166
|
console.log(`
|
|
1856
2167
|
${pc.bold(pc.cyan("LaraTS CLI"))} ${pc.gray(`v${VERSION}`)}
|
|
@@ -1862,19 +2173,26 @@ ${pc.bold("USAGE:")}
|
|
|
1862
2173
|
|
|
1863
2174
|
${pc.bold("COMMANDS:")}
|
|
1864
2175
|
${pc.green("scan")} Scan repository & generate ARCHITECTURE.md
|
|
2176
|
+
${pc.green("view")}, ${pc.green("preview")} Open interactive architecture diagram in local browser
|
|
2177
|
+
${pc.green("ai")} Export architecture context prompt for AI (Cursor, Claude, Copilot)
|
|
1865
2178
|
${pc.green("scaffold")}, ${pc.green("make")} [name] Scaffold a new module or feature with clean boilerplate
|
|
1866
2179
|
${pc.green("info")} Show detected project type & architectural flavor
|
|
1867
2180
|
${pc.green("help")} Display this help message
|
|
1868
2181
|
|
|
1869
2182
|
${pc.bold("OPTIONS:")}
|
|
1870
|
-
${pc.yellow("-o, --output")} <file> Output
|
|
2183
|
+
${pc.yellow("-o, --output")} <file> Output path for scan (default: ARCHITECTURE.md)
|
|
2184
|
+
${pc.yellow("--view")} Open local browser viewer immediately after scanning
|
|
2185
|
+
${pc.yellow("--copy")} Copy AI architecture context prompt to clipboard
|
|
2186
|
+
${pc.yellow("--cursor")} Inject architecture rules into .cursorrules
|
|
2187
|
+
${pc.yellow("--claude")} Inject architecture rules into CLAUDE.md
|
|
2188
|
+
${pc.yellow("-t, --tree")} Print directory tree in terminal
|
|
2189
|
+
${pc.yellow("--html")} Also export standalone ARCHITECTURE.html
|
|
1871
2190
|
${pc.yellow("-m, --modular")} Enforce modular directory structure
|
|
1872
2191
|
${pc.yellow("-s, --standard")} Enforce standard/layered directory structure
|
|
1873
2192
|
${pc.yellow("--dry-run")} Simulate generation without writing files
|
|
1874
2193
|
${pc.yellow("-f, --force")} Overwrite files if they already exist
|
|
1875
2194
|
${pc.yellow("-v, --version")} Display CLI version
|
|
1876
2195
|
${pc.yellow("-h, --help")} Display this help message
|
|
1877
|
-
|
|
1878
2196
|
${pc.bold("EXAMPLES:")}
|
|
1879
2197
|
${pc.cyan("$")} larats
|
|
1880
2198
|
${pc.cyan("$")} larats scan
|
|
@@ -1887,6 +2205,113 @@ function handleCancel() {
|
|
|
1887
2205
|
p.cancel(pc.yellow("Operation cancelled. See you next time! \u{1F44B}"));
|
|
1888
2206
|
process.exit(0);
|
|
1889
2207
|
}
|
|
2208
|
+
async function checkAndPromptStar(customStatsPath, forceAnswer) {
|
|
2209
|
+
try {
|
|
2210
|
+
const configDir = path4.join(os2.homedir(), ".larats");
|
|
2211
|
+
const statsFile = customStatsPath || path4.join(configDir, "stats.json");
|
|
2212
|
+
if (!fs3.existsSync(path4.dirname(statsFile))) {
|
|
2213
|
+
fs3.mkdirSync(path4.dirname(statsFile), { recursive: true });
|
|
2214
|
+
}
|
|
2215
|
+
let stats = {};
|
|
2216
|
+
if (fs3.existsSync(statsFile)) {
|
|
2217
|
+
stats = JSON.parse(fs3.readFileSync(statsFile, "utf-8"));
|
|
2218
|
+
}
|
|
2219
|
+
const runs = (stats.runs || 0) + 1;
|
|
2220
|
+
stats.runs = runs;
|
|
2221
|
+
if (runs >= 3 && !stats.hasPromptedStar) {
|
|
2222
|
+
stats.hasPromptedStar = true;
|
|
2223
|
+
const repoUrl = "https://github.com/SidiqFatuh187/larats-cli";
|
|
2224
|
+
if (process.stdin.isTTY || typeof forceAnswer === "boolean") {
|
|
2225
|
+
const shouldStar = typeof forceAnswer === "boolean" ? forceAnswer : await p.confirm({
|
|
2226
|
+
message: "\u2B50 Enjoying LaraTS? Would you like to support the project with a star on GitHub?",
|
|
2227
|
+
initialValue: true
|
|
2228
|
+
});
|
|
2229
|
+
if (shouldStar && !p.isCancel(shouldStar)) {
|
|
2230
|
+
openViewer(repoUrl);
|
|
2231
|
+
p.log.success(pc.green("Thank you so much for your support! \u2B50 Opening GitHub repository in your browser..."));
|
|
2232
|
+
} else {
|
|
2233
|
+
p.log.message(pc.gray("No problem, enjoy building with LaraTS! \u{1F44D}"));
|
|
2234
|
+
}
|
|
2235
|
+
} else {
|
|
2236
|
+
console.log(
|
|
2237
|
+
`
|
|
2238
|
+
${pc.yellow("\u2B50 Enjoying LaraTS?")} If you find this CLI useful, please consider giving it a star on GitHub:
|
|
2239
|
+
${pc.bold(pc.cyan("\u{1F449} " + repoUrl))}
|
|
2240
|
+
`
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
fs3.writeFileSync(statsFile, JSON.stringify(stats, null, 2), "utf-8");
|
|
2245
|
+
} catch {
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function openViewer(destination) {
|
|
2249
|
+
const startCmd = process.platform === "win32" ? `start "" "${destination}"` : process.platform === "darwin" ? `open "${destination}"` : `xdg-open "${destination}"`;
|
|
2250
|
+
try {
|
|
2251
|
+
exec(startCmd, (err) => {
|
|
2252
|
+
if (err) {
|
|
2253
|
+
p.log.warn(`Could not open browser automatically. Please open ${pc.cyan(destination)} manually.`);
|
|
2254
|
+
}
|
|
2255
|
+
});
|
|
2256
|
+
} catch {
|
|
2257
|
+
p.log.warn(`Could not open browser automatically. Please open ${pc.cyan(destination)} manually.`);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
async function runAi(projectInfo, options = {}) {
|
|
2261
|
+
const spinner2 = p.spinner();
|
|
2262
|
+
spinner2.start("Scanning codebase & formulating AI architecture context...");
|
|
2263
|
+
try {
|
|
2264
|
+
const scanResult = await scanProject(projectInfo);
|
|
2265
|
+
spinner2.stop(pc.green(`Architecture context ready (${scanResult.scanDurationMs}ms)`));
|
|
2266
|
+
const aiContext = generateAiContext(scanResult);
|
|
2267
|
+
let handled = false;
|
|
2268
|
+
if (options.cursor) {
|
|
2269
|
+
const cursorPath = path4.resolve(projectInfo.rootPath, ".cursorrules");
|
|
2270
|
+
const existing = fs3.existsSync(cursorPath) ? fs3.readFileSync(cursorPath, "utf-8") : "";
|
|
2271
|
+
const updated = mergeAiRules(existing, aiContext);
|
|
2272
|
+
fs3.writeFileSync(cursorPath, updated, "utf-8");
|
|
2273
|
+
p.log.success(pc.green(`\u2714 Architecture rules injected into ${pc.bold(".cursorrules")}`));
|
|
2274
|
+
handled = true;
|
|
2275
|
+
}
|
|
2276
|
+
if (options.claude) {
|
|
2277
|
+
const claudePath = path4.resolve(projectInfo.rootPath, "CLAUDE.md");
|
|
2278
|
+
const existing = fs3.existsSync(claudePath) ? fs3.readFileSync(claudePath, "utf-8") : "";
|
|
2279
|
+
const updated = mergeAiRules(existing, aiContext);
|
|
2280
|
+
fs3.writeFileSync(claudePath, updated, "utf-8");
|
|
2281
|
+
p.log.success(pc.green(`\u2714 Architecture rules injected into ${pc.bold("CLAUDE.md")}`));
|
|
2282
|
+
handled = true;
|
|
2283
|
+
}
|
|
2284
|
+
if (options.copy) {
|
|
2285
|
+
const copied = await copyToClipboard(aiContext);
|
|
2286
|
+
if (copied) {
|
|
2287
|
+
p.log.success(pc.green("\u2714 Architecture context copied to clipboard! Ready to paste into ChatGPT or Claude."));
|
|
2288
|
+
} else {
|
|
2289
|
+
p.log.warn("Could not access system clipboard. Printing prompt to console instead.");
|
|
2290
|
+
console.log(`
|
|
2291
|
+
${aiContext}
|
|
2292
|
+
`);
|
|
2293
|
+
}
|
|
2294
|
+
handled = true;
|
|
2295
|
+
}
|
|
2296
|
+
if (!handled) {
|
|
2297
|
+
console.log(`
|
|
2298
|
+
${pc.bold(pc.cyan("--- AI Codebase Architecture Prompt ---"))}
|
|
2299
|
+
${aiContext}
|
|
2300
|
+
${pc.bold(pc.cyan("---------------------------------------"))}
|
|
2301
|
+
`);
|
|
2302
|
+
p.note(
|
|
2303
|
+
`Tip: Run with ${pc.yellow("--copy")} to copy directly to clipboard,
|
|
2304
|
+
or ${pc.yellow("--cursor")} / ${pc.yellow("--claude")} to inject into rules files automatically!`,
|
|
2305
|
+
pc.green("Prompt Ready for AI \u{1F916}")
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
p.outro(pc.cyan("Done!"));
|
|
2309
|
+
} catch (err) {
|
|
2310
|
+
spinner2.stop(pc.red("Failed to generate AI context"));
|
|
2311
|
+
p.log.error(err instanceof Error ? err.message : String(err));
|
|
2312
|
+
process.exitCode = 1;
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
1890
2315
|
async function runScan(projectInfo, outputPath = "ARCHITECTURE.md", options = {}) {
|
|
1891
2316
|
const spinner2 = p.spinner();
|
|
1892
2317
|
spinner2.start("Scanning codebase structure & components (strict ignore list active)...");
|
|
@@ -1915,15 +2340,25 @@ To protect user files from accidental overwrites, use --force to overwrite.`
|
|
|
1915
2340
|
const scanResult = await scanProject(projectInfo);
|
|
1916
2341
|
const nonZeroLayers = Object.values(scanResult.components).filter((c) => c.count > 0).length;
|
|
1917
2342
|
const totalComponents = Object.values(scanResult.components).reduce((sum, c) => sum + c.count, 0);
|
|
2343
|
+
const terminalFlow = renderTerminalFlow(scanResult);
|
|
1918
2344
|
if (options.dryRun) {
|
|
1919
2345
|
spinner2.stop(pc.green(`Scan completed in ${scanResult.scanDurationMs}ms (dry run)`));
|
|
1920
2346
|
p.note(
|
|
1921
|
-
`${pc.bold("
|
|
2347
|
+
`${pc.bold("Architecture Flow:")}
|
|
2348
|
+
${pc.cyan(terminalFlow)}
|
|
2349
|
+
|
|
2350
|
+
${pc.bold("Scanned Files:")} ${scanResult.totalFilesScanned}
|
|
1922
2351
|
${pc.bold("Total Components:")} ${totalComponents} across ${nonZeroLayers} active architectural layers
|
|
1923
2352
|
${pc.bold("Modules Detected:")} ${scanResult.modules.length > 0 ? scanResult.modules.join(", ") : "None (monolithic structure)"}
|
|
1924
2353
|
${pc.bold("Simulated Output:")} Would write ARCHITECTURE.md to ${pc.cyan(outputPath)} (dry run: no files written)`,
|
|
1925
2354
|
pc.green("Architecture Map (Dry Run) \u{1F5FA}\uFE0F")
|
|
1926
2355
|
);
|
|
2356
|
+
if (options.tree) {
|
|
2357
|
+
console.log(`
|
|
2358
|
+
${pc.bold("Directory Tree:")}
|
|
2359
|
+
${scanResult.directoryTree}
|
|
2360
|
+
`);
|
|
2361
|
+
}
|
|
1927
2362
|
p.outro(pc.cyan("Dry run completed: no files written."));
|
|
1928
2363
|
return;
|
|
1929
2364
|
}
|
|
@@ -1934,14 +2369,38 @@ ${pc.bold("Simulated Output:")} Would write ARCHITECTURE.md to ${pc.cyan(outputP
|
|
|
1934
2369
|
fs3.mkdirSync(parentDir, { recursive: true });
|
|
1935
2370
|
}
|
|
1936
2371
|
fs3.writeFileSync(destination, markdown, "utf-8");
|
|
2372
|
+
const shouldWriteHtml = options.html || options.view;
|
|
2373
|
+
if (shouldWriteHtml) {
|
|
2374
|
+
const htmlDestination = destination.replace(/\.md$/i, "") + ".html";
|
|
2375
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2376
|
+
p.log.success(`HTML architecture saved to ${pc.cyan(path4.relative(projectInfo.rootPath, htmlDestination) || htmlDestination)}`);
|
|
2377
|
+
if (options.view) {
|
|
2378
|
+
openViewer(htmlDestination);
|
|
2379
|
+
p.log.info(pc.green("\u{1F310} Opened interactive architecture diagram in your browser!"));
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
if (options.tree) {
|
|
2383
|
+
console.log(`
|
|
2384
|
+
${pc.bold("Directory Tree:")}
|
|
2385
|
+
${scanResult.directoryTree}
|
|
2386
|
+
`);
|
|
2387
|
+
}
|
|
1937
2388
|
p.note(
|
|
1938
|
-
`${pc.bold("
|
|
2389
|
+
`${pc.bold("Architecture Flow:")}
|
|
2390
|
+
${pc.cyan(terminalFlow)}
|
|
2391
|
+
|
|
2392
|
+
${pc.bold("Scanned Files:")} ${scanResult.totalFilesScanned}
|
|
1939
2393
|
${pc.bold("Total Components:")} ${totalComponents} across ${nonZeroLayers} active architectural layers
|
|
1940
2394
|
${pc.bold("Modules Detected:")} ${scanResult.modules.length > 0 ? scanResult.modules.join(", ") : "None (monolithic structure)"}
|
|
1941
|
-
${pc.bold("Documentation:")} Saved to ${pc.cyan(path4.relative(projectInfo.rootPath, destination) || outputPath)}
|
|
2395
|
+
${pc.bold("Documentation:")} Saved to ${pc.cyan(path4.relative(projectInfo.rootPath, destination) || outputPath)}
|
|
2396
|
+
|
|
2397
|
+
${pc.bold("\u{1F4A1} How to view diagram:")}
|
|
2398
|
+
\u2022 On GitHub: Push ${outputPath} (Mermaid renders automatically)
|
|
2399
|
+
\u2022 In VS Code: Press ${pc.yellow("Ctrl+Shift+V")} for Markdown Preview
|
|
2400
|
+
\u2022 In Browser: Run ${pc.cyan("npx larats view")} for instant local web preview`,
|
|
1942
2401
|
pc.green("Architecture Map Generated! \u{1F5FA}\uFE0F")
|
|
1943
2402
|
);
|
|
1944
|
-
p.outro(pc.cyan(`Open ${outputPath}
|
|
2403
|
+
p.outro(pc.cyan(`Done! Open ${outputPath} or run "npx larats view" to inspect architecture!`));
|
|
1945
2404
|
} catch (err) {
|
|
1946
2405
|
spinner2.stop(pc.red("Scan failed"));
|
|
1947
2406
|
p.log.error(err.message || String(err));
|
|
@@ -2172,14 +2631,20 @@ async function main() {
|
|
|
2172
2631
|
standard: { type: "boolean", short: "s" },
|
|
2173
2632
|
force: { type: "boolean", short: "f" },
|
|
2174
2633
|
"dry-run": { type: "boolean" },
|
|
2634
|
+
view: { type: "boolean" },
|
|
2635
|
+
tree: { type: "boolean", short: "t" },
|
|
2636
|
+
html: { type: "boolean" },
|
|
2175
2637
|
version: { type: "boolean", short: "v" },
|
|
2638
|
+
copy: { type: "boolean" },
|
|
2639
|
+
cursor: { type: "boolean" },
|
|
2640
|
+
claude: { type: "boolean" },
|
|
2176
2641
|
help: { type: "boolean", short: "h" }
|
|
2177
2642
|
},
|
|
2178
2643
|
allowPositionals: true,
|
|
2179
2644
|
strict: true
|
|
2180
2645
|
});
|
|
2181
2646
|
} catch (err) {
|
|
2182
|
-
console.error(pc.red(`Error: ${err.message}`));
|
|
2647
|
+
console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
2183
2648
|
showHelp();
|
|
2184
2649
|
process.exitCode = 1;
|
|
2185
2650
|
return;
|
|
@@ -2193,8 +2658,17 @@ async function main() {
|
|
|
2193
2658
|
return;
|
|
2194
2659
|
}
|
|
2195
2660
|
const command = parsed.positionals[0];
|
|
2196
|
-
const VALID_COMMANDS =
|
|
2197
|
-
|
|
2661
|
+
const VALID_COMMANDS = {
|
|
2662
|
+
scan: true,
|
|
2663
|
+
view: true,
|
|
2664
|
+
preview: true,
|
|
2665
|
+
scaffold: true,
|
|
2666
|
+
make: true,
|
|
2667
|
+
info: true,
|
|
2668
|
+
ai: true,
|
|
2669
|
+
help: true
|
|
2670
|
+
};
|
|
2671
|
+
if (command && !VALID_COMMANDS[command]) {
|
|
2198
2672
|
console.error(pc.red(`Error: Unknown command "${command}".`));
|
|
2199
2673
|
showHelp();
|
|
2200
2674
|
process.exitCode = 1;
|
|
@@ -2205,14 +2679,52 @@ async function main() {
|
|
|
2205
2679
|
const isStandardFlag = parsed.values.standard;
|
|
2206
2680
|
const isForceFlag = parsed.values.force;
|
|
2207
2681
|
const isDryRunFlag = parsed.values["dry-run"];
|
|
2682
|
+
const isViewFlag = parsed.values.view;
|
|
2683
|
+
const isTreeFlag = parsed.values.tree;
|
|
2684
|
+
const isHtmlFlag = parsed.values.html;
|
|
2208
2685
|
const projectInfo = await detectProjectType(process.cwd());
|
|
2209
2686
|
if (command === "scan") {
|
|
2210
2687
|
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - Scanning Architecture`);
|
|
2211
2688
|
p.log.info(`Detected: ${pc.green(projectInfo.summary)}`);
|
|
2212
2689
|
await runScan(projectInfo, outputPath, {
|
|
2213
2690
|
force: isForceFlag,
|
|
2214
|
-
dryRun: isDryRunFlag
|
|
2691
|
+
dryRun: isDryRunFlag,
|
|
2692
|
+
view: isViewFlag,
|
|
2693
|
+
tree: isTreeFlag,
|
|
2694
|
+
html: isHtmlFlag
|
|
2695
|
+
});
|
|
2696
|
+
await checkAndPromptStar();
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
if (command === "view" || command === "preview") {
|
|
2700
|
+
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - Architecture Viewer`);
|
|
2701
|
+
p.log.info(`Target: ${pc.green(projectInfo.summary)}`);
|
|
2702
|
+
const spinner2 = p.spinner();
|
|
2703
|
+
spinner2.start("Scanning repository & generating architecture map...");
|
|
2704
|
+
const scanResult = await scanProject(projectInfo);
|
|
2705
|
+
spinner2.stop(pc.green(`Scanned ${scanResult.totalFilesScanned} files in ${scanResult.scanDurationMs}ms`));
|
|
2706
|
+
const htmlDestination = path4.resolve(projectInfo.rootPath, "ARCHITECTURE.html");
|
|
2707
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2708
|
+
openViewer(htmlDestination);
|
|
2709
|
+
p.note(
|
|
2710
|
+
`${pc.bold("File:")} ${pc.cyan("ARCHITECTURE.html")}
|
|
2711
|
+
Interactive Mermaid.js flowchart & component breakdown loaded in your browser.
|
|
2712
|
+
Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
2713
|
+
pc.green("Opened in Browser! \u{1F310}")
|
|
2714
|
+
);
|
|
2715
|
+
p.outro(pc.cyan("Done!"));
|
|
2716
|
+
await checkAndPromptStar();
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
if (command === "ai") {
|
|
2720
|
+
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - AI Architecture Context`);
|
|
2721
|
+
p.log.info(`Target: ${pc.green(projectInfo.summary)}`);
|
|
2722
|
+
await runAi(projectInfo, {
|
|
2723
|
+
copy: parsed.values.copy,
|
|
2724
|
+
cursor: parsed.values.cursor,
|
|
2725
|
+
claude: parsed.values.claude
|
|
2215
2726
|
});
|
|
2727
|
+
await checkAndPromptStar();
|
|
2216
2728
|
return;
|
|
2217
2729
|
}
|
|
2218
2730
|
if (command === "scaffold" || command === "make") {
|
|
@@ -2225,12 +2737,14 @@ async function main() {
|
|
|
2225
2737
|
force: isForceFlag,
|
|
2226
2738
|
dryRun: isDryRunFlag
|
|
2227
2739
|
});
|
|
2740
|
+
await checkAndPromptStar();
|
|
2228
2741
|
return;
|
|
2229
2742
|
}
|
|
2230
2743
|
if (command === "info") {
|
|
2231
2744
|
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)}`);
|
|
2232
2745
|
showProjectInfo(projectInfo);
|
|
2233
2746
|
p.outro(pc.cyan("Done."));
|
|
2747
|
+
await checkAndPromptStar();
|
|
2234
2748
|
return;
|
|
2235
2749
|
}
|
|
2236
2750
|
if (!process.stdin.isTTY) {
|
|
@@ -2248,6 +2762,16 @@ async function main() {
|
|
|
2248
2762
|
label: "\u{1F50D} Scan Architecture & Generate ARCHITECTURE.md",
|
|
2249
2763
|
hint: "Creates component stats, directory tree & Mermaid flowchart"
|
|
2250
2764
|
},
|
|
2765
|
+
{
|
|
2766
|
+
value: "ai",
|
|
2767
|
+
label: "\u{1F916} Export Architecture Context for AI (Cursor, Claude, Copilot)",
|
|
2768
|
+
hint: "Generate architecture rules for AI coding agents"
|
|
2769
|
+
},
|
|
2770
|
+
{
|
|
2771
|
+
value: "view",
|
|
2772
|
+
label: "\u{1F310} Open Interactive Architecture Viewer in Browser",
|
|
2773
|
+
hint: "Spawns local browser tab with Mermaid flowchart & stats"
|
|
2774
|
+
},
|
|
2251
2775
|
{
|
|
2252
2776
|
value: "scaffold",
|
|
2253
2777
|
label: "\u{1F3D7}\uFE0F Scaffold New Feature / Module",
|
|
@@ -2270,8 +2794,38 @@ async function main() {
|
|
|
2270
2794
|
if (action === "scan") {
|
|
2271
2795
|
await runScan(projectInfo, outputPath, {
|
|
2272
2796
|
force: isForceFlag,
|
|
2273
|
-
dryRun: isDryRunFlag
|
|
2797
|
+
dryRun: isDryRunFlag,
|
|
2798
|
+
view: isViewFlag,
|
|
2799
|
+
tree: isTreeFlag,
|
|
2800
|
+
html: isHtmlFlag
|
|
2274
2801
|
});
|
|
2802
|
+
} else if (action === "ai") {
|
|
2803
|
+
const aiTarget = await p.select({
|
|
2804
|
+
message: "How would you like to export the AI architecture context?",
|
|
2805
|
+
options: [
|
|
2806
|
+
{ value: "copy", label: "\u{1F4CB} Copy directly to Clipboard", hint: "Ready to paste into ChatGPT or Claude web" },
|
|
2807
|
+
{ value: "cursor", label: "\u{1F5B1}\uFE0F Save into .cursorrules", hint: "Inject rules for Cursor AI" },
|
|
2808
|
+
{ value: "claude", label: "\u{1F9E0} Save into CLAUDE.md", hint: "Inject rules for Claude Code" },
|
|
2809
|
+
{ value: "print", label: "\u{1F5A5}\uFE0F Display prompt in terminal", hint: "View formatted markdown text" }
|
|
2810
|
+
]
|
|
2811
|
+
});
|
|
2812
|
+
if (p.isCancel(aiTarget)) {
|
|
2813
|
+
handleCancel();
|
|
2814
|
+
}
|
|
2815
|
+
await runAi(projectInfo, {
|
|
2816
|
+
copy: aiTarget === "copy",
|
|
2817
|
+
cursor: aiTarget === "cursor",
|
|
2818
|
+
claude: aiTarget === "claude"
|
|
2819
|
+
});
|
|
2820
|
+
} else if (action === "view") {
|
|
2821
|
+
const spinner2 = p.spinner();
|
|
2822
|
+
spinner2.start("Scanning repository & generating architecture map...");
|
|
2823
|
+
const scanResult = await scanProject(projectInfo);
|
|
2824
|
+
spinner2.stop(pc.green(`Scanned in ${scanResult.scanDurationMs}ms`));
|
|
2825
|
+
const htmlDestination = path4.resolve(projectInfo.rootPath, "ARCHITECTURE.html");
|
|
2826
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2827
|
+
openViewer(htmlDestination);
|
|
2828
|
+
p.outro(pc.green("\u{1F310} Opened interactive architecture diagram in your browser!"));
|
|
2275
2829
|
} else if (action === "scaffold") {
|
|
2276
2830
|
await runScaffold(projectInfo, void 0, {
|
|
2277
2831
|
modular: isModularFlag,
|
|
@@ -2283,9 +2837,13 @@ async function main() {
|
|
|
2283
2837
|
showProjectInfo(projectInfo);
|
|
2284
2838
|
p.outro(pc.cyan("Done!"));
|
|
2285
2839
|
}
|
|
2840
|
+
await checkAndPromptStar();
|
|
2286
2841
|
}
|
|
2287
2842
|
main().catch((err) => {
|
|
2288
2843
|
p.log.error(pc.red(`Fatal error: ${err.message || String(err)}`));
|
|
2289
2844
|
process.exit(1);
|
|
2290
2845
|
});
|
|
2846
|
+
export {
|
|
2847
|
+
checkAndPromptStar
|
|
2848
|
+
};
|
|
2291
2849
|
//# sourceMappingURL=index.js.map
|