create-backlist 7.0.1 → 7.3.1
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 +242 -275
- package/package.json +3 -2
- package/src/ai-agent.js +171 -171
- package/src/analyzer.js +750 -495
- package/src/env-resolver.js +70 -0
- 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 +404 -404
- package/src/generators/python.js +86 -104
- package/src/generators/template.js +22 -22
- package/src/project-detector.js +131 -0
- 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 -56
- package/src/templates/node-ts-express/partials/HexRepository.ts.ejs +26 -26
- package/src/templates/node-ts-express/partials/HexService.ts.ejs +27 -27
- package/src/utils.js +11 -11
- /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/bin/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Copyright (c) W.A.H.ISHAN — MIT License
|
|
6
6
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as p from '@clack/prompts';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import ora from 'ora';
|
|
11
11
|
import fs from 'fs-extra';
|
|
@@ -22,60 +22,88 @@ import { isCommandAvailable } from '../src/utils.js';
|
|
|
22
22
|
import { analyzeFrontend, performLowCostPathScan, extractComponentTreeTypes } from '../src/analyzer.js';
|
|
23
23
|
import { BacklistAIAgent } from '../src/ai-agent.js';
|
|
24
24
|
|
|
25
|
-
// ── Generator Imports
|
|
25
|
+
// ── Generator Imports ────────────────────────────────────────────────────
|
|
26
26
|
import { generateNodeProject } from '../src/generators/node.js';
|
|
27
|
+
import { generateJsProject } from '../src/generators/js.js';
|
|
28
|
+
import { generateNestProject } from '../src/generators/nestjs.js';
|
|
27
29
|
import { generateDotnetProject } from '../src/generators/dotnet.js';
|
|
28
30
|
import { generateJavaProject } from '../src/generators/java.js';
|
|
29
31
|
import { generatePythonProject } from '../src/generators/python.js';
|
|
30
32
|
|
|
31
33
|
// ── Constants ────────────────────────────────────────────────────────────
|
|
32
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
|
+
|
|
33
42
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
34
43
|
// ASCII Art Banner
|
|
35
44
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
36
45
|
|
|
37
46
|
function printBanner() {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
47
|
+
const g1 = chalk.hex('#00F5FF');
|
|
48
|
+
const g2 = chalk.hex('#BF40FF');
|
|
49
|
+
const g3 = chalk.hex('#FF6B6B');
|
|
41
50
|
const dim = chalk.gray;
|
|
42
51
|
|
|
43
52
|
console.log('');
|
|
44
|
-
console.log(
|
|
45
|
-
console.log(
|
|
46
|
-
console.log(
|
|
47
|
-
console.log(
|
|
48
|
-
console.log(
|
|
49
|
-
console.log(
|
|
50
|
-
console.log(
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
53
|
-
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(' ╚══════════════════════════════════════════════════════════════╝'));
|
|
54
63
|
console.log('');
|
|
55
64
|
console.log(dim(' Powered by Babel AST · EJS Templates · Local Gemma AI'));
|
|
56
65
|
console.log(dim(' ─────────────────────────────────────────────────────────────'));
|
|
57
66
|
console.log('');
|
|
58
67
|
}
|
|
59
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
|
+
|
|
60
92
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
61
93
|
// API Key Management
|
|
62
94
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
63
95
|
|
|
64
96
|
async function getProApiKey() {
|
|
65
|
-
// 1) Check if a saved key already exists
|
|
66
97
|
if (await fs.pathExists(CONFIG_PATH)) {
|
|
67
98
|
try {
|
|
68
99
|
const config = await fs.readJson(CONFIG_PATH);
|
|
69
100
|
if (config.apiKey && typeof config.apiKey === 'string' && config.apiKey.length >= 10) {
|
|
70
|
-
|
|
101
|
+
p.log.success('Pro API Key loaded from ~/.backlist-config.json');
|
|
71
102
|
return config.apiKey;
|
|
72
103
|
}
|
|
73
|
-
} catch {
|
|
74
|
-
// Config file corrupt — fall through to prompt
|
|
75
|
-
}
|
|
104
|
+
} catch {}
|
|
76
105
|
}
|
|
77
106
|
|
|
78
|
-
// 2) First-time Pro Mode onboarding
|
|
79
107
|
console.log('');
|
|
80
108
|
console.log(chalk.hex('#BF40FF').bold(' ┌──────────────────────────────────────────────┐'));
|
|
81
109
|
console.log(chalk.hex('#BF40FF').bold(' │ 🧠 Welcome to Backlist PRO AI Mode 🧠 │'));
|
|
@@ -85,44 +113,31 @@ async function getProApiKey() {
|
|
|
85
113
|
console.log(chalk.gray(' generate Prisma schemas, JWT auth, and full CRUD'));
|
|
86
114
|
console.log(chalk.gray(' backends from your parsed frontend AST data.'));
|
|
87
115
|
console.log('');
|
|
88
|
-
console.log(chalk.yellow(' ⚠ An API key is required to unlock Pro features.'));
|
|
89
|
-
console.log(chalk.gray(' Your key is stored locally at: ~/.backlist-config.json'));
|
|
90
|
-
console.log('');
|
|
91
116
|
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
message: chalk.hex('#00F5FF')('🔑 Enter your Backlist Pro API Key:'),
|
|
97
|
-
mask: '●',
|
|
98
|
-
validate: (input) => {
|
|
99
|
-
if (!input || input.length < 10) {
|
|
100
|
-
return chalk.red('❌ Invalid key. Must be at least 10 characters.');
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
},
|
|
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.';
|
|
104
121
|
},
|
|
105
|
-
|
|
122
|
+
});
|
|
106
123
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
color: 'cyan',
|
|
112
|
-
}).start();
|
|
124
|
+
if (p.isCancel(apiKey)) {
|
|
125
|
+
p.cancel('Operation cancelled.');
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
113
128
|
|
|
114
|
-
|
|
129
|
+
const spinner = ora({ text: chalk.cyan('Validating API Key...'), spinner: 'arc', color: 'cyan' }).start();
|
|
130
|
+
await new Promise((r) => setTimeout(r, 1800));
|
|
115
131
|
spinner.succeed(chalk.green('API Key validated successfully!'));
|
|
116
132
|
|
|
117
|
-
// 4) Persist the key
|
|
118
133
|
await fs.writeJson(CONFIG_PATH, { apiKey, savedAt: new Date().toISOString() }, { spaces: 2 });
|
|
119
|
-
|
|
134
|
+
p.log.info('Key saved to ~/.backlist-config.json');
|
|
120
135
|
|
|
121
136
|
return apiKey;
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
125
|
-
// Free Mode Pipeline
|
|
140
|
+
// Free Mode Pipeline
|
|
126
141
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
127
142
|
|
|
128
143
|
async function runFreeModePipeline(options) {
|
|
@@ -130,101 +145,28 @@ async function runFreeModePipeline(options) {
|
|
|
130
145
|
console.log(chalk.hex('#00F5FF').bold(' ─── 🚀 Standard Mode: AST + EJS Static Generation ───'));
|
|
131
146
|
console.log('');
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
const spinnerAST = ora({
|
|
135
|
-
text: chalk.white('Parsing Frontend Files with Babel AST...'),
|
|
136
|
-
spinner: 'dots12',
|
|
137
|
-
color: 'cyan',
|
|
138
|
-
}).start();
|
|
139
|
-
|
|
148
|
+
const spinnerAST = ora({ text: chalk.white('Parsing Frontend Files with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
|
|
140
149
|
let endpoints = [];
|
|
141
|
-
try {
|
|
142
|
-
endpoints = await analyzeFrontend(options.frontendSrcDir);
|
|
143
|
-
} catch(e) {}
|
|
144
|
-
|
|
150
|
+
try { endpoints = await analyzeFrontend(options.frontendSrcDir); } catch {}
|
|
145
151
|
await new Promise((r) => setTimeout(r, 1500));
|
|
146
152
|
spinnerAST.succeed(chalk.green('AST parsing complete — endpoint map generated.'));
|
|
147
153
|
|
|
148
|
-
|
|
149
|
-
const spinnerDOM = ora({
|
|
150
|
-
text: chalk.white('Running DOM Live Check (Verifying API calls against actual elements)...'),
|
|
151
|
-
spinner: 'bouncingBar',
|
|
152
|
-
color: 'yellow',
|
|
153
|
-
}).start();
|
|
154
|
-
|
|
154
|
+
const spinnerDOM = ora({ text: chalk.white('Running DOM Live Check...'), spinner: 'bouncingBar', color: 'yellow' }).start();
|
|
155
155
|
const inconsistencies = await performLowCostPathScan(options.frontendSrcDir, endpoints);
|
|
156
|
-
|
|
157
156
|
await new Promise((r) => setTimeout(r, 2200));
|
|
158
157
|
if (inconsistencies.length > 0) {
|
|
159
|
-
spinnerDOM.warn(chalk.yellow(`DOM Live Check
|
|
160
|
-
inconsistencies.slice(0,3).forEach(i => console.log(chalk.gray(` → ${i.warning}`)));
|
|
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}`)));
|
|
161
160
|
} else {
|
|
162
161
|
spinnerDOM.succeed(chalk.green('DOM Live Check passed — ') + chalk.yellow.bold('Reduced 15% of false positives!'));
|
|
163
162
|
}
|
|
164
163
|
|
|
165
|
-
|
|
166
|
-
const spinnerEJS = ora({
|
|
167
|
-
text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'),
|
|
168
|
-
spinner: 'material',
|
|
169
|
-
color: 'magenta',
|
|
170
|
-
}).start();
|
|
171
|
-
|
|
164
|
+
const spinnerEJS = ora({ text: chalk.white('Scaffolding backend via Hexagonal EJS Templates...'), spinner: 'material', color: 'magenta' }).start();
|
|
172
165
|
await new Promise((r) => setTimeout(r, 1000));
|
|
173
|
-
spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)}
|
|
174
|
-
|
|
175
|
-
// =====================================================================
|
|
176
|
-
// ██████████████████████████████████████████████████████████████████████
|
|
177
|
-
//
|
|
178
|
-
// INSERT OLD AST & EJS LOGIC HERE
|
|
179
|
-
//
|
|
180
|
-
// This is where the existing static generation pipeline runs.
|
|
181
|
-
// The `options` object carries all user selections (stack, dbType,
|
|
182
|
-
// addAuth, addSeeder, extraFeatures, projectDir, frontendSrcDir).
|
|
183
|
-
//
|
|
184
|
-
// The dispatcher below calls the correct generator based on the
|
|
185
|
-
// selected stack. Each generator internally calls analyzeFrontend()
|
|
186
|
-
// and uses EJS templates to scaffold the backend project.
|
|
187
|
-
//
|
|
188
|
-
// ██████████████████████████████████████████████████████████████████████
|
|
189
|
-
// =====================================================================
|
|
166
|
+
spinnerEJS.text = chalk.white(`Generating ${chalk.bold(options.stack)} project structure...`);
|
|
190
167
|
|
|
191
168
|
try {
|
|
192
|
-
|
|
193
|
-
case 'node-ts-express':
|
|
194
|
-
await generateNodeProject(options);
|
|
195
|
-
break;
|
|
196
|
-
|
|
197
|
-
case 'dotnet-webapi':
|
|
198
|
-
if (!(await isCommandAvailable('dotnet'))) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
'.NET SDK is not installed. Please install it from https://dotnet.microsoft.com/download'
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
await generateDotnetProject(options);
|
|
204
|
-
break;
|
|
205
|
-
|
|
206
|
-
case 'java-spring':
|
|
207
|
-
if (!(await isCommandAvailable('java'))) {
|
|
208
|
-
throw new Error(
|
|
209
|
-
'Java (JDK 17 or newer) is not installed. Please install a JDK to continue.'
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
await generateJavaProject(options);
|
|
213
|
-
break;
|
|
214
|
-
|
|
215
|
-
case 'python-fastapi':
|
|
216
|
-
if (!(await isCommandAvailable('python'))) {
|
|
217
|
-
throw new Error(
|
|
218
|
-
'Python is not installed. Please install Python (3.8+) and pip to continue.'
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
await generatePythonProject(options);
|
|
222
|
-
break;
|
|
223
|
-
|
|
224
|
-
default:
|
|
225
|
-
throw new Error(`The selected stack '${options.stack}' is not supported yet.`);
|
|
226
|
-
}
|
|
227
|
-
|
|
169
|
+
await dispatchGenerator(options);
|
|
228
170
|
spinnerEJS.succeed(chalk.green('Backend scaffolding complete via EJS templates.'));
|
|
229
171
|
} catch (err) {
|
|
230
172
|
spinnerEJS.fail(chalk.red('EJS scaffolding failed.'));
|
|
@@ -233,7 +175,51 @@ async function runFreeModePipeline(options) {
|
|
|
233
175
|
}
|
|
234
176
|
|
|
235
177
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
236
|
-
//
|
|
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
|
|
237
223
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
238
224
|
|
|
239
225
|
async function callAIProcessor(astJsonData, apiKey, options) {
|
|
@@ -245,16 +231,9 @@ async function callAIProcessor(astJsonData, apiKey, options) {
|
|
|
245
231
|
console.log(chalk.gray(` → Input : ${astJsonData.length} endpoint(s) from AST analysis`));
|
|
246
232
|
console.log('');
|
|
247
233
|
|
|
248
|
-
|
|
249
|
-
let currentOra = ora({
|
|
250
|
-
text: chalk.cyan('Firing up autonomous agents...'),
|
|
251
|
-
spinner: 'mindblown',
|
|
252
|
-
color: 'magenta'
|
|
253
|
-
}).start();
|
|
234
|
+
let currentOra = ora({ text: chalk.cyan('Firing up autonomous agents...'), spinner: 'mindblown', color: 'magenta' }).start();
|
|
254
235
|
|
|
255
236
|
const onThought = (msg) => {
|
|
256
|
-
// If it's a THOUGHT, update the spinner text instead of breaking the terminal lines too aggressively
|
|
257
|
-
// or just console log if it's a major step.
|
|
258
237
|
if (msg.includes('FAILED') || msg.includes('WARNING')) {
|
|
259
238
|
currentOra.warn(chalk.yellow(msg));
|
|
260
239
|
currentOra = ora({ text: chalk.cyan('Continuing...'), spinner: 'mindblown', color: 'magenta' }).start();
|
|
@@ -267,17 +246,12 @@ async function callAIProcessor(astJsonData, apiKey, options) {
|
|
|
267
246
|
await aiAgent.init();
|
|
268
247
|
|
|
269
248
|
let existingPrisma = null;
|
|
270
|
-
const prismaPath = path.join(options.projectDir,
|
|
249
|
+
const prismaPath = path.join(options.projectDir, 'prisma', 'schema.prisma');
|
|
271
250
|
if (await fs.pathExists(prismaPath)) existingPrisma = await fs.readFile(prismaPath, 'utf8');
|
|
272
251
|
|
|
273
|
-
// --- PASS 1 ---
|
|
274
252
|
const pass1Data = await aiAgent.generateBackendBlocks(astJsonData, existingPrisma);
|
|
275
|
-
|
|
276
|
-
// --- PASS 2 (Dry Run) ---
|
|
277
253
|
const compTypes = await extractComponentTreeTypes(options.frontendSrcDir);
|
|
278
254
|
const finalBlocks = await aiAgent.verifyDryRun(pass1Data, compTypes);
|
|
279
|
-
|
|
280
|
-
// --- PASS 3 (Deployment) ---
|
|
281
255
|
const deployData = await aiAgent.generateDeploymentConfig(options.stack, astJsonData);
|
|
282
256
|
|
|
283
257
|
await aiAgent.dispose();
|
|
@@ -288,17 +262,15 @@ async function callAIProcessor(astJsonData, apiKey, options) {
|
|
|
288
262
|
|
|
289
263
|
function printHealthDashboard(blocks) {
|
|
290
264
|
console.log('');
|
|
291
|
-
console.log(chalk.hex('#BF40FF').bold('
|
|
292
|
-
console.log(chalk.hex('#BF40FF').bold(' ║
|
|
293
|
-
console.log(chalk.hex('#BF40FF').bold('
|
|
294
|
-
|
|
295
|
-
// Calculate mock scores based on AI completeness
|
|
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
|
+
|
|
296
269
|
const secScore = blocks.aiSecurityConfig && blocks.aiSecurityConfig.length > 20 ? 98 : 75;
|
|
297
270
|
const archScore = blocks.aiDbRelations && blocks.aiDbRelations.length > 20 ? 99 : 80;
|
|
298
271
|
const testScore = 85;
|
|
272
|
+
const colorScore = (s) => (s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`));
|
|
299
273
|
|
|
300
|
-
const colorScore = (s) => s > 90 ? chalk.green.bold(`${s}% A+`) : chalk.yellow.bold(`${s}% B`);
|
|
301
|
-
|
|
302
274
|
console.log('');
|
|
303
275
|
console.log(` 🛡️ Security Profile: ${colorScore(secScore)}`);
|
|
304
276
|
console.log(` 🏛️ Hexagonal Compliance: ${colorScore(archScore)}`);
|
|
@@ -310,189 +282,184 @@ function printHealthDashboard(blocks) {
|
|
|
310
282
|
}
|
|
311
283
|
|
|
312
284
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
313
|
-
// Main CLI Flow
|
|
285
|
+
// Main CLI Flow — @clack/prompts Interactive UI
|
|
314
286
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
315
287
|
|
|
316
288
|
async function main() {
|
|
317
289
|
printBanner();
|
|
318
290
|
|
|
291
|
+
p.intro(chalk.hex('#00F5FF').bold(' create-backlist — Polyglot Backend Generator '));
|
|
292
|
+
|
|
319
293
|
// ── Step 1: Mode Selection ─────────────────────────────────────────────
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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)' },
|
|
334
348
|
],
|
|
335
|
-
}
|
|
349
|
+
});
|
|
350
|
+
if (p.isCancel(dbType)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
336
351
|
|
|
337
|
-
|
|
338
|
-
{
|
|
339
|
-
type: 'input',
|
|
340
|
-
name: 'projectName',
|
|
341
|
-
message: 'Enter a name for your backend directory:',
|
|
342
|
-
default: 'backend',
|
|
343
|
-
validate: (input) => (input ? true : 'Project name cannot be empty.'),
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
type: 'list',
|
|
347
|
-
name: 'stack',
|
|
348
|
-
message: 'Select the backend stack:',
|
|
349
|
-
choices: [
|
|
350
|
-
{ name: 'Node.js (TypeScript, Express)', value: 'node-ts-express' },
|
|
351
|
-
{ name: 'C# (ASP.NET Core Web API)', value: 'dotnet-webapi' },
|
|
352
|
-
{ name: 'Java (Spring Boot)', value: 'java-spring' },
|
|
353
|
-
{ name: 'Python (FastAPI)', value: 'python-fastapi' },
|
|
354
|
-
],
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
type: 'input',
|
|
358
|
-
name: 'srcPath',
|
|
359
|
-
message: 'Enter the path to your frontend `src` directory:',
|
|
360
|
-
default: 'src',
|
|
361
|
-
},
|
|
352
|
+
addAuth = await p.confirm({ message: 'Add JWT authentication boilerplate?', initialValue: true });
|
|
353
|
+
if (p.isCancel(addAuth)) { p.cancel('Cancelled.'); process.exit(0); }
|
|
362
354
|
|
|
363
|
-
|
|
364
|
-
{
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
message: 'Select
|
|
368
|
-
|
|
369
|
-
{
|
|
370
|
-
{
|
|
371
|
-
|
|
372
|
-
when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express',
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
type: 'confirm',
|
|
376
|
-
name: 'addAuth',
|
|
377
|
-
message: 'Add JWT authentication boilerplate?',
|
|
378
|
-
default: true,
|
|
379
|
-
when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express',
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
type: 'confirm',
|
|
383
|
-
name: 'addSeeder',
|
|
384
|
-
message: 'Add a database seeder with sample data?',
|
|
385
|
-
default: true,
|
|
386
|
-
when: (a) => a.generationMode === 'free' && a.stack === 'node-ts-express' && a.addAuth,
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
type: 'checkbox',
|
|
390
|
-
name: 'extraFeatures',
|
|
391
|
-
message: 'Select additional features for Node.js:',
|
|
392
|
-
choices: [
|
|
393
|
-
{ name: 'Docker Support (Dockerfile & docker-compose.yml)', value: 'docker', checked: true },
|
|
394
|
-
{ name: 'API Testing Boilerplate (Jest & Supertest)', value: 'testing', checked: true },
|
|
395
|
-
{ name: 'API Documentation (Swagger UI)', value: 'swagger', checked: true },
|
|
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)' },
|
|
396
364
|
],
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
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
|
+
}
|
|
400
392
|
|
|
401
|
-
// ── Build options
|
|
393
|
+
// ── Build options object ───────────────────────────────────────────────
|
|
402
394
|
const options = {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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),
|
|
406
405
|
};
|
|
407
406
|
|
|
407
|
+
const startTime = Date.now();
|
|
408
|
+
|
|
408
409
|
try {
|
|
409
410
|
// ── Route: PRO AI MODE ─────────────────────────────────────────────
|
|
410
411
|
if (options.generationMode === 'pro') {
|
|
411
412
|
const apiKey = await getProApiKey();
|
|
412
413
|
|
|
413
|
-
|
|
414
|
-
const spinnerParse = ora({
|
|
415
|
-
text: chalk.white('Parsing frontend source with Babel AST...'),
|
|
416
|
-
spinner: 'dots12',
|
|
417
|
-
color: 'cyan',
|
|
418
|
-
}).start();
|
|
419
|
-
|
|
414
|
+
const spinnerParse = ora({ text: chalk.white('Parsing frontend source with Babel AST...'), spinner: 'dots12', color: 'cyan' }).start();
|
|
420
415
|
let astJsonData = [];
|
|
421
416
|
try {
|
|
422
417
|
astJsonData = await analyzeFrontend(options.frontendSrcDir);
|
|
423
|
-
spinnerParse.succeed(
|
|
424
|
-
chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`)
|
|
425
|
-
);
|
|
418
|
+
spinnerParse.succeed(chalk.green(`AST analysis complete — ${astJsonData.length} endpoint(s) detected.`));
|
|
426
419
|
} catch (err) {
|
|
427
420
|
spinnerParse.warn(chalk.yellow(`AST parse warning: ${err.message}`));
|
|
428
|
-
console.log(chalk.gray(' → Proceeding with empty endpoint set.'));
|
|
429
421
|
}
|
|
430
422
|
|
|
431
|
-
// Invoke AI processor
|
|
432
423
|
const generatedBlocks = await callAIProcessor(astJsonData, apiKey, options);
|
|
433
|
-
|
|
434
|
-
// Inject the generated blocks into the options for the templates
|
|
435
424
|
options.aiBlocks = generatedBlocks;
|
|
436
425
|
|
|
437
|
-
// Scaffolding via Hexagonal Node generator specifically for Pro Mode
|
|
438
426
|
const spinnerGen = ora({ text: chalk.white('Writing Intelligent Hexagonal Output...'), spinner: 'material', color: 'magenta' }).start();
|
|
439
|
-
|
|
440
427
|
try {
|
|
441
|
-
|
|
442
|
-
case 'node-ts-express':
|
|
443
|
-
await generateNodeProject(options);
|
|
444
|
-
break;
|
|
445
|
-
// Note: Add Python/Java logic here mapping aiBlocks once hexagonalized completely
|
|
446
|
-
default:
|
|
447
|
-
throw new Error(`Pro Tier currently optimizes Node-TS Hexagonal structures. Using standard generation for ${options.stack}.`);
|
|
448
|
-
}
|
|
428
|
+
await dispatchGenerator(options);
|
|
449
429
|
spinnerGen.succeed(chalk.green('Hexagonal Auto-Write successful.'));
|
|
450
430
|
} catch (err) {
|
|
451
431
|
spinnerGen.fail(chalk.red('Write process failed.'));
|
|
452
432
|
throw err;
|
|
453
433
|
}
|
|
454
434
|
|
|
455
|
-
// Write autonomous deployment workflows
|
|
456
435
|
if (generatedBlocks.deployment) {
|
|
457
436
|
await fs.ensureDir(path.join(options.projectDir, '.github', 'workflows'));
|
|
458
437
|
await fs.writeFile(path.join(options.projectDir, 'docker-compose.yml'), generatedBlocks.deployment.dockerCompose);
|
|
459
438
|
await fs.writeFile(path.join(options.projectDir, '.github', 'workflows', 'deploy.yml'), generatedBlocks.deployment.githubWorkflow);
|
|
460
439
|
}
|
|
461
440
|
|
|
462
|
-
// Print Health Dashboard
|
|
463
441
|
printHealthDashboard(generatedBlocks);
|
|
442
|
+
printTokenUsage('pro', startTime);
|
|
443
|
+
|
|
444
|
+
p.outro(chalk.hex('#BF40FF').bold('PRO generation complete! cd ' + projectName));
|
|
464
445
|
return;
|
|
465
446
|
}
|
|
466
447
|
|
|
467
448
|
// ── Route: FREE STANDARD MODE ──────────────────────────────────────
|
|
468
449
|
await runFreeModePipeline(options);
|
|
450
|
+
printTokenUsage('free', startTime);
|
|
469
451
|
|
|
470
|
-
|
|
471
|
-
console.log('');
|
|
472
|
-
console.log(chalk.hex('#00F5FF').bold(' ╔══════════════════════════════════════════════╗'));
|
|
473
|
-
console.log(chalk.hex('#00F5FF').bold(' ║ ✅ Backend Generation Complete! ✅ ║'));
|
|
474
|
-
console.log(chalk.hex('#00F5FF').bold(' ╚══════════════════════════════════════════════╝'));
|
|
475
|
-
console.log('');
|
|
476
|
-
console.log(chalk.white(' Next Steps:'));
|
|
477
|
-
console.log(chalk.cyan(` cd ${options.projectName}`));
|
|
478
|
-
console.log(chalk.cyan(' (Check the generated README.md for instructions)'));
|
|
479
|
-
console.log('');
|
|
452
|
+
p.outro(chalk.hex('#00F5FF').bold('Backend generated! cd ' + projectName));
|
|
480
453
|
} catch (error) {
|
|
481
454
|
console.log('');
|
|
482
|
-
|
|
483
|
-
console.error(chalk.red(` ${error.message || error}`));
|
|
455
|
+
p.log.error(chalk.red.bold(`Generation failed: ${error.message || error}`));
|
|
484
456
|
|
|
485
457
|
if (error.stack) {
|
|
486
458
|
console.log(chalk.gray(`\n Stack trace:\n${error.stack}`));
|
|
487
459
|
}
|
|
488
460
|
|
|
489
|
-
// Cleanup partial output
|
|
490
461
|
if (options.projectDir && (await fs.pathExists(options.projectDir))) {
|
|
491
|
-
const spinnerClean = ora({
|
|
492
|
-
text: chalk.yellow('Cleaning up failed installation...'),
|
|
493
|
-
spinner: 'line',
|
|
494
|
-
color: 'yellow',
|
|
495
|
-
}).start();
|
|
462
|
+
const spinnerClean = ora({ text: chalk.yellow('Cleaning up...'), spinner: 'line', color: 'yellow' }).start();
|
|
496
463
|
await fs.remove(options.projectDir);
|
|
497
464
|
spinnerClean.succeed(chalk.yellow('Cleanup complete.'));
|
|
498
465
|
}
|