nestcraftx 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.fr.md +74 -0
- package/CHANGELOG.md +74 -0
- package/PROGRESS.md +70 -67
- package/README.fr.md +60 -69
- package/bin/nestcraft.js +8 -0
- package/commands/demo.js +12 -46
- package/commands/generate.js +551 -2
- package/commands/generateConf.js +4 -4
- package/commands/help.js +11 -0
- package/commands/info.js +2 -3
- package/commands/list.js +93 -0
- package/commands/new.js +28 -56
- package/jest.config.js +9 -0
- package/package.json +7 -2
- package/readme.md +59 -68
- package/tests/e2e/generator.spec.js +71 -0
- package/tests/unit/appModuleUpdater.spec.js +111 -0
- package/tests/unit/cliParser.spec.js +74 -0
- package/tests/unit/controllerGenerator.spec.js +145 -0
- package/tests/unit/dtoGenerator.spec.js +87 -0
- package/tests/unit/entityGenerator.spec.js +65 -0
- package/tests/unit/generateCommand.spec.js +100 -0
- package/tests/unit/health.spec.js +69 -0
- package/tests/unit/listCommand.spec.js +91 -0
- package/tests/unit/middlewareGenerator.spec.js +64 -0
- package/tests/unit/newCommand.spec.js +88 -0
- package/tests/unit/relationCommand.spec.js +202 -0
- package/tests/unit/throttler.spec.js +117 -0
- package/utils/app-module.updater.js +6 -0
- package/utils/configs/setupCleanArchitecture.js +1 -1
- package/utils/configs/setupLightArchitecture.js +98 -26
- package/utils/envGenerator.js +9 -1
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +9 -0
- package/utils/fullModeInput.js +12 -229
- package/utils/generators/application/dtoGenerator.js +26 -8
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/cleanModuleGenerator.js +40 -11
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +114 -4
- package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
- package/utils/generators/lightModuleGenerator.js +14 -0
- package/utils/generators/presentation/controllerGenerator.js +70 -19
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/askRelationCommand.js +98 -0
- package/utils/interactive/entityBuilder.js +411 -0
- package/utils/lightModeInput.js +22 -241
- package/utils/setups/orms/typeOrmSetup.js +42 -8
- package/utils/setups/projectSetup.js +88 -10
- package/utils/setups/setupAuth.js +335 -20
- package/utils/setups/setupDatabase.js +4 -1
- package/utils/setups/setupHealth.js +74 -0
- package/utils/setups/setupMongoose.js +84 -32
- package/utils/setups/setupPrisma.js +151 -4
- package/utils/setups/setupSwagger.js +15 -8
- package/utils/setups/setupThrottler.js +92 -0
- package/utils/shell.js +61 -9
package/utils/lightModeInput.js
CHANGED
|
@@ -6,6 +6,7 @@ const { capitalize } = require("./userInput");
|
|
|
6
6
|
const { logWarning } = require("./loggers/logWarning");
|
|
7
7
|
const { logInfo } = require("./loggers/logInfo");
|
|
8
8
|
const { getPackageManager } = require("./utils");
|
|
9
|
+
const { buildEntityInteractive, buildRelationsInteractive } = require("./interactive/entityBuilder");
|
|
9
10
|
const actualInquirer = inquirer.default || inquirer;
|
|
10
11
|
|
|
11
12
|
async function getLightModeInputs(projectName, flags) {
|
|
@@ -93,12 +94,16 @@ async function getLightModeInputs(projectName, flags) {
|
|
|
93
94
|
// JWT Auth config
|
|
94
95
|
const useAuth = getAuthChoice(flags);
|
|
95
96
|
|
|
97
|
+
// Throttler config
|
|
98
|
+
const useThrottler = getThrottlerChoice(flags);
|
|
99
|
+
|
|
96
100
|
const packageManager = await getPackageManager(flags);
|
|
97
101
|
inputs.packageManager = packageManager;
|
|
98
102
|
|
|
99
103
|
inputs.useAuth = useAuth;
|
|
100
104
|
inputs.useSwagger = useSwagger;
|
|
101
105
|
inputs.useDocker = useDocker;
|
|
106
|
+
inputs.useThrottler = useThrottler;
|
|
102
107
|
|
|
103
108
|
if (useAuth) {
|
|
104
109
|
console.log(
|
|
@@ -139,7 +144,17 @@ async function getLightModeInputs(projectName, flags) {
|
|
|
139
144
|
);
|
|
140
145
|
if (addEntities) {
|
|
141
146
|
console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
|
|
142
|
-
|
|
147
|
+
while (true) {
|
|
148
|
+
const entity = await buildEntityInteractive();
|
|
149
|
+
if (entity) {
|
|
150
|
+
inputs.entitiesData.entities.push(entity);
|
|
151
|
+
console.log(
|
|
152
|
+
`${info("[INFO]")} Entity "${entity.name}" added with ${entity.fields.length} field(s)`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
const addMore = readline.keyInYNStrict("Add another entity?");
|
|
156
|
+
if (!addMore) break;
|
|
157
|
+
}
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
// Demander les relations entre entités
|
|
@@ -149,7 +164,7 @@ async function getLightModeInputs(projectName, flags) {
|
|
|
149
164
|
);
|
|
150
165
|
if (wantsRelation) {
|
|
151
166
|
console.log(`\n${info("[INFO]")} Configuring relationships`);
|
|
152
|
-
await
|
|
167
|
+
await buildRelationsInteractive(inputs.entitiesData);
|
|
153
168
|
}
|
|
154
169
|
}
|
|
155
170
|
|
|
@@ -213,248 +228,14 @@ function getDockerChoice(flags) {
|
|
|
213
228
|
return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
|
|
214
229
|
}
|
|
215
230
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
let name;
|
|
219
|
-
while (true) {
|
|
220
|
-
name = readline.question("\nEntity name (empty to finish) : ");
|
|
221
|
-
if (!name) return;
|
|
222
|
-
if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
|
|
223
|
-
logWarning("Invalid name. Use letters, numbers, and _ only.");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const fields = [];
|
|
227
|
-
console.log(` Fields for entity "${name}" :`);
|
|
228
|
-
|
|
229
|
-
while (true) {
|
|
230
|
-
const fieldName = readline.question(" Field name (empty to finish) : ");
|
|
231
|
-
if (!fieldName) break;
|
|
232
|
-
|
|
233
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fieldName)) {
|
|
234
|
-
logWarning("Invalid field name.");
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const baseTypeChoices = [
|
|
239
|
-
"string",
|
|
240
|
-
"text",
|
|
241
|
-
"number",
|
|
242
|
-
"decimal",
|
|
243
|
-
"boolean",
|
|
244
|
-
"Date",
|
|
245
|
-
"uuid",
|
|
246
|
-
"json",
|
|
247
|
-
"enum",
|
|
248
|
-
"array",
|
|
249
|
-
"object",
|
|
250
|
-
];
|
|
251
|
-
|
|
252
|
-
const typeQuestion = {
|
|
253
|
-
type: "list",
|
|
254
|
-
name: "ftype",
|
|
255
|
-
message: `Type of "${fieldName}"`,
|
|
256
|
-
default: "string",
|
|
257
|
-
choices: baseTypeChoices,
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const typeAnswer = await actualInquirer.prompt([typeQuestion]);
|
|
261
|
-
let fieldType = typeAnswer.ftype; // --- ADVANCED LOGIC FOR ARRAY, ENUM, AND OBJECT ---
|
|
262
|
-
// Déplace le curseur d'une ligne vers le haut :
|
|
263
|
-
process.stdout.write("\x1B[1A");
|
|
264
|
-
// Efface la ligne (où se trouvait le "√ Type for...") :
|
|
265
|
-
process.stdout.write("\x1B[K");
|
|
266
|
-
|
|
267
|
-
if (fieldType === "array") {
|
|
268
|
-
// Prompt spécifique pour le type interne du tableau
|
|
269
|
-
const arrayInnerQuestion = {
|
|
270
|
-
type: "list",
|
|
271
|
-
name: "innerType",
|
|
272
|
-
message: `Type of elements in "${fieldName}[]"`,
|
|
273
|
-
default: "string", // Exclure array et object du sous-type pour simplifier
|
|
274
|
-
choices: baseTypeChoices.filter(
|
|
275
|
-
(c) => c !== "array" && c !== "object"
|
|
276
|
-
),
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
|
|
280
|
-
fieldType = `${innerAnswer.innerType}[]`;
|
|
281
|
-
} else if (fieldType === "enum") {
|
|
282
|
-
const enumName = capitalize(fieldName) + "Enum";
|
|
283
|
-
logInfo(
|
|
284
|
-
`Enum type selected. Remember to define ${enumName} in your code.`
|
|
285
|
-
);
|
|
286
|
-
fieldType = enumName;
|
|
287
|
-
} else if (fieldType === "object") {
|
|
288
|
-
// Prompt pour nommer l'objet (ou 'json' par défaut)
|
|
289
|
-
const objectNameQuestion = {
|
|
290
|
-
type: "input",
|
|
291
|
-
name: "objectName",
|
|
292
|
-
message: `Complex type name (DTO/Class or leave 'json') :`,
|
|
293
|
-
default: "json",
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
|
|
297
|
-
fieldType = capitalize(objectAnswer.objectName.trim() || "json");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
console.log(
|
|
301
|
-
` Field type for "${fieldName}" : ${fieldType} ${success("[✓]")}`
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
fields.push({ name: fieldName, type: fieldType });
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (fields.length > 0) {
|
|
308
|
-
entitiesData.entities.push({ name, fields });
|
|
309
|
-
console.log(
|
|
310
|
-
`${info("[INFO]")} Entity "${name}" added with ${
|
|
311
|
-
fields.length
|
|
312
|
-
} field(s)`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const addMore = readline.keyInYNStrict("Add another entity?");
|
|
317
|
-
if (!addMore) break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Manages adding relationships between entities via an interactive interface.
|
|
323
|
-
* All prompts and logs are in English.
|
|
324
|
-
* @param {Object} entitiesData - The object containing entities and relations.
|
|
325
|
-
*/
|
|
326
|
-
async function addRelations(entitiesData) {
|
|
327
|
-
if (entitiesData.entities.length < 2) {
|
|
328
|
-
logInfo("At least two entities are required to create a relationship.");
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
let configuring = true;
|
|
333
|
-
|
|
334
|
-
while (configuring) {
|
|
335
|
-
const entityNames = entitiesData.entities.map((e) => e.name);
|
|
336
|
-
|
|
337
|
-
// 1. Interactive selection of entities
|
|
338
|
-
const answers = await actualInquirer.prompt([
|
|
339
|
-
{
|
|
340
|
-
type: "list",
|
|
341
|
-
name: "fromName",
|
|
342
|
-
message: "Select the source entity (From) :",
|
|
343
|
-
choices: entityNames,
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
type: "list",
|
|
347
|
-
name: "toName",
|
|
348
|
-
message: (prev) =>
|
|
349
|
-
`To which entity do you want to link ${prev.fromName} ?`,
|
|
350
|
-
choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
|
|
351
|
-
},
|
|
352
|
-
]);
|
|
353
|
-
|
|
354
|
-
// --- CHECK FOR EXISTING RELATIONSHIPS ---
|
|
355
|
-
const alreadyExists = entitiesData.relations.find(
|
|
356
|
-
(rel) =>
|
|
357
|
-
(rel.from === answers.fromName && rel.to === answers.toName) ||
|
|
358
|
-
(rel.from === answers.toName && rel.to === answers.fromName)
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
if (alreadyExists) {
|
|
362
|
-
logWarning(
|
|
363
|
-
`A relationship already exists between ${answers.fromName} and ${answers.toName} (${alreadyExists.type}).`
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const { tryAgain } = await actualInquirer.prompt([
|
|
367
|
-
{
|
|
368
|
-
type: "confirm",
|
|
369
|
-
name: "tryAgain",
|
|
370
|
-
message: "Would you like to choose different entities ?",
|
|
371
|
-
default: true,
|
|
372
|
-
},
|
|
373
|
-
]);
|
|
374
|
-
|
|
375
|
-
if (!tryAgain) break;
|
|
376
|
-
// Restart the loop
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// 2. Select Relationship Type
|
|
381
|
-
const typeAnswer = await actualInquirer.prompt([
|
|
382
|
-
{
|
|
383
|
-
type: "list",
|
|
384
|
-
name: "relType",
|
|
385
|
-
message: "What is the relationship type?",
|
|
386
|
-
choices: [
|
|
387
|
-
{
|
|
388
|
-
name: `1-1 (One-to-One) : ${answers.fromName} is linked to exactly one ${answers.toName}`,
|
|
389
|
-
value: "1-1",
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
name: `1-n (One-to-Many) : ${answers.fromName} owns multiple ${answers.toName}s`,
|
|
393
|
-
value: "1-n",
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
name: `n-1 (Many-to-One) : Multiple ${answers.fromName}s belong to one ${answers.toName}`,
|
|
397
|
-
value: "n-1",
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: `n-n (Many-to-Many) : Multiple ${answers.fromName}s are linked to multiple ${answers.toName}s`,
|
|
401
|
-
value: "n-n",
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
},
|
|
405
|
-
]);
|
|
406
|
-
|
|
407
|
-
const from = entitiesData.entities.find((e) => e.name === answers.fromName);
|
|
408
|
-
const to = entitiesData.entities.find((e) => e.name === answers.toName);
|
|
409
|
-
const relType = typeAnswer.relType;
|
|
410
|
-
|
|
411
|
-
// 3. Register relationship
|
|
412
|
-
entitiesData.relations.push({
|
|
413
|
-
from: from.name,
|
|
414
|
-
to: to.name,
|
|
415
|
-
type: relType,
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// 4. Automatic field injection
|
|
419
|
-
const fromLow = from.name.toLowerCase();
|
|
420
|
-
const toLow = to.name.toLowerCase();
|
|
421
|
-
if (relType === "1-1") {
|
|
422
|
-
from.fields.push(
|
|
423
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
424
|
-
{ name: toLow, type: to.name }
|
|
425
|
-
);
|
|
426
|
-
} else if (relType === "1-n") {
|
|
427
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
428
|
-
to.fields.push(
|
|
429
|
-
{ name: `${fromLow}Id`, type: "string" },
|
|
430
|
-
{ name: fromLow, type: from.name }
|
|
431
|
-
);
|
|
432
|
-
} else if (relType === "n-1") {
|
|
433
|
-
from.fields.push(
|
|
434
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
435
|
-
{ name: toLow, type: to.name }
|
|
436
|
-
);
|
|
437
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
438
|
-
} else if (relType === "n-n") {
|
|
439
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
440
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
441
|
-
}
|
|
231
|
+
function getThrottlerChoice(flags) {
|
|
232
|
+
if (flags.throttler !== undefined) {
|
|
442
233
|
console.log(
|
|
443
|
-
`${
|
|
234
|
+
`${info("[INFO]")} Throttler: ${flags.throttler ? "Yes" : "No"} (via flag)`
|
|
444
235
|
);
|
|
445
|
-
|
|
446
|
-
// 5. Ask to continue
|
|
447
|
-
const { addMore } = await actualInquirer.prompt([
|
|
448
|
-
{
|
|
449
|
-
type: "confirm",
|
|
450
|
-
name: "addMore",
|
|
451
|
-
message: "Do you want to add another relationship ?",
|
|
452
|
-
default: false,
|
|
453
|
-
},
|
|
454
|
-
]);
|
|
455
|
-
|
|
456
|
-
configuring = addMore;
|
|
236
|
+
return flags.throttler === true || flags.throttler === "true";
|
|
457
237
|
}
|
|
238
|
+
return readline.keyInYNStrict(`${info("[?]")} Enable Rate Limiting (Throttler) ?`);
|
|
458
239
|
}
|
|
459
240
|
|
|
460
241
|
module.exports = { getLightModeInputs };
|
|
@@ -21,8 +21,11 @@ async function setupTypeORM(inputs) {
|
|
|
21
21
|
logInfo("📦 Installing TypeORM and PostgreSQL dependencies...");
|
|
22
22
|
|
|
23
23
|
const mode = inputs.mode;
|
|
24
|
+
const pkgManager = inputs.packageManager || "npm";
|
|
25
|
+
const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
|
|
26
|
+
|
|
24
27
|
await runCommand(
|
|
25
|
-
|
|
28
|
+
`${installCmd} @nestjs/typeorm@^10.0.2 typeorm@^0.3.20 pg@^8.11.3 reflect-metadata@^0.2.1`,
|
|
26
29
|
"TypeORM and PostgreSQL dependencies installed successfully"
|
|
27
30
|
); // Updating app.module.ts with TypeORM
|
|
28
31
|
|
|
@@ -162,12 +165,39 @@ async function setupTypeORM(inputs) {
|
|
|
162
165
|
columnOptions.push("default: Role.USER");
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
if (field.
|
|
168
|
+
if (field.unique || field.name.toLowerCase() === "email") {
|
|
169
|
+
columnOptions.push("unique: true");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const isNullable = field.nullable || field.optional;
|
|
173
|
+
if (isNullable) {
|
|
166
174
|
columnOptions.push("nullable: true");
|
|
167
175
|
}
|
|
168
176
|
|
|
169
|
-
if (field.
|
|
170
|
-
|
|
177
|
+
if (field.default !== undefined && field.default !== null && field.default !== "") {
|
|
178
|
+
let defaultVal = field.default;
|
|
179
|
+
if (typeof defaultVal === "string") {
|
|
180
|
+
if (defaultVal === "true") defaultVal = true;
|
|
181
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
182
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
183
|
+
defaultVal = "CURRENT_TIMESTAMP";
|
|
184
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
185
|
+
defaultVal = Number(defaultVal);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (defaultVal === "CURRENT_TIMESTAMP") {
|
|
189
|
+
columnOptions.push(`default: () => 'CURRENT_TIMESTAMP'`);
|
|
190
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
191
|
+
columnOptions.push(`default: ${defaultVal}`);
|
|
192
|
+
} else if (mapping.type === "enum") {
|
|
193
|
+
if (mapping.tsType && /^[A-Z]/.test(mapping.tsType)) {
|
|
194
|
+
columnOptions.push(`default: ${mapping.tsType}.${defaultVal.toUpperCase()}`);
|
|
195
|
+
} else {
|
|
196
|
+
columnOptions.push(`default: '${defaultVal}'`);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
columnOptions.push(`default: '${defaultVal}'`);
|
|
200
|
+
}
|
|
171
201
|
}
|
|
172
202
|
|
|
173
203
|
// 2. Gestion des Imports d'Enums Custom
|
|
@@ -186,7 +216,7 @@ async function setupTypeORM(inputs) {
|
|
|
186
216
|
// 3. Construction du champ
|
|
187
217
|
fieldsContent += `
|
|
188
218
|
@Column({ ${columnOptions.join(", ")} })
|
|
189
|
-
${field.name}${
|
|
219
|
+
${field.name}${isNullable ? "?" : ""}: ${mapping.tsType};
|
|
190
220
|
`;
|
|
191
221
|
}
|
|
192
222
|
|
|
@@ -419,13 +449,17 @@ async function setupTypeOrmSeeding(inputs) {
|
|
|
419
449
|
"typeorm-extension",
|
|
420
450
|
];
|
|
421
451
|
|
|
452
|
+
const pkgManager = inputs.packageManager || "npm";
|
|
453
|
+
const installDevCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add -D` : "npm install -D";
|
|
454
|
+
const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
|
|
455
|
+
|
|
422
456
|
await runCommand(
|
|
423
|
-
`${
|
|
457
|
+
`${installDevCmd} ${typeOrmDevDeps.join(" ")}`,
|
|
424
458
|
"❌ Failed to install TypeORM seeding dependencies"
|
|
425
459
|
);
|
|
426
460
|
|
|
427
461
|
await runCommand(
|
|
428
|
-
`${
|
|
462
|
+
`${installCmd} bcrypt`,
|
|
429
463
|
"❌ Failed to install bcrypt"
|
|
430
464
|
);
|
|
431
465
|
|
|
@@ -435,7 +469,7 @@ async function setupTypeOrmSeeding(inputs) {
|
|
|
435
469
|
"typeorm-ts-node-commonjs migration:run -d ./src/database/typeorm.config.ts",
|
|
436
470
|
"typeorm:seed":
|
|
437
471
|
"typeorm-extension seed:run -d src/database/typeorm.config.ts",
|
|
438
|
-
seed:
|
|
472
|
+
seed: `${pkgManager} run typeorm:seed`,
|
|
439
473
|
};
|
|
440
474
|
|
|
441
475
|
await updatePackageJson(inputs, typeOrmScripts);
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
const readline = require("readline-sync");
|
|
2
|
-
const { logInfo } = require("../loggers/logInfo");
|
|
3
|
-
const { runCommand } = require("../shell");
|
|
4
2
|
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
5
4
|
const { info } = require("../colors");
|
|
5
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
6
|
+
const { logError } = require("../loggers/logError");
|
|
7
|
+
const { runCommand } = require("../shell");
|
|
8
|
+
|
|
9
|
+
// Pipeline imports
|
|
10
|
+
const { setupCleanArchitecture } = require("../configs/setupCleanArchitecture");
|
|
11
|
+
const { setupLightArchitecture } = require("../configs/setupLightArchitecture");
|
|
12
|
+
const { setupAuth } = require("./setupAuth");
|
|
13
|
+
const { setupSwagger } = require("./setupSwagger");
|
|
14
|
+
const { setupThrottler } = require("./setupThrottler");
|
|
15
|
+
const { setupHealth } = require("./setupHealth");
|
|
16
|
+
const { setupDatabase } = require("./setupDatabase");
|
|
17
|
+
const { configureDocker } = require("../configs/configureDocker");
|
|
18
|
+
const { setupBootstrapLogger } = require("./setupLogger");
|
|
19
|
+
const { generateEnvFile, writeEnvFile } = require("../envGenerator");
|
|
20
|
+
const { saveProjectConfig } = require("../file-utils/saveProjectConfig");
|
|
21
|
+
const { printSetupWarnings } = require("../shell");
|
|
6
22
|
|
|
7
23
|
async function createProject(inputs) {
|
|
8
24
|
if (fs.existsSync(inputs.projectName)) {
|
|
@@ -13,34 +29,96 @@ async function createProject(inputs) {
|
|
|
13
29
|
);
|
|
14
30
|
|
|
15
31
|
if (confirmation) {
|
|
16
|
-
// confirmation is true if the user presses 'y'
|
|
17
32
|
console.log("Deleting existing project...");
|
|
18
33
|
fs.rmSync(inputs.projectName, { recursive: true, force: true });
|
|
19
34
|
} else {
|
|
20
35
|
console.log("Operation cancelled by user. Exiting.");
|
|
21
|
-
|
|
36
|
+
throw new Error("Operation cancelled by user.");
|
|
22
37
|
}
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
logInfo(`Creating NestJS project: ${inputs.projectName}`);
|
|
26
41
|
|
|
27
|
-
// Remains asynchronous as runCommand likely involves external processes (npm/npx)
|
|
28
42
|
await runCommand(
|
|
29
|
-
`npx @nestjs/cli new ${inputs.projectName} --package-manager ${inputs.packageManager}`,
|
|
43
|
+
`npx @nestjs/cli new ${inputs.projectName} --package-manager ${inputs.packageManager} --skip-install`,
|
|
30
44
|
"Failed to create NestJS project"
|
|
31
45
|
);
|
|
32
46
|
|
|
33
47
|
// Changing the current process directory to the project root
|
|
34
|
-
process.
|
|
48
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
49
|
+
if (!isDryRun) {
|
|
50
|
+
process.chdir(inputs.projectName);
|
|
51
|
+
} else {
|
|
52
|
+
console.log(`[DRY-RUN] Simulated: change directory to ${inputs.projectName}`);
|
|
53
|
+
}
|
|
35
54
|
|
|
36
55
|
// Installing dependencies
|
|
37
56
|
logInfo("Installing dependencies...");
|
|
38
57
|
|
|
39
|
-
// Remains asynchronous as runCommand likely involves external processes (npm/npx)
|
|
40
58
|
await runCommand(
|
|
41
|
-
`${inputs.packageManager} add @nestjs/config class-validator class-transformer dotenv`,
|
|
59
|
+
`${inputs.packageManager} add @nestjs/config@^3.2.0 class-validator@^0.14.1 class-transformer@^0.5.1 dotenv@^16.4.5`,
|
|
42
60
|
"Failed to install dependencies"
|
|
43
61
|
);
|
|
44
62
|
}
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Runs the complete project scaffolding and setup pipeline in sequence.
|
|
66
|
+
* Handles CWD tracking and restores the working directory on failure.
|
|
67
|
+
* @param {object} inputs
|
|
68
|
+
*/
|
|
69
|
+
async function runProjectSetupPipeline(inputs) {
|
|
70
|
+
const originalCwd = process.cwd();
|
|
71
|
+
try {
|
|
72
|
+
logInfo("Starting project generation...");
|
|
73
|
+
|
|
74
|
+
await createProject(inputs);
|
|
75
|
+
|
|
76
|
+
if (inputs.mode === "light") {
|
|
77
|
+
await setupLightArchitecture(inputs);
|
|
78
|
+
} else {
|
|
79
|
+
await setupCleanArchitecture(inputs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (inputs.useThrottler) {
|
|
83
|
+
await setupThrottler(inputs);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await setupHealth();
|
|
87
|
+
|
|
88
|
+
if (inputs.useAuth) {
|
|
89
|
+
await setupAuth(inputs);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (inputs.useSwagger) {
|
|
93
|
+
await setupSwagger(inputs.swaggerInputs);
|
|
94
|
+
} else {
|
|
95
|
+
setupBootstrapLogger();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (inputs.useDocker) {
|
|
99
|
+
await configureDocker(inputs);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await setupDatabase(inputs);
|
|
103
|
+
|
|
104
|
+
const envContent = await generateEnvFile(inputs);
|
|
105
|
+
writeEnvFile(envContent);
|
|
106
|
+
|
|
107
|
+
await saveProjectConfig(inputs);
|
|
108
|
+
|
|
109
|
+
// Show warnings
|
|
110
|
+
printSetupWarnings();
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logError(`Error during project creation: ${error.message}`);
|
|
114
|
+
if (process.cwd() !== originalCwd) {
|
|
115
|
+
process.chdir(originalCwd);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
createProject,
|
|
123
|
+
runProjectSetupPipeline,
|
|
124
|
+
};
|