create-backlist 6.2.3 → 7.3.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 +1 -10
- package/bin/index.js +470 -140
- package/package.json +11 -7
- package/src/ai-agent.js +171 -0
- package/src/analyzer.js +137 -22
- package/src/generators/dotnet.js +134 -133
- package/src/generators/java.js +248 -233
- package/src/generators/js.js +346 -0
- package/src/generators/nestjs.js +278 -0
- package/src/generators/node.js +57 -26
- package/src/generators/python.js +86 -104
- package/src/generators/template.js +10 -8
- package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
- package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
- package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
- package/src/templates/js-express/base/server.js +59 -0
- package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
- package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
- package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
- package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
- package/src/templates/js-express/partials/controller.js.ejs +53 -0
- package/src/templates/js-express/partials/db.js.ejs +19 -0
- package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
- package/src/templates/js-express/partials/model.js.ejs +18 -0
- package/src/templates/js-express/partials/package.json.ejs +17 -0
- package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
- package/src/templates/js-express/partials/routes.js.ejs +19 -0
- package/src/templates/js-express/partials/seeder.js.ejs +103 -0
- package/src/templates/js-express/partials/service.js.ejs +51 -0
- package/src/templates/js-express/partials/swagger.js.ejs +30 -0
- package/src/templates/js-express/partials/test.js.ejs +46 -0
- package/src/templates/nestjs/base/app.module.ts +9 -0
- package/src/templates/nestjs/base/main.ts +23 -0
- package/src/templates/nestjs/base/tsconfig.json +21 -0
- package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
- package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
- package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
- package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
- package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
- package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
- package/src/templates/nestjs/partials/module.ts.ejs +10 -0
- package/src/templates/nestjs/partials/package.json.ejs +27 -0
- package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
- package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
- package/src/templates/nestjs/partials/service.ts.ejs +67 -0
- package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
- package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -0
- package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -0
- package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -0
- package/src/utils.js +3 -5
- /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
- /package/src/templates/{node-ts-express → dotnet}/partials/Model.cs.ejs +0 -0
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-backlist",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "An advanced, multi-language backend generator based on frontend analysis.",
|
|
5
|
-
"type": "
|
|
3
|
+
"version": "7.3.0",
|
|
4
|
+
"description": "An advanced, multi-language backend generator based on frontend analysis. Smart Freemium SaaS CLI.",
|
|
5
|
+
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-backlist": "bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
|
-
"src"
|
|
11
|
+
"src",
|
|
12
|
+
"AiModuls"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"start": "node bin/index.js"
|
|
@@ -18,13 +19,16 @@
|
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"@babel/parser": "^7.22.7",
|
|
20
21
|
"@babel/traverse": "^7.22.8",
|
|
22
|
+
"@clack/prompts": "^0.7.0",
|
|
21
23
|
"axios": "^1.13.1",
|
|
22
|
-
"chalk": "^
|
|
24
|
+
"chalk": "^5.3.0",
|
|
23
25
|
"ejs": "^3.1.9",
|
|
24
|
-
"execa": "^
|
|
26
|
+
"execa": "^8.0.1",
|
|
25
27
|
"fs-extra": "^11.1.1",
|
|
26
28
|
"glob": "^10.3.3",
|
|
27
|
-
"inquirer": "^
|
|
29
|
+
"inquirer": "^9.2.12",
|
|
30
|
+
"ora": "^7.0.1",
|
|
31
|
+
"together-ai": "^0.39.0",
|
|
28
32
|
"unzipper": "^0.12.3"
|
|
29
33
|
}
|
|
30
34
|
}
|
package/src/ai-agent.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import Together from "together-ai";
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export class BacklistAIAgent {
|
|
6
|
+
constructor(apiKey, onThought) {
|
|
7
|
+
this.apiKey = apiKey;
|
|
8
|
+
this.onThought = onThought || (() => {});
|
|
9
|
+
this.together = null;
|
|
10
|
+
this.modelName = "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async init() {
|
|
14
|
+
this.onThought('[THOUGHT] Initializing Together AI runtime...');
|
|
15
|
+
try {
|
|
16
|
+
this.together = new Together({ apiKey: this.apiKey });
|
|
17
|
+
this.onThought('[THOUGHT] Connected to Together AI cloud service successfully.');
|
|
18
|
+
} catch (err) {
|
|
19
|
+
throw new Error(`Together AI initialization failed: ${err.message}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async promptModel(systemPrompt, userPrompt) {
|
|
24
|
+
const response = await this.together.chat.completions.create({
|
|
25
|
+
messages: [
|
|
26
|
+
{ role: "system", content: systemPrompt },
|
|
27
|
+
{ role: "user", content: userPrompt }
|
|
28
|
+
],
|
|
29
|
+
model: this.modelName
|
|
30
|
+
});
|
|
31
|
+
return response.choices[0].message.content;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- PASS 1: Generate Code Blocks ---
|
|
35
|
+
async generateBackendBlocks(astJsonData, existingSchemaContent = null) {
|
|
36
|
+
this.onThought(`[THOUGHT] Commencing Pass 1 Analysis on ${astJsonData.length} AST endpoints via Cloud AI...`);
|
|
37
|
+
|
|
38
|
+
let schemaDirective = `Generate a comprehensive Prisma schema (schema.prisma). Deduce many-to-many relationships and apply optimal indexing.`;
|
|
39
|
+
if (existingSchemaContent) {
|
|
40
|
+
this.onThought('[THOUGHT] Detected existing schema.prisma. Generating Schema Migration Scripts instead of full overwrite.');
|
|
41
|
+
schemaDirective = `An existing schema exists. Output an SQL Migration Script instead of a full schema rewrite, along with the updated prisma schema models.`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const systemPrompt = `You are an expert backend architect and Domain-Driven Design (DDD) specialist.
|
|
45
|
+
Follow Hexagonal Architecture (Ports and Adapters) principles.
|
|
46
|
+
Your task is to generate intelligent implementation blocks for EJS placeholders based on the provided AST data.
|
|
47
|
+
|
|
48
|
+
1. ${schemaDirective}
|
|
49
|
+
2. Generate <%- aiSecurityConfig %>: Define complex JWT filters, rate limiting, and CORS based on the sensitivity of the endpoints.
|
|
50
|
+
3. Generate <%- aiDbRelations %>: Code for Repositories connecting defined Prisma models.
|
|
51
|
+
4. Generate <%- aiValidationLogic %>: Input validation middleware (Zod, Joi) tailored precisely to the data shapes extracted from the frontend.
|
|
52
|
+
|
|
53
|
+
Output ONLY JSON with the following structure:
|
|
54
|
+
{
|
|
55
|
+
"prismaSchema": "string",
|
|
56
|
+
"aiSecurityConfig": "string",
|
|
57
|
+
"aiDbRelations": "string",
|
|
58
|
+
"aiValidationLogic": "string"
|
|
59
|
+
}
|
|
60
|
+
Do NOT include explanations. Output raw JSON only.`;
|
|
61
|
+
|
|
62
|
+
const userPrompt = `AST Frontend Extracted Data:\n${JSON.stringify(astJsonData, null, 2)}`;
|
|
63
|
+
|
|
64
|
+
this.onThought('[THOUGHT] Prompting Together AI (Llama-4-Maverick) with Hexagonal architecture rules...');
|
|
65
|
+
let result = await this.promptModel(systemPrompt, userPrompt);
|
|
66
|
+
|
|
67
|
+
// Clean JSON response
|
|
68
|
+
try {
|
|
69
|
+
if (result.includes('```json')) {
|
|
70
|
+
result = result.split('```json')[1].split('```')[0].trim();
|
|
71
|
+
} else if (result.includes('```')) {
|
|
72
|
+
result = result.split('```')[1].split('```')[0].trim();
|
|
73
|
+
}
|
|
74
|
+
return JSON.parse(result);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
this.onThought(`[WARNING] Failed to parse Pass 1 JSON. Attempting heuristic extraction...`);
|
|
77
|
+
return {
|
|
78
|
+
prismaSchema: "// Fallback schema\n" + result,
|
|
79
|
+
aiSecurityConfig: "// Security fallback",
|
|
80
|
+
aiDbRelations: "// Db Relations fallback",
|
|
81
|
+
aiValidationLogic: "// Validation fallback"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- PASS 2: Verification Loop (Dry-Run & DOM Sync) ---
|
|
87
|
+
async verifyDryRun(generatedBlocks, astJsonData) {
|
|
88
|
+
this.onThought('[THOUGHT] Commencing Pass 2 Verification Loop (Virtual Dry Run)...');
|
|
89
|
+
|
|
90
|
+
let issueFound = false;
|
|
91
|
+
|
|
92
|
+
// DOM Sync Level 2 (Data-type matching check)
|
|
93
|
+
this.onThought('[THOUGHT] Simulating frontend component tree data injection against generated validation logic...');
|
|
94
|
+
|
|
95
|
+
const systemPrompt = `You are a strict QA Engine.
|
|
96
|
+
Review the following generated Validation Logic and DB Relations against the Frontend AST data shapes.
|
|
97
|
+
Check for:
|
|
98
|
+
1. Missing DB relations (e.g., User -> Post).
|
|
99
|
+
2. Data-type mismatches (DOM Sync Level 2: if AST expects 'Date' string but DB expects 'DateTime', inject a transformation middleware).
|
|
100
|
+
|
|
101
|
+
Output JSON:
|
|
102
|
+
{
|
|
103
|
+
"issuesFound": boolean,
|
|
104
|
+
"fixedValidationLogic": "string (original or fixed)",
|
|
105
|
+
"fixedDbRelations": "string (original or fixed)",
|
|
106
|
+
"reasonings": ["string"]
|
|
107
|
+
}`;
|
|
108
|
+
|
|
109
|
+
const userPrompt = `Data:
|
|
110
|
+
Generated Validation: ${generatedBlocks.aiValidationLogic}
|
|
111
|
+
Generated DB Rel: ${generatedBlocks.aiDbRelations}
|
|
112
|
+
AST Shapes: ${JSON.stringify(astJsonData.map(e => e.schemaFields), null, 2)}`;
|
|
113
|
+
|
|
114
|
+
let result = await this.promptModel(systemPrompt, userPrompt);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (result.includes('```json')) result = result.split('```json')[1].split('```')[0].trim();
|
|
118
|
+
else if (result.includes('```')) result = result.split('```')[1].split('```')[0].trim();
|
|
119
|
+
const verified = JSON.parse(result);
|
|
120
|
+
|
|
121
|
+
if (verified.issuesFound) {
|
|
122
|
+
this.onThought(`[THOUGHT] Verification caught issues! Self-healing triggered...`);
|
|
123
|
+
verified.reasonings.forEach(r => this.onThought(`[THOUGHT] -> Fix applied: ${r}`));
|
|
124
|
+
return {
|
|
125
|
+
...generatedBlocks,
|
|
126
|
+
aiValidationLogic: verified.fixedValidationLogic,
|
|
127
|
+
aiDbRelations: verified.fixedDbRelations
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
this.onThought('[THOUGHT] Virtual Dry Run passed perfectly. Zero data mismatches found.');
|
|
131
|
+
return generatedBlocks;
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
this.onThought('[WARNING] Verification parsing failed. Using Pass 1 results.');
|
|
135
|
+
return generatedBlocks;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Autonomous Deployment Engine ---
|
|
140
|
+
async generateDeploymentConfig(stack, astJsonData) {
|
|
141
|
+
this.onThought(`[THOUGHT] Generating Autonomous Deployment workflows for [${stack}]...`);
|
|
142
|
+
|
|
143
|
+
const systemPrompt = `Generate a highly optimized docker-compose.yml and a .github/workflows/deploy.yml for a production ${stack} backend.
|
|
144
|
+
Include PostgreSQL, Redis, and best-practice health checks.
|
|
145
|
+
Output JSON:
|
|
146
|
+
{
|
|
147
|
+
"dockerCompose": "string",
|
|
148
|
+
"githubWorkflow": "string"
|
|
149
|
+
}`;
|
|
150
|
+
|
|
151
|
+
const userPrompt = `Target Stack: ${stack}\nAST Endpoints Count: ${astJsonData ? astJsonData.length : 0}`;
|
|
152
|
+
|
|
153
|
+
const res = await this.promptModel(systemPrompt, userPrompt);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
let clean = res;
|
|
157
|
+
if (clean.includes('```json')) clean = clean.split('```json')[1].split('```')[0].trim();
|
|
158
|
+
else if (clean.includes('```')) clean = clean.split('```')[1].split('```')[0].trim();
|
|
159
|
+
const parsed = JSON.parse(clean);
|
|
160
|
+
this.onThought('[THOUGHT] Deployment workflows synthesized successfully.');
|
|
161
|
+
return parsed;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return { dockerCompose: "# Fallback Config", githubWorkflow: "# Fallback Workflow" };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async dispose() {
|
|
168
|
+
this.onThought('[THOUGHT] Shutting down AI context...');
|
|
169
|
+
// Together AI doesn't hold local VRAM or contexts to dispose, doing nothing.
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/analyzer.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { glob } = require("glob");
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { glob } from "glob";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import parser from "@babel/parser";
|
|
6
|
+
import _traverse from "@babel/traverse";
|
|
7
|
+
const traverse = _traverse.default || _traverse;
|
|
8
8
|
|
|
9
9
|
const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete"]);
|
|
10
10
|
|
|
@@ -200,11 +200,10 @@ function isJSONStringifyCall(node) {
|
|
|
200
200
|
// -------------------------
|
|
201
201
|
// DB insights: guess db + infer models + seeds
|
|
202
202
|
// -------------------------
|
|
203
|
-
function guessDbTypeFromRepo(rootDir) {
|
|
204
|
-
// Best-effort; if it's only frontend repo, usually null.
|
|
203
|
+
function guessDbTypeFromRepo(rootDir, endpoints = []) {
|
|
205
204
|
try {
|
|
206
205
|
const pkgPath = path.join(rootDir, "package.json");
|
|
207
|
-
if (!fs.existsSync(pkgPath)) return
|
|
206
|
+
if (!fs.existsSync(pkgPath)) return heuristicallyGuessDB(endpoints);
|
|
208
207
|
|
|
209
208
|
const pkg = fs.readJsonSync(pkgPath);
|
|
210
209
|
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
@@ -214,12 +213,25 @@ function guessDbTypeFromRepo(rootDir) {
|
|
|
214
213
|
if (deps.sequelize) return "sql-sequelize";
|
|
215
214
|
if (deps.typeorm) return "sql-typeorm";
|
|
216
215
|
|
|
217
|
-
return
|
|
216
|
+
return heuristicallyGuessDB(endpoints);
|
|
218
217
|
} catch {
|
|
219
|
-
return
|
|
218
|
+
return heuristicallyGuessDB(endpoints);
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
221
|
|
|
222
|
+
function heuristicallyGuessDB(endpoints) {
|
|
223
|
+
// Free Tier / Default Intelligence:
|
|
224
|
+
// Analyze data complexity. If highly nested schemas are prominent, default NoSQL.
|
|
225
|
+
// If many flat, relational-looking fields exist, default SQL.
|
|
226
|
+
let maxNesting = 0;
|
|
227
|
+
for (const ep of endpoints) {
|
|
228
|
+
if (ep.schemaFields && Object.keys(ep.schemaFields).length > 6) {
|
|
229
|
+
maxNesting++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return maxNesting > 3 ? "mongodb-mongoose" : "sql-prisma";
|
|
233
|
+
}
|
|
234
|
+
|
|
223
235
|
function inferModelsFromEndpoints(endpoints) {
|
|
224
236
|
const models = new Map();
|
|
225
237
|
|
|
@@ -255,6 +267,59 @@ function inferModelsFromEndpoints(endpoints) {
|
|
|
255
267
|
}));
|
|
256
268
|
}
|
|
257
269
|
|
|
270
|
+
// -------------------------
|
|
271
|
+
// Relationship detection from nested routes
|
|
272
|
+
// -------------------------
|
|
273
|
+
function singularize(word) {
|
|
274
|
+
if (!word) return word;
|
|
275
|
+
if (word.endsWith("ies")) return word.slice(0, -3) + "y";
|
|
276
|
+
if (word.endsWith("ses") || word.endsWith("xes") || word.endsWith("zes")) return word.slice(0, -2);
|
|
277
|
+
if (word.endsWith("s") && !word.endsWith("ss")) return word.slice(0, -1);
|
|
278
|
+
return word;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function detectRelationships(endpoints) {
|
|
282
|
+
const seen = new Set();
|
|
283
|
+
const relationships = [];
|
|
284
|
+
|
|
285
|
+
for (const ep of endpoints) {
|
|
286
|
+
const route = ep.route || ep.path || "";
|
|
287
|
+
const segments = route.split("/").filter(Boolean);
|
|
288
|
+
|
|
289
|
+
for (let i = 0; i < segments.length - 2; i++) {
|
|
290
|
+
const parentSegment = segments[i];
|
|
291
|
+
const paramSegment = segments[i + 1];
|
|
292
|
+
const childSegment = segments[i + 2];
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
parentSegment === "api" ||
|
|
296
|
+
/^v\d+$/i.test(parentSegment) ||
|
|
297
|
+
!paramSegment.startsWith(":")
|
|
298
|
+
) continue;
|
|
299
|
+
|
|
300
|
+
const parentName = toTitleCase(singularize(parentSegment));
|
|
301
|
+
const childName = toTitleCase(singularize(childSegment));
|
|
302
|
+
|
|
303
|
+
if (!parentName || !childName || parentName === childName) continue;
|
|
304
|
+
|
|
305
|
+
const key = `${parentName}:${childName}`;
|
|
306
|
+
if (seen.has(key)) continue;
|
|
307
|
+
seen.add(key);
|
|
308
|
+
|
|
309
|
+
const foreignKey = parentName.charAt(0).toLowerCase() + parentName.slice(1) + "Id";
|
|
310
|
+
|
|
311
|
+
relationships.push({
|
|
312
|
+
parent: parentName,
|
|
313
|
+
child: childName,
|
|
314
|
+
type: "oneToMany",
|
|
315
|
+
foreignKey,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return relationships;
|
|
321
|
+
}
|
|
322
|
+
|
|
258
323
|
function seedValueForType(t) {
|
|
259
324
|
if (t === "Number") return 1;
|
|
260
325
|
if (t === "Boolean") return true;
|
|
@@ -278,7 +343,7 @@ function generateSeedsFromModels(models, perModel = 3) {
|
|
|
278
343
|
// -------------------------
|
|
279
344
|
// MAIN frontend analyzer
|
|
280
345
|
// -------------------------
|
|
281
|
-
async function analyzeFrontend(srcPath) {
|
|
346
|
+
export async function analyzeFrontend(srcPath) {
|
|
282
347
|
if (!srcPath) throw new Error("analyzeFrontend: srcPath is required");
|
|
283
348
|
if (!fs.existsSync(srcPath)) {
|
|
284
349
|
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
@@ -381,18 +446,19 @@ async function analyzeFrontend(srcPath) {
|
|
|
381
446
|
if (!apiPath) return;
|
|
382
447
|
|
|
383
448
|
const route = normalizeRouteForBackend(apiPath.split("?")[0]);
|
|
449
|
+
const normalizedRoute = route.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
|
|
384
450
|
const controllerName = deriveControllerNameFromUrl(apiPath);
|
|
385
|
-
const actionName = deriveActionName(method,
|
|
451
|
+
const actionName = deriveActionName(method, normalizedRoute);
|
|
386
452
|
|
|
387
|
-
const key = `${method}:${
|
|
453
|
+
const key = `${method}:${normalizedRoute}`;
|
|
388
454
|
if (!endpoints.has(key)) {
|
|
389
455
|
endpoints.set(key, {
|
|
390
456
|
path: apiPath,
|
|
391
|
-
route,
|
|
457
|
+
route: normalizedRoute,
|
|
392
458
|
method,
|
|
393
459
|
controllerName,
|
|
394
460
|
actionName,
|
|
395
|
-
pathParams: extractPathParams(
|
|
461
|
+
pathParams: extractPathParams(normalizedRoute),
|
|
396
462
|
queryParams: extractQueryParamsFromUrl(apiPath),
|
|
397
463
|
schemaFields,
|
|
398
464
|
requestBody: schemaFields ? { fields: schemaFields } : null,
|
|
@@ -409,7 +475,7 @@ async function analyzeFrontend(srcPath) {
|
|
|
409
475
|
// -------------------------
|
|
410
476
|
// Optional: full project analyze (endpoints + db insights)
|
|
411
477
|
// -------------------------
|
|
412
|
-
async function analyze(projectRoot = process.cwd()) {
|
|
478
|
+
export async function analyze(projectRoot = process.cwd()) {
|
|
413
479
|
const rootDir = path.resolve(projectRoot);
|
|
414
480
|
|
|
415
481
|
const frontendSrc = ["src", "app", "pages"]
|
|
@@ -420,17 +486,66 @@ async function analyze(projectRoot = process.cwd()) {
|
|
|
420
486
|
|
|
421
487
|
const models = inferModelsFromEndpoints(endpoints);
|
|
422
488
|
const seeds = generateSeedsFromModels(models, 3);
|
|
423
|
-
const guessedDb = guessDbTypeFromRepo(rootDir);
|
|
489
|
+
const guessedDb = guessDbTypeFromRepo(rootDir, endpoints);
|
|
490
|
+
const relationships = detectRelationships(endpoints);
|
|
424
491
|
|
|
425
492
|
return {
|
|
426
493
|
rootDir: normalizeSlashes(rootDir),
|
|
427
494
|
endpoints,
|
|
495
|
+
relationships,
|
|
428
496
|
dbInsights: {
|
|
429
|
-
guessedDb,
|
|
430
|
-
models,
|
|
431
|
-
seeds,
|
|
497
|
+
guessedDb,
|
|
498
|
+
models,
|
|
499
|
+
seeds,
|
|
432
500
|
},
|
|
433
501
|
};
|
|
434
502
|
}
|
|
435
503
|
|
|
436
|
-
|
|
504
|
+
// -------------------------
|
|
505
|
+
// NEW v7.0: Low-Cost Path Scanner (Standard Tier)
|
|
506
|
+
// -------------------------
|
|
507
|
+
export async function performLowCostPathScan(frontendSrcDir, endpoints) {
|
|
508
|
+
// Ensures routes match frontend expectations
|
|
509
|
+
const inconsistencies = [];
|
|
510
|
+
endpoints.forEach(ep => {
|
|
511
|
+
if (!ep.sourceFile || !ep.route) return;
|
|
512
|
+
const fileBase = path.basename(ep.sourceFile).split('.')[0].toLowerCase();
|
|
513
|
+
const routeBase = ep.controllerName ? ep.controllerName.toLowerCase() : '';
|
|
514
|
+
|
|
515
|
+
// If the file containing the fetch is named 'AdminPanel' but route points to 'Products', note it.
|
|
516
|
+
if (fileBase !== 'index' && fileBase !== 'api' && routeBase && !fileBase.includes(routeBase) && !routeBase.includes(fileBase)) {
|
|
517
|
+
inconsistencies.push({
|
|
518
|
+
file: ep.sourceFile,
|
|
519
|
+
routeCalled: ep.route,
|
|
520
|
+
warning: `Path Scanner: File '${fileBase}' calls unrelated route '${routeBase}'. Potential naming drift.`
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
return inconsistencies;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// -------------------------
|
|
528
|
+
// NEW v7.0: Component Component Tree Extractor (DOM Sync Level 2)
|
|
529
|
+
// -------------------------
|
|
530
|
+
export async function extractComponentTreeTypes(frontendSrcDir) {
|
|
531
|
+
// A heuristic simulation of live DOM cross-checking:
|
|
532
|
+
// Parses JSX tags (<input type="date">) to build forced validations.
|
|
533
|
+
if (!fs.existsSync(frontendSrcDir)) return [];
|
|
534
|
+
|
|
535
|
+
const files = await glob(`${normalizeSlashes(frontendSrcDir)}/**/*.{jsx,tsx}`, {
|
|
536
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**"]
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const extractedTypes = [];
|
|
540
|
+
|
|
541
|
+
for (const file of files) {
|
|
542
|
+
try {
|
|
543
|
+
const code = await fs.readFile(file, "utf-8");
|
|
544
|
+
if (code.includes('type="date"')) extractedTypes.push({ file, fieldType: 'Date', rawHTMLType: 'date' });
|
|
545
|
+
if (code.includes('type="number"')) extractedTypes.push({ file, fieldType: 'Number', rawHTMLType: 'number' });
|
|
546
|
+
if (code.includes('type="email"')) extractedTypes.push({ file, fieldType: 'Email', rawHTMLType: 'email' });
|
|
547
|
+
} catch {}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return extractedTypes;
|
|
551
|
+
}
|