larats 1.0.2 → 1.1.0
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 +42 -4
- package/dist/index.js +333 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# LaraTS ⚡
|
|
2
2
|
|
|
3
3
|
> **Smart Architecture Visualizer & Contextual Module Scaffolder CLI**
|
|
4
|
-
> Tailor-made for **Laravel**
|
|
4
|
+
> Tailor-made for **Laravel**, **TypeScript**, and **JavaScript (Node.js)** projects.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/larats)
|
|
7
7
|
[](https://github.com/SidiqFatuh187/Larats/actions/workflows/ci.yml)
|
|
@@ -30,9 +30,12 @@ Run instantly without installation via `npx`:
|
|
|
30
30
|
# Interactive menu mode
|
|
31
31
|
npx larats
|
|
32
32
|
|
|
33
|
-
#
|
|
33
|
+
# Scan architecture & display terminal pipeline
|
|
34
34
|
npx larats scan
|
|
35
35
|
|
|
36
|
+
# Open interactive Mermaid diagram in your browser
|
|
37
|
+
npx larats view
|
|
38
|
+
|
|
36
39
|
# Or scaffold a new module directly
|
|
37
40
|
npx larats scaffold Payment
|
|
38
41
|
```
|
|
@@ -44,6 +47,38 @@ npm install -g larats
|
|
|
44
47
|
larats
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 👀 How to View Your Project Architecture
|
|
54
|
+
|
|
55
|
+
LaraTS makes your codebase architecture visible in 4 convenient ways:
|
|
56
|
+
|
|
57
|
+
1. 🌐 **Interactive Local Browser (`larats view` / `larats scan --view`)**:
|
|
58
|
+
Spawns a local browser tab displaying the interactive Mermaid flowchart, component counts, and directory tree.
|
|
59
|
+
```bash
|
|
60
|
+
npx larats view
|
|
61
|
+
# or scan and open viewer immediately
|
|
62
|
+
npx larats scan --view
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
2. 📟 **Terminal Architecture Pipeline**:
|
|
66
|
+
Every time you run `larats scan`, LaraTS prints an ASCII pipeline of your request lifecycle directly in the terminal:
|
|
67
|
+
```text
|
|
68
|
+
Client ──> Routes [4] ──> Controllers [4] ──> Services [2] ──> Models [6] ──> Database
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
3. 🐙 **GitHub & GitLab Native Preview**:
|
|
72
|
+
Commit and push `ARCHITECTURE.md` to your repository. GitHub natively renders Mermaid.js flowcharts into interactive graphics directly on the web page.
|
|
73
|
+
|
|
74
|
+
4. 💻 **VS Code & Cursor**:
|
|
75
|
+
Open `ARCHITECTURE.md` and press `Ctrl+Shift+V` (`Cmd+Shift+V` on macOS) to open the built-in Markdown Preview.
|
|
76
|
+
|
|
77
|
+
5. 📄 **Standalone HTML Export (`--html`)**:
|
|
78
|
+
Generate a self-contained `ARCHITECTURE.html` file that can be opened in any web browser without any server:
|
|
79
|
+
```bash
|
|
80
|
+
npx larats scan --html
|
|
81
|
+
```
|
|
47
82
|
---
|
|
48
83
|
|
|
49
84
|
## ✨ Key Features
|
|
@@ -106,19 +141,22 @@ USAGE:
|
|
|
106
141
|
|
|
107
142
|
COMMANDS:
|
|
108
143
|
scan Scan repository & generate ARCHITECTURE.md
|
|
144
|
+
view, preview Open interactive architecture diagram in local browser
|
|
109
145
|
scaffold, make [name] Scaffold a new module or feature with clean boilerplate
|
|
110
146
|
info Show detected project type & architectural flavor
|
|
111
147
|
help Display help message
|
|
112
148
|
|
|
113
149
|
OPTIONS:
|
|
114
|
-
-o, --output <file> Output
|
|
150
|
+
-o, --output <file> Output path for scan (default: ARCHITECTURE.md)
|
|
151
|
+
--view Open local browser viewer immediately after scanning
|
|
152
|
+
-t, --tree Print directory tree in terminal
|
|
153
|
+
--html Also export standalone ARCHITECTURE.html
|
|
115
154
|
-m, --modular Enforce modular directory structure
|
|
116
155
|
-s, --standard Enforce standard/layered directory structure
|
|
117
156
|
--dry-run Simulate generation without writing files
|
|
118
157
|
-f, --force Overwrite files if they already exist
|
|
119
158
|
-v, --version Display CLI version
|
|
120
159
|
-h, --help Display help message
|
|
121
|
-
```
|
|
122
160
|
|
|
123
161
|
### Examples
|
|
124
162
|
|
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,183 @@ ${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
|
+
}
|
|
911
1093
|
|
|
912
1094
|
// src/generator/scaffolder.ts
|
|
913
1095
|
import fs2 from "fs";
|
|
@@ -1850,7 +2032,7 @@ async function scaffoldModule(projectInfo, options) {
|
|
|
1850
2032
|
}
|
|
1851
2033
|
|
|
1852
2034
|
// src/index.ts
|
|
1853
|
-
var VERSION = true ? "1.0
|
|
2035
|
+
var VERSION = true ? "1.1.0" : "1.1.0";
|
|
1854
2036
|
function showHelp() {
|
|
1855
2037
|
console.log(`
|
|
1856
2038
|
${pc.bold(pc.cyan("LaraTS CLI"))} ${pc.gray(`v${VERSION}`)}
|
|
@@ -1862,19 +2044,22 @@ ${pc.bold("USAGE:")}
|
|
|
1862
2044
|
|
|
1863
2045
|
${pc.bold("COMMANDS:")}
|
|
1864
2046
|
${pc.green("scan")} Scan repository & generate ARCHITECTURE.md
|
|
2047
|
+
${pc.green("view")}, ${pc.green("preview")} Open interactive architecture diagram in local browser
|
|
1865
2048
|
${pc.green("scaffold")}, ${pc.green("make")} [name] Scaffold a new module or feature with clean boilerplate
|
|
1866
2049
|
${pc.green("info")} Show detected project type & architectural flavor
|
|
1867
2050
|
${pc.green("help")} Display this help message
|
|
1868
2051
|
|
|
1869
2052
|
${pc.bold("OPTIONS:")}
|
|
1870
|
-
${pc.yellow("-o, --output")} <file> Output
|
|
2053
|
+
${pc.yellow("-o, --output")} <file> Output path for scan (default: ARCHITECTURE.md)
|
|
2054
|
+
${pc.yellow("--view")} Open local browser viewer immediately after scanning
|
|
2055
|
+
${pc.yellow("-t, --tree")} Print directory tree in terminal
|
|
2056
|
+
${pc.yellow("--html")} Also export standalone ARCHITECTURE.html
|
|
1871
2057
|
${pc.yellow("-m, --modular")} Enforce modular directory structure
|
|
1872
2058
|
${pc.yellow("-s, --standard")} Enforce standard/layered directory structure
|
|
1873
2059
|
${pc.yellow("--dry-run")} Simulate generation without writing files
|
|
1874
2060
|
${pc.yellow("-f, --force")} Overwrite files if they already exist
|
|
1875
2061
|
${pc.yellow("-v, --version")} Display CLI version
|
|
1876
2062
|
${pc.yellow("-h, --help")} Display this help message
|
|
1877
|
-
|
|
1878
2063
|
${pc.bold("EXAMPLES:")}
|
|
1879
2064
|
${pc.cyan("$")} larats
|
|
1880
2065
|
${pc.cyan("$")} larats scan
|
|
@@ -1887,6 +2072,43 @@ function handleCancel() {
|
|
|
1887
2072
|
p.cancel(pc.yellow("Operation cancelled. See you next time! \u{1F44B}"));
|
|
1888
2073
|
process.exit(0);
|
|
1889
2074
|
}
|
|
2075
|
+
function checkAndPromptStar() {
|
|
2076
|
+
try {
|
|
2077
|
+
const configDir = path4.join(os2.homedir(), ".larats");
|
|
2078
|
+
const statsFile = path4.join(configDir, "stats.json");
|
|
2079
|
+
if (!fs3.existsSync(configDir)) {
|
|
2080
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
2081
|
+
}
|
|
2082
|
+
let stats = {};
|
|
2083
|
+
if (fs3.existsSync(statsFile)) {
|
|
2084
|
+
stats = JSON.parse(fs3.readFileSync(statsFile, "utf-8"));
|
|
2085
|
+
}
|
|
2086
|
+
const runs = (stats.runs || 0) + 1;
|
|
2087
|
+
stats.runs = runs;
|
|
2088
|
+
fs3.writeFileSync(statsFile, JSON.stringify(stats, null, 2), "utf-8");
|
|
2089
|
+
if (runs === 2 || runs === 3) {
|
|
2090
|
+
console.log(
|
|
2091
|
+
`
|
|
2092
|
+
${pc.yellow("\u2B50 Enjoying LaraTS?")} If you find this CLI useful, please consider giving it a star on GitHub:
|
|
2093
|
+
${pc.bold(pc.cyan("\u{1F449} https://github.com/SidiqFatuh187/larats-cli"))}
|
|
2094
|
+
`
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
} catch {
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
function openViewer(destination) {
|
|
2101
|
+
const startCmd = process.platform === "win32" ? `start "" "${destination}"` : process.platform === "darwin" ? `open "${destination}"` : `xdg-open "${destination}"`;
|
|
2102
|
+
try {
|
|
2103
|
+
exec(startCmd, (err) => {
|
|
2104
|
+
if (err) {
|
|
2105
|
+
p.log.warn(`Could not open browser automatically. Please open ${pc.cyan(destination)} manually.`);
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
} catch {
|
|
2109
|
+
p.log.warn(`Could not open browser automatically. Please open ${pc.cyan(destination)} manually.`);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
1890
2112
|
async function runScan(projectInfo, outputPath = "ARCHITECTURE.md", options = {}) {
|
|
1891
2113
|
const spinner2 = p.spinner();
|
|
1892
2114
|
spinner2.start("Scanning codebase structure & components (strict ignore list active)...");
|
|
@@ -1915,15 +2137,25 @@ To protect user files from accidental overwrites, use --force to overwrite.`
|
|
|
1915
2137
|
const scanResult = await scanProject(projectInfo);
|
|
1916
2138
|
const nonZeroLayers = Object.values(scanResult.components).filter((c) => c.count > 0).length;
|
|
1917
2139
|
const totalComponents = Object.values(scanResult.components).reduce((sum, c) => sum + c.count, 0);
|
|
2140
|
+
const terminalFlow = renderTerminalFlow(scanResult);
|
|
1918
2141
|
if (options.dryRun) {
|
|
1919
2142
|
spinner2.stop(pc.green(`Scan completed in ${scanResult.scanDurationMs}ms (dry run)`));
|
|
1920
2143
|
p.note(
|
|
1921
|
-
`${pc.bold("
|
|
2144
|
+
`${pc.bold("Architecture Flow:")}
|
|
2145
|
+
${pc.cyan(terminalFlow)}
|
|
2146
|
+
|
|
2147
|
+
${pc.bold("Scanned Files:")} ${scanResult.totalFilesScanned}
|
|
1922
2148
|
${pc.bold("Total Components:")} ${totalComponents} across ${nonZeroLayers} active architectural layers
|
|
1923
2149
|
${pc.bold("Modules Detected:")} ${scanResult.modules.length > 0 ? scanResult.modules.join(", ") : "None (monolithic structure)"}
|
|
1924
2150
|
${pc.bold("Simulated Output:")} Would write ARCHITECTURE.md to ${pc.cyan(outputPath)} (dry run: no files written)`,
|
|
1925
2151
|
pc.green("Architecture Map (Dry Run) \u{1F5FA}\uFE0F")
|
|
1926
2152
|
);
|
|
2153
|
+
if (options.tree) {
|
|
2154
|
+
console.log(`
|
|
2155
|
+
${pc.bold("Directory Tree:")}
|
|
2156
|
+
${scanResult.directoryTree}
|
|
2157
|
+
`);
|
|
2158
|
+
}
|
|
1927
2159
|
p.outro(pc.cyan("Dry run completed: no files written."));
|
|
1928
2160
|
return;
|
|
1929
2161
|
}
|
|
@@ -1934,14 +2166,38 @@ ${pc.bold("Simulated Output:")} Would write ARCHITECTURE.md to ${pc.cyan(outputP
|
|
|
1934
2166
|
fs3.mkdirSync(parentDir, { recursive: true });
|
|
1935
2167
|
}
|
|
1936
2168
|
fs3.writeFileSync(destination, markdown, "utf-8");
|
|
2169
|
+
const shouldWriteHtml = options.html || options.view;
|
|
2170
|
+
if (shouldWriteHtml) {
|
|
2171
|
+
const htmlDestination = destination.replace(/\.md$/i, "") + ".html";
|
|
2172
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2173
|
+
p.log.success(`HTML architecture saved to ${pc.cyan(path4.relative(projectInfo.rootPath, htmlDestination) || htmlDestination)}`);
|
|
2174
|
+
if (options.view) {
|
|
2175
|
+
openViewer(htmlDestination);
|
|
2176
|
+
p.log.info(pc.green("\u{1F310} Opened interactive architecture diagram in your browser!"));
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
if (options.tree) {
|
|
2180
|
+
console.log(`
|
|
2181
|
+
${pc.bold("Directory Tree:")}
|
|
2182
|
+
${scanResult.directoryTree}
|
|
2183
|
+
`);
|
|
2184
|
+
}
|
|
1937
2185
|
p.note(
|
|
1938
|
-
`${pc.bold("
|
|
2186
|
+
`${pc.bold("Architecture Flow:")}
|
|
2187
|
+
${pc.cyan(terminalFlow)}
|
|
2188
|
+
|
|
2189
|
+
${pc.bold("Scanned Files:")} ${scanResult.totalFilesScanned}
|
|
1939
2190
|
${pc.bold("Total Components:")} ${totalComponents} across ${nonZeroLayers} active architectural layers
|
|
1940
2191
|
${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)}
|
|
2192
|
+
${pc.bold("Documentation:")} Saved to ${pc.cyan(path4.relative(projectInfo.rootPath, destination) || outputPath)}
|
|
2193
|
+
|
|
2194
|
+
${pc.bold("\u{1F4A1} How to view diagram:")}
|
|
2195
|
+
\u2022 On GitHub: Push ${outputPath} (Mermaid renders automatically)
|
|
2196
|
+
\u2022 In VS Code: Press ${pc.yellow("Ctrl+Shift+V")} for Markdown Preview
|
|
2197
|
+
\u2022 In Browser: Run ${pc.cyan("npx larats view")} for instant local web preview`,
|
|
1942
2198
|
pc.green("Architecture Map Generated! \u{1F5FA}\uFE0F")
|
|
1943
2199
|
);
|
|
1944
|
-
p.outro(pc.cyan(`Open ${outputPath}
|
|
2200
|
+
p.outro(pc.cyan(`Done! Open ${outputPath} or run "npx larats view" to inspect architecture!`));
|
|
1945
2201
|
} catch (err) {
|
|
1946
2202
|
spinner2.stop(pc.red("Scan failed"));
|
|
1947
2203
|
p.log.error(err.message || String(err));
|
|
@@ -2172,6 +2428,9 @@ async function main() {
|
|
|
2172
2428
|
standard: { type: "boolean", short: "s" },
|
|
2173
2429
|
force: { type: "boolean", short: "f" },
|
|
2174
2430
|
"dry-run": { type: "boolean" },
|
|
2431
|
+
view: { type: "boolean" },
|
|
2432
|
+
tree: { type: "boolean", short: "t" },
|
|
2433
|
+
html: { type: "boolean" },
|
|
2175
2434
|
version: { type: "boolean", short: "v" },
|
|
2176
2435
|
help: { type: "boolean", short: "h" }
|
|
2177
2436
|
},
|
|
@@ -2193,8 +2452,16 @@ async function main() {
|
|
|
2193
2452
|
return;
|
|
2194
2453
|
}
|
|
2195
2454
|
const command = parsed.positionals[0];
|
|
2196
|
-
const VALID_COMMANDS =
|
|
2197
|
-
|
|
2455
|
+
const VALID_COMMANDS = {
|
|
2456
|
+
scan: true,
|
|
2457
|
+
view: true,
|
|
2458
|
+
preview: true,
|
|
2459
|
+
scaffold: true,
|
|
2460
|
+
make: true,
|
|
2461
|
+
info: true,
|
|
2462
|
+
help: true
|
|
2463
|
+
};
|
|
2464
|
+
if (command && !VALID_COMMANDS[command]) {
|
|
2198
2465
|
console.error(pc.red(`Error: Unknown command "${command}".`));
|
|
2199
2466
|
showHelp();
|
|
2200
2467
|
process.exitCode = 1;
|
|
@@ -2205,14 +2472,41 @@ async function main() {
|
|
|
2205
2472
|
const isStandardFlag = parsed.values.standard;
|
|
2206
2473
|
const isForceFlag = parsed.values.force;
|
|
2207
2474
|
const isDryRunFlag = parsed.values["dry-run"];
|
|
2475
|
+
const isViewFlag = parsed.values.view;
|
|
2476
|
+
const isTreeFlag = parsed.values.tree;
|
|
2477
|
+
const isHtmlFlag = parsed.values.html;
|
|
2208
2478
|
const projectInfo = await detectProjectType(process.cwd());
|
|
2209
2479
|
if (command === "scan") {
|
|
2210
2480
|
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - Scanning Architecture`);
|
|
2211
2481
|
p.log.info(`Detected: ${pc.green(projectInfo.summary)}`);
|
|
2212
2482
|
await runScan(projectInfo, outputPath, {
|
|
2213
2483
|
force: isForceFlag,
|
|
2214
|
-
dryRun: isDryRunFlag
|
|
2484
|
+
dryRun: isDryRunFlag,
|
|
2485
|
+
view: isViewFlag,
|
|
2486
|
+
tree: isTreeFlag,
|
|
2487
|
+
html: isHtmlFlag
|
|
2215
2488
|
});
|
|
2489
|
+
checkAndPromptStar();
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
if (command === "view" || command === "preview") {
|
|
2493
|
+
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)} - Architecture Viewer`);
|
|
2494
|
+
p.log.info(`Target: ${pc.green(projectInfo.summary)}`);
|
|
2495
|
+
const spinner2 = p.spinner();
|
|
2496
|
+
spinner2.start("Scanning repository & generating architecture map...");
|
|
2497
|
+
const scanResult = await scanProject(projectInfo);
|
|
2498
|
+
spinner2.stop(pc.green(`Scanned ${scanResult.totalFilesScanned} files in ${scanResult.scanDurationMs}ms`));
|
|
2499
|
+
const htmlDestination = path4.resolve(projectInfo.rootPath, "ARCHITECTURE.html");
|
|
2500
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2501
|
+
openViewer(htmlDestination);
|
|
2502
|
+
p.note(
|
|
2503
|
+
`${pc.bold("File:")} ${pc.cyan("ARCHITECTURE.html")}
|
|
2504
|
+
Interactive Mermaid.js flowchart & component breakdown loaded in your browser.
|
|
2505
|
+
Double-click ${pc.cyan("ARCHITECTURE.html")} anytime to view offline!`,
|
|
2506
|
+
pc.green("Opened in Browser! \u{1F310}")
|
|
2507
|
+
);
|
|
2508
|
+
p.outro(pc.cyan("Done!"));
|
|
2509
|
+
checkAndPromptStar();
|
|
2216
2510
|
return;
|
|
2217
2511
|
}
|
|
2218
2512
|
if (command === "scaffold" || command === "make") {
|
|
@@ -2225,12 +2519,14 @@ async function main() {
|
|
|
2225
2519
|
force: isForceFlag,
|
|
2226
2520
|
dryRun: isDryRunFlag
|
|
2227
2521
|
});
|
|
2522
|
+
checkAndPromptStar();
|
|
2228
2523
|
return;
|
|
2229
2524
|
}
|
|
2230
2525
|
if (command === "info") {
|
|
2231
2526
|
p.intro(`${pc.bgCyan(pc.black(" LaraTS "))} ${pc.gray(`v${VERSION}`)}`);
|
|
2232
2527
|
showProjectInfo(projectInfo);
|
|
2233
2528
|
p.outro(pc.cyan("Done."));
|
|
2529
|
+
checkAndPromptStar();
|
|
2234
2530
|
return;
|
|
2235
2531
|
}
|
|
2236
2532
|
if (!process.stdin.isTTY) {
|
|
@@ -2248,6 +2544,11 @@ async function main() {
|
|
|
2248
2544
|
label: "\u{1F50D} Scan Architecture & Generate ARCHITECTURE.md",
|
|
2249
2545
|
hint: "Creates component stats, directory tree & Mermaid flowchart"
|
|
2250
2546
|
},
|
|
2547
|
+
{
|
|
2548
|
+
value: "view",
|
|
2549
|
+
label: "\u{1F310} Open Interactive Architecture Viewer in Browser",
|
|
2550
|
+
hint: "Spawns local browser tab with Mermaid flowchart & stats"
|
|
2551
|
+
},
|
|
2251
2552
|
{
|
|
2252
2553
|
value: "scaffold",
|
|
2253
2554
|
label: "\u{1F3D7}\uFE0F Scaffold New Feature / Module",
|
|
@@ -2270,8 +2571,20 @@ async function main() {
|
|
|
2270
2571
|
if (action === "scan") {
|
|
2271
2572
|
await runScan(projectInfo, outputPath, {
|
|
2272
2573
|
force: isForceFlag,
|
|
2273
|
-
dryRun: isDryRunFlag
|
|
2574
|
+
dryRun: isDryRunFlag,
|
|
2575
|
+
view: isViewFlag,
|
|
2576
|
+
tree: isTreeFlag,
|
|
2577
|
+
html: isHtmlFlag
|
|
2274
2578
|
});
|
|
2579
|
+
} else if (action === "view") {
|
|
2580
|
+
const spinner2 = p.spinner();
|
|
2581
|
+
spinner2.start("Scanning repository & generating architecture map...");
|
|
2582
|
+
const scanResult = await scanProject(projectInfo);
|
|
2583
|
+
spinner2.stop(pc.green(`Scanned in ${scanResult.scanDurationMs}ms`));
|
|
2584
|
+
const htmlDestination = path4.resolve(projectInfo.rootPath, "ARCHITECTURE.html");
|
|
2585
|
+
fs3.writeFileSync(htmlDestination, generateArchitectureHtml(scanResult), "utf-8");
|
|
2586
|
+
openViewer(htmlDestination);
|
|
2587
|
+
p.outro(pc.green("\u{1F310} Opened interactive architecture diagram in your browser!"));
|
|
2275
2588
|
} else if (action === "scaffold") {
|
|
2276
2589
|
await runScaffold(projectInfo, void 0, {
|
|
2277
2590
|
modular: isModularFlag,
|
|
@@ -2283,9 +2596,13 @@ async function main() {
|
|
|
2283
2596
|
showProjectInfo(projectInfo);
|
|
2284
2597
|
p.outro(pc.cyan("Done!"));
|
|
2285
2598
|
}
|
|
2599
|
+
checkAndPromptStar();
|
|
2286
2600
|
}
|
|
2287
2601
|
main().catch((err) => {
|
|
2288
2602
|
p.log.error(pc.red(`Fatal error: ${err.message || String(err)}`));
|
|
2289
2603
|
process.exit(1);
|
|
2290
2604
|
});
|
|
2605
|
+
export {
|
|
2606
|
+
checkAndPromptStar
|
|
2607
|
+
};
|
|
2291
2608
|
//# sourceMappingURL=index.js.map
|