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.
Files changed (52) hide show
  1. package/README.md +1 -10
  2. package/bin/index.js +470 -140
  3. package/package.json +11 -7
  4. package/src/ai-agent.js +171 -0
  5. package/src/analyzer.js +137 -22
  6. package/src/generators/dotnet.js +134 -133
  7. package/src/generators/java.js +248 -233
  8. package/src/generators/js.js +346 -0
  9. package/src/generators/nestjs.js +278 -0
  10. package/src/generators/node.js +57 -26
  11. package/src/generators/python.js +86 -104
  12. package/src/generators/template.js +10 -8
  13. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -0
  14. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -0
  15. package/src/templates/java-spring/partials/Controller.java.ejs +3 -3
  16. package/src/templates/js-express/base/server.js +59 -0
  17. package/src/templates/js-express/partials/Dockerfile.ejs +12 -0
  18. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -0
  19. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -0
  20. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -0
  21. package/src/templates/js-express/partials/controller.js.ejs +53 -0
  22. package/src/templates/js-express/partials/db.js.ejs +19 -0
  23. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -0
  24. package/src/templates/js-express/partials/model.js.ejs +18 -0
  25. package/src/templates/js-express/partials/package.json.ejs +17 -0
  26. package/src/templates/js-express/partials/prisma.schema.ejs +21 -0
  27. package/src/templates/js-express/partials/routes.js.ejs +19 -0
  28. package/src/templates/js-express/partials/seeder.js.ejs +103 -0
  29. package/src/templates/js-express/partials/service.js.ejs +51 -0
  30. package/src/templates/js-express/partials/swagger.js.ejs +30 -0
  31. package/src/templates/js-express/partials/test.js.ejs +46 -0
  32. package/src/templates/nestjs/base/app.module.ts +9 -0
  33. package/src/templates/nestjs/base/main.ts +23 -0
  34. package/src/templates/nestjs/base/tsconfig.json +21 -0
  35. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -0
  36. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -0
  37. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -0
  38. package/src/templates/nestjs/partials/controller.ts.ejs +34 -0
  39. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -0
  40. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -0
  41. package/src/templates/nestjs/partials/module.ts.ejs +10 -0
  42. package/src/templates/nestjs/partials/package.json.ejs +27 -0
  43. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -0
  44. package/src/templates/nestjs/partials/schema.ts.ejs +19 -0
  45. package/src/templates/nestjs/partials/service.ts.ejs +67 -0
  46. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -0
  47. package/src/templates/node-ts-express/partials/HexController.ts.ejs +56 -0
  48. package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -0
  49. package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -0
  50. package/src/utils.js +3 -5
  51. /package/src/templates/{node-ts-express → dotnet}/partials/DbContext.cs.ejs +0 -0
  52. /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": "6.2.3",
4
- "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
- "type": "commonjs",
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": "^4.1.2",
24
+ "chalk": "^5.3.0",
23
25
  "ejs": "^3.1.9",
24
- "execa": "^6.1.0",
26
+ "execa": "^8.0.1",
25
27
  "fs-extra": "^11.1.1",
26
28
  "glob": "^10.3.3",
27
- "inquirer": "^8.2.4",
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
  }
@@ -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
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- const fs = require("fs-extra");
3
- const path = require("path");
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
- const parser = require("@babel/parser");
7
- const traverse = require("@babel/traverse").default;
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 null;
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 null;
216
+ return heuristicallyGuessDB(endpoints);
218
217
  } catch {
219
- return null;
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, route);
451
+ const actionName = deriveActionName(method, normalizedRoute);
386
452
 
387
- const key = `${method}:${route}`;
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(route),
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, // null | mongodb-mongoose | sql-prisma | ...
430
- models, // inferred entities + fields
431
- seeds, // dummy seed rows
497
+ guessedDb,
498
+ models,
499
+ seeds,
432
500
  },
433
501
  };
434
502
  }
435
503
 
436
- module.exports = { analyzeFrontend, analyze };
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
+ }