larats 1.0.0 → 1.0.2
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 +21 -11
- package/dist/index.js +851 -281
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/dist/index.cjs +0 -1744
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
package/dist/index.cjs
DELETED
|
@@ -1,1744 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
|
|
26
|
-
// src/index.ts
|
|
27
|
-
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
28
|
-
var import_node_path3 = __toESM(require("path"), 1);
|
|
29
|
-
var p = __toESM(require("@clack/prompts"), 1);
|
|
30
|
-
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
31
|
-
|
|
32
|
-
// src/detector.ts
|
|
33
|
-
var import_node_fs = __toESM(require("fs"), 1);
|
|
34
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
35
|
-
function readJsonSafe(filePath) {
|
|
36
|
-
try {
|
|
37
|
-
if (!import_node_fs.default.existsSync(filePath)) return null;
|
|
38
|
-
const content = import_node_fs.default.readFileSync(filePath, "utf-8");
|
|
39
|
-
return JSON.parse(content);
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
async function detectProjectType(targetDir = process.cwd()) {
|
|
45
|
-
const resolvedDir = import_node_path.default.resolve(targetDir);
|
|
46
|
-
const dirName = import_node_path.default.basename(resolvedDir);
|
|
47
|
-
const hasArtisan = import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "artisan"));
|
|
48
|
-
const composerPath = import_node_path.default.join(resolvedDir, "composer.json");
|
|
49
|
-
const hasComposer = import_node_fs.default.existsSync(composerPath);
|
|
50
|
-
const tsconfigPath = import_node_path.default.join(resolvedDir, "tsconfig.json");
|
|
51
|
-
const hasTsConfig = import_node_fs.default.existsSync(tsconfigPath);
|
|
52
|
-
const packageJsonPath = import_node_path.default.join(resolvedDir, "package.json");
|
|
53
|
-
const hasPackageJson = import_node_fs.default.existsSync(packageJsonPath);
|
|
54
|
-
if (hasArtisan && hasComposer) {
|
|
55
|
-
const composerData = readJsonSafe(composerPath);
|
|
56
|
-
const allDeps = {
|
|
57
|
-
...composerData?.require || {},
|
|
58
|
-
...composerData?.["require-dev"] || {}
|
|
59
|
-
};
|
|
60
|
-
const hasFilament = Object.keys(allDeps).some((dep) => dep.startsWith("filament/"));
|
|
61
|
-
const hasLivewire = "livewire/livewire" in allDeps;
|
|
62
|
-
const hasInertia = "inertiajs/inertia-laravel" in allDeps;
|
|
63
|
-
const hasSanctum = "laravel/sanctum" in allDeps;
|
|
64
|
-
const rawVersion = allDeps["laravel/framework"] || "";
|
|
65
|
-
const cleanVersion = rawVersion.replace(/[\^~><=]/g, "").trim();
|
|
66
|
-
const appModulesDir = import_node_path.default.join(resolvedDir, "app", "Modules");
|
|
67
|
-
const rootModulesDir = import_node_path.default.join(resolvedDir, "modules");
|
|
68
|
-
const isModular = import_node_fs.default.existsSync(appModulesDir) || import_node_fs.default.existsSync(rootModulesDir);
|
|
69
|
-
const modulesDir = import_node_fs.default.existsSync(appModulesDir) ? "app/Modules" : import_node_fs.default.existsSync(rootModulesDir) ? "modules" : void 0;
|
|
70
|
-
const extras = [];
|
|
71
|
-
if (hasFilament) extras.push("Filament");
|
|
72
|
-
if (hasLivewire) extras.push("Livewire");
|
|
73
|
-
if (hasInertia) extras.push("Inertia");
|
|
74
|
-
if (isModular) extras.push("Modular Architecture");
|
|
75
|
-
const summary = `Laravel ${cleanVersion || ""}${extras.length > 0 ? ` (${extras.join(", ")})` : ""}`.trim();
|
|
76
|
-
return {
|
|
77
|
-
type: "laravel",
|
|
78
|
-
name: composerData?.name?.split("/")[1] || dirName,
|
|
79
|
-
rootPath: resolvedDir,
|
|
80
|
-
laravel: {
|
|
81
|
-
version: cleanVersion || void 0,
|
|
82
|
-
hasFilament,
|
|
83
|
-
hasLivewire,
|
|
84
|
-
hasInertia,
|
|
85
|
-
hasSanctum,
|
|
86
|
-
isModular,
|
|
87
|
-
modulesDir
|
|
88
|
-
},
|
|
89
|
-
summary
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (hasTsConfig && hasPackageJson) {
|
|
93
|
-
const pkg = readJsonSafe(packageJsonPath);
|
|
94
|
-
const allDeps = {
|
|
95
|
-
...pkg?.dependencies || {},
|
|
96
|
-
...pkg?.devDependencies || {}
|
|
97
|
-
};
|
|
98
|
-
let framework = "unknown";
|
|
99
|
-
if ("@nestjs/core" in allDeps) framework = "nest";
|
|
100
|
-
else if ("next" in allDeps) framework = "next";
|
|
101
|
-
else if ("express" in allDeps) framework = "express";
|
|
102
|
-
else if ("fastify" in allDeps) framework = "fastify";
|
|
103
|
-
else if ("hono" in allDeps) framework = "hono";
|
|
104
|
-
else if ("react" in allDeps) framework = "react";
|
|
105
|
-
else if ("vue" in allDeps) framework = "vue";
|
|
106
|
-
else framework = "node-vanilla";
|
|
107
|
-
const hasPrisma = "prisma" in allDeps || "@prisma/client" in allDeps || import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "prisma"));
|
|
108
|
-
const hasTypeORM = "typeorm" in allDeps;
|
|
109
|
-
const hasDrizzle = "drizzle-orm" in allDeps;
|
|
110
|
-
const isMonorepo = Boolean(pkg?.workspaces) || import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "pnpm-workspace.yaml")) || import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "turbo.json")) || import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "lerna.json"));
|
|
111
|
-
const hasSrc = import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "src"));
|
|
112
|
-
const srcDir = hasSrc ? "src" : ".";
|
|
113
|
-
const isModular = import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, srcDir, "modules")) || import_node_fs.default.existsSync(import_node_path.default.join(resolvedDir, "modules"));
|
|
114
|
-
const extras = [framework !== "node-vanilla" ? framework : "TypeScript"];
|
|
115
|
-
if (hasPrisma) extras.push("Prisma");
|
|
116
|
-
if (hasTypeORM) extras.push("TypeORM");
|
|
117
|
-
if (hasDrizzle) extras.push("Drizzle");
|
|
118
|
-
if (isMonorepo) extras.push("Monorepo");
|
|
119
|
-
if (isModular) extras.push("Modular");
|
|
120
|
-
const summary = `TypeScript (${extras.join(", ")})`;
|
|
121
|
-
return {
|
|
122
|
-
type: "typescript",
|
|
123
|
-
name: pkg?.name || dirName,
|
|
124
|
-
rootPath: resolvedDir,
|
|
125
|
-
typescript: {
|
|
126
|
-
framework,
|
|
127
|
-
hasPrisma,
|
|
128
|
-
hasTypeORM,
|
|
129
|
-
hasDrizzle,
|
|
130
|
-
isMonorepo,
|
|
131
|
-
isModular,
|
|
132
|
-
srcDir
|
|
133
|
-
},
|
|
134
|
-
summary
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
let fallbackName = dirName;
|
|
138
|
-
if (hasPackageJson) {
|
|
139
|
-
const pkg = readJsonSafe(packageJsonPath);
|
|
140
|
-
if (pkg?.name) fallbackName = pkg.name;
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
type: "unknown",
|
|
144
|
-
name: fallbackName,
|
|
145
|
-
rootPath: resolvedDir,
|
|
146
|
-
summary: "Generic / Unknown Project"
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/scanner.ts
|
|
151
|
-
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
152
|
-
var SAFE_IGNORE_LIST = [
|
|
153
|
-
// Dependencies & package management
|
|
154
|
-
"**/node_modules/**",
|
|
155
|
-
"**/vendor/**",
|
|
156
|
-
"**/.pnpm/**",
|
|
157
|
-
"**/.yarn/**",
|
|
158
|
-
// Version control
|
|
159
|
-
"**/.git/**",
|
|
160
|
-
"**/.svn/**",
|
|
161
|
-
"**/.hg/**",
|
|
162
|
-
// Storage, cache & build outputs
|
|
163
|
-
"**/storage/**",
|
|
164
|
-
"**/dist/**",
|
|
165
|
-
"**/build/**",
|
|
166
|
-
"**/out/**",
|
|
167
|
-
"**/.next/**",
|
|
168
|
-
"**/.nuxt/**",
|
|
169
|
-
"**/.turbo/**",
|
|
170
|
-
"**/.cache/**",
|
|
171
|
-
"**/coverage/**",
|
|
172
|
-
"**/.temp/**",
|
|
173
|
-
"**/tmp/**",
|
|
174
|
-
// IDEs & OS files
|
|
175
|
-
"**/.idea/**",
|
|
176
|
-
"**/.vscode/**",
|
|
177
|
-
"**/.DS_Store",
|
|
178
|
-
"**/Thumbs.db",
|
|
179
|
-
// Secrets & environment configurations
|
|
180
|
-
"**/.env*",
|
|
181
|
-
"**/*.pem",
|
|
182
|
-
"**/*.key",
|
|
183
|
-
"**/*.pfx",
|
|
184
|
-
"**/*.cert",
|
|
185
|
-
"**/*.crt",
|
|
186
|
-
"**/*.secret",
|
|
187
|
-
"**/*.id_rsa",
|
|
188
|
-
"**/*.pub",
|
|
189
|
-
// Logs & Lock files
|
|
190
|
-
"**/*.log",
|
|
191
|
-
"**/*.lock",
|
|
192
|
-
"**/package-lock.json",
|
|
193
|
-
"**/yarn.lock",
|
|
194
|
-
"**/pnpm-lock.yaml",
|
|
195
|
-
"**/composer.lock"
|
|
196
|
-
];
|
|
197
|
-
function buildCleanDirectoryTree(files, maxDepth = 3) {
|
|
198
|
-
const root = { name: "", isDir: true, children: /* @__PURE__ */ new Map() };
|
|
199
|
-
for (const file of files) {
|
|
200
|
-
const parts = file.split(/[/\\]/);
|
|
201
|
-
let current = root;
|
|
202
|
-
for (let i = 0; i < parts.length; i++) {
|
|
203
|
-
const part = parts[i];
|
|
204
|
-
const isLast = i === parts.length - 1;
|
|
205
|
-
if (!current.children.has(part)) {
|
|
206
|
-
current.children.set(part, {
|
|
207
|
-
name: part,
|
|
208
|
-
isDir: !isLast,
|
|
209
|
-
children: /* @__PURE__ */ new Map()
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
current = current.children.get(part);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const lines = [];
|
|
216
|
-
function renderNode(node, prefix, currentDepth) {
|
|
217
|
-
if (currentDepth > maxDepth) return;
|
|
218
|
-
const entries = Array.from(node.children.values()).sort((a, b) => {
|
|
219
|
-
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
220
|
-
return a.name.localeCompare(b.name);
|
|
221
|
-
});
|
|
222
|
-
if (currentDepth === maxDepth && entries.length > 0) {
|
|
223
|
-
lines.push(`${prefix}\u2514\u2500\u2500 ... (${entries.length} items hidden)`);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const maxEntriesPerDir = 8;
|
|
227
|
-
const displayedEntries = entries.slice(0, maxEntriesPerDir);
|
|
228
|
-
const hiddenCount = entries.length - displayedEntries.length;
|
|
229
|
-
displayedEntries.forEach((child, index) => {
|
|
230
|
-
const isLast = index === displayedEntries.length - 1 && hiddenCount === 0;
|
|
231
|
-
const pointer = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
232
|
-
const displayName = child.isDir ? `${child.name}/` : child.name;
|
|
233
|
-
lines.push(`${prefix}${pointer}${displayName}`);
|
|
234
|
-
if (child.isDir) {
|
|
235
|
-
const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
236
|
-
renderNode(child, nextPrefix, currentDepth + 1);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
if (hiddenCount > 0) {
|
|
240
|
-
lines.push(`${prefix}\u2514\u2500\u2500 ... (${hiddenCount} more files/folders)`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const topLevel = Array.from(root.children.values()).sort((a, b) => {
|
|
244
|
-
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
245
|
-
return a.name.localeCompare(b.name);
|
|
246
|
-
});
|
|
247
|
-
topLevel.forEach((child, index) => {
|
|
248
|
-
const isLast = index === topLevel.length - 1;
|
|
249
|
-
const pointer = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
250
|
-
const displayName = child.isDir ? `${child.name}/` : child.name;
|
|
251
|
-
lines.push(`${pointer}${displayName}`);
|
|
252
|
-
if (child.isDir) {
|
|
253
|
-
const nextPrefix = isLast ? " " : "\u2502 ";
|
|
254
|
-
renderNode(child, nextPrefix, 1);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
return lines.join("\n");
|
|
258
|
-
}
|
|
259
|
-
async function scanProject(projectInfo) {
|
|
260
|
-
const startTime = Date.now();
|
|
261
|
-
const root = projectInfo.rootPath;
|
|
262
|
-
const allFiles = await (0, import_fast_glob.default)("**/*", {
|
|
263
|
-
cwd: root,
|
|
264
|
-
ignore: SAFE_IGNORE_LIST,
|
|
265
|
-
dot: false,
|
|
266
|
-
onlyFiles: true
|
|
267
|
-
});
|
|
268
|
-
const normalizedFiles = allFiles.map((f) => f.replace(/\\/g, "/"));
|
|
269
|
-
const components = {};
|
|
270
|
-
const modulesSet = /* @__PURE__ */ new Set();
|
|
271
|
-
if (projectInfo.type === "laravel") {
|
|
272
|
-
const models = normalizedFiles.filter((f) => /^app\/(Models\/.*|.*Model)\.php$/i.test(f));
|
|
273
|
-
components["Models"] = {
|
|
274
|
-
name: "Eloquent Models",
|
|
275
|
-
count: models.length,
|
|
276
|
-
files: models,
|
|
277
|
-
description: "Database entity mappings and business logic models"
|
|
278
|
-
};
|
|
279
|
-
const controllers = normalizedFiles.filter((f) => /^app\/Http\/Controllers\/.*\.php$/i.test(f));
|
|
280
|
-
components["Controllers"] = {
|
|
281
|
-
name: "HTTP Controllers",
|
|
282
|
-
count: controllers.length,
|
|
283
|
-
files: controllers,
|
|
284
|
-
description: "Request handling and response orchestration"
|
|
285
|
-
};
|
|
286
|
-
const services = normalizedFiles.filter((f) => /^app\/(Services|Domain\/.*\/Services)\/.*\.php$/i.test(f));
|
|
287
|
-
components["Services"] = {
|
|
288
|
-
name: "Service Classes",
|
|
289
|
-
count: services.length,
|
|
290
|
-
files: services,
|
|
291
|
-
description: "Encapsulated business logic and external integrations"
|
|
292
|
-
};
|
|
293
|
-
const actions = normalizedFiles.filter((f) => /^app\/(Actions|Domain\/.*\/Actions)\/.*\.php$/i.test(f));
|
|
294
|
-
components["Actions"] = {
|
|
295
|
-
name: "Action Classes",
|
|
296
|
-
count: actions.length,
|
|
297
|
-
files: actions,
|
|
298
|
-
description: "Single-responsibility invokable operations"
|
|
299
|
-
};
|
|
300
|
-
const requests = normalizedFiles.filter((f) => /^app\/Http\/Requests\/.*\.php$/i.test(f));
|
|
301
|
-
components["Form Requests"] = {
|
|
302
|
-
name: "Form Requests",
|
|
303
|
-
count: requests.length,
|
|
304
|
-
files: requests,
|
|
305
|
-
description: "Input validation and authorization rules"
|
|
306
|
-
};
|
|
307
|
-
const filament = normalizedFiles.filter((f) => /^app\/Filament\/.*\.php$/i.test(f));
|
|
308
|
-
if (filament.length > 0 || projectInfo.laravel?.hasFilament) {
|
|
309
|
-
components["Filament Resources"] = {
|
|
310
|
-
name: "Filament Admin Resources",
|
|
311
|
-
count: filament.length,
|
|
312
|
-
files: filament,
|
|
313
|
-
description: "Admin panel CRUD pages, widgets, and schemas"
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const routes = normalizedFiles.filter((f) => /^routes\/.*\.php$/i.test(f));
|
|
317
|
-
components["Routes"] = {
|
|
318
|
-
name: "Routing Definitions",
|
|
319
|
-
count: routes.length,
|
|
320
|
-
files: routes,
|
|
321
|
-
description: "Web, API, and console endpoint routes"
|
|
322
|
-
};
|
|
323
|
-
const migrations = normalizedFiles.filter((f) => /^database\/migrations\/.*\.php$/i.test(f));
|
|
324
|
-
components["Migrations"] = {
|
|
325
|
-
name: "Database Migrations",
|
|
326
|
-
count: migrations.length,
|
|
327
|
-
files: migrations,
|
|
328
|
-
description: "Schema version control and table definitions"
|
|
329
|
-
};
|
|
330
|
-
const moduleFiles = normalizedFiles.filter((f) => /^app\/Modules\/([^/]+)/i.test(f) || /^modules\/([^/]+)/i.test(f));
|
|
331
|
-
for (const mf of moduleFiles) {
|
|
332
|
-
const match = mf.match(/^(?:app\/Modules|modules)\/([^/]+)/i);
|
|
333
|
-
if (match && match[1]) modulesSet.add(match[1]);
|
|
334
|
-
}
|
|
335
|
-
} else if (projectInfo.type === "typescript") {
|
|
336
|
-
const controllers = normalizedFiles.filter((f) => /(?:^src\/.*(?:\.|\/)controller\.(?:ts|js)$)/i.test(f));
|
|
337
|
-
components["Controllers"] = {
|
|
338
|
-
name: "Controllers",
|
|
339
|
-
count: controllers.length,
|
|
340
|
-
files: controllers,
|
|
341
|
-
description: "HTTP request handlers and response dispatchers"
|
|
342
|
-
};
|
|
343
|
-
const services = normalizedFiles.filter((f) => /(?:^src\/.*(?:\.|\/)service\.(?:ts|js)$)/i.test(f));
|
|
344
|
-
components["Services"] = {
|
|
345
|
-
name: "Services",
|
|
346
|
-
count: services.length,
|
|
347
|
-
files: services,
|
|
348
|
-
description: "Core domain logic and application workflows"
|
|
349
|
-
};
|
|
350
|
-
const repositories = normalizedFiles.filter((f) => /(?:^src\/.*(?:\.|\/)repository\.(?:ts|js)$)/i.test(f));
|
|
351
|
-
components["Repositories"] = {
|
|
352
|
-
name: "Repositories",
|
|
353
|
-
count: repositories.length,
|
|
354
|
-
files: repositories,
|
|
355
|
-
description: "Data access abstraction and database queries"
|
|
356
|
-
};
|
|
357
|
-
const routes = normalizedFiles.filter((f) => /(?:^src\/.*(?:\.|\/)routes?\.(?:ts|js)$)/i.test(f));
|
|
358
|
-
components["Routes"] = {
|
|
359
|
-
name: "Routes",
|
|
360
|
-
count: routes.length,
|
|
361
|
-
files: routes,
|
|
362
|
-
description: "API endpoint mappings and router handlers"
|
|
363
|
-
};
|
|
364
|
-
const dtos = normalizedFiles.filter((f) => /(?:^src\/.*(?:\.|\/)(?:dto|schema)\.(?:ts|js)$)/i.test(f));
|
|
365
|
-
components["DTOs & Schemas"] = {
|
|
366
|
-
name: "DTOs & Schemas",
|
|
367
|
-
count: dtos.length,
|
|
368
|
-
files: dtos,
|
|
369
|
-
description: "Data transfer objects and request validation schemas"
|
|
370
|
-
};
|
|
371
|
-
const models = normalizedFiles.filter(
|
|
372
|
-
(f) => /(?:^src\/.*(?:\.|\/)(?:model|entity)\.(?:ts|js)$|^prisma\/schema\.prisma$)/i.test(f)
|
|
373
|
-
);
|
|
374
|
-
components["Models & Entities"] = {
|
|
375
|
-
name: "Models & Entities",
|
|
376
|
-
count: models.length,
|
|
377
|
-
files: models,
|
|
378
|
-
description: "Database schema definitions and entity declarations"
|
|
379
|
-
};
|
|
380
|
-
const componentsFiles = normalizedFiles.filter((f) => /^src\/(?:components|ui)\/.*\.(?:tsx|jsx|vue|ts|js)$/i.test(f));
|
|
381
|
-
if (componentsFiles.length > 0) {
|
|
382
|
-
components["UI Components"] = {
|
|
383
|
-
name: "UI Components",
|
|
384
|
-
count: componentsFiles.length,
|
|
385
|
-
files: componentsFiles,
|
|
386
|
-
description: "Reusable user interface views and components"
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
const moduleFiles = normalizedFiles.filter((f) => /^src\/modules\/([^/]+)/i.test(f));
|
|
390
|
-
for (const mf of moduleFiles) {
|
|
391
|
-
const match = mf.match(/^src\/modules\/([^/]+)/i);
|
|
392
|
-
if (match && match[1]) modulesSet.add(match[1]);
|
|
393
|
-
}
|
|
394
|
-
} else {
|
|
395
|
-
const srcFiles = normalizedFiles.filter((f) => /^src\//i.test(f));
|
|
396
|
-
const testFiles = normalizedFiles.filter((f) => /(?:test|spec)\.[a-z]+$/i.test(f));
|
|
397
|
-
components["Source Files"] = {
|
|
398
|
-
name: "Source Files",
|
|
399
|
-
count: srcFiles.length,
|
|
400
|
-
files: srcFiles,
|
|
401
|
-
description: "Main application source code"
|
|
402
|
-
};
|
|
403
|
-
components["Test Files"] = {
|
|
404
|
-
name: "Test Files",
|
|
405
|
-
count: testFiles.length,
|
|
406
|
-
files: testFiles,
|
|
407
|
-
description: "Automated test suites and specifications"
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
const directoryTree = buildCleanDirectoryTree(normalizedFiles, 3);
|
|
411
|
-
const scanDurationMs = Date.now() - startTime;
|
|
412
|
-
return {
|
|
413
|
-
projectInfo,
|
|
414
|
-
totalFilesScanned: normalizedFiles.length,
|
|
415
|
-
components,
|
|
416
|
-
modules: Array.from(modulesSet).sort(),
|
|
417
|
-
directoryTree,
|
|
418
|
-
scanDurationMs
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// src/generator/markdown.ts
|
|
423
|
-
function generateMermaidDiagram(scanResult) {
|
|
424
|
-
const { projectInfo, components, modules } = scanResult;
|
|
425
|
-
if (projectInfo.type === "laravel") {
|
|
426
|
-
const hasFilament = projectInfo.laravel?.hasFilament || (components["Filament Resources"]?.count ?? 0) > 0;
|
|
427
|
-
const hasActions = (components["Actions"]?.count ?? 0) > 0;
|
|
428
|
-
const hasServices = (components["Services"]?.count ?? 0) > 0;
|
|
429
|
-
let diagram = `flowchart TD
|
|
430
|
-
%% Styling Definitions
|
|
431
|
-
classDef client fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0369A1;
|
|
432
|
-
classDef routing fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#B45309;
|
|
433
|
-
classDef controller fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#15803D;
|
|
434
|
-
classDef service fill:#F3E8FF,stroke:#9333EA,stroke-width:2px,color:#7E22CE;
|
|
435
|
-
classDef data fill:#FEE2E2,stroke:#DC2626,stroke-width:2px,color:#B91C1C;
|
|
436
|
-
classDef db fill:#F1F5F9,stroke:#475569,stroke-width:2px,color:#334155;
|
|
437
|
-
|
|
438
|
-
subgraph Layer1 ["1. Client & Entrypoints"]
|
|
439
|
-
Client["Client / Browser / API Consumer"]:::client
|
|
440
|
-
Routes["HTTP Routes<br/>(routes/web.php, routes/api.php)"]:::routing
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
subgraph Layer2 ["2. Request Pipeline & Security"]
|
|
444
|
-
Middleware["Middleware Pipeline<br/>(Auth, CSRF, Rate Limiting)"]:::routing
|
|
445
|
-
FormRequests["Form Requests<br/>(Validation & Authorization)"]:::routing
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
subgraph Layer3 ["3. Controllers & Presentation"]
|
|
449
|
-
Controllers["HTTP Controllers<br/>(app/Http/Controllers)"]:::controller
|
|
450
|
-
`;
|
|
451
|
-
if (hasFilament) {
|
|
452
|
-
diagram += ` Filament["Filament Admin Panel<br/>(app/Filament/Resources)"]:::controller
|
|
453
|
-
`;
|
|
454
|
-
}
|
|
455
|
-
diagram += ` end
|
|
456
|
-
|
|
457
|
-
subgraph Layer4 ["4. Domain & Business Logic"]
|
|
458
|
-
`;
|
|
459
|
-
if (hasServices || !hasActions) {
|
|
460
|
-
diagram += ` Services["Service Layer<br/>(app/Services)"]:::service
|
|
461
|
-
`;
|
|
462
|
-
}
|
|
463
|
-
if (hasActions) {
|
|
464
|
-
diagram += ` Actions["Action Classes<br/>(app/Actions)"]:::service
|
|
465
|
-
`;
|
|
466
|
-
}
|
|
467
|
-
diagram += ` end
|
|
468
|
-
|
|
469
|
-
subgraph Layer5 ["5. Data Persistence & Storage"]
|
|
470
|
-
Models["Eloquent Models<br/>(app/Models)"]:::data
|
|
471
|
-
Database[("Database<br/>(MySQL / Postgres / SQLite)")]:::db
|
|
472
|
-
Migrations["Database Migrations<br/>(database/migrations)"]:::data
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
Client --> Routes
|
|
476
|
-
Routes --> Middleware
|
|
477
|
-
Middleware --> FormRequests
|
|
478
|
-
FormRequests --> Controllers
|
|
479
|
-
`;
|
|
480
|
-
if (hasServices && hasActions) {
|
|
481
|
-
diagram += ` Controllers --> Services
|
|
482
|
-
Controllers --> Actions
|
|
483
|
-
Services --> Models
|
|
484
|
-
Actions --> Models
|
|
485
|
-
`;
|
|
486
|
-
} else if (hasActions) {
|
|
487
|
-
diagram += ` Controllers --> Actions
|
|
488
|
-
Actions --> Models
|
|
489
|
-
`;
|
|
490
|
-
} else {
|
|
491
|
-
diagram += ` Controllers --> Services
|
|
492
|
-
Services --> Models
|
|
493
|
-
`;
|
|
494
|
-
}
|
|
495
|
-
if (hasFilament) {
|
|
496
|
-
diagram += ` Client --> Filament
|
|
497
|
-
Filament --> Models
|
|
498
|
-
`;
|
|
499
|
-
}
|
|
500
|
-
diagram += ` Models --> Database
|
|
501
|
-
Migrations -.-> Database
|
|
502
|
-
`;
|
|
503
|
-
if (modules.length > 0) {
|
|
504
|
-
diagram += `
|
|
505
|
-
subgraph ModulesLayer ["\u{1F4E6} Detected Modules (${modules.length})"]
|
|
506
|
-
`;
|
|
507
|
-
modules.slice(0, 6).forEach((mod, idx) => {
|
|
508
|
-
diagram += ` Mod_${idx}["${mod} Module"]:::service
|
|
509
|
-
`;
|
|
510
|
-
});
|
|
511
|
-
if (modules.length > 6) {
|
|
512
|
-
diagram += ` Mod_More["...and ${modules.length - 6} more modules"]:::service
|
|
513
|
-
`;
|
|
514
|
-
}
|
|
515
|
-
diagram += ` end
|
|
516
|
-
`;
|
|
517
|
-
}
|
|
518
|
-
return diagram;
|
|
519
|
-
}
|
|
520
|
-
if (projectInfo.type === "typescript") {
|
|
521
|
-
const fw = projectInfo.typescript?.framework || "Express";
|
|
522
|
-
const orm = projectInfo.typescript?.hasPrisma ? "Prisma" : projectInfo.typescript?.hasTypeORM ? "TypeORM" : "ORM";
|
|
523
|
-
let diagram = `flowchart TD
|
|
524
|
-
%% Styling Definitions
|
|
525
|
-
classDef client fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0369A1;
|
|
526
|
-
classDef routing fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#B45309;
|
|
527
|
-
classDef controller fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#15803D;
|
|
528
|
-
classDef service fill:#F3E8FF,stroke:#9333EA,stroke-width:2px,color:#7E22CE;
|
|
529
|
-
classDef data fill:#FEE2E2,stroke:#DC2626,stroke-width:2px,color:#B91C1C;
|
|
530
|
-
classDef db fill:#F1F5F9,stroke:#475569,stroke-width:2px,color:#334155;
|
|
531
|
-
|
|
532
|
-
subgraph Layer1 ["1. Client & API Gateway"]
|
|
533
|
-
Client["Frontend / API Client"]:::client
|
|
534
|
-
Router["Router & Endpoints<br/>(${fw} Routes)"]:::routing
|
|
535
|
-
end
|
|
536
|
-
|
|
537
|
-
subgraph Layer2 ["2. Validation & Guards"]
|
|
538
|
-
Middleware["Auth & Error Middleware"]:::routing
|
|
539
|
-
DTOs["DTOs & Schemas<br/>(Input Validation)"]:::routing
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
subgraph Layer3 ["3. Controllers"]
|
|
543
|
-
Controllers["Controllers<br/>(Request Handling)"]:::controller
|
|
544
|
-
end
|
|
545
|
-
|
|
546
|
-
subgraph Layer4 ["4. Application Services"]
|
|
547
|
-
Services["Domain Services<br/>(Business Logic)"]:::service
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
subgraph Layer5 ["5. Data Layer & Storage"]
|
|
551
|
-
Repositories["Repositories / ${orm}<br/>(Data Access Layer)"]:::data
|
|
552
|
-
Database[("Database<br/>(SQL / NoSQL)")]:::db
|
|
553
|
-
end
|
|
554
|
-
|
|
555
|
-
Client --> Router
|
|
556
|
-
Router --> Middleware
|
|
557
|
-
Middleware --> DTOs
|
|
558
|
-
DTOs --> Controllers
|
|
559
|
-
Controllers --> Services
|
|
560
|
-
Services --> Repositories
|
|
561
|
-
Repositories --> Database
|
|
562
|
-
`;
|
|
563
|
-
if (modules.length > 0) {
|
|
564
|
-
diagram += `
|
|
565
|
-
subgraph ModulesLayer ["\u{1F4E6} Detected Feature Modules (${modules.length})"]
|
|
566
|
-
`;
|
|
567
|
-
modules.slice(0, 6).forEach((mod, idx) => {
|
|
568
|
-
diagram += ` Mod_${idx}["${mod} Module"]:::service
|
|
569
|
-
`;
|
|
570
|
-
});
|
|
571
|
-
if (modules.length > 6) {
|
|
572
|
-
diagram += ` Mod_More["...and ${modules.length - 6} more modules"]:::service
|
|
573
|
-
`;
|
|
574
|
-
}
|
|
575
|
-
diagram += ` end
|
|
576
|
-
`;
|
|
577
|
-
}
|
|
578
|
-
return diagram;
|
|
579
|
-
}
|
|
580
|
-
return `flowchart TD
|
|
581
|
-
classDef client fill:#E0F2FE,stroke:#0284C7,stroke-width:2px,color:#0369A1;
|
|
582
|
-
classDef core fill:#DCFCE7,stroke:#16A34A,stroke-width:2px,color:#15803D;
|
|
583
|
-
classDef test fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#B45309;
|
|
584
|
-
|
|
585
|
-
subgraph App ["Application Architecture"]
|
|
586
|
-
Entry["Entrypoint / CLI"]:::client
|
|
587
|
-
Source["Source Core Logic (src/)"]:::core
|
|
588
|
-
Tests["Test Suites"]:::test
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
Entry --> Source
|
|
592
|
-
Tests -.-> Source
|
|
593
|
-
`;
|
|
594
|
-
}
|
|
595
|
-
function generateArchitectureMarkdown(scanResult) {
|
|
596
|
-
const { projectInfo, totalFilesScanned, components, modules, directoryTree, scanDurationMs } = scanResult;
|
|
597
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19) + " UTC";
|
|
598
|
-
const mermaid = generateMermaidDiagram(scanResult);
|
|
599
|
-
const componentRows = Object.entries(components).map(([key, comp]) => {
|
|
600
|
-
const sampleFiles = comp.files.slice(0, 3).map((f) => `\`${f}\``).join(", ");
|
|
601
|
-
const moreSuffix = comp.files.length > 3 ? ` *(+${comp.files.length - 3} more)*` : "";
|
|
602
|
-
return `| **${comp.name}** | \`${comp.count}\` | ${comp.description} | ${sampleFiles || "-"} ${moreSuffix} |`;
|
|
603
|
-
}).join("\n");
|
|
604
|
-
let modulesSection = "";
|
|
605
|
-
if (modules.length > 0) {
|
|
606
|
-
modulesSection = `
|
|
607
|
-
## \u{1F4E6} Detected Modules & Domains (${modules.length})
|
|
608
|
-
|
|
609
|
-
This project organizes features into modular domains:
|
|
610
|
-
|
|
611
|
-
${modules.map((m) => `- **\`${m}\`**`).join("\n")}
|
|
612
|
-
`;
|
|
613
|
-
}
|
|
614
|
-
return `# \u{1F3DB}\uFE0F Architecture Overview: ${projectInfo.name}
|
|
615
|
-
|
|
616
|
-
> Auto-generated by **[LaraTS CLI](https://github.com/SidiqFatuh187/Larats)** on \`${timestamp}\`.
|
|
617
|
-
> **100% Local & Safe:** Generated entirely in offline mode via local filesystem metadata analysis. Zero data transmitted.
|
|
618
|
-
|
|
619
|
-
---
|
|
620
|
-
|
|
621
|
-
## \u{1F4CC} Project Summary
|
|
622
|
-
|
|
623
|
-
| Attribute | Details |
|
|
624
|
-
| :--- | :--- |
|
|
625
|
-
| **Project Name** | \`${projectInfo.name}\` |
|
|
626
|
-
| **Project Type** | **${projectInfo.type.toUpperCase()}** |
|
|
627
|
-
| **Detected Stack** | \`${projectInfo.summary}\` |
|
|
628
|
-
| **Scanned Files** | \`${totalFilesScanned}\` files indexed (safe ignore filter applied) |
|
|
629
|
-
| **Scan Duration** | \`${scanDurationMs} ms\` |
|
|
630
|
-
|
|
631
|
-
---
|
|
632
|
-
|
|
633
|
-
## \u{1F4CA} Architectural Components Statistics
|
|
634
|
-
|
|
635
|
-
| Component Layer | Count | Description | Sample Files |
|
|
636
|
-
| :--- | :---: | :--- | :--- |
|
|
637
|
-
${componentRows || "| *No architectural components identified* | `0` | - | - |"}
|
|
638
|
-
${modulesSection}
|
|
639
|
-
---
|
|
640
|
-
|
|
641
|
-
## \u{1F5FA}\uFE0F Visual Architecture Flowchart
|
|
642
|
-
|
|
643
|
-
Visual representation of the project layers and request lifecycle rendered with [Mermaid.js](https://mermaid.js.org/):
|
|
644
|
-
|
|
645
|
-
\`\`\`mermaid
|
|
646
|
-
${mermaid}
|
|
647
|
-
\`\`\`
|
|
648
|
-
|
|
649
|
-
---
|
|
650
|
-
|
|
651
|
-
## \u{1F332} Directory Structure Tree
|
|
652
|
-
|
|
653
|
-
Clean, high-level directory tree showing key folders (ignoring build artifacts, vendor files, and sensitive keys):
|
|
654
|
-
|
|
655
|
-
\`\`\`text
|
|
656
|
-
${directoryTree || "(Empty directory structure)"}
|
|
657
|
-
\`\`\`
|
|
658
|
-
|
|
659
|
-
---
|
|
660
|
-
|
|
661
|
-
## \u{1F512} Security & Privacy Notice
|
|
662
|
-
|
|
663
|
-
This architectural map was produced with strict privacy rules:
|
|
664
|
-
- **Zero Network Transmission:** Executed locally without telemetry or outbound communication.
|
|
665
|
-
- **Safe Exclusion Filter:** Ignored \`node_modules\`, \`vendor\`, \`.env*\`, key files, credentials, and cache folders.
|
|
666
|
-
- **Metadata Only:** Scanned file names and relative paths only; no secrets or private business contents were extracted.
|
|
667
|
-
|
|
668
|
-
*Generated with \u2764\uFE0F by LaraTS CLI.*
|
|
669
|
-
`;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// src/generator/scaffolder.ts
|
|
673
|
-
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
674
|
-
var import_node_path2 = __toESM(require("path"), 1);
|
|
675
|
-
|
|
676
|
-
// src/templates/laravel.ts
|
|
677
|
-
function getNaming(input) {
|
|
678
|
-
const words = input.trim().replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]/g, " ").split(/\s+/).filter(Boolean);
|
|
679
|
-
if (words.length === 0) {
|
|
680
|
-
words.push("SampleModule");
|
|
681
|
-
}
|
|
682
|
-
const pascal = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
683
|
-
const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
684
|
-
const snake = words.map((w) => w.toLowerCase()).join("_");
|
|
685
|
-
const kebab = words.map((w) => w.toLowerCase()).join("-");
|
|
686
|
-
const pluralSuffix = snake.endsWith("s") || snake.endsWith("ch") || snake.endsWith("sh") ? "es" : "s";
|
|
687
|
-
const pluralSnake = snake + pluralSuffix;
|
|
688
|
-
const pluralPascal = pascal + pluralSuffix;
|
|
689
|
-
return { pascal, camel, kebab, snake, pluralSnake, pluralPascal };
|
|
690
|
-
}
|
|
691
|
-
function laravelModelTemplate(names, namespace = "App\\Models") {
|
|
692
|
-
return `<?php
|
|
693
|
-
|
|
694
|
-
namespace ${namespace};
|
|
695
|
-
|
|
696
|
-
use IlluminateDatabaseEloquentFactoriesHasFactory;
|
|
697
|
-
use IlluminateDatabaseEloquentModel;
|
|
698
|
-
|
|
699
|
-
class ${names.pascal} extends Model
|
|
700
|
-
{
|
|
701
|
-
use HasFactory;
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* The table associated with the model.
|
|
705
|
-
*
|
|
706
|
-
* @var string
|
|
707
|
-
*/
|
|
708
|
-
protected $table = '${names.pluralSnake}';
|
|
709
|
-
|
|
710
|
-
/**
|
|
711
|
-
* The attributes that are mass assignable.
|
|
712
|
-
*
|
|
713
|
-
* @var list<string>
|
|
714
|
-
*/
|
|
715
|
-
protected $fillable = [
|
|
716
|
-
'name',
|
|
717
|
-
'status',
|
|
718
|
-
'metadata',
|
|
719
|
-
];
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Get the attributes that should be cast.
|
|
723
|
-
*
|
|
724
|
-
* @return array<string, string>
|
|
725
|
-
*/
|
|
726
|
-
protected function casts(): array
|
|
727
|
-
{
|
|
728
|
-
return [
|
|
729
|
-
'metadata' => 'array',
|
|
730
|
-
'created_at' => 'datetime',
|
|
731
|
-
'updated_at' => 'datetime',
|
|
732
|
-
];
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
`;
|
|
736
|
-
}
|
|
737
|
-
function laravelControllerTemplate(names, options = {}) {
|
|
738
|
-
const namespace = options.namespace || "App\\Http\\Controllers";
|
|
739
|
-
const modelNamespace = options.modelNamespace || "App\\Models";
|
|
740
|
-
const serviceNamespace = options.serviceNamespace || "App\\Services";
|
|
741
|
-
const requestNamespace = options.requestNamespace || "App\\Http\\Requests";
|
|
742
|
-
return `<?php
|
|
743
|
-
|
|
744
|
-
namespace ${namespace};
|
|
745
|
-
|
|
746
|
-
use ${modelNamespace}\\${names.pascal};
|
|
747
|
-
use ${serviceNamespace}\\${names.pascal}Service;
|
|
748
|
-
use ${requestNamespace}\\${names.pascal}Request;
|
|
749
|
-
use IlluminateHttpJsonResponse;
|
|
750
|
-
use IlluminateHttpRequest;
|
|
751
|
-
|
|
752
|
-
class ${names.pascal}Controller extends Controller
|
|
753
|
-
{
|
|
754
|
-
public function __construct(
|
|
755
|
-
protected ${names.pascal}Service $${names.camel}Service
|
|
756
|
-
) {}
|
|
757
|
-
|
|
758
|
-
/**
|
|
759
|
-
* Display a listing of ${names.pascal} resources.
|
|
760
|
-
*/
|
|
761
|
-
public function index(Request $request): JsonResponse
|
|
762
|
-
{
|
|
763
|
-
$items = $this->${names.camel}Service->getAll($request->all());
|
|
764
|
-
|
|
765
|
-
return response()->json([
|
|
766
|
-
'status' => 'success',
|
|
767
|
-
'data' => $items,
|
|
768
|
-
]);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Display the specified ${names.pascal} resource.
|
|
773
|
-
*/
|
|
774
|
-
public function show(int $id): JsonResponse
|
|
775
|
-
{
|
|
776
|
-
$item = $this->${names.camel}Service->getById($id);
|
|
777
|
-
|
|
778
|
-
return response()->json([
|
|
779
|
-
'status' => 'success',
|
|
780
|
-
'data' => $item,
|
|
781
|
-
]);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Store a newly created ${names.pascal} in storage.
|
|
786
|
-
*/
|
|
787
|
-
public function store(${names.pascal}Request $request): JsonResponse
|
|
788
|
-
{
|
|
789
|
-
$item = $this->${names.camel}Service->create($request->validated());
|
|
790
|
-
|
|
791
|
-
return response()->json([
|
|
792
|
-
'status' => 'success',
|
|
793
|
-
'message' => '${names.pascal} created successfully.',
|
|
794
|
-
'data' => $item,
|
|
795
|
-
], 201);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/**
|
|
799
|
-
* Update the specified ${names.pascal} in storage.
|
|
800
|
-
*/
|
|
801
|
-
public function update(${names.pascal}Request $request, int $id): JsonResponse
|
|
802
|
-
{
|
|
803
|
-
$item = $this->${names.camel}Service->update($id, $request->validated());
|
|
804
|
-
|
|
805
|
-
return response()->json([
|
|
806
|
-
'status' => 'success',
|
|
807
|
-
'message' => '${names.pascal} updated successfully.',
|
|
808
|
-
'data' => $item,
|
|
809
|
-
]);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* Remove the specified ${names.pascal} from storage.
|
|
814
|
-
*/
|
|
815
|
-
public function destroy(int $id): JsonResponse
|
|
816
|
-
{
|
|
817
|
-
$this->${names.camel}Service->delete($id);
|
|
818
|
-
|
|
819
|
-
return response()->json([
|
|
820
|
-
'status' => 'success',
|
|
821
|
-
'message' => '${names.pascal} deleted successfully.',
|
|
822
|
-
]);
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
`;
|
|
826
|
-
}
|
|
827
|
-
function laravelServiceTemplate(names, options = {}) {
|
|
828
|
-
const namespace = options.namespace || "App\\Services";
|
|
829
|
-
const modelNamespace = options.modelNamespace || "App\\Models";
|
|
830
|
-
return `<?php
|
|
831
|
-
|
|
832
|
-
namespace ${namespace};
|
|
833
|
-
|
|
834
|
-
use ${modelNamespace}\\${names.pascal};
|
|
835
|
-
use IlluminateDatabaseEloquentCollection;
|
|
836
|
-
|
|
837
|
-
class ${names.pascal}Service
|
|
838
|
-
{
|
|
839
|
-
/**
|
|
840
|
-
* Retrieve all ${names.pluralPascal}.
|
|
841
|
-
*/
|
|
842
|
-
public function getAll(array $filters = []): Collection
|
|
843
|
-
{
|
|
844
|
-
return ${names.pascal}::query()
|
|
845
|
-
->latest()
|
|
846
|
-
->get();
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Find ${names.pascal} by primary key.
|
|
851
|
-
*/
|
|
852
|
-
public function getById(int $id): ${names.pascal}
|
|
853
|
-
{
|
|
854
|
-
return ${names.pascal}::findOrFail($id);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Create a new ${names.pascal}.
|
|
859
|
-
*/
|
|
860
|
-
public function create(array $data): ${names.pascal}
|
|
861
|
-
{
|
|
862
|
-
return ${names.pascal}::create($data);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* Update an existing ${names.pascal}.
|
|
867
|
-
*/
|
|
868
|
-
public function update(int $id, array $data): ${names.pascal}
|
|
869
|
-
{
|
|
870
|
-
$item = $this->getById($id);
|
|
871
|
-
$item->update($data);
|
|
872
|
-
|
|
873
|
-
return $item;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Delete a ${names.pascal}.
|
|
878
|
-
*/
|
|
879
|
-
public function delete(int $id): bool
|
|
880
|
-
{
|
|
881
|
-
$item = $this->getById($id);
|
|
882
|
-
|
|
883
|
-
return (bool) $item->delete();
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
`;
|
|
887
|
-
}
|
|
888
|
-
function laravelActionTemplate(names, options = {}) {
|
|
889
|
-
const namespace = options.namespace || "App\\Actions";
|
|
890
|
-
const modelNamespace = options.modelNamespace || "App\\Models";
|
|
891
|
-
return `<?php
|
|
892
|
-
|
|
893
|
-
namespace ${namespace};
|
|
894
|
-
|
|
895
|
-
use ${modelNamespace}\\${names.pascal};
|
|
896
|
-
|
|
897
|
-
class Create${names.pascal}Action
|
|
898
|
-
{
|
|
899
|
-
/**
|
|
900
|
-
* Execute the ${names.pascal} creation workflow.
|
|
901
|
-
*/
|
|
902
|
-
public function execute(array $data): ${names.pascal}
|
|
903
|
-
{
|
|
904
|
-
return ${names.pascal}::create($data);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
`;
|
|
908
|
-
}
|
|
909
|
-
function laravelRequestTemplate(names, namespace = "App\\Http\\Requests") {
|
|
910
|
-
return `<?php
|
|
911
|
-
|
|
912
|
-
namespace ${namespace};
|
|
913
|
-
|
|
914
|
-
use IlluminateFoundationHttpFormRequest;
|
|
915
|
-
|
|
916
|
-
class ${names.pascal}Request extends FormRequest
|
|
917
|
-
{
|
|
918
|
-
/**
|
|
919
|
-
* Determine if the user is authorized to make this request.
|
|
920
|
-
*/
|
|
921
|
-
public function authorize(): bool
|
|
922
|
-
{
|
|
923
|
-
return true;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Get the validation rules that apply to the request.
|
|
928
|
-
*
|
|
929
|
-
* @return array<string, list<string>>
|
|
930
|
-
*/
|
|
931
|
-
public function rules(): array
|
|
932
|
-
{
|
|
933
|
-
return [
|
|
934
|
-
'name' => ['required', 'string', 'max:255'],
|
|
935
|
-
'status' => ['nullable', 'string', 'in:draft,active,archived'],
|
|
936
|
-
'metadata' => ['nullable', 'array'],
|
|
937
|
-
];
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
/**
|
|
941
|
-
* Get custom error messages for validation rules.
|
|
942
|
-
*
|
|
943
|
-
* @return array<string, string>
|
|
944
|
-
*/
|
|
945
|
-
public function messages(): array
|
|
946
|
-
{
|
|
947
|
-
return [
|
|
948
|
-
'name.required' => 'The ${names.camel} name field is required.',
|
|
949
|
-
];
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
`;
|
|
953
|
-
}
|
|
954
|
-
function laravelMigrationTemplate(names) {
|
|
955
|
-
return `<?php
|
|
956
|
-
|
|
957
|
-
use IlluminateDatabaseMigrationsMigration;
|
|
958
|
-
use IlluminateDatabaseSchemaBlueprint;
|
|
959
|
-
use IlluminateSupportFacadesSchema;
|
|
960
|
-
|
|
961
|
-
return new class extends Migration
|
|
962
|
-
{
|
|
963
|
-
/**
|
|
964
|
-
* Run the migrations.
|
|
965
|
-
*/
|
|
966
|
-
public function up(): void
|
|
967
|
-
{
|
|
968
|
-
Schema::create('${names.pluralSnake}', function (Blueprint $table) {
|
|
969
|
-
$table->id();
|
|
970
|
-
$table->string('name');
|
|
971
|
-
$table->string('status')->default('draft');
|
|
972
|
-
$table->json('metadata')->nullable();
|
|
973
|
-
$table->timestamps();
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
/**
|
|
978
|
-
* Reverse the migrations.
|
|
979
|
-
*/
|
|
980
|
-
public function down(): void
|
|
981
|
-
{
|
|
982
|
-
Schema::dropIfExists('${names.pluralSnake}');
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
`;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// src/templates/typescript.ts
|
|
989
|
-
function tsControllerTemplate(names, isModular = true) {
|
|
990
|
-
const serviceImport = isModular ? `./${names.kebab}.service.js` : `../services/${names.kebab}.service.js`;
|
|
991
|
-
const dtoImport = isModular ? `./${names.kebab}.dto.js` : `../dtos/${names.kebab}.dto.js`;
|
|
992
|
-
return `import type { Request, Response, NextFunction } from 'express';
|
|
993
|
-
import { ${names.pascal}Service } from '${serviceImport}';
|
|
994
|
-
import type { Create${names.pascal}Dto, Update${names.pascal}Dto } from '${dtoImport}';
|
|
995
|
-
|
|
996
|
-
export class ${names.pascal}Controller {
|
|
997
|
-
constructor(private readonly ${names.camel}Service: ${names.pascal}Service = new ${names.pascal}Service()) {}
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* GET /api/${names.kebab}
|
|
1001
|
-
*/
|
|
1002
|
-
getAll = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
1003
|
-
try {
|
|
1004
|
-
const items = await this.${names.camel}Service.findAll(req.query);
|
|
1005
|
-
res.status(200).json({ status: 'success', data: items });
|
|
1006
|
-
} catch (error) {
|
|
1007
|
-
next(error);
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* GET /api/${names.kebab}/:id
|
|
1013
|
-
*/
|
|
1014
|
-
getById = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
1015
|
-
try {
|
|
1016
|
-
const { id } = req.params;
|
|
1017
|
-
const item = await this.${names.camel}Service.findById(id);
|
|
1018
|
-
if (!item) {
|
|
1019
|
-
res.status(404).json({ status: 'fail', message: '${names.pascal} not found' });
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
res.status(200).json({ status: 'success', data: item });
|
|
1023
|
-
} catch (error) {
|
|
1024
|
-
next(error);
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
/**
|
|
1029
|
-
* POST /api/${names.kebab}
|
|
1030
|
-
*/
|
|
1031
|
-
create = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
1032
|
-
try {
|
|
1033
|
-
const payload: Create${names.pascal}Dto = req.body;
|
|
1034
|
-
const created = await this.${names.camel}Service.create(payload);
|
|
1035
|
-
res.status(201).json({ status: 'success', data: created });
|
|
1036
|
-
} catch (error) {
|
|
1037
|
-
next(error);
|
|
1038
|
-
}
|
|
1039
|
-
};
|
|
1040
|
-
|
|
1041
|
-
/**
|
|
1042
|
-
* PUT /api/${names.kebab}/:id
|
|
1043
|
-
*/
|
|
1044
|
-
update = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
1045
|
-
try {
|
|
1046
|
-
const { id } = req.params;
|
|
1047
|
-
const payload: Update${names.pascal}Dto = req.body;
|
|
1048
|
-
const updated = await this.${names.camel}Service.update(id, payload);
|
|
1049
|
-
res.status(200).json({ status: 'success', data: updated });
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
next(error);
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
* DELETE /api/${names.kebab}/:id
|
|
1057
|
-
*/
|
|
1058
|
-
delete = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
|
1059
|
-
try {
|
|
1060
|
-
const { id } = req.params;
|
|
1061
|
-
await this.${names.camel}Service.delete(id);
|
|
1062
|
-
res.status(200).json({ status: 'success', message: '${names.pascal} deleted successfully' });
|
|
1063
|
-
} catch (error) {
|
|
1064
|
-
next(error);
|
|
1065
|
-
}
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
`;
|
|
1069
|
-
}
|
|
1070
|
-
function tsServiceTemplate(names, isModular = true) {
|
|
1071
|
-
const repoImport = isModular ? `./${names.kebab}.repository.js` : `../repositories/${names.kebab}.repository.js`;
|
|
1072
|
-
const dtoImport = isModular ? `./${names.kebab}.dto.js` : `../dtos/${names.kebab}.dto.js`;
|
|
1073
|
-
return `import { ${names.pascal}Repository } from '${repoImport}';
|
|
1074
|
-
import type { ${names.pascal}Entity, Create${names.pascal}Dto, Update${names.pascal}Dto } from '${dtoImport}';
|
|
1075
|
-
|
|
1076
|
-
export class ${names.pascal}Service {
|
|
1077
|
-
constructor(private readonly ${names.camel}Repository: ${names.pascal}Repository = new ${names.pascal}Repository()) {}
|
|
1078
|
-
|
|
1079
|
-
async findAll(query: Record<string, any> = {}): Promise<${names.pascal}Entity[]> {
|
|
1080
|
-
return this.${names.camel}Repository.findAll(query);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
async findById(id: string): Promise<${names.pascal}Entity | null> {
|
|
1084
|
-
return this.${names.camel}Repository.findById(id);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
async create(data: Create${names.pascal}Dto): Promise<${names.pascal}Entity> {
|
|
1088
|
-
return this.${names.camel}Repository.create(data);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
async update(id: string, data: Update${names.pascal}Dto): Promise<${names.pascal}Entity> {
|
|
1092
|
-
return this.${names.camel}Repository.update(id, data);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
async delete(id: string): Promise<boolean> {
|
|
1096
|
-
return this.${names.camel}Repository.delete(id);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
`;
|
|
1100
|
-
}
|
|
1101
|
-
function tsRepositoryTemplate(names, isModular = true) {
|
|
1102
|
-
const dtoImport = isModular ? `./${names.kebab}.dto.js` : `../dtos/${names.kebab}.dto.js`;
|
|
1103
|
-
return `import type { ${names.pascal}Entity, Create${names.pascal}Dto, Update${names.pascal}Dto } from '${dtoImport}';
|
|
1104
|
-
|
|
1105
|
-
export interface I${names.pascal}Repository {
|
|
1106
|
-
findAll(query?: Record<string, any>): Promise<${names.pascal}Entity[]>;
|
|
1107
|
-
findById(id: string): Promise<${names.pascal}Entity | null>;
|
|
1108
|
-
create(data: Create${names.pascal}Dto): Promise<${names.pascal}Entity>;
|
|
1109
|
-
update(id: string, data: Update${names.pascal}Dto): Promise<${names.pascal}Entity>;
|
|
1110
|
-
delete(id: string): Promise<boolean>;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
export class ${names.pascal}Repository implements I${names.pascal}Repository {
|
|
1114
|
-
// In-memory store fallback. Replace with Prisma / TypeORM / Drizzle client calls.
|
|
1115
|
-
private storage: Map<string, ${names.pascal}Entity> = new Map();
|
|
1116
|
-
|
|
1117
|
-
async findAll(_query?: Record<string, any>): Promise<${names.pascal}Entity[]> {
|
|
1118
|
-
return Array.from(this.storage.values());
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
async findById(id: string): Promise<${names.pascal}Entity | null> {
|
|
1122
|
-
return this.storage.get(id) || null;
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
async create(data: Create${names.pascal}Dto): Promise<${names.pascal}Entity> {
|
|
1126
|
-
const id = (Math.random() * 100000).toFixed(0);
|
|
1127
|
-
const newRecord: ${names.pascal}Entity = {
|
|
1128
|
-
id,
|
|
1129
|
-
...data,
|
|
1130
|
-
status: data.status || 'draft',
|
|
1131
|
-
createdAt: new Date(),
|
|
1132
|
-
updatedAt: new Date(),
|
|
1133
|
-
};
|
|
1134
|
-
this.storage.set(id, newRecord);
|
|
1135
|
-
return newRecord;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
async update(id: string, data: Update${names.pascal}Dto): Promise<${names.pascal}Entity> {
|
|
1139
|
-
const existing = await this.findById(id);
|
|
1140
|
-
if (!existing) {
|
|
1141
|
-
throw new Error(\`${names.pascal} with id \${id} not found\`);
|
|
1142
|
-
}
|
|
1143
|
-
const updated: ${names.pascal}Entity = {
|
|
1144
|
-
...existing,
|
|
1145
|
-
...data,
|
|
1146
|
-
updatedAt: new Date(),
|
|
1147
|
-
};
|
|
1148
|
-
this.storage.set(id, updated);
|
|
1149
|
-
return updated;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
async delete(id: string): Promise<boolean> {
|
|
1153
|
-
return this.storage.delete(id);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
`;
|
|
1157
|
-
}
|
|
1158
|
-
function tsRoutesTemplate(names, isModular = true) {
|
|
1159
|
-
const controllerImport = isModular ? `./${names.kebab}.controller.js` : `../controllers/${names.kebab}.controller.js`;
|
|
1160
|
-
return `import { Router } from 'express';
|
|
1161
|
-
import { ${names.pascal}Controller } from '${controllerImport}';
|
|
1162
|
-
|
|
1163
|
-
const router = Router();
|
|
1164
|
-
const controller = new ${names.pascal}Controller();
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* Route declarations for ${names.pascal}
|
|
1168
|
-
*/
|
|
1169
|
-
router.get('/', controller.getAll);
|
|
1170
|
-
router.get('/:id', controller.getById);
|
|
1171
|
-
router.post('/', controller.create);
|
|
1172
|
-
router.put('/:id', controller.update);
|
|
1173
|
-
router.delete('/:id', controller.delete);
|
|
1174
|
-
|
|
1175
|
-
export const ${names.camel}Routes = router;
|
|
1176
|
-
export default router;
|
|
1177
|
-
`;
|
|
1178
|
-
}
|
|
1179
|
-
function tsDtoTemplate(names) {
|
|
1180
|
-
return `export interface ${names.pascal}Entity {
|
|
1181
|
-
id: string;
|
|
1182
|
-
name: string;
|
|
1183
|
-
status: 'draft' | 'active' | 'archived';
|
|
1184
|
-
description?: string;
|
|
1185
|
-
metadata?: Record<string, unknown>;
|
|
1186
|
-
createdAt: Date;
|
|
1187
|
-
updatedAt: Date;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
export interface Create${names.pascal}Dto {
|
|
1191
|
-
name: string;
|
|
1192
|
-
status?: 'draft' | 'active' | 'archived';
|
|
1193
|
-
description?: string;
|
|
1194
|
-
metadata?: Record<string, unknown>;
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
export interface Update${names.pascal}Dto {
|
|
1198
|
-
name?: string;
|
|
1199
|
-
status?: 'draft' | 'active' | 'archived';
|
|
1200
|
-
description?: string;
|
|
1201
|
-
metadata?: Record<string, unknown>;
|
|
1202
|
-
}
|
|
1203
|
-
`;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// src/generator/scaffolder.ts
|
|
1207
|
-
function getMigrationTimestamp() {
|
|
1208
|
-
const now = /* @__PURE__ */ new Date();
|
|
1209
|
-
const YYYY = now.getFullYear();
|
|
1210
|
-
const MM = String(now.getMonth() + 1).padStart(2, "0");
|
|
1211
|
-
const DD = String(now.getDate()).padStart(2, "0");
|
|
1212
|
-
const hh = String(now.getHours()).padStart(2, "0");
|
|
1213
|
-
const mm = String(now.getMinutes()).padStart(2, "0");
|
|
1214
|
-
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
1215
|
-
return `${YYYY}_${MM}_${DD}_${hh}${mm}${ss}`;
|
|
1216
|
-
}
|
|
1217
|
-
async function scaffoldModule(projectInfo, options) {
|
|
1218
|
-
const root = projectInfo.rootPath;
|
|
1219
|
-
const names = getNaming(options.moduleName);
|
|
1220
|
-
const filesToGenerate = [];
|
|
1221
|
-
const isModular = options.isModular ?? (projectInfo.type === "laravel" ? Boolean(projectInfo.laravel?.isModular) : projectInfo.type === "typescript" ? Boolean(projectInfo.typescript?.isModular) : true);
|
|
1222
|
-
if (projectInfo.type === "laravel") {
|
|
1223
|
-
const selected = new Set(options.components);
|
|
1224
|
-
if (isModular) {
|
|
1225
|
-
const baseDir = `app/Modules/${names.pascal}`;
|
|
1226
|
-
const baseNamespace = `App\\Modules\\${names.pascal}`;
|
|
1227
|
-
if (selected.has("model")) {
|
|
1228
|
-
filesToGenerate.push({
|
|
1229
|
-
relativePath: `${baseDir}/Models/${names.pascal}.php`,
|
|
1230
|
-
content: laravelModelTemplate(names, `${baseNamespace}\\Models`)
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
if (selected.has("controller")) {
|
|
1234
|
-
filesToGenerate.push({
|
|
1235
|
-
relativePath: `${baseDir}/Controllers/${names.pascal}Controller.php`,
|
|
1236
|
-
content: laravelControllerTemplate(names, {
|
|
1237
|
-
namespace: `${baseNamespace}\\Controllers`,
|
|
1238
|
-
modelNamespace: `${baseNamespace}\\Models`,
|
|
1239
|
-
serviceNamespace: `${baseNamespace}\\Services`,
|
|
1240
|
-
requestNamespace: `${baseNamespace}\\Requests`
|
|
1241
|
-
})
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1244
|
-
if (selected.has("service")) {
|
|
1245
|
-
filesToGenerate.push({
|
|
1246
|
-
relativePath: `${baseDir}/Services/${names.pascal}Service.php`,
|
|
1247
|
-
content: laravelServiceTemplate(names, {
|
|
1248
|
-
namespace: `${baseNamespace}\\Services`,
|
|
1249
|
-
modelNamespace: `${baseNamespace}\\Models`
|
|
1250
|
-
})
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
if (selected.has("action")) {
|
|
1254
|
-
filesToGenerate.push({
|
|
1255
|
-
relativePath: `${baseDir}/Actions/Create${names.pascal}Action.php`,
|
|
1256
|
-
content: laravelActionTemplate(names, {
|
|
1257
|
-
namespace: `${baseNamespace}\\Actions`,
|
|
1258
|
-
modelNamespace: `${baseNamespace}\\Models`
|
|
1259
|
-
})
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
if (selected.has("request")) {
|
|
1263
|
-
filesToGenerate.push({
|
|
1264
|
-
relativePath: `${baseDir}/Requests/${names.pascal}Request.php`,
|
|
1265
|
-
content: laravelRequestTemplate(names, `${baseNamespace}\\Requests`)
|
|
1266
|
-
});
|
|
1267
|
-
}
|
|
1268
|
-
if (selected.has("migration")) {
|
|
1269
|
-
const timestamp = getMigrationTimestamp();
|
|
1270
|
-
filesToGenerate.push({
|
|
1271
|
-
relativePath: `database/migrations/${timestamp}_create_${names.pluralSnake}_table.php`,
|
|
1272
|
-
content: laravelMigrationTemplate(names)
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
} else {
|
|
1276
|
-
if (selected.has("model")) {
|
|
1277
|
-
filesToGenerate.push({
|
|
1278
|
-
relativePath: `app/Models/${names.pascal}.php`,
|
|
1279
|
-
content: laravelModelTemplate(names, "App\\Models")
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
if (selected.has("controller")) {
|
|
1283
|
-
filesToGenerate.push({
|
|
1284
|
-
relativePath: `app/Http/Controllers/${names.pascal}Controller.php`,
|
|
1285
|
-
content: laravelControllerTemplate(names)
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
if (selected.has("service")) {
|
|
1289
|
-
filesToGenerate.push({
|
|
1290
|
-
relativePath: `app/Services/${names.pascal}Service.php`,
|
|
1291
|
-
content: laravelServiceTemplate(names)
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
if (selected.has("action")) {
|
|
1295
|
-
filesToGenerate.push({
|
|
1296
|
-
relativePath: `app/Actions/Create${names.pascal}Action.php`,
|
|
1297
|
-
content: laravelActionTemplate(names)
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
if (selected.has("request")) {
|
|
1301
|
-
filesToGenerate.push({
|
|
1302
|
-
relativePath: `app/Http/Requests/${names.pascal}Request.php`,
|
|
1303
|
-
content: laravelRequestTemplate(names)
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
if (selected.has("migration")) {
|
|
1307
|
-
const timestamp = getMigrationTimestamp();
|
|
1308
|
-
filesToGenerate.push({
|
|
1309
|
-
relativePath: `database/migrations/${timestamp}_create_${names.pluralSnake}_table.php`,
|
|
1310
|
-
content: laravelMigrationTemplate(names)
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
} else {
|
|
1315
|
-
const selected = new Set(options.components);
|
|
1316
|
-
const srcDir = projectInfo.typescript?.srcDir || "src";
|
|
1317
|
-
if (isModular) {
|
|
1318
|
-
const moduleFolder = `${srcDir}/modules/${names.kebab}`;
|
|
1319
|
-
if (selected.has("controller")) {
|
|
1320
|
-
filesToGenerate.push({
|
|
1321
|
-
relativePath: `${moduleFolder}/${names.kebab}.controller.ts`,
|
|
1322
|
-
content: tsControllerTemplate(names, true)
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
if (selected.has("service")) {
|
|
1326
|
-
filesToGenerate.push({
|
|
1327
|
-
relativePath: `${moduleFolder}/${names.kebab}.service.ts`,
|
|
1328
|
-
content: tsServiceTemplate(names, true)
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
if (selected.has("repository")) {
|
|
1332
|
-
filesToGenerate.push({
|
|
1333
|
-
relativePath: `${moduleFolder}/${names.kebab}.repository.ts`,
|
|
1334
|
-
content: tsRepositoryTemplate(names, true)
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
if (selected.has("route")) {
|
|
1338
|
-
filesToGenerate.push({
|
|
1339
|
-
relativePath: `${moduleFolder}/${names.kebab}.routes.ts`,
|
|
1340
|
-
content: tsRoutesTemplate(names, true)
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
if (selected.has("dto")) {
|
|
1344
|
-
filesToGenerate.push({
|
|
1345
|
-
relativePath: `${moduleFolder}/${names.kebab}.dto.ts`,
|
|
1346
|
-
content: tsDtoTemplate(names)
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
if (filesToGenerate.length > 1) {
|
|
1350
|
-
const exports2 = [];
|
|
1351
|
-
if (selected.has("controller")) exports2.push(`export * from './${names.kebab}.controller.js';`);
|
|
1352
|
-
if (selected.has("service")) exports2.push(`export * from './${names.kebab}.service.js';`);
|
|
1353
|
-
if (selected.has("repository")) exports2.push(`export * from './${names.kebab}.repository.js';`);
|
|
1354
|
-
if (selected.has("route")) exports2.push(`export * from './${names.kebab}.routes.js';`);
|
|
1355
|
-
if (selected.has("dto")) exports2.push(`export * from './${names.kebab}.dto.js';`);
|
|
1356
|
-
filesToGenerate.push({
|
|
1357
|
-
relativePath: `${moduleFolder}/index.ts`,
|
|
1358
|
-
content: `${exports2.join("\n")}
|
|
1359
|
-
`
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
} else {
|
|
1363
|
-
if (selected.has("controller")) {
|
|
1364
|
-
filesToGenerate.push({
|
|
1365
|
-
relativePath: `${srcDir}/controllers/${names.kebab}.controller.ts`,
|
|
1366
|
-
content: tsControllerTemplate(names, false)
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
if (selected.has("service")) {
|
|
1370
|
-
filesToGenerate.push({
|
|
1371
|
-
relativePath: `${srcDir}/services/${names.kebab}.service.ts`,
|
|
1372
|
-
content: tsServiceTemplate(names, false)
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
if (selected.has("repository")) {
|
|
1376
|
-
filesToGenerate.push({
|
|
1377
|
-
relativePath: `${srcDir}/repositories/${names.kebab}.repository.ts`,
|
|
1378
|
-
content: tsRepositoryTemplate(names, false)
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
if (selected.has("route")) {
|
|
1382
|
-
filesToGenerate.push({
|
|
1383
|
-
relativePath: `${srcDir}/routes/${names.kebab}.routes.ts`,
|
|
1384
|
-
content: tsRoutesTemplate(names, false)
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
if (selected.has("dto")) {
|
|
1388
|
-
filesToGenerate.push({
|
|
1389
|
-
relativePath: `${srcDir}/dtos/${names.kebab}.dto.ts`,
|
|
1390
|
-
content: tsDtoTemplate(names)
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
const results = [];
|
|
1396
|
-
let createdCount = 0;
|
|
1397
|
-
let skippedCount = 0;
|
|
1398
|
-
let errorCount = 0;
|
|
1399
|
-
for (const item of filesToGenerate) {
|
|
1400
|
-
const fullPath = import_node_path2.default.resolve(root, item.relativePath);
|
|
1401
|
-
if (import_node_fs2.default.existsSync(fullPath) && !options.forceOverwrite) {
|
|
1402
|
-
results.push({
|
|
1403
|
-
filePath: fullPath,
|
|
1404
|
-
relativeFilePath: item.relativePath,
|
|
1405
|
-
status: "skipped",
|
|
1406
|
-
reason: "File already exists (overwrite protected)"
|
|
1407
|
-
});
|
|
1408
|
-
skippedCount++;
|
|
1409
|
-
continue;
|
|
1410
|
-
}
|
|
1411
|
-
if (options.dryRun) {
|
|
1412
|
-
results.push({
|
|
1413
|
-
filePath: fullPath,
|
|
1414
|
-
relativeFilePath: item.relativePath,
|
|
1415
|
-
status: "created",
|
|
1416
|
-
reason: "Dry run simulated"
|
|
1417
|
-
});
|
|
1418
|
-
createdCount++;
|
|
1419
|
-
continue;
|
|
1420
|
-
}
|
|
1421
|
-
try {
|
|
1422
|
-
const parentDir = import_node_path2.default.dirname(fullPath);
|
|
1423
|
-
if (!import_node_fs2.default.existsSync(parentDir)) {
|
|
1424
|
-
import_node_fs2.default.mkdirSync(parentDir, { recursive: true });
|
|
1425
|
-
}
|
|
1426
|
-
import_node_fs2.default.writeFileSync(fullPath, item.content, "utf-8");
|
|
1427
|
-
results.push({
|
|
1428
|
-
filePath: fullPath,
|
|
1429
|
-
relativeFilePath: item.relativePath,
|
|
1430
|
-
status: "created"
|
|
1431
|
-
});
|
|
1432
|
-
createdCount++;
|
|
1433
|
-
} catch (err) {
|
|
1434
|
-
results.push({
|
|
1435
|
-
filePath: fullPath,
|
|
1436
|
-
relativeFilePath: item.relativePath,
|
|
1437
|
-
status: "error",
|
|
1438
|
-
reason: err.message || String(err)
|
|
1439
|
-
});
|
|
1440
|
-
errorCount++;
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
return {
|
|
1444
|
-
moduleName: names.pascal,
|
|
1445
|
-
targetDir: root,
|
|
1446
|
-
files: results,
|
|
1447
|
-
createdCount,
|
|
1448
|
-
skippedCount,
|
|
1449
|
-
errorCount
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
// src/index.ts
|
|
1454
|
-
var VERSION = "1.0.0";
|
|
1455
|
-
function showHelp() {
|
|
1456
|
-
console.log(`
|
|
1457
|
-
${import_picocolors.default.bold(import_picocolors.default.cyan("LaraTS CLI"))} ${import_picocolors.default.gray(`v${VERSION}`)}
|
|
1458
|
-
${import_picocolors.default.gray("Smart Architecture Visualizer & Contextual Module Scaffolder")}
|
|
1459
|
-
|
|
1460
|
-
${import_picocolors.default.bold("USAGE:")}
|
|
1461
|
-
${import_picocolors.default.cyan("$")} npx larats [command] [options]
|
|
1462
|
-
${import_picocolors.default.cyan("$")} larats [command] [options]
|
|
1463
|
-
|
|
1464
|
-
${import_picocolors.default.bold("COMMANDS:")}
|
|
1465
|
-
${import_picocolors.default.green("scan")} Scan repository & generate ARCHITECTURE.md
|
|
1466
|
-
${import_picocolors.default.green("scaffold")}, ${import_picocolors.default.green("make")} [name] Scaffold a new module or feature with clean boilerplate
|
|
1467
|
-
${import_picocolors.default.green("info")} Show detected project type & architectural flavor
|
|
1468
|
-
${import_picocolors.default.green("help")} Display this help message
|
|
1469
|
-
|
|
1470
|
-
${import_picocolors.default.bold("OPTIONS:")}
|
|
1471
|
-
${import_picocolors.default.yellow("-o, --output")} <file> Output markdown path for scan (default: ARCHITECTURE.md)
|
|
1472
|
-
${import_picocolors.default.yellow("-m, --modular")} Enforce modular directory structure
|
|
1473
|
-
${import_picocolors.default.yellow("-s, --standard")} Enforce standard/layered directory structure
|
|
1474
|
-
${import_picocolors.default.yellow("--dry-run")} Simulate generation without writing files
|
|
1475
|
-
${import_picocolors.default.yellow("-f, --force")} Overwrite files if they already exist
|
|
1476
|
-
${import_picocolors.default.yellow("-v, --version")} Display CLI version
|
|
1477
|
-
${import_picocolors.default.yellow("-h, --help")} Display this help message
|
|
1478
|
-
|
|
1479
|
-
${import_picocolors.default.bold("EXAMPLES:")}
|
|
1480
|
-
${import_picocolors.default.cyan("$")} larats
|
|
1481
|
-
${import_picocolors.default.cyan("$")} larats scan
|
|
1482
|
-
${import_picocolors.default.cyan("$")} larats scan --output docs/ARCHITECTURE.md
|
|
1483
|
-
${import_picocolors.default.cyan("$")} larats scaffold Payment
|
|
1484
|
-
${import_picocolors.default.cyan("$")} larats make Order --modular
|
|
1485
|
-
`);
|
|
1486
|
-
}
|
|
1487
|
-
function handleCancel() {
|
|
1488
|
-
p.cancel(import_picocolors.default.yellow("Operation cancelled. See you next time! \u{1F44B}"));
|
|
1489
|
-
process.exit(0);
|
|
1490
|
-
}
|
|
1491
|
-
async function runScan(projectInfo, outputPath = "ARCHITECTURE.md") {
|
|
1492
|
-
const spinner2 = p.spinner();
|
|
1493
|
-
spinner2.start("Scanning codebase structure & components (strict ignore list active)...");
|
|
1494
|
-
try {
|
|
1495
|
-
const scanResult = await scanProject(projectInfo);
|
|
1496
|
-
spinner2.stop(import_picocolors.default.green(`Scan completed in ${scanResult.scanDurationMs}ms`));
|
|
1497
|
-
const markdown = generateArchitectureMarkdown(scanResult);
|
|
1498
|
-
const destination = import_node_path3.default.resolve(projectInfo.rootPath, outputPath);
|
|
1499
|
-
const parentDir = import_node_path3.default.dirname(destination);
|
|
1500
|
-
if (!import_node_fs3.default.existsSync(parentDir)) {
|
|
1501
|
-
import_node_fs3.default.mkdirSync(parentDir, { recursive: true });
|
|
1502
|
-
}
|
|
1503
|
-
import_node_fs3.default.writeFileSync(destination, markdown, "utf-8");
|
|
1504
|
-
p.note(
|
|
1505
|
-
`${import_picocolors.default.bold("Scanned Files:")} ${scanResult.totalFilesScanned}
|
|
1506
|
-
${import_picocolors.default.bold("Components Identified:")} ${Object.keys(scanResult.components).length}
|
|
1507
|
-
${import_picocolors.default.bold("Modules Detected:")} ${scanResult.modules.length > 0 ? scanResult.modules.join(", ") : "None (monolithic structure)"}
|
|
1508
|
-
${import_picocolors.default.bold("Documentation:")} Saved to ${import_picocolors.default.cyan(import_node_path3.default.relative(projectInfo.rootPath, destination) || outputPath)}`,
|
|
1509
|
-
import_picocolors.default.green("Architecture Map Generated! \u{1F5FA}\uFE0F")
|
|
1510
|
-
);
|
|
1511
|
-
p.outro(import_picocolors.default.cyan(`Open ${outputPath} to view your project structure & Mermaid diagram!`));
|
|
1512
|
-
} catch (err) {
|
|
1513
|
-
spinner2.stop(import_picocolors.default.red("Scan failed"));
|
|
1514
|
-
p.log.error(err.message || String(err));
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
async function runScaffold(projectInfo, moduleNameArg, cliOptions = {}) {
|
|
1518
|
-
let moduleName = moduleNameArg;
|
|
1519
|
-
if (!moduleName) {
|
|
1520
|
-
const input = await p.text({
|
|
1521
|
-
message: "What is the name of the new module or feature?",
|
|
1522
|
-
placeholder: "e.g. Payment, Order, UserProfile",
|
|
1523
|
-
validate(val) {
|
|
1524
|
-
if (!val || !val.trim()) return "Module name cannot be empty.";
|
|
1525
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(val.trim())) return "Use only alphanumeric characters, dashes, or underscores.";
|
|
1526
|
-
}
|
|
1527
|
-
});
|
|
1528
|
-
if (p.isCancel(input)) handleCancel();
|
|
1529
|
-
moduleName = input.trim();
|
|
1530
|
-
}
|
|
1531
|
-
let selectedComponents = [];
|
|
1532
|
-
if (projectInfo.type === "laravel") {
|
|
1533
|
-
const componentsPrompt = await p.multiselect({
|
|
1534
|
-
message: `Select components to scaffold for ${import_picocolors.default.cyan(moduleName)}:`,
|
|
1535
|
-
options: [
|
|
1536
|
-
{ value: "model", label: "Eloquent Model", hint: "Includes table name, fillables, and casts" },
|
|
1537
|
-
{ value: "controller", label: "Resourceful Controller", hint: "Includes standard CRUD methods & DI service" },
|
|
1538
|
-
{ value: "service", label: "Service Class", hint: "Encapsulated business logic operations" },
|
|
1539
|
-
{ value: "action", label: "Single Action Class", hint: "Invokable single-responsibility class" },
|
|
1540
|
-
{ value: "request", label: "Form Request", hint: "Request validation rules & authorization" },
|
|
1541
|
-
{ value: "migration", label: "Database Migration", hint: "Schema definition with timestamps" }
|
|
1542
|
-
],
|
|
1543
|
-
initialValues: ["model", "controller", "service", "request", "migration"],
|
|
1544
|
-
required: true
|
|
1545
|
-
});
|
|
1546
|
-
if (p.isCancel(componentsPrompt)) handleCancel();
|
|
1547
|
-
selectedComponents = componentsPrompt;
|
|
1548
|
-
} else {
|
|
1549
|
-
const componentsPrompt = await p.multiselect({
|
|
1550
|
-
message: `Select components to scaffold for ${import_picocolors.default.cyan(moduleName)}:`,
|
|
1551
|
-
options: [
|
|
1552
|
-
{ value: "controller", label: "Controller", hint: "Express request handlers & response orchestration" },
|
|
1553
|
-
{ value: "service", label: "Service", hint: "Application logic & repository coordination" },
|
|
1554
|
-
{ value: "repository", label: "Repository", hint: "Data persistence abstraction & queries" },
|
|
1555
|
-
{ value: "route", label: "Router & Routes", hint: "Express router mapping endpoints to controller" },
|
|
1556
|
-
{ value: "dto", label: "DTOs & Interfaces", hint: "Typed interfaces for entities and payloads" }
|
|
1557
|
-
],
|
|
1558
|
-
initialValues: ["controller", "service", "repository", "route", "dto"],
|
|
1559
|
-
required: true
|
|
1560
|
-
});
|
|
1561
|
-
if (p.isCancel(componentsPrompt)) handleCancel();
|
|
1562
|
-
selectedComponents = componentsPrompt;
|
|
1563
|
-
}
|
|
1564
|
-
let isModular = true;
|
|
1565
|
-
if (cliOptions.modular) {
|
|
1566
|
-
isModular = true;
|
|
1567
|
-
} else if (cliOptions.standard) {
|
|
1568
|
-
isModular = false;
|
|
1569
|
-
} else {
|
|
1570
|
-
const defaultModular = projectInfo.type === "laravel" ? Boolean(projectInfo.laravel?.isModular) : projectInfo.type === "typescript" ? Boolean(projectInfo.typescript?.isModular) : true;
|
|
1571
|
-
const structurePrompt = await p.select({
|
|
1572
|
-
message: "Choose folder architecture structure:",
|
|
1573
|
-
options: projectInfo.type === "laravel" ? [
|
|
1574
|
-
{
|
|
1575
|
-
value: "modular",
|
|
1576
|
-
label: "Modular Domain Structure",
|
|
1577
|
-
hint: `app/Modules/${moduleName}/[Models, Controllers, Services]`
|
|
1578
|
-
},
|
|
1579
|
-
{
|
|
1580
|
-
value: "standard",
|
|
1581
|
-
label: "Standard Laravel Structure",
|
|
1582
|
-
hint: `app/Models, app/Http/Controllers, app/Services`
|
|
1583
|
-
}
|
|
1584
|
-
] : [
|
|
1585
|
-
{
|
|
1586
|
-
value: "modular",
|
|
1587
|
-
label: "Modular Feature Structure",
|
|
1588
|
-
hint: `src/modules/${moduleName.toLowerCase()}/...`
|
|
1589
|
-
},
|
|
1590
|
-
{
|
|
1591
|
-
value: "standard",
|
|
1592
|
-
label: "Layered Folder Structure",
|
|
1593
|
-
hint: `src/controllers, src/services, src/routes...`
|
|
1594
|
-
}
|
|
1595
|
-
],
|
|
1596
|
-
initialValue: defaultModular ? "modular" : "standard"
|
|
1597
|
-
});
|
|
1598
|
-
if (p.isCancel(structurePrompt)) handleCancel();
|
|
1599
|
-
isModular = structurePrompt === "modular";
|
|
1600
|
-
}
|
|
1601
|
-
const spinner2 = p.spinner();
|
|
1602
|
-
spinner2.start(`Generating boilerplate files for ${import_picocolors.default.cyan(moduleName)}...`);
|
|
1603
|
-
const result = await scaffoldModule(projectInfo, {
|
|
1604
|
-
moduleName,
|
|
1605
|
-
components: selectedComponents,
|
|
1606
|
-
isModular,
|
|
1607
|
-
dryRun: cliOptions.dryRun,
|
|
1608
|
-
forceOverwrite: cliOptions.force
|
|
1609
|
-
});
|
|
1610
|
-
spinner2.stop("Generation finished!");
|
|
1611
|
-
const fileLines = result.files.map((f) => {
|
|
1612
|
-
if (f.status === "created") {
|
|
1613
|
-
return ` ${import_picocolors.default.green("\u2714")} ${f.relativeFilePath} ${import_picocolors.default.gray("(created)")}`;
|
|
1614
|
-
} else if (f.status === "skipped") {
|
|
1615
|
-
return ` ${import_picocolors.default.yellow("\u26A0")} ${f.relativeFilePath} ${import_picocolors.default.yellow(`(skipped - ${f.reason})`)}`;
|
|
1616
|
-
} else {
|
|
1617
|
-
return ` ${import_picocolors.default.red("\u2716")} ${f.relativeFilePath} ${import_picocolors.default.red(`(error - ${f.reason})`)}`;
|
|
1618
|
-
}
|
|
1619
|
-
});
|
|
1620
|
-
p.note(
|
|
1621
|
-
fileLines.join("\n") + `
|
|
1622
|
-
|
|
1623
|
-
${import_picocolors.default.bold("Summary:")} ${import_picocolors.default.green(`${result.createdCount} created`)}, ${import_picocolors.default.yellow(`${result.skippedCount} skipped`)}, ${import_picocolors.default.red(`${result.errorCount} errors`)}`,
|
|
1624
|
-
import_picocolors.default.cyan(`Module Scaffolding: ${moduleName}`)
|
|
1625
|
-
);
|
|
1626
|
-
if (result.skippedCount > 0 && !cliOptions.force) {
|
|
1627
|
-
p.log.warn(import_picocolors.default.yellow("Anti-Overwrite Protection prevented modifying existing files. Use --force to overwrite if intended."));
|
|
1628
|
-
}
|
|
1629
|
-
p.outro(import_picocolors.default.green(`\u2728 Successfully scaffolded ${moduleName} module!`));
|
|
1630
|
-
}
|
|
1631
|
-
function showProjectInfo(projectInfo) {
|
|
1632
|
-
let details = `${import_picocolors.default.bold("Project Name:")} ${projectInfo.name}
|
|
1633
|
-
${import_picocolors.default.bold("Project Type:")} ${projectInfo.type.toUpperCase()}
|
|
1634
|
-
${import_picocolors.default.bold("Flavor / Stack:")} ${projectInfo.summary}
|
|
1635
|
-
${import_picocolors.default.bold("Working Path:")} ${projectInfo.rootPath}`;
|
|
1636
|
-
if (projectInfo.type === "laravel" && projectInfo.laravel) {
|
|
1637
|
-
details += `
|
|
1638
|
-
${import_picocolors.default.bold("Filament Admin:")} ${projectInfo.laravel.hasFilament ? import_picocolors.default.green("Yes") : "No"}
|
|
1639
|
-
${import_picocolors.default.bold("Livewire:")} ${projectInfo.laravel.hasLivewire ? import_picocolors.default.green("Yes") : "No"}
|
|
1640
|
-
${import_picocolors.default.bold("Inertia.js:")} ${projectInfo.laravel.hasInertia ? import_picocolors.default.green("Yes") : "No"}
|
|
1641
|
-
${import_picocolors.default.bold("Modular App:")} ${projectInfo.laravel.isModular ? import_picocolors.default.green("Yes") : "No"}`;
|
|
1642
|
-
} else if (projectInfo.type === "typescript" && projectInfo.typescript) {
|
|
1643
|
-
details += `
|
|
1644
|
-
${import_picocolors.default.bold("Framework:")} ${projectInfo.typescript.framework}
|
|
1645
|
-
${import_picocolors.default.bold("Prisma ORM:")} ${projectInfo.typescript.hasPrisma ? import_picocolors.default.green("Yes") : "No"}
|
|
1646
|
-
${import_picocolors.default.bold("Monorepo:")} ${projectInfo.typescript.isMonorepo ? import_picocolors.default.green("Yes") : "No"}
|
|
1647
|
-
${import_picocolors.default.bold("Modular Src:")} ${projectInfo.typescript.isModular ? import_picocolors.default.green("Yes") : "No"}`;
|
|
1648
|
-
}
|
|
1649
|
-
p.note(details, import_picocolors.default.cyan("Detected Project Architecture"));
|
|
1650
|
-
}
|
|
1651
|
-
async function main() {
|
|
1652
|
-
const args = process.argv.slice(2);
|
|
1653
|
-
if (args.includes("--help") || args.includes("-h") || args[0] === "help") {
|
|
1654
|
-
showHelp();
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
1658
|
-
console.log(`larats v${VERSION}`);
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
let outputPath = "ARCHITECTURE.md";
|
|
1662
|
-
const outputIdx = args.findIndex((a) => a === "--output" || a === "-o");
|
|
1663
|
-
if (outputIdx !== -1 && args[outputIdx + 1]) {
|
|
1664
|
-
outputPath = args[outputIdx + 1];
|
|
1665
|
-
}
|
|
1666
|
-
const isModularFlag = args.includes("--modular") || args.includes("-m");
|
|
1667
|
-
const isStandardFlag = args.includes("--standard") || args.includes("-s");
|
|
1668
|
-
const isForceFlag = args.includes("--force") || args.includes("-f");
|
|
1669
|
-
const isDryRunFlag = args.includes("--dry-run");
|
|
1670
|
-
const command = args[0] && !args[0].startsWith("-") ? args[0] : null;
|
|
1671
|
-
const projectInfo = await detectProjectType(process.cwd());
|
|
1672
|
-
if (command === "scan") {
|
|
1673
|
-
p.intro(`${import_picocolors.default.bgCyan(import_picocolors.default.black(" LaraTS "))} ${import_picocolors.default.gray(`v${VERSION}`)} - Scanning Architecture`);
|
|
1674
|
-
p.log.info(`Detected: ${import_picocolors.default.green(projectInfo.summary)}`);
|
|
1675
|
-
await runScan(projectInfo, outputPath);
|
|
1676
|
-
return;
|
|
1677
|
-
}
|
|
1678
|
-
if (command === "scaffold" || command === "make") {
|
|
1679
|
-
const moduleName = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
|
|
1680
|
-
p.intro(`${import_picocolors.default.bgCyan(import_picocolors.default.black(" LaraTS "))} ${import_picocolors.default.gray(`v${VERSION}`)} - Module Scaffolder`);
|
|
1681
|
-
p.log.info(`Target Project: ${import_picocolors.default.green(projectInfo.summary)}`);
|
|
1682
|
-
await runScaffold(projectInfo, moduleName, {
|
|
1683
|
-
modular: isModularFlag,
|
|
1684
|
-
standard: isStandardFlag,
|
|
1685
|
-
force: isForceFlag,
|
|
1686
|
-
dryRun: isDryRunFlag
|
|
1687
|
-
});
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
if (command === "info") {
|
|
1691
|
-
p.intro(`${import_picocolors.default.bgCyan(import_picocolors.default.black(" LaraTS "))} ${import_picocolors.default.gray(`v${VERSION}`)}`);
|
|
1692
|
-
showProjectInfo(projectInfo);
|
|
1693
|
-
p.outro(import_picocolors.default.cyan("Done."));
|
|
1694
|
-
return;
|
|
1695
|
-
}
|
|
1696
|
-
p.intro(`${import_picocolors.default.bgCyan(import_picocolors.default.black(" LaraTS "))} ${import_picocolors.default.bold("Smart Architecture Visualizer & Scaffolder")} ${import_picocolors.default.gray(`v${VERSION}`)}`);
|
|
1697
|
-
p.log.info(`Working Directory: ${import_picocolors.default.gray(process.cwd())}`);
|
|
1698
|
-
p.log.info(`Detected Architecture: ${import_picocolors.default.bold(import_picocolors.default.green(projectInfo.summary))}`);
|
|
1699
|
-
const action = await p.select({
|
|
1700
|
-
message: "What would you like to do?",
|
|
1701
|
-
options: [
|
|
1702
|
-
{
|
|
1703
|
-
value: "scan",
|
|
1704
|
-
label: "\u{1F50D} Scan Architecture & Generate ARCHITECTURE.md",
|
|
1705
|
-
hint: "Creates component stats, directory tree & Mermaid flowchart"
|
|
1706
|
-
},
|
|
1707
|
-
{
|
|
1708
|
-
value: "scaffold",
|
|
1709
|
-
label: "\u{1F3D7}\uFE0F Scaffold New Feature / Module",
|
|
1710
|
-
hint: "Interactively generate boilerplate components with safety checks"
|
|
1711
|
-
},
|
|
1712
|
-
{
|
|
1713
|
-
value: "info",
|
|
1714
|
-
label: "\u{1F4CA} Inspect Detected Architecture Details",
|
|
1715
|
-
hint: "View detected frameworks, ORMs, and configurations"
|
|
1716
|
-
},
|
|
1717
|
-
{
|
|
1718
|
-
value: "exit",
|
|
1719
|
-
label: "\u{1F6AA} Exit"
|
|
1720
|
-
}
|
|
1721
|
-
]
|
|
1722
|
-
});
|
|
1723
|
-
if (p.isCancel(action) || action === "exit") {
|
|
1724
|
-
handleCancel();
|
|
1725
|
-
}
|
|
1726
|
-
if (action === "scan") {
|
|
1727
|
-
await runScan(projectInfo, outputPath);
|
|
1728
|
-
} else if (action === "scaffold") {
|
|
1729
|
-
await runScaffold(projectInfo, void 0, {
|
|
1730
|
-
modular: isModularFlag,
|
|
1731
|
-
standard: isStandardFlag,
|
|
1732
|
-
force: isForceFlag,
|
|
1733
|
-
dryRun: isDryRunFlag
|
|
1734
|
-
});
|
|
1735
|
-
} else if (action === "info") {
|
|
1736
|
-
showProjectInfo(projectInfo);
|
|
1737
|
-
p.outro(import_picocolors.default.cyan("Done!"));
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
main().catch((err) => {
|
|
1741
|
-
p.log.error(import_picocolors.default.red(`Fatal error: ${err.message || String(err)}`));
|
|
1742
|
-
process.exit(1);
|
|
1743
|
-
});
|
|
1744
|
-
//# sourceMappingURL=index.cjs.map
|