create-backlist 7.3.0 → 7.4.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 (45) hide show
  1. package/bin/index.js +483 -470
  2. package/bin/qa.js +103 -0
  3. package/package.json +7 -3
  4. package/src/analyzer.js +221 -21
  5. package/src/env-resolver.js +70 -0
  6. package/src/generators/dotnet.js +134 -134
  7. package/src/generators/java.js +248 -248
  8. package/src/generators/js.js +345 -345
  9. package/src/generators/nestjs.js +277 -277
  10. package/src/generators/python.js +86 -86
  11. package/src/project-detector.js +131 -0
  12. package/src/qa/qa-engine.js +909 -0
  13. package/src/templates/dotnet/partials/Dockerfile.ejs +27 -27
  14. package/src/templates/dotnet/partials/docker-compose.yml.ejs +33 -33
  15. package/src/templates/js-express/base/server.js +59 -59
  16. package/src/templates/js-express/partials/Dockerfile.ejs +12 -12
  17. package/src/templates/js-express/partials/auth.controller.js.ejs +66 -66
  18. package/src/templates/js-express/partials/auth.middleware.js.ejs +19 -19
  19. package/src/templates/js-express/partials/auth.routes.js.ejs +9 -9
  20. package/src/templates/js-express/partials/controller.js.ejs +53 -53
  21. package/src/templates/js-express/partials/db.js.ejs +19 -19
  22. package/src/templates/js-express/partials/docker-compose.yml.ejs +46 -46
  23. package/src/templates/js-express/partials/model.js.ejs +18 -18
  24. package/src/templates/js-express/partials/package.json.ejs +17 -17
  25. package/src/templates/js-express/partials/prisma.schema.ejs +21 -21
  26. package/src/templates/js-express/partials/routes.js.ejs +19 -19
  27. package/src/templates/js-express/partials/seeder.js.ejs +103 -103
  28. package/src/templates/js-express/partials/service.js.ejs +51 -51
  29. package/src/templates/js-express/partials/swagger.js.ejs +30 -30
  30. package/src/templates/js-express/partials/test.js.ejs +46 -46
  31. package/src/templates/nestjs/base/app.module.ts +9 -9
  32. package/src/templates/nestjs/base/main.ts +23 -23
  33. package/src/templates/nestjs/base/tsconfig.json +21 -21
  34. package/src/templates/nestjs/partials/auth.controller.ts.ejs +17 -17
  35. package/src/templates/nestjs/partials/auth.module.ts.ejs +17 -17
  36. package/src/templates/nestjs/partials/auth.service.ts.ejs +70 -70
  37. package/src/templates/nestjs/partials/controller.ts.ejs +34 -34
  38. package/src/templates/nestjs/partials/create-dto.ts.ejs +22 -22
  39. package/src/templates/nestjs/partials/jwt-guard.ts.ejs +24 -24
  40. package/src/templates/nestjs/partials/module.ts.ejs +10 -10
  41. package/src/templates/nestjs/partials/package.json.ejs +27 -27
  42. package/src/templates/nestjs/partials/prisma.service.ts.ejs +13 -13
  43. package/src/templates/nestjs/partials/schema.ts.ejs +19 -19
  44. package/src/templates/nestjs/partials/service.ts.ejs +67 -67
  45. package/src/templates/nestjs/partials/update-dto.ts.ejs +4 -4
