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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # LaraTS ⚡
2
2
 
3
3
  > **Smart Architecture Visualizer & Contextual Module Scaffolder CLI**
4
- > Tailor-made for **Laravel** & **TypeScript** (Node.js / Express / React / Monorepo) projects.
4
+ > Tailor-made for **Laravel**, **TypeScript**, and **JavaScript (Node.js)** projects.
5
5
 
6
6
  [![npm version](https://img.shields.io/npm/v/larats.svg)](https://www.npmjs.com/package/larats)
7
7
  [![CI](https://github.com/SidiqFatuh187/Larats/actions/workflows/ci.yml/badge.svg)](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
- # Or scan architecture directly
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 markdown path for scan (default: ARCHITECTURE.md)
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 (hasTsConfig && hasPackageJson) {
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 extras = [framework !== "node-vanilla" ? framework : "TypeScript"];
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, "&lt;").replace(/>/g, "&gt;").replace(/`/g, "'").replace(/[\r\n]+/g, " ").trim();
513
+ let cleaned = str.replace(/"/g, "'").replace(/</g, "&lt;").replace(/>/g, "&gt;").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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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> &bull; 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 &amp; 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.2" : "1.0.1";
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 markdown path for scan (default: ARCHITECTURE.md)
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("Scanned Files:")} ${scanResult.totalFilesScanned}
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("Scanned Files:")} ${scanResult.totalFilesScanned}
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} to view your project structure & Mermaid diagram!`));
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 = /* @__PURE__ */ new Set(["scan", "scaffold", "make", "info", "help"]);
2197
- if (command && !VALID_COMMANDS.has(command)) {
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