create-backlist 10.1.0 → 10.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +1411 -1057
- package/package.json +1 -1
- package/src/generators/dotnet.js +137 -81
- package/src/generators/java.js +118 -130
- package/src/generators/js.js +199 -207
- package/src/generators/nestjs.js +168 -155
- package/src/generators/node.js +212 -194
- package/src/generators/python.js +130 -45
- package/src/generators/template.js +47 -2
- package/src/qa/qa-engine.js +1863 -564
- package/src/templates/dotnet/partials/Controller.cs.ejs +264 -16
- package/src/templates/dotnet/partials/DbContext.cs.ejs +93 -3
- package/src/templates/dotnet/partials/Model.cs.ejs +192 -31
package/src/generators/nestjs.js
CHANGED
|
@@ -4,12 +4,32 @@ import fs from 'fs-extra';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import ejs from 'ejs';
|
|
6
6
|
import { analyzeFrontend } from '../analyzer.js';
|
|
7
|
-
import { renderAndWrite, getTemplatePath } from './template.js';
|
|
7
|
+
import { renderAndWrite, renderAndWriteAll, getTemplatePath, preloadTemplates } from './template.js';
|
|
8
|
+
|
|
9
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function timer() {
|
|
12
|
+
const s = Date.now();
|
|
13
|
+
return () => `${((Date.now() - s) / 1000).toFixed(2)}s`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function withRetry(fn, { attempts = 3, baseDelay = 300, label = 'task' } = {}) {
|
|
17
|
+
let lastErr;
|
|
18
|
+
for (let i = 0; i < attempts; i++) {
|
|
19
|
+
try { return await fn(); } catch (err) {
|
|
20
|
+
lastErr = err;
|
|
21
|
+
if (i < attempts - 1) {
|
|
22
|
+
const delay = baseDelay * 2 ** i;
|
|
23
|
+
console.log(chalk.yellow(` [RETRY] ${label} (${i + 1}/${attempts}), retrying in ${delay}ms...`));
|
|
24
|
+
await new Promise(r => setTimeout(r, delay));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw lastErr;
|
|
29
|
+
}
|
|
8
30
|
|
|
9
31
|
function safePascalName(name) {
|
|
10
|
-
const cleaned = String(name || 'Default')
|
|
11
|
-
.split('?')[0]
|
|
12
|
-
.replace(/[^a-zA-Z0-9]/g, '');
|
|
32
|
+
const cleaned = String(name || 'Default').split('?')[0].replace(/[^a-zA-Z0-9]/g, '');
|
|
13
33
|
if (!cleaned) return 'Default';
|
|
14
34
|
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
15
35
|
}
|
|
@@ -25,69 +45,99 @@ function sanitizeEndpoints(endpoints) {
|
|
|
25
45
|
});
|
|
26
46
|
}
|
|
27
47
|
|
|
48
|
+
// ─── Main Generator ───────────────────────────────────────────────────────────
|
|
49
|
+
|
|
28
50
|
export async function generateNestProject(options) {
|
|
29
|
-
const {
|
|
30
|
-
|
|
31
|
-
projectName,
|
|
32
|
-
frontendSrcDir,
|
|
33
|
-
dbType,
|
|
34
|
-
addAuth,
|
|
35
|
-
addSeeder,
|
|
36
|
-
extraFeatures = [],
|
|
37
|
-
} = options;
|
|
51
|
+
const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options;
|
|
52
|
+
const totalTimer = timer();
|
|
38
53
|
|
|
39
54
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
// ── Step 1: Analyze + preload templates in parallel ────────────────────────
|
|
56
|
+
const step1 = timer();
|
|
57
|
+
console.log(chalk.blue(' -> [1] Analyzing frontend & preloading templates in parallel...'));
|
|
58
|
+
|
|
59
|
+
const templatePrefetch = [
|
|
60
|
+
'nestjs/partials/package.json.ejs',
|
|
61
|
+
'nestjs/partials/module.ts.ejs',
|
|
62
|
+
'nestjs/partials/controller.ts.ejs',
|
|
63
|
+
'nestjs/partials/service.ts.ejs',
|
|
64
|
+
'nestjs/partials/create-dto.ts.ejs',
|
|
65
|
+
'nestjs/partials/update-dto.ts.ejs',
|
|
66
|
+
'nestjs/partials/prisma.service.ts.ejs',
|
|
67
|
+
...(dbType === 'mongoose' ? ['nestjs/partials/schema.ts.ejs'] : []),
|
|
68
|
+
...(addAuth ? ['nestjs/partials/auth.module.ts.ejs', 'nestjs/partials/auth.controller.ts.ejs', 'nestjs/partials/auth.service.ts.ejs', 'nestjs/partials/jwt-guard.ts.ejs'] : []),
|
|
69
|
+
...(addSeeder ? ['nestjs/partials/seeder.ts.ejs'] : []),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const [endpointsRaw] = await Promise.all([
|
|
73
|
+
analyzeFrontend(frontendSrcDir),
|
|
74
|
+
preloadTemplates(templatePrefetch).catch(() => {}),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
let endpoints = Array.isArray(endpointsRaw) && endpointsRaw.length > 0
|
|
78
|
+
? sanitizeEndpoints(endpointsRaw)
|
|
79
|
+
: [];
|
|
80
|
+
|
|
81
|
+
console.log(
|
|
82
|
+
endpoints.length > 0
|
|
83
|
+
? chalk.green(` -> Found ${endpoints.length} endpoints. ${chalk.gray(step1())}`)
|
|
84
|
+
: chalk.yellow(` -> No endpoints found. Basic project will be created. ${chalk.gray(step1())}`)
|
|
85
|
+
);
|
|
50
86
|
|
|
51
|
-
//
|
|
87
|
+
// ── Step 2: Build model map ────────────────────────────────────────────────
|
|
52
88
|
const modelsToGenerate = new Map();
|
|
53
89
|
endpoints.forEach((ep) => {
|
|
54
90
|
if (!ep) return;
|
|
55
91
|
const ctrl = safePascalName(ep.controllerName);
|
|
56
92
|
if (ctrl === 'Default' || ctrl === 'Auth') return;
|
|
57
93
|
if (!modelsToGenerate.has(ctrl)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
name: key,
|
|
62
|
-
type,
|
|
63
|
-
isUnique: key === 'email',
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
94
|
+
const fields = ep.schemaFields
|
|
95
|
+
? Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' }))
|
|
96
|
+
: [];
|
|
66
97
|
modelsToGenerate.set(ctrl, { name: ctrl, fields });
|
|
67
98
|
}
|
|
68
99
|
});
|
|
69
100
|
|
|
70
101
|
if (addAuth && !modelsToGenerate.has('User')) {
|
|
71
|
-
modelsToGenerate.set('User', {
|
|
72
|
-
name: '
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{ name: 'password', type: 'String' },
|
|
77
|
-
],
|
|
78
|
-
});
|
|
102
|
+
modelsToGenerate.set('User', { name: 'User', fields: [
|
|
103
|
+
{ name: 'name', type: 'String' },
|
|
104
|
+
{ name: 'email', type: 'String', isUnique: true },
|
|
105
|
+
{ name: 'password', type: 'String' },
|
|
106
|
+
]});
|
|
79
107
|
}
|
|
80
108
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
await fs.ensureDir(srcDir);
|
|
109
|
+
// ── Step 3: Scaffold dirs + copy base files in parallel ───────────────────
|
|
110
|
+
const step3 = timer();
|
|
111
|
+
console.log(chalk.blue(' -> [3] Scaffolding NestJS project structure in parallel...'));
|
|
85
112
|
|
|
86
|
-
|
|
87
|
-
await fs.copy(getTemplatePath('nestjs/base/app.module.ts'), path.join(srcDir, 'app.module.ts'));
|
|
88
|
-
await fs.copy(getTemplatePath('nestjs/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'));
|
|
113
|
+
const srcDir = path.join(projectDir, 'src');
|
|
89
114
|
|
|
90
|
-
//
|
|
115
|
+
// Pre-compute all module dirs
|
|
116
|
+
const moduleDirs = Array.from(modelsToGenerate.keys())
|
|
117
|
+
.filter(n => n !== 'Auth')
|
|
118
|
+
.flatMap(name => [
|
|
119
|
+
path.join(srcDir, name.toLowerCase()),
|
|
120
|
+
path.join(srcDir, name.toLowerCase(), 'dto'),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
const extraDirs = [
|
|
124
|
+
...(dbType === 'prisma' ? [path.join(srcDir, 'prisma'), path.join(projectDir, 'prisma')] : []),
|
|
125
|
+
...(addAuth ? [path.join(srcDir, 'auth')] : []),
|
|
126
|
+
...(addSeeder ? [path.join(projectDir, 'scripts')] : []),
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
await Promise.all([
|
|
130
|
+
fs.ensureDir(srcDir),
|
|
131
|
+
...moduleDirs.map(d => fs.ensureDir(d)),
|
|
132
|
+
...extraDirs.map(d => fs.ensureDir(d)),
|
|
133
|
+
fs.copy(getTemplatePath('nestjs/base/main.ts'), path.join(srcDir, 'main.ts')),
|
|
134
|
+
fs.copy(getTemplatePath('nestjs/base/app.module.ts'), path.join(srcDir, 'app.module.ts')),
|
|
135
|
+
fs.copy(getTemplatePath('nestjs/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json')),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
console.log(chalk.gray(` Scaffolding done. ${step3()}`));
|
|
139
|
+
|
|
140
|
+
// ── Step 4: package.json + nest-cli.json ──────────────────────────────────
|
|
91
141
|
const pkgTpl = await fs.readFile(getTemplatePath('nestjs/partials/package.json.ejs'), 'utf-8');
|
|
92
142
|
const packageJsonContent = JSON.parse(ejs.render(pkgTpl, { projectName }));
|
|
93
143
|
|
|
@@ -109,64 +159,43 @@ export async function generateNestProject(options) {
|
|
|
109
159
|
packageJsonContent.scripts.seed = 'ts-node scripts/seeder.ts';
|
|
110
160
|
}
|
|
111
161
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// --- nest-cli.json ---
|
|
115
|
-
await fs.writeJson(path.join(projectDir, 'nest-cli.json'), {
|
|
162
|
+
const nestCliJson = {
|
|
116
163
|
"$schema": "https://json.schemastore.org/nest-cli",
|
|
117
164
|
"collection": "@nestjs/schematics",
|
|
118
165
|
"sourceRoot": "src",
|
|
119
|
-
"compilerOptions": { "deleteOutDir": true }
|
|
120
|
-
}
|
|
166
|
+
"compilerOptions": { "deleteOutDir": true },
|
|
167
|
+
};
|
|
121
168
|
|
|
122
|
-
|
|
169
|
+
await Promise.all([
|
|
170
|
+
fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 }),
|
|
171
|
+
fs.writeJson(path.join(projectDir, 'nest-cli.json'), nestCliJson, { spaces: 2 }),
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
// ── Step 5: Generate all NestJS modules in PARALLEL ───────────────────────
|
|
175
|
+
const step5 = timer();
|
|
123
176
|
const moduleImports = [];
|
|
124
177
|
const moduleNames = [];
|
|
178
|
+
const renderTasks = [];
|
|
125
179
|
|
|
126
180
|
if (modelsToGenerate.size > 0) {
|
|
127
|
-
console.log(chalk.blue(
|
|
181
|
+
console.log(chalk.blue(` -> [5] Generating ${modelsToGenerate.size} NestJS module(s) in parallel...`));
|
|
128
182
|
|
|
129
183
|
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
130
184
|
if (modelName === 'Auth') continue;
|
|
131
|
-
|
|
132
185
|
const moduleDir = path.join(srcDir, modelName.toLowerCase());
|
|
133
186
|
const dtoDir = path.join(moduleDir, 'dto');
|
|
134
|
-
await fs.ensureDir(dtoDir);
|
|
135
|
-
|
|
136
187
|
const tplData = { modelName, dbType, fields: modelData.fields || [] };
|
|
137
188
|
|
|
138
|
-
|
|
139
|
-
getTemplatePath('nestjs/partials/module.ts.ejs'),
|
|
140
|
-
path.join(moduleDir, `${modelName.toLowerCase()}.
|
|
141
|
-
tplData
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
getTemplatePath('nestjs/partials/controller.ts.ejs'),
|
|
145
|
-
path.join(moduleDir, `${modelName.toLowerCase()}.controller.ts`),
|
|
146
|
-
tplData
|
|
147
|
-
);
|
|
148
|
-
await renderAndWrite(
|
|
149
|
-
getTemplatePath('nestjs/partials/service.ts.ejs'),
|
|
150
|
-
path.join(moduleDir, `${modelName.toLowerCase()}.service.ts`),
|
|
151
|
-
tplData
|
|
152
|
-
);
|
|
153
|
-
await renderAndWrite(
|
|
154
|
-
getTemplatePath('nestjs/partials/create-dto.ts.ejs'),
|
|
155
|
-
path.join(dtoDir, `create-${modelName.toLowerCase()}.dto.ts`),
|
|
156
|
-
tplData
|
|
157
|
-
);
|
|
158
|
-
await renderAndWrite(
|
|
159
|
-
getTemplatePath('nestjs/partials/update-dto.ts.ejs'),
|
|
160
|
-
path.join(dtoDir, `update-${modelName.toLowerCase()}.dto.ts`),
|
|
161
|
-
tplData
|
|
189
|
+
renderTasks.push(
|
|
190
|
+
{ templatePath: getTemplatePath('nestjs/partials/module.ts.ejs'), outPath: path.join(moduleDir, `${modelName.toLowerCase()}.module.ts`), data: tplData },
|
|
191
|
+
{ templatePath: getTemplatePath('nestjs/partials/controller.ts.ejs'), outPath: path.join(moduleDir, `${modelName.toLowerCase()}.controller.ts`), data: tplData },
|
|
192
|
+
{ templatePath: getTemplatePath('nestjs/partials/service.ts.ejs'), outPath: path.join(moduleDir, `${modelName.toLowerCase()}.service.ts`), data: tplData },
|
|
193
|
+
{ templatePath: getTemplatePath('nestjs/partials/create-dto.ts.ejs'), outPath: path.join(dtoDir, `create-${modelName.toLowerCase()}.dto.ts`), data: tplData },
|
|
194
|
+
{ templatePath: getTemplatePath('nestjs/partials/update-dto.ts.ejs'), outPath: path.join(dtoDir, `update-${modelName.toLowerCase()}.dto.ts`), data: tplData }
|
|
162
195
|
);
|
|
163
196
|
|
|
164
197
|
if (dbType === 'mongoose') {
|
|
165
|
-
|
|
166
|
-
getTemplatePath('nestjs/partials/schema.ts.ejs'),
|
|
167
|
-
path.join(moduleDir, `${modelName.toLowerCase()}.schema.ts`),
|
|
168
|
-
tplData
|
|
169
|
-
);
|
|
198
|
+
renderTasks.push({ templatePath: getTemplatePath('nestjs/partials/schema.ts.ejs'), outPath: path.join(moduleDir, `${modelName.toLowerCase()}.schema.ts`), data: tplData });
|
|
170
199
|
}
|
|
171
200
|
|
|
172
201
|
moduleImports.push(`import { ${modelName}Module } from './${modelName.toLowerCase()}/${modelName.toLowerCase()}.module';`);
|
|
@@ -174,96 +203,80 @@ export async function generateNestProject(options) {
|
|
|
174
203
|
}
|
|
175
204
|
}
|
|
176
205
|
|
|
177
|
-
//
|
|
206
|
+
// Prisma module tasks
|
|
178
207
|
if (dbType === 'prisma') {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
getTemplatePath('
|
|
183
|
-
path.join(prismaDir, 'prisma.service.ts'),
|
|
184
|
-
{}
|
|
208
|
+
const prismaModuleContent = `import { Global, Module } from '@nestjs/common';\nimport { PrismaService } from './prisma.service';\n\n@Global()\n@Module({\n providers: [PrismaService],\n exports: [PrismaService],\n})\nexport class PrismaModule {}\n`;
|
|
209
|
+
renderTasks.push(
|
|
210
|
+
{ templatePath: getTemplatePath('nestjs/partials/prisma.service.ts.ejs'), outPath: path.join(srcDir, 'prisma', 'prisma.service.ts'), data: {} },
|
|
211
|
+
{ templatePath: getTemplatePath('js-express/partials/prisma.schema.ejs'), outPath: path.join(projectDir, 'prisma', 'schema.prisma'), data: { models: Array.from(modelsToGenerate.values()) } }
|
|
185
212
|
);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
@Global()
|
|
190
|
-
@Module({
|
|
191
|
-
providers: [PrismaService],
|
|
192
|
-
exports: [PrismaService],
|
|
193
|
-
})
|
|
194
|
-
export class PrismaModule {}
|
|
195
|
-
`;
|
|
196
|
-
await fs.writeFile(path.join(prismaDir, 'prisma.module.ts'), prismaModuleContent);
|
|
213
|
+
// Write the prisma module content directly (not via template)
|
|
214
|
+
renderTasks.push({ _raw: true, outPath: path.join(srcDir, 'prisma', 'prisma.module.ts'), content: prismaModuleContent });
|
|
197
215
|
moduleImports.unshift("import { PrismaModule } from './prisma/prisma.module';");
|
|
198
216
|
moduleNames.unshift('PrismaModule');
|
|
199
|
-
|
|
200
|
-
await fs.ensureDir(path.join(projectDir, 'prisma'));
|
|
201
|
-
await renderAndWrite(
|
|
202
|
-
getTemplatePath('js-express/partials/prisma.schema.ejs'),
|
|
203
|
-
path.join(projectDir, 'prisma', 'schema.prisma'),
|
|
204
|
-
{ models: Array.from(modelsToGenerate.values()) }
|
|
205
|
-
);
|
|
206
217
|
}
|
|
207
218
|
|
|
208
|
-
// --- Mongoose config in AppModule ---
|
|
209
219
|
if (dbType === 'mongoose') {
|
|
210
220
|
moduleImports.unshift("import { MongooseModule } from '@nestjs/mongoose';");
|
|
211
221
|
moduleNames.unshift(`MongooseModule.forRoot(process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}')`);
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
//
|
|
224
|
+
// Auth tasks
|
|
215
225
|
if (addAuth) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
getTemplatePath('nestjs/partials/auth.module.ts.ejs'),
|
|
222
|
-
path.join(authDir, 'auth.module.ts'),
|
|
223
|
-
{ dbType }
|
|
224
|
-
);
|
|
225
|
-
await renderAndWrite(
|
|
226
|
-
getTemplatePath('nestjs/partials/auth.controller.ts.ejs'),
|
|
227
|
-
path.join(authDir, 'auth.controller.ts'),
|
|
228
|
-
{ dbType }
|
|
229
|
-
);
|
|
230
|
-
await renderAndWrite(
|
|
231
|
-
getTemplatePath('nestjs/partials/auth.service.ts.ejs'),
|
|
232
|
-
path.join(authDir, 'auth.service.ts'),
|
|
233
|
-
{ dbType }
|
|
226
|
+
renderTasks.push(
|
|
227
|
+
{ templatePath: getTemplatePath('nestjs/partials/auth.module.ts.ejs'), outPath: path.join(srcDir, 'auth', 'auth.module.ts'), data: { dbType } },
|
|
228
|
+
{ templatePath: getTemplatePath('nestjs/partials/auth.controller.ts.ejs'), outPath: path.join(srcDir, 'auth', 'auth.controller.ts'), data: { dbType } },
|
|
229
|
+
{ templatePath: getTemplatePath('nestjs/partials/auth.service.ts.ejs'), outPath: path.join(srcDir, 'auth', 'auth.service.ts'), data: { dbType } },
|
|
230
|
+
{ templatePath: getTemplatePath('nestjs/partials/jwt-guard.ts.ejs'), outPath: path.join(srcDir, 'auth', 'jwt-auth.guard.ts'), data: {} }
|
|
234
231
|
);
|
|
235
|
-
await renderAndWrite(
|
|
236
|
-
getTemplatePath('nestjs/partials/jwt-guard.ts.ejs'),
|
|
237
|
-
path.join(authDir, 'jwt-auth.guard.ts'),
|
|
238
|
-
{}
|
|
239
|
-
);
|
|
240
|
-
|
|
241
232
|
moduleImports.push("import { AuthModule } from './auth/auth.module';");
|
|
242
233
|
moduleNames.push('AuthModule');
|
|
243
234
|
}
|
|
244
235
|
|
|
245
|
-
//
|
|
236
|
+
// Seeder
|
|
237
|
+
if (addSeeder) {
|
|
238
|
+
renderTasks.push({
|
|
239
|
+
templatePath: getTemplatePath('nestjs/partials/seeder.ts.ejs'),
|
|
240
|
+
outPath: path.join(projectDir, 'scripts', 'seeder.ts'),
|
|
241
|
+
data: { projectName, models: Array.from(modelsToGenerate.values()) },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Fire all render tasks in parallel
|
|
246
|
+
const rawTasks = renderTasks.filter(t => t._raw);
|
|
247
|
+
const normalTasks = renderTasks.filter(t => !t._raw);
|
|
248
|
+
|
|
249
|
+
await Promise.all([
|
|
250
|
+
renderAndWriteAll(normalTasks, 12),
|
|
251
|
+
...rawTasks.map(t => fs.outputFile(t.outPath, t.content)),
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
console.log(chalk.gray(` Modules generated. ${step5()}`));
|
|
255
|
+
|
|
256
|
+
// ── Step 6: Inject into app.module.ts ─────────────────────────────────────
|
|
246
257
|
let appModuleContent = await fs.readFile(path.join(srcDir, 'app.module.ts'), 'utf-8');
|
|
247
|
-
appModuleContent = appModuleContent
|
|
248
|
-
'// INJECT:IMPORTS',
|
|
249
|
-
|
|
250
|
-
);
|
|
251
|
-
appModuleContent = appModuleContent.replace(
|
|
252
|
-
'// INJECT:MODULES',
|
|
253
|
-
moduleNames.join(',\n ')
|
|
254
|
-
);
|
|
258
|
+
appModuleContent = appModuleContent
|
|
259
|
+
.replace('// INJECT:IMPORTS', moduleImports.join('\n'))
|
|
260
|
+
.replace('// INJECT:MODULES', moduleNames.join(',\n '));
|
|
255
261
|
await fs.writeFile(path.join(srcDir, 'app.module.ts'), appModuleContent);
|
|
256
262
|
|
|
257
|
-
//
|
|
258
|
-
console.log(chalk.magenta(' -> Installing dependencies...
|
|
259
|
-
|
|
263
|
+
// ── Step 7: Install deps (with retry) ─────────────────────────────────────
|
|
264
|
+
console.log(chalk.magenta(' -> [7] Installing dependencies...'));
|
|
265
|
+
const step7 = timer();
|
|
266
|
+
|
|
267
|
+
await withRetry(() => execa('npm', ['install'], { cwd: projectDir }), {
|
|
268
|
+
attempts: 2, baseDelay: 1000, label: 'npm install',
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log(chalk.gray(` npm install done. ${step7()}`));
|
|
260
272
|
|
|
261
273
|
if (dbType === 'prisma') {
|
|
262
|
-
|
|
263
|
-
|
|
274
|
+
await withRetry(() => execa('npx', ['prisma', 'generate'], { cwd: projectDir }), {
|
|
275
|
+
attempts: 2, baseDelay: 500, label: 'prisma generate',
|
|
276
|
+
});
|
|
264
277
|
}
|
|
265
278
|
|
|
266
|
-
//
|
|
279
|
+
// ── Step 8: .env.example ───────────────────────────────────────────────────
|
|
267
280
|
let envContent = `PORT=8000\n`;
|
|
268
281
|
if (dbType === 'mongoose') envContent += `MONGO_URI=mongodb://127.0.0.1:27017/${projectName}\n`;
|
|
269
282
|
if (dbType === 'prisma') envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${projectName}?schema=public"\n`;
|
|
@@ -271,7 +284,7 @@ export class PrismaModule {}
|
|
|
271
284
|
|
|
272
285
|
await fs.writeFile(path.join(projectDir, '.env.example'), envContent);
|
|
273
286
|
|
|
274
|
-
console.log(chalk.green(
|
|
287
|
+
console.log(chalk.green(` -> ✓ NestJS backend generation complete. Total: ${chalk.bold(totalTimer())}`));
|
|
275
288
|
} catch (error) {
|
|
276
289
|
throw error;
|
|
277
290
|
}
|