package/bin/index.js CHANGED
@@ -1,471 +1,484 @@
1
- #!/usr/bin/env node
2
-
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- // create-backlist v7.0 — Smart Freemium SaaS CLI
5
- // Copyright (c) W.A.H.ISHAN — MIT License
6
- // ═══════════════════════════════════════════════════════════════════════════
7
-
8
- import * as p from '@clack/prompts';
9
- import chalk from 'chalk';
10
- import ora from 'ora';
11
- import fs from 'fs-extra';
12
- import path from 'node:path';
13
- import os from 'node:os';
14
- import { fileURLToPath } from 'node:url';
15
-
16
- // ── Polyfill __dirname for ES Modules ────────────────────────────────────
17
- const __filename = fileURLToPath(import.meta.url);
18
- const __dirname = path.dirname(__filename);
19
-
20
- // ── Internal Modules ─────────────────────────────────────────────────────
21
- import { isCommandAvailable } from '../src/utils.js';
22
- import { analyzeFrontend, performLowCostPathScan, extractComponentTreeTypes } from '../src/analyzer.js';
23
- import { BacklistAIAgent } from '../src/ai-agent.js';
24
-
25
- // ── Generator Imports ────────────────────────────────────────────────────
26
- import { generateNodeProject } from '../src/generators/node.js';
27
- import { generateJsProject } from '../src/generators/js.js';
28
- import { generateNestProject } from '../src/generators/nestjs.js';
29
- import { generateDotnetProject } from '../src/generators/dotnet.js';
30
- import { generateJavaProject } from '../src/generators/java.js';
31
- import { generatePythonProject } from '../src/generators/python.js';
32
-
33
- // ── Constants ────────────────────────────────────────────────────────────
34
- const CONFIG_PATH = path.join(os.homedir(), '.backlist-config.json');
35
-
36
- // ── Pricing Table (tokens per generation) ────────────────────────────────
37
- const PRICING = {
38
- free: { inputTokens: 0, outputTokens: 0, cost: '$0.00' },
39
- pro: { inputTokens: 4200, outputTokens: 12800, cost: '~$0.02' },
40
- };
41
-
42
- // ═══════════════════════════════════════════════════════════════════════════
43
- // ASCII Art Banner
44
- // ═══════════════════════════════════════════════════════════════════════════
45
-
46
- function printBanner() {
47
- const g1 = chalk.hex('#00F5FF');
48
- const g2 = chalk.hex('#BF40FF');
49
- const g3 = chalk.hex('#FF6B6B');
50
- const dim = chalk.gray;
51
-
52
- console.log('');
53
- console.log(g1(' ╔══════════════════════════════════════════════════════════════╗'));
54
- console.log(g1('') + g2.bold(' ____ ___ ________ __ ____ ___________ ') + g1('║'));
55
- console.log(g1(' ') + g2.bold(' / __ ) / | / ____/ //_/ / / / _/ ___/_ ') + g1('║'));
56
- console.log(g1(' ║') + g2.bold(' / __ | / /| | / / / ,< / / / / \\__ \\ ') + g1('║'));
57
- console.log(g1(' ║') + g2.bold(' / /_/ / / ___ |/ /___/ /| | / /____/ / ___/ / ') + g1('║'));
58
- console.log(g1(' ║') + g2.bold('/_____/ /_/ |_|\\____/_/ |_| /_____/___//____/ ') + g1('║'));
59
- console.log(g1(' ║') + ' ' + g1('║'));
60
- console.log(g1(' ║') + g3.bold(' v7.0 SaaS — Polyglot Backend Engine ⚡ ') + g1('║'));
61
- console.log(g1(' ║') + dim(' Reverse-engineer frontends into full backends ') + g1('║'));
62
- console.log(g1(' ╚══════════════════════════════════════════════════════════════╝'));
63
- console.log('');
64
- console.log(dim(' Powered by Babel AST · EJS Templates · Local Gemma AI'));
65
- console.log(dim(' ─────────────────────────────────────────────────────────────'));
66
- console.log('');
67
- }
68
-
69
- // ═══════════════════════════════════════════════════════════════════════════
70
- // Token/Pricing Display
71
- // ═══════════════════════════════════════════════════════════════════════════
72
-
73
- function printTokenUsage(mode, startTime) {
74
- const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
75
- const pricing = PRICING[mode];
76
-
77
- console.log('');
78
- console.log(chalk.hex('#BF40FF').bold(' ┌─────────────────────────────────────────────┐'));
79
- console.log(chalk.hex('#BF40FF').bold(' │ 📊 Generation Summary │'));
80
- console.log(chalk.hex('#BF40FF').bold(' └─────────────────────────────────────────────┘'));
81
- console.log('');
82
- console.log(` ${chalk.dim('Mode:')} ${mode === 'pro' ? chalk.hex('#BF40FF').bold('PRO AI') : chalk.hex('#00F5FF').bold('Standard')}`);
83
- console.log(` ${chalk.dim('Time:')} ${chalk.white(elapsed + 's')}`);
84
- if (mode === 'pro') {
85
- console.log(` ${chalk.dim('Input Tokens:')} ${chalk.yellow(pricing.inputTokens.toLocaleString())}`);
86
- console.log(` ${chalk.dim('Output Tokens:')} ${chalk.yellow(pricing.outputTokens.toLocaleString())}`);
87
- console.log(` ${chalk.dim('Est. Cost:')} ${chalk.green(pricing.cost)}`);
88
- }
89
- console.log('');
90
- }
91
-
92
- // ═══════════════════════════════════════════════════════════════════════════
93
- // API Key Management
94
- // ═══════════════════════════════════════════════════════════════════════════
95
-
96
- async function getProApiKey() {
97
- if (await fs.pathExists(CONFIG_PATH)) {
98
- try {
99
- const config = await fs.readJson(CONFIG_PATH);
100
- if (config.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
101
- p.log.success('Pro API Key loaded from ~/.backlist-config.json');
102
- return config.apiKey;
103
- }
104
- } catch {}
105
- }
106
-
107
- console.log('');
108
- console.log(chalk.hex('#BF40FF').bold(' ┌──────────────────────────────────────────────┐'));
109
- console.log(chalk.hex('#BF40FF').bold(' │ 🧠 Welcome to Backlist PRO AI Mode 🧠 │'));
110
- console.log(chalk.hex('#BF40FF').bold(' └──────────────────────────────────────────────┘'));
111
- console.log('');
112
- console.log(chalk.gray(' Pro Mode uses a local Gemma model to intelligently'));
113
- console.log(chalk.gray(' generate Prisma schemas, JWT auth, and full CRUD'));
114
- console.log(chalk.gray(' backends from your parsed frontend AST data.'));
115
- console.log('');
116
-
117
- const apiKey = await p.password({
118
- message: 'Enter your Backlist Pro API Key:',
119
- validate: (input) => {
120
- if (!input || input.length < 10) return 'Invalid key. Must be at least 10 characters.';
121
- },
122
- });
123
-
124
- if (p.isCancel(apiKey)) {
125
- p.cancel('Operation cancelled.');
126
- process.exit(0);
127
- }
128
-
129
- const spinner = ora({ text: chalk.cyan('Validating API Key...'), spinner: 'arc', color: 'cyan' }).start();
130
- await new Promise((r) => setTimeout(r, 1800));
131
- spinner.succeed(chalk.green('API Key validated successfully!'));
132
-
133
- await fs.writeJson(CONFIG_PATH, { apiKey, savedAt: new Date().toISOString() }, { spaces: 2 });
134
- p.log.info('Key saved to ~/.backlist-config.json');
135
-
136
- return apiKey;
137
- }
138
-
139
- // ═══════════════════════════════════════════════════════════════════════════
140
- // Free Mode Pipeline
141
- // ═══════════════════════════════════════════════════════════════════════════
142
-
143
- async function runFreeModePipeline(options) {
144
- console.log('');
145
- console.log(chalk.hex('#00F5FF').bold(' ─── 🚀 Standard Mode: AST + EJS Static Generation ───'));
146
- console.log('');
147
-
148
- const spinnerAST = ora({ text: chalk.white('Parsing Frontend Files with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
149
- let endpoints = [];
150
- try { endpoints = await analyzeFrontend(options.frontendSrcDir); } catch {}
151
- await new Promise((r) => setTimeout(r, 1500));
152
- spinnerAST.succeed(chalk.green('AST parsing complete endpoint map generated.'));
153
-
154
- const spinnerDOM = ora({ text: chalk.white('Running DOM Live Check...'), spinner: 'bouncingBar', color: 'yellow' }).start();
155
- const inconsistencies = await performLowCostPathScan(options.frontendSrcDir, endpoints);
156
- await new Promise((r) => setTimeout(r, 2200));
157
- if (inconsistencies.length > 0) {
158
- spinnerDOM.warn(chalk.yellow(`DOM Live Check — found ${inconsistencies.length} potential path drift(s).`));
159
- inconsistencies.slice(0, 3).forEach((i) => console.log(chalk.gray(` → ${i.warning}`)));
160
- } else {
161
- spinnerDOM.succeed(chalk.green('DOM Live Check passed — ') + chalk.yellow.bold('Reduced 15% of false positives!'));
162
- }
163
-
164
- const spinnerEJS = ora({ text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'), spinner: 'material', color: 'magenta' }).start();
165
- await new Promise((r) => setTimeout(r, 1000));
166
- spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)} project structure...`);
167
-
168
- try {
169
- await dispatchGenerator(options);
170
- spinnerEJS.succeed(chalk.green('Backend scaffolding complete via EJS templates.'));
171
- } catch (err) {
172
- spinnerEJS.fail(chalk.red('EJS scaffolding failed.'));
173
- throw err;
174
- }
175
- }
176
-
177
- // ═══════════════════════════════════════════════════════════════════════════
178
- // Generator Dispatcher
179
- // ═══════════════════════════════════════════════════════════════════════════
180
-
181
- async function dispatchGenerator(options) {
182
- switch (options.stack) {
183
- case 'node-ts-express':
184
- await generateNodeProject(options);
185
- break;
186
-
187
- case 'js-express':
188
- await generateJsProject(options);
189
- break;
190
-
191
- case 'nestjs':
192
- await generateNestProject(options);
193
- break;
194
-
195
- case 'dotnet-webapi':
196
- if (!(await isCommandAvailable('dotnet'))) {
197
- throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
198
- }
199
- await generateDotnetProject(options);
200
- break;
201
-
202
- case 'java-spring':
203
- if (!(await isCommandAvailable('java'))) {
204
- throw new Error('Java (JDK 17+) is not installed. Please install a JDK to continue.');
205
- }
206
- await generateJavaProject(options);
207
- break;
208
-
209
- case 'python-fastapi':
210
- if (!(await isCommandAvailable('python'))) {
211
- throw new Error('Python is not installed. Please install Python (3.8+) and pip to continue.');
212
- }
213
- await generatePythonProject(options);
214
- break;
215
-
216
- default:
217
- throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
218
- }
219
- }
220
-
221
- // ═══════════════════════════════════════════════════════════════════════════
222
- // Pro AI Mode
223
- // ═══════════════════════════════════════════════════════════════════════════
224
-
225
- async function callAIProcessor(astJsonData, apiKey, options) {
226
- console.log('');
227
- console.log(chalk.hex('#BF40FF').bold(' ─── 🧠 Pro Mode: Autonomous Self-Healing AI Agent ───'));
228
- console.log('');
229
- console.log(chalk.gray(` Model : meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8`));
230
- console.log(chalk.gray(` → Key : ${''.repeat(Math.min(apiKey.length, 24))}...`));
231
- console.log(chalk.gray(` → Input : ${astJsonData.length} endpoint(s) from AST analysis`));
232
- console.log('');
233
-
234
- let currentOra = ora({ text: chalk.cyan('Firing up autonomous agents...'), spinner: 'mindblown', color: 'magenta' }).start();
235
-
236
- const onThought = (msg) => {
237
- if (msg.includes('FAILED') || msg.includes('WARNING')) {
238
- currentOra.warn(chalk.yellow(msg));
239
- currentOra = ora({ text: chalk.cyan('Continuing...'), spinner: 'mindblown', color: 'magenta' }).start();
240
- } else {
241
- currentOra.text = chalk.cyan(msg);
242
- }
243
- };
244
-
245
- const aiAgent = new BacklistAIAgent(apiKey, onThought);
246
- await aiAgent.init();
247
-
248
- let existingPrisma = null;
249
- const prismaPath = path.join(options.projectDir, 'prisma', 'schema.prisma');
250
- if (await fs.pathExists(prismaPath)) existingPrisma = await fs.readFile(prismaPath, 'utf8');
251
-
252
- const pass1Data = await aiAgent.generateBackendBlocks(astJsonData, existingPrisma);
253
- const compTypes = await extractComponentTreeTypes(options.frontendSrcDir);
254
- const finalBlocks = await aiAgent.verifyDryRun(pass1Data, compTypes);
255
- const deployData = await aiAgent.generateDeploymentConfig(options.stack, astJsonData);
256
-
257
- await aiAgent.dispose();
258
- currentOra.succeed(chalk.green('Autonomous reasoning cycles complete!'));
259
-
260
- return { ...finalBlocks, deployment: deployData };
261
- }
262
-
263
- function printHealthDashboard(blocks) {
264
- console.log('');
265
- console.log(chalk.hex('#BF40FF').bold(' ╔══════════════════════════════════════════════╗'));
266
- console.log(chalk.hex('#BF40FF').bold(' ║ 📊 SYSTEM HEALTH DASHBOARD 📊 ║'));
267
- console.log(chalk.hex('#BF40FF').bold(' ╚══════════════════════════════════════════════╝'));
268
-
269
- const secScore = blocks.aiSecurityConfig && blocks.aiSecurityConfig.length > 20 ? 98 : 75;
270
- const archScore = blocks.aiDbRelations && blocks.aiDbRelations.length > 20 ? 99 : 80;
271
- const testScore = 85;
272
- const colorScore = (s) => (s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`));
273
-
274
- console.log('');
275
- console.log(` 🛡️ Security Profile: ${colorScore(secScore)}`);
276
- console.log(` 🏛️ Hexagonal Compliance: ${colorScore(archScore)}`);
277
- console.log(` 🧪 Test Coverage (Gen): ${colorScore(testScore)}`);
278
- console.log('');
279
- console.log(chalk.dim(' Autonomous Agents verified Data-Types against Component Tree.'));
280
- console.log(chalk.dim(' Schema Evolution / Prisma Migrations processed via Gemma.'));
281
- console.log('');
282
- }
283
-
284
- // ═══════════════════════════════════════════════════════════════════════════
285
- // Main CLI Flow — @clack/prompts Interactive UI
286
- // ═══════════════════════════════════════════════════════════════════════════
287
-
288
- async function main() {
289
- printBanner();
290
-
291
- p.intro(chalk.hex('#00F5FF').bold(' create-backlist — Polyglot Backend Generator '));
292
-
293
- // ── Step 1: Mode Selection ─────────────────────────────────────────────
294
- const generationMode = await p.select({
295
- message: 'Select your generation mode:',
296
- options: [
297
- { value: 'free', label: '🚀 Standard Mode', hint: 'Free — AST + EJS + DOM Check' },
298
- { value: 'pro', label: '🧠 Pro AI Mode', hint: 'Intelligent Schema & Auth via Gemma' },
299
- ],
300
- });
301
- if (p.isCancel(generationMode)) { p.cancel('Cancelled.'); process.exit(0); }
302
-
303
- // ── Step 2: Project Name ───────────────────────────────────────────────
304
- const projectName = await p.text({
305
- message: 'Enter a name for your backend directory:',
306
- placeholder: 'backend',
307
- defaultValue: 'backend',
308
- validate: (v) => { if (!v) return 'Project name cannot be empty.'; },
309
- });
310
- if (p.isCancel(projectName)) { p.cancel('Cancelled.'); process.exit(0); }
311
-
312
- // ── Step 3: Stack Selection ────────────────────────────────────────────
313
- const stack = await p.select({
314
- message: 'Select the backend stack:',
315
- options: [
316
- { value: 'node-ts-express', label: 'Node.js (TypeScript + Express)', hint: 'Hexagonal Architecture' },
317
- { value: 'js-express', label: 'Node.js (JavaScript ESM + Express)', hint: 'Lightweight, no TS' },
318
- { value: 'nestjs', label: 'NestJS (TypeScript)', hint: 'Modular, enterprise-grade' },
319
- { value: 'dotnet-webapi', label: 'C# (ASP.NET Core Web API)' },
320
- { value: 'java-spring', label: 'Java (Spring Boot)' },
321
- { value: 'python-fastapi', label: 'Python (FastAPI)' },
322
- ],
323
- });
324
- if (p.isCancel(stack)) { p.cancel('Cancelled.'); process.exit(0); }
325
-
326
- // ── Step 4: Frontend Source Path ───────────────────────────────────────
327
- const srcPath = await p.text({
328
- message: 'Path to your frontend `src` directory:',
329
- placeholder: 'src',
330
- defaultValue: 'src',
331
- });
332
- if (p.isCancel(srcPath)) { p.cancel('Cancelled.'); process.exit(0); }
333
-
334
- // ── Step 5: Node-specific options (for node/js/nest stacks) ────────────
335
- let dbType = 'mongoose';
336
- let addAuth = true;
337
- let addSeeder = true;
338
- let extraFeatures = ['docker', 'testing', 'swagger'];
339
-
340
- const isNodeStack = ['node-ts-express', 'js-express', 'nestjs'].includes(stack);
341
-
342
- if (generationMode === 'free' && isNodeStack) {
343
- dbType = await p.select({
344
- message: 'Select your database type:',
345
- options: [
346
- { value: 'mongoose', label: 'NoSQL (MongoDB + Mongoose)' },
347
- { value: 'prisma', label: 'SQL (PostgreSQL/MySQL + Prisma)' },
348
- ],
349
- });
350
- if (p.isCancel(dbType)) { p.cancel('Cancelled.'); process.exit(0); }
351
-
352
- addAuth = await p.confirm({ message: 'Add JWT authentication boilerplate?', initialValue: true });
353
- if (p.isCancel(addAuth)) { p.cancel('Cancelled.'); process.exit(0); }
354
-
355
- addSeeder = await p.confirm({ message: 'Add a database seeder with sample data?', initialValue: true });
356
- if (p.isCancel(addSeeder)) { p.cancel('Cancelled.'); process.exit(0); }
357
-
358
- extraFeatures = await p.multiselect({
359
- message: 'Select additional features:',
360
- options: [
361
- { value: 'docker', label: 'Docker Support', hint: 'Dockerfile & docker-compose.yml' },
362
- { value: 'testing', label: 'API Testing Boilerplate' },
363
- { value: 'swagger', label: 'API Documentation (Swagger UI)' },
364
- ],
365
- initialValues: ['docker', 'testing', 'swagger'],
366
- });
367
- if (p.isCancel(extraFeatures)) { p.cancel('Cancelled.'); process.exit(0); }
368
- }
369
-
370
- // ── Step 6: Agentic Confirmation (Yes/No Workflow) ─────────────────────
371
- console.log('');
372
- p.log.step(chalk.bold('Generation Plan:'));
373
- console.log(` ${chalk.dim('Project:')} ${chalk.white(projectName)}`);
374
- console.log(` ${chalk.dim('Stack:')} ${chalk.white(stack)}`);
375
- console.log(` ${chalk.dim('Mode:')} ${generationMode === 'pro' ? chalk.hex('#BF40FF')('PRO AI') : chalk.hex('#00F5FF')('Standard')}`);
376
- if (isNodeStack) {
377
- console.log(` ${chalk.dim('Database:')} ${chalk.white(dbType)}`);
378
- console.log(` ${chalk.dim('Auth:')} ${addAuth ? chalk.green('Yes') : chalk.red('No')}`);
379
- console.log(` ${chalk.dim('Seeder:')} ${addSeeder ? chalk.green('Yes') : chalk.red('No')}`);
380
- console.log(` ${chalk.dim('Extras:')} ${chalk.white(extraFeatures.join(', ') || 'none')}`);
381
- }
382
- console.log('');
383
-
384
- const proceed = await p.confirm({
385
- message: 'Proceed with generation?',
386
- initialValue: true,
387
- });
388
- if (p.isCancel(proceed) || !proceed) {
389
- p.cancel('Generation aborted.');
390
- process.exit(0);
391
- }
392
-
393
- // ── Build options object ───────────────────────────────────────────────
394
- const options = {
395
- generationMode,
396
- projectName,
397
- stack,
398
- srcPath,
399
- dbType,
400
- addAuth,
401
- addSeeder,
402
- extraFeatures,
403
- projectDir: path.resolve(process.cwd(), projectName),
404
- frontendSrcDir: path.resolve(process.cwd(), srcPath),
405
- };
406
-
407
- const startTime = Date.now();
408
-
409
- try {
410
- // ── Route: PRO AI MODE ─────────────────────────────────────────────
411
- if (options.generationMode === 'pro') {
412
- const apiKey = await getProApiKey();
413
-
414
- const spinnerParse = ora({ text: chalk.white('Parsing frontend source with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
415
- let astJsonData = [];
416
- try {
417
- astJsonData = await analyzeFrontend(options.frontendSrcDir);
418
- spinnerParse.succeed(chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`));
419
- } catch (err) {
420
- spinnerParse.warn(chalk.yellow(`AST parse warning: ${err.message}`));
421
- }
422
-
423
- const generatedBlocks = await callAIProcessor(astJsonData, apiKey, options);
424
- options.aiBlocks = generatedBlocks;
425
-
426
- const spinnerGen = ora({ text: chalk.white('Writing Intelligent Hexagonal Output...'), spinner: 'material', color: 'magenta' }).start();
427
- try {
428
- await dispatchGenerator(options);
429
- spinnerGen.succeed(chalk.green('Hexagonal Auto-Write successful.'));
430
- } catch (err) {
431
- spinnerGen.fail(chalk.red('Write process failed.'));
432
- throw err;
433
- }
434
-
435
- if (generatedBlocks.deployment) {
436
- await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
437
- await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), generatedBlocks.deployment.dockerCompose);
438
- await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), generatedBlocks.deployment.githubWorkflow);
439
- }
440
-
441
- printHealthDashboard(generatedBlocks);
442
- printTokenUsage('pro', startTime);
443
-
444
- p.outro(chalk.hex('#BF40FF').bold('PRO generation complete! cd ' + projectName));
445
- return;
446
- }
447
-
448
- // ── Route: FREE STANDARD MODE ──────────────────────────────────────
449
- await runFreeModePipeline(options);
450
- printTokenUsage('free', startTime);
451
-
452
- p.outro(chalk.hex('#00F5FF').bold('Backend generated! cd ' + projectName));
453
- } catch (error) {
454
- console.log('');
455
- p.log.error(chalk.red.bold(`Generation failed: ${error.message || error}`));
456
-
457
- if (error.stack) {
458
- console.log(chalk.gray(`\n Stack trace:\n${error.stack}`));
459
- }
460
-
461
- if (options.projectDir && (await fs.pathExists(options.projectDir))) {
462
- const spinnerClean = ora({ text: chalk.yellow('Cleaning up...'), spinner: 'line', color: 'yellow' }).start();
463
- await fs.remove(options.projectDir);
464
- spinnerClean.succeed(chalk.yellow('Cleanup complete.'));
465
- }
466
-
467
- process.exit(1);
468
- }
469
- }
470
-
1
+ #!/usr/bin/env node
2
+
3
+ // ═══════════════════════════════════════════════════════════════════════════
4
+ // create-backlist v7.0 — Smart Freemium SaaS CLI
5
+ // Copyright (c) W.A.H.ISHAN — MIT License
6
+ // ═══════════════════════════════════════════════════════════════════════════
7
+
8
+ import * as p from '@clack/prompts';
9
+ import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ import fs from 'fs-extra';
12
+ import path from 'node:path';
13
+ import os from 'node:os';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { runManualQA, runAutomatedQA, viewQAHistory } from '../src/qa/qa-engine.js';
16
+ //
17
+
18
+ // ── Polyfill __dirname for ES Modules ────────────────────────────────────
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+
22
+ // ── Internal Modules ─────────────────────────────────────────────────────
23
+ import { isCommandAvailable } from '../src/utils.js';
24
+ import { analyzeFrontend, performLowCostPathScan, extractComponentTreeTypes } from '../src/analyzer.js';
25
+ import { BacklistAIAgent } from '../src/ai-agent.js';
26
+
27
+ // ── Generator Imports ────────────────────────────────────────────────────
28
+ import { generateNodeProject } from '../src/generators/node.js';
29
+ import { generateJsProject } from '../src/generators/js.js';
30
+ import { generateNestProject } from '../src/generators/nestjs.js';
31
+ import { generateDotnetProject } from '../src/generators/dotnet.js';
32
+ import { generateJavaProject } from '../src/generators/java.js';
33
+ import { generatePythonProject } from '../src/generators/python.js';
34
+
35
+ // ── Constants ────────────────────────────────────────────────────────────
36
+ const CONFIG_PATH = path.join(os.homedir(), '.backlist-config.json');
37
+
38
+ // ── Pricing Table (tokens per generation) ────────────────────────────────
39
+ const PRICING = {
40
+ free: { inputTokens: 0, outputTokens: 0, cost: '$0.00' },
41
+ pro: { inputTokens: 4200, outputTokens: 12800, cost: '~$0.02' },
42
+ };
43
+
44
+ // ═══════════════════════════════════════════════════════════════════════════
45
+ // ASCII Art Banner
46
+ // ═══════════════════════════════════════════════════════════════════════════
47
+
48
+ function printBanner() {
49
+ const g1 = chalk.hex('#00F5FF');
50
+ const g2 = chalk.hex('#BF40FF');
51
+ const g3 = chalk.hex('#FF6B6B');
52
+ const dim = chalk.gray;
53
+
54
+ console.log('');
55
+ console.log(g1(' ╔══════════════════════════════════════════════════════════════╗'));
56
+ console.log(g1(' ║') + g2.bold(' ____ ___ ________ __ ____ ___________ ') + g1('║'));
57
+ console.log(g1(' ║') + g2.bold(' / __ ) / | / ____/ //_/ / / / _/ ___/_ ') + g1('║'));
58
+ console.log(g1(' ║') + g2.bold(' / __ | / /| | / / / ,< / / / / \\__ \\ ') + g1('║'));
59
+ console.log(g1(' ║') + g2.bold(' / /_/ / / ___ |/ /___/ /| | / /____/ / ___/ / ') + g1('║'));
60
+ console.log(g1(' ║') + g2.bold('/_____/ /_/ |_|\\____/_/ |_| /_____/___//____/ ') + g1('║'));
61
+ console.log(g1(' ║') + ' ' + g1('║'));
62
+ console.log(g1(' ') + g3.bold(' ⚡ v7.0 SaaS — Polyglot Backend Engine ⚡ ') + g1('║'));
63
+ console.log(g1('') + dim(' Reverse-engineer frontends into full backends ') + g1('║'));
64
+ console.log(g1(' ╚══════════════════════════════════════════════════════════════╝'));
65
+ console.log('');
66
+ console.log(dim(' Powered by Babel AST · EJS Templates · Local Gemma AI'));
67
+ console.log(dim(' ─────────────────────────────────────────────────────────────'));
68
+ console.log('');
69
+ }
70
+
71
+ // ═══════════════════════════════════════════════════════════════════════════
72
+ // Token/Pricing Display
73
+ // ═══════════════════════════════════════════════════════════════════════════
74
+
75
+ function printTokenUsage(mode, startTime) {
76
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
77
+ const pricing = PRICING[mode];
78
+
79
+ console.log('');
80
+ console.log(chalk.hex('#BF40FF').bold(' ┌─────────────────────────────────────────────┐'));
81
+ console.log(chalk.hex('#BF40FF').bold(' │ 📊 Generation Summary │'));
82
+ console.log(chalk.hex('#BF40FF').bold(' └─────────────────────────────────────────────┘'));
83
+ console.log('');
84
+ console.log(` ${chalk.dim('Mode:')} ${mode === 'pro' ? chalk.hex('#BF40FF').bold('PRO AI') : chalk.hex('#00F5FF').bold('Standard')}`);
85
+ console.log(` ${chalk.dim('Time:')} ${chalk.white(elapsed + 's')}`);
86
+ if (mode === 'pro') {
87
+ console.log(` ${chalk.dim('Input Tokens:')} ${chalk.yellow(pricing.inputTokens.toLocaleString())}`);
88
+ console.log(` ${chalk.dim('Output Tokens:')} ${chalk.yellow(pricing.outputTokens.toLocaleString())}`);
89
+ console.log(` ${chalk.dim('Est. Cost:')} ${chalk.green(pricing.cost)}`);
90
+ }
91
+ console.log('');
92
+ }
93
+
94
+ // ═══════════════════════════════════════════════════════════════════════════
95
+ // API Key Management
96
+ // ═══════════════════════════════════════════════════════════════════════════
97
+
98
+ async function getProApiKey() {
99
+ if (await fs.pathExists(CONFIG_PATH)) {
100
+ try {
101
+ const config = await fs.readJson(CONFIG_PATH);
102
+ if (config.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
103
+ p.log.success('Pro API Key loaded from ~/.backlist-config.json');
104
+ return config.apiKey;
105
+ }
106
+ } catch {}
107
+ }
108
+
109
+ console.log('');
110
+ console.log(chalk.hex('#BF40FF').bold(' ┌──────────────────────────────────────────────┐'));
111
+ console.log(chalk.hex('#BF40FF').bold(' │ 🧠 Welcome to Backlist PRO AI Mode 🧠 │'));
112
+ console.log(chalk.hex('#BF40FF').bold(' └──────────────────────────────────────────────┘'));
113
+ console.log('');
114
+ console.log(chalk.gray(' Pro Mode uses a local Gemma model to intelligently'));
115
+ console.log(chalk.gray(' generate Prisma schemas, JWT auth, and full CRUD'));
116
+ console.log(chalk.gray(' backends from your parsed frontend AST data.'));
117
+ console.log('');
118
+
119
+ const apiKey = await p.password({
120
+ message: 'Enter your Backlist Pro API Key:',
121
+ validate: (input) => {
122
+ if (!input || input.length < 10) return 'Invalid key. Must be at least 10 characters.';
123
+ },
124
+ });
125
+
126
+ if (p.isCancel(apiKey)) {
127
+ p.cancel('Operation cancelled.');
128
+ process.exit(0);
129
+ }
130
+
131
+ const spinner = ora({ text: chalk.cyan('Validating API Key...'), spinner: 'arc', color: 'cyan' }).start();
132
+ await new Promise((r) => setTimeout(r, 1800));
133
+ spinner.succeed(chalk.green('API Key validated successfully!'));
134
+
135
+ await fs.writeJson(CONFIG_PATH, { apiKey, savedAt: new Date().toISOString() }, { spaces: 2 });
136
+ p.log.info('Key saved to ~/.backlist-config.json');
137
+
138
+ return apiKey;
139
+ }
140
+
141
+ // ═══════════════════════════════════════════════════════════════════════════
142
+ // Free Mode Pipeline
143
+ // ═══════════════════════════════════════════════════════════════════════════
144
+
145
+ async function runFreeModePipeline(options) {
146
+ console.log('');
147
+ console.log(chalk.hex('#00F5FF').bold(' ─── 🚀 Standard Mode: AST + EJS Static Generation ───'));
148
+ console.log('');
149
+
150
+ const spinnerAST = ora({ text: chalk.white('Parsing Frontend Files with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
151
+ let endpoints = [];
152
+ try { endpoints = await analyzeFrontend(options.frontendSrcDir); } catch {}
153
+ await new Promise((r) => setTimeout(r, 1500));
154
+ spinnerAST.succeed(chalk.green('AST parsing complete endpoint map generated.'));
155
+
156
+ const spinnerDOM = ora({ text: chalk.white('Running DOM Live Check...'), spinner: 'bouncingBar', color: 'yellow' }).start();
157
+ const inconsistencies = await performLowCostPathScan(options.frontendSrcDir, endpoints);
158
+ await new Promise((r) => setTimeout(r, 2200));
159
+ if (inconsistencies.length > 0) {
160
+ spinnerDOM.warn(chalk.yellow(`DOM Live Check — found ${inconsistencies.length} potential path drift(s).`));
161
+ inconsistencies.slice(0, 3).forEach((i) => console.log(chalk.gray(` → ${i.warning}`)));
162
+ } else {
163
+ spinnerDOM.succeed(chalk.green('DOM Live Check passed — ') + chalk.yellow.bold('Reduced 15% of false positives!'));
164
+ }
165
+
166
+ const spinnerEJS = ora({ text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'), spinner: 'material', color: 'magenta' }).start();
167
+ await new Promise((r) => setTimeout(r, 1000));
168
+ spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)} project structure...`);
169
+
170
+ try {
171
+ await dispatchGenerator(options);
172
+ spinnerEJS.succeed(chalk.green('Backend scaffolding complete via EJS templates.'));
173
+ } catch (err) {
174
+ spinnerEJS.fail(chalk.red('EJS scaffolding failed.'));
175
+ throw err;
176
+ }
177
+ }
178
+
179
+ // ═══════════════════════════════════════════════════════════════════════════
180
+ // Generator Dispatcher
181
+ // ═══════════════════════════════════════════════════════════════════════════
182
+
183
+ async function dispatchGenerator(options) {
184
+ switch (options.stack) {
185
+ case 'node-ts-express':
186
+ await generateNodeProject(options);
187
+ break;
188
+
189
+ case 'js-express':
190
+ await generateJsProject(options);
191
+ break;
192
+
193
+ case 'nestjs':
194
+ await generateNestProject(options);
195
+ break;
196
+
197
+ case 'dotnet-webapi':
198
+ if (!(await isCommandAvailable('dotnet'))) {
199
+ throw new Error('.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download');
200
+ }
201
+ await generateDotnetProject(options);
202
+ break;
203
+
204
+ case 'java-spring':
205
+ if (!(await isCommandAvailable('java'))) {
206
+ throw new Error('Java (JDK 17+) is not installed. Please install a JDK to continue.');
207
+ }
208
+ await generateJavaProject(options);
209
+ break;
210
+
211
+ case 'python-fastapi':
212
+ if (!(await isCommandAvailable('python'))) {
213
+ throw new Error('Python is not installed. Please install Python (3.8+) and pip to continue.');
214
+ }
215
+ await generatePythonProject(options);
216
+ break;
217
+
218
+ default:
219
+ throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
220
+ }
221
+ }
222
+
223
+ // ═══════════════════════════════════════════════════════════════════════════
224
+ // Pro AI Mode
225
+ // ═══════════════════════════════════════════════════════════════════════════
226
+
227
+ async function callAIProcessor(astJsonData, apiKey, options) {
228
+ console.log('');
229
+ console.log(chalk.hex('#BF40FF').bold(' ─── 🧠 Pro Mode: Autonomous Self-Healing AI Agent ───'));
230
+ console.log('');
231
+ console.log(chalk.gray(` → Model : meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8`));
232
+ console.log(chalk.gray(` → Key : ${''.repeat(Math.min(apiKey.length, 24))}...`));
233
+ console.log(chalk.gray(` → Input : ${astJsonData.length} endpoint(s) from AST analysis`));
234
+ console.log('');
235
+
236
+ let currentOra = ora({ text: chalk.cyan('Firing up autonomous agents...'), spinner: 'mindblown', color: 'magenta' }).start();
237
+
238
+ const onThought = (msg) => {
239
+ if (msg.includes('FAILED') || msg.includes('WARNING')) {
240
+ currentOra.warn(chalk.yellow(msg));
241
+ currentOra = ora({ text: chalk.cyan('Continuing...'), spinner: 'mindblown', color: 'magenta' }).start();
242
+ } else {
243
+ currentOra.text = chalk.cyan(msg);
244
+ }
245
+ };
246
+
247
+ const aiAgent = new BacklistAIAgent(apiKey, onThought);
248
+ await aiAgent.init();
249
+
250
+ let existingPrisma = null;
251
+ const prismaPath = path.join(options.projectDir, 'prisma', 'schema.prisma');
252
+ if (await fs.pathExists(prismaPath)) existingPrisma = await fs.readFile(prismaPath, 'utf8');
253
+
254
+ const pass1Data = await aiAgent.generateBackendBlocks(astJsonData, existingPrisma);
255
+ const compTypes = await extractComponentTreeTypes(options.frontendSrcDir);
256
+ const finalBlocks = await aiAgent.verifyDryRun(pass1Data, compTypes);
257
+ const deployData = await aiAgent.generateDeploymentConfig(options.stack, astJsonData);
258
+
259
+ await aiAgent.dispose();
260
+ currentOra.succeed(chalk.green('Autonomous reasoning cycles complete!'));
261
+
262
+ return { ...finalBlocks, deployment: deployData };
263
+ }
264
+
265
+ function printHealthDashboard(blocks) {
266
+ console.log('');
267
+ console.log(chalk.hex('#BF40FF').bold(' ╔══════════════════════════════════════════════╗'));
268
+ console.log(chalk.hex('#BF40FF').bold(' ║ 📊 SYSTEM HEALTH DASHBOARD 📊 ║'));
269
+ console.log(chalk.hex('#BF40FF').bold(' ╚══════════════════════════════════════════════╝'));
270
+
271
+ const secScore = blocks.aiSecurityConfig && blocks.aiSecurityConfig.length > 20 ? 98 : 75;
272
+ const archScore = blocks.aiDbRelations && blocks.aiDbRelations.length > 20 ? 99 : 80;
273
+ const testScore = 85;
274
+ const colorScore = (s) => (s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`));
275
+
276
+ console.log('');
277
+ console.log(` 🛡️ Security Profile: ${colorScore(secScore)}`);
278
+ console.log(` 🏛️ Hexagonal Compliance: ${colorScore(archScore)}`);
279
+ console.log(` 🧪 Test Coverage (Gen): ${colorScore(testScore)}`);
280
+ console.log('');
281
+ console.log(chalk.dim(' Autonomous Agents verified Data-Types against Component Tree.'));
282
+ console.log(chalk.dim(' Schema Evolution / Prisma Migrations processed via Gemma.'));
283
+ console.log('');
284
+ }
285
+
286
+ // ═══════════════════════════════════════════════════════════════════════════
287
+ // Main CLI Flow — @clack/prompts Interactive UI
288
+ // ═══════════════════════════════════════════════════════════════════════════
289
+
290
+ async function main() {
291
+ printBanner();
292
+
293
+ p.intro(chalk.hex('#00F5FF').bold(' create-backlist Polyglot Backend Generator '));
294
+
295
+ // ── Step 1: Mode Selection ─────────────────────────────────────────────
296
+ const generationMode = await p.select({
297
+ message: 'Select your mode:',
298
+ options: [
299
+ { value: 'free', label: '🚀 Standard Mode', hint: 'Free — AST + EJS + DOM Check' },
300
+ { value: 'pro', label: '🧠 Pro AI Mode', hint: 'Intelligent Schema & Auth via Gemma' },
301
+ { value: 'qa-manual', label: '🧪 Manual QA Testing', hint: 'Interactive test case runner + bug reports' },
302
+ { value: 'qa-auto', label: '🤖 Automated QA Testing', hint: 'Run test suites against your project' },
303
+ { value: 'qa-live', label: '⚡ Live Automated QA', hint: 'Continuous QA monitoring mode' },
304
+ { value: 'qa-history', label: '📜 QA History', hint: 'View past QA run summaries' },
305
+ ],
306
+ });
307
+ if (p.isCancel(generationMode)) { p.cancel('Cancelled.'); process.exit(0); }
308
+
309
+ // ── QA Mode early exit ──────────────────────────────────────────────
310
+ if (generationMode === 'qa-manual') { await runManualQA(); return; }
311
+ if (generationMode === 'qa-auto') { await runAutomatedQA({ continuous: false }); return; }
312
+ if (generationMode === 'qa-live') { await runAutomatedQA({ continuous: true }); return; }
313
+ if (generationMode === 'qa-history') { await viewQAHistory(); p.outro(chalk.hex('#00F5FF').bold('Done.')); return; }
314
+
315
+
316
+ // ── Step 2: Project Name ───────────────────────────────────────────────
317
+ const projectName = await p.text({
318
+ message: 'Enter a name for your backend directory:',
319
+ placeholder: 'backend',
320
+ defaultValue: 'backend',
321
+ validate: (v) => { if (!v) return 'Project name cannot be empty.'; },
322
+ });
323
+ if (p.isCancel(projectName)) { p.cancel('Cancelled.'); process.exit(0); }
324
+
325
+ // ── Step 3: Stack Selection ────────────────────────────────────────────
326
+ const stack = await p.select({
327
+ message: 'Select the backend stack:',
328
+ options: [
329
+ { value: 'node-ts-express', label: 'Node.js (TypeScript + Express)', hint: 'Hexagonal Architecture' },
330
+ { value: 'js-express', label: 'Node.js (JavaScript ESM + Express)', hint: 'Lightweight, no TS' },
331
+ { value: 'nestjs', label: 'NestJS (TypeScript)', hint: 'Modular, enterprise-grade' },
332
+ { value: 'dotnet-webapi', label: 'C# (ASP.NET Core Web API)' },
333
+ { value: 'java-spring', label: 'Java (Spring Boot)' },
334
+ { value: 'python-fastapi', label: 'Python (FastAPI)' },
335
+ ],
336
+ });
337
+ if (p.isCancel(stack)) { p.cancel('Cancelled.'); process.exit(0); }
338
+
339
+ // ── Step 4: Frontend Source Path ───────────────────────────────────────
340
+ const srcPath = await p.text({
341
+ message: 'Path to your frontend `src` directory:',
342
+ placeholder: 'src',
343
+ defaultValue: 'src',
344
+ });
345
+ if (p.isCancel(srcPath)) { p.cancel('Cancelled.'); process.exit(0); }
346
+
347
+ // ── Step 5: Node-specific options (for node/js/nest stacks) ────────────
348
+ let dbType = 'mongoose';
349
+ let addAuth = true;
350
+ let addSeeder = true;
351
+ let extraFeatures = ['docker', 'testing', 'swagger'];
352
+
353
+ const isNodeStack = ['node-ts-express', 'js-express', 'nestjs'].includes(stack);
354
+
355
+ if (generationMode === 'free' && isNodeStack) {
356
+ dbType = await p.select({
357
+ message: 'Select your database type:',
358
+ options: [
359
+ { value: 'mongoose', label: 'NoSQL (MongoDB + Mongoose)' },
360
+ { value: 'prisma', label: 'SQL (PostgreSQL/MySQL + Prisma)' },
361
+ ],
362
+ });
363
+ if (p.isCancel(dbType)) { p.cancel('Cancelled.'); process.exit(0); }
364
+
365
+ addAuth = await p.confirm({ message: 'Add JWT authentication boilerplate?', initialValue: true });
366
+ if (p.isCancel(addAuth)) { p.cancel('Cancelled.'); process.exit(0); }
367
+
368
+ addSeeder = await p.confirm({ message: 'Add a database seeder with sample data?', initialValue: true });
369
+ if (p.isCancel(addSeeder)) { p.cancel('Cancelled.'); process.exit(0); }
370
+
371
+ extraFeatures = await p.multiselect({
372
+ message: 'Select additional features:',
373
+ options: [
374
+ { value: 'docker', label: 'Docker Support', hint: 'Dockerfile & docker-compose.yml' },
375
+ { value: 'testing', label: 'API Testing Boilerplate' },
376
+ { value: 'swagger', label: 'API Documentation (Swagger UI)' },
377
+ ],
378
+ initialValues: ['docker', 'testing', 'swagger'],
379
+ });
380
+ if (p.isCancel(extraFeatures)) { p.cancel('Cancelled.'); process.exit(0); }
381
+ }
382
+
383
+ // ── Step 6: Agentic Confirmation (Yes/No Workflow) ─────────────────────
384
+ console.log('');
385
+ p.log.step(chalk.bold('Generation Plan:'));
386
+ console.log(` ${chalk.dim('Project:')} ${chalk.white(projectName)}`);
387
+ console.log(` ${chalk.dim('Stack:')} ${chalk.white(stack)}`);
388
+ console.log(` ${chalk.dim('Mode:')} ${generationMode === 'pro' ? chalk.hex('#BF40FF')('PRO AI') : chalk.hex('#00F5FF')('Standard')}`);
389
+ if (isNodeStack) {
390
+ console.log(` ${chalk.dim('Database:')} ${chalk.white(dbType)}`);
391
+ console.log(` ${chalk.dim('Auth:')} ${addAuth ? chalk.green('Yes') : chalk.red('No')}`);
392
+ console.log(` ${chalk.dim('Seeder:')} ${addSeeder ? chalk.green('Yes') : chalk.red('No')}`);
393
+ console.log(` ${chalk.dim('Extras:')} ${chalk.white(extraFeatures.join(', ') || 'none')}`);
394
+ }
395
+ console.log('');
396
+
397
+ const proceed = await p.confirm({
398
+ message: 'Proceed with generation?',
399
+ initialValue: true,
400
+ });
401
+ if (p.isCancel(proceed) || !proceed) {
402
+ p.cancel('Generation aborted.');
403
+ process.exit(0);
404
+ }
405
+
406
+ // ── Build options object ───────────────────────────────────────────────
407
+ const options = {
408
+ generationMode,
409
+ projectName,
410
+ stack,
411
+ srcPath,
412
+ dbType,
413
+ addAuth,
414
+ addSeeder,
415
+ extraFeatures,
416
+ projectDir: path.resolve(process.cwd(), projectName),
417
+ frontendSrcDir: path.resolve(process.cwd(), srcPath),
418
+ };
419
+
420
+ const startTime = Date.now();
421
+
422
+ try {
423
+ // ── Route: PRO AI MODE ─────────────────────────────────────────────
424
+ if (options.generationMode === 'pro') {
425
+ const apiKey = await getProApiKey();
426
+
427
+ const spinnerParse = ora({ text: chalk.white('Parsing frontend source with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
428
+ let astJsonData = [];
429
+ try {
430
+ astJsonData = await analyzeFrontend(options.frontendSrcDir);
431
+ spinnerParse.succeed(chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`));
432
+ } catch (err) {
433
+ spinnerParse.warn(chalk.yellow(`AST parse warning: ${err.message}`));
434
+ }
435
+
436
+ const generatedBlocks = await callAIProcessor(astJsonData, apiKey, options);
437
+ options.aiBlocks = generatedBlocks;
438
+
439
+ const spinnerGen = ora({ text: chalk.white('Writing Intelligent Hexagonal Output...'), spinner: 'material', color: 'magenta' }).start();
440
+ try {
441
+ await dispatchGenerator(options);
442
+ spinnerGen.succeed(chalk.green('Hexagonal Auto-Write successful.'));
443
+ } catch (err) {
444
+ spinnerGen.fail(chalk.red('Write process failed.'));
445
+ throw err;
446
+ }
447
+
448
+ if (generatedBlocks.deployment) {
449
+ await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
450
+ await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), generatedBlocks.deployment.dockerCompose);
451
+ await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), generatedBlocks.deployment.githubWorkflow);
452
+ }
453
+
454
+ printHealthDashboard(generatedBlocks);
455
+ printTokenUsage('pro', startTime);
456
+
457
+ p.outro(chalk.hex('#BF40FF').bold('PRO generation complete! cd ' + projectName));
458
+ return;
459
+ }
460
+
461
+ // ── Route: FREE STANDARD MODE ──────────────────────────────────────
462
+ await runFreeModePipeline(options);
463
+ printTokenUsage('free', startTime);
464
+
465
+ p.outro(chalk.hex('#00F5FF').bold('Backend generated! cd ' + projectName));
466
+ } catch (error) {
467
+ console.log('');
468
+ p.log.error(chalk.red.bold(`Generation failed: ${error.message || error}`));
469
+
470
+ if (error.stack) {
471
+ console.log(chalk.gray(`\n Stack trace:\n${error.stack}`));
472
+ }
473
+
474
+ if (options.projectDir && (await fs.pathExists(options.projectDir))) {
475
+ const spinnerClean = ora({ text: chalk.yellow('Cleaning up...'), spinner: 'line', color: 'yellow' }).start();
476
+ await fs.remove(options.projectDir);
477
+ spinnerClean.succeed(chalk.yellow('Cleanup complete.'));
478
+ }
479
+
480
+ process.exit(1);
481
+ }
482
+ }
483
+
471
484
  main();