nestcraftx 0.3.0 → 0.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.
package/PROGRESS.md CHANGED
@@ -9,15 +9,15 @@
9
9
  | Phase | Statut | Tickets | Complétés | Progression |
10
10
  |---|---|---|---|---|
11
11
  | Phase 0 — Stabilisation (v0.3.0) | ✅ Terminé | 17 | 17 | 100% |
12
- | Phase 1 — Refactorisation (v0.4.0) | 🔵 En cours | 18 | 9 | 50% |
12
+ | Phase 1 — Refactorisation (v0.4.0) | Terminé | 18 | 18 | 100% |
13
13
  | Phase 2 — Qualité & Sécurité (v0.5.0) | ⏸️ En attente | 10 | 0 | 0% |
14
14
  | Phase 3 — Tests & CI/CD (v0.6.0) | ⏸️ En attente | 15 | 0 | 0% |
15
15
  | Phase 4 — Features (v0.7–0.9) | ⏸️ En attente | 14 | 0 | 0% |
16
16
  | Phase 5 — Documentation (v0.9.x) | ⏸️ En attente | 8 | 0 | 0% |
17
17
  | Phase 6 — Release v1.0 | ⏸️ En attente | 7 | 0 | 0% |
18
- | **TOTAL** | | **89** | **26** | **29%** |
18
+ | **TOTAL** | | **89** | **35** | **39%** |
19
19
 
20
- > Mis à jour : 2026-06-07
20
+ > Mis à jour : 2026-06-16
21
21
 
22
22
  ---
23
23
 
@@ -79,20 +79,20 @@
79
79
 
80
80
  | Ticket | Description | Fichier(s) | Statut | Date |
81
81
  |---|---|---|---|---|
82
- | PROD-04a | Étendre modèle de champ : `unique`, `nullable`, `default` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | À faire | |
83
- | PROD-05 | Écran de révision d'entité (modifier, supprimer, renommer) | `utils/fullModeInput.js`, `utils/lightModeInput.js` | À faire | |
84
- | PROD-04b | Refonte state-based : `EntityBuilder` navigable | `utils/fullModeInput.js` | À faire | |
85
- | MP-02 | Dédupliquer `addCustomEntities` et `addRelations` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | À faire | |
82
+ | PROD-04a | Étendre modèle de champ : `unique`, `nullable`, `default` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | Terminé | 2026-06-14 |
83
+ | PROD-05 | Écran de révision d'entité (modifier, supprimer, renommer) | `utils/fullModeInput.js`, `utils/lightModeInput.js` | Terminé | 2026-06-14 |
84
+ | PROD-04b | Refonte state-based : `EntityBuilder` navigable | `utils/fullModeInput.js` | Terminé | 2026-06-14 |
85
+ | MP-02 | Dédupliquer `addCustomEntities` et `addRelations` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | Terminé | 2026-06-14 |
86
86
 
87
87
  ### 1C — Architecture générale
88
88
 
89
89
  | Ticket | Description | Fichier(s) | Statut | Date |
90
90
  |---|---|---|---|---|
91
- | MA-05 | Extraire flow commun `demo.js` → partager avec `new.js` | `commands/demo.js`, `commands/new.js` | À faire | |
92
- | MP-04 | Remplacer `execSync` par `execa` avec timeout | `utils/shell.js` | À faire | |
93
- | MA-01 | Mécanisme de rapport final (accumuler warnings, afficher en bloc) | `utils/shell.js`, `commands/new.js` | À faire | |
94
- | MA-02 | Isoler `process.chdir` — vérifier CWD avant chaque I/O | `utils/setups/projectSetup.js` | À faire | |
95
- | DRY-01 | Ajouter `--dry-run` flag (simuler sans écrire) | `bin/nestcraft.js`, `commands/new.js` | À faire | |
91
+ | MA-05 | Extraire flow commun `demo.js` → partager avec `new.js` | `commands/demo.js`, `commands/new.js` | Terminé | 2026-06-16 |
92
+ | MP-04 | Remplacer `execSync` par `execa` avec timeout | `utils/shell.js` | Terminé | 2026-06-16 |
93
+ | MA-01 | Mécanisme de rapport final (accumuler warnings, afficher en bloc) | `utils/shell.js`, `commands/new.js` | Terminé | 2026-06-16 |
94
+ | MA-02 | Isoler `process.chdir` — vérifier CWD avant chaque I/O | `utils/setups/projectSetup.js` | Terminé | 2026-06-16 |
95
+ | DRY-01 | Ajouter `--dry-run` flag (simuler sans écrire) | `bin/nestcraft.js`, `commands/new.js` | Terminé | 2026-06-16 |
96
96
 
97
97
  ---
98
98
 
@@ -239,6 +239,7 @@
239
239
  | 2026-06-05 | 0.3.0-wip | PROD-03, PROD-01a, PROD-01b, PROD-01c, BUG-09 | `runCommand` refactorisé (PROD-03). Prisma relations : capitalisation forcée des modèles et types, déduplication complète des clés étrangères (PROD-01a, b, c). Correction de la boucle asynchrone des use cases clean architecture (BUG-09). |
240
240
  | 2026-06-06 | 0.3.0-wip | BUG-01, BUG-03, BUG-06 | Unification du module system en CommonJS (BUG-01). Validation non-interactive des flags (BUG-03). Utilisation de inputs.packageManager dynamique dans `setupAuth.js` au lieu de npm en dur (BUG-06). |
241
241
  | 2026-06-07 | 0.3.0 | BUG-07, BUG-04, BUG-08, BUG-12, MP-07a, MP-07b, BUG-02, BUG-11, BUG-12b | Clôture de la Phase 0. Mongoose .env préservé. createFile supporte overwrite. safeUpdateAppModule robuste aux crochets imbriqués. help.js dynamic. Nettoyage de code mort et debug logs. |
242
+ | 2026-06-14 | 0.3.0-wip | PROD-04a, PROD-04b, PROD-05, MP-02 | Refonte state-based interactive de la saisie d'entités avec EntityBuilder. Support des contraintes unique, nullable et valeurs par défaut. Alignement et déduplication des générateurs (Prisma, TypeORM, Mongoose, DTOs, et Domain Entities). |
242
243
 
243
244
  ---
244
245
 
package/commands/demo.js CHANGED
@@ -1,21 +1,7 @@
1
1
  const inquirer = require("inquirer");
2
2
  const { logInfo } = require("../utils/loggers/logInfo");
3
3
  const { logSuccess } = require("../utils/loggers/logSuccess");
4
- const { createProject } = require("../utils/setups/projectSetup");
5
- const {
6
- setupCleanArchitecture,
7
- } = require("../utils/configs/setupCleanArchitecture");
8
- const {
9
- setupLightArchitecture,
10
- } = require("../utils/configs/setupLightArchitecture");
11
- const { setupAuth } = require("../utils/setups/setupAuth");
12
- const { setupSwagger } = require("../utils/setups/setupSwagger");
13
- const { setupDatabase } = require("../utils/setups/setupDatabase");
14
- const { configureDocker } = require("../utils/configs/configureDocker");
15
- const { generateEnvFile, writeEnvFile } = require("../utils/envGenerator");
16
- const { setupBootstrapLogger } = require("../utils/setups/setupLogger");
17
- const { saveProjectConfig } = require("../utils/file-utils/saveProjectConfig");
18
- const { printSetupWarnings } = require("../utils/shell");
4
+ const { runProjectSetupPipeline } = require("../utils/setups/projectSetup");
19
5
  const actualInquirer = inquirer.default || inquirer;
20
6
 
21
7
  async function demoCommand(flags = {}) {
@@ -210,37 +196,7 @@ async function demoCommand(flags = {}) {
210
196
  dbConfig,
211
197
  };
212
198
 
213
- await createProject(demoInputs);
214
-
215
- if (isLight) {
216
- await setupLightArchitecture(demoInputs);
217
- } else {
218
- await setupCleanArchitecture(demoInputs);
219
- }
220
-
221
- if (useAuth) {
222
- await setupAuth(demoInputs);
223
- }
224
-
225
- if (useSwagger) {
226
- await setupSwagger(demoInputs.swaggerInputs);
227
- } else {
228
- await setupBootstrapLogger();
229
- }
230
-
231
- if (useDocker) {
232
- await configureDocker(demoInputs);
233
- }
234
-
235
- await setupDatabase(demoInputs);
236
-
237
- const envContent = await generateEnvFile(demoInputs);
238
- writeEnvFile(envContent);
239
-
240
- await saveProjectConfig(demoInputs);
241
-
242
- // Show any non-critical warnings accumulated during generation
243
- printSetupWarnings();
199
+ await runProjectSetupPipeline(demoInputs);
244
200
 
245
201
  console.log("\n" + "=".repeat(60));
246
202
  logSuccess("Demonstration project created successfully!");
package/commands/new.js CHANGED
@@ -1,26 +1,12 @@
1
1
  const { getFullModeInputs } = require("../utils/fullModeInput");
2
2
  const { getLightModeInputs } = require("../utils/lightModeInput");
3
- const { createProject } = require("../utils/setups/projectSetup");
4
- const {
5
- setupCleanArchitecture,
6
- } = require("../utils/configs/setupCleanArchitecture");
7
- const {
8
- setupLightArchitecture,
9
- } = require("../utils/configs/setupLightArchitecture");
10
- const { setupAuth } = require("../utils/setups/setupAuth");
11
- const { setupSwagger } = require("../utils/setups/setupSwagger");
12
- const { setupDatabase } = require("../utils/setups/setupDatabase");
13
- const { configureDocker } = require("../utils/configs/configureDocker");
14
- const { setupBootstrapLogger } = require("../utils/setups/setupLogger");
3
+ const { runProjectSetupPipeline } = require("../utils/setups/projectSetup");
15
4
  const { logSuccess } = require("../utils/loggers/logSuccess");
16
5
  const { logInfo } = require("../utils/loggers/logInfo");
17
6
  const { logError } = require("../utils/loggers/logError");
18
- const { generateEnvFile, writeEnvFile } = require("../utils/envGenerator");
19
7
  const readline = require("readline-sync");
20
8
  const { info, warning } = require("../utils/colors");
21
9
  const { logWarning } = require("../utils/loggers/logWarning");
22
- const { saveProjectConfig } = require("../utils/file-utils/saveProjectConfig");
23
- const { printSetupWarnings } = require("../utils/shell");
24
10
 
25
11
  async function newCommand(projectName, flags = {}) {
26
12
  if (!projectName) {
@@ -240,40 +226,9 @@ function buildFullModeFromFlags(projectName, flags) {
240
226
 
241
227
  async function executeProjectSetup(inputs) {
242
228
  try {
243
- logInfo("Starting project generation...");
244
-
245
- await createProject(inputs);
246
-
247
- if (inputs.mode === "light") {
248
- await setupLightArchitecture(inputs);
249
- } else {
250
- await setupCleanArchitecture(inputs);
251
- }
252
-
253
- if (inputs.useAuth) await setupAuth(inputs);
254
-
255
- if (inputs.useSwagger) {
256
- await setupSwagger(inputs.swaggerInputs);
257
- } else {
258
- setupBootstrapLogger();
259
- }
260
-
261
- if (inputs.useDocker) await configureDocker(inputs);
262
-
263
- await setupDatabase(inputs);
264
-
265
- const envContent = await generateEnvFile(inputs);
266
- writeEnvFile(envContent);
267
-
268
- await saveProjectConfig(inputs);
269
-
270
- // Show any non-critical warnings accumulated during generation
271
- printSetupWarnings();
272
-
229
+ await runProjectSetupPipeline(inputs);
273
230
  printSuccessSummary(inputs);
274
231
  } catch (error) {
275
- // Erreur lors de la creation du projet: ${error.message}
276
- logError(`Error during project creation: ${error.message}`);
277
232
  throw error;
278
233
  }
279
234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestcraftx",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Modern CLI to generate scalable NestJS projects with Clean Architecture (FULL) and MVP mode (LIGHT) - Enhanced with auto-generated .env secrets, complete ORM support, and entity relations",
5
5
  "main": "bin/nestcraft.js",
6
6
  "bin": {
@@ -24,6 +24,12 @@ const { capitalize } = require("./helpers");
24
24
  * @param {string} entity - Le nom de l'entité en minuscule (ex: "user", "post")
25
25
  */
26
26
  async function safeUpdateAppModule(entity) {
27
+ const isDryRun = process.argv.includes("--dry-run");
28
+ if (isDryRun) {
29
+ console.log(`[DRY-RUN] Simulated: safe update app.module.ts with ${capitalize(entity)}Module`);
30
+ return;
31
+ }
32
+
27
33
  const filePath = "src/app.module.ts";
28
34
  const moduleName = `${capitalize(entity)}Module`;
29
35
  const importLine = `import { ${moduleName} } from 'src/${entity}/${entity}.module';`;
@@ -85,6 +85,12 @@ function buildDatabaseUrl(user, password, host, port, database, orm) {
85
85
  }
86
86
 
87
87
  function writeEnvFile(envContent, projectPath = ".") {
88
+ const isDryRun = process.argv.includes("--dry-run");
89
+ if (isDryRun) {
90
+ console.log(`[DRY-RUN] Simulated: write env file to ${path.join(projectPath, ".env")}`);
91
+ console.log(`[DRY-RUN] Simulated: write env example file to ${path.join(projectPath, ".env.example")}`);
92
+ return;
93
+ }
88
94
  // Écriture du .env (avec secrets)
89
95
  const envPath = path.join(projectPath, ".env");
90
96
  fs.writeFileSync(envPath, envContent, "utf8");
@@ -15,6 +15,11 @@ const fs = require("fs");
15
15
  * @param {string} directoryPath - Le chemin du répertoire à créer.
16
16
  */
17
17
  async function createDirectory(directoryPath) {
18
+ const isDryRun = process.argv.includes("--dry-run");
19
+ if (isDryRun) {
20
+ console.log(`[DRY-RUN] Simulated: create directory ${directoryPath}`);
21
+ return;
22
+ }
18
23
  try {
19
24
  if (!fs.existsSync(directoryPath)) {
20
25
  fs.mkdirSync(directoryPath, { recursive: true });
@@ -36,6 +41,11 @@ async function createDirectory(directoryPath) {
36
41
  * @param {{ path: string, contente: string, overwrite?: boolean }} fileData
37
42
  */
38
43
  async function createFile(fileData) {
44
+ const isDryRun = process.argv.includes("--dry-run");
45
+ if (isDryRun) {
46
+ console.log(`[DRY-RUN] Simulated: create/write file ${fileData.path}`);
47
+ return;
48
+ }
39
49
  try {
40
50
  if (!fs.existsSync(fileData.path)) {
41
51
  fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
@@ -59,6 +69,11 @@ async function createFile(fileData) {
59
69
  * @param {{ path: string, pattern: string|RegExp, replacement: string }} options
60
70
  */
61
71
  async function updateFile({ path, pattern, replacement }) {
72
+ const isDryRun = process.argv.includes("--dry-run");
73
+ if (isDryRun) {
74
+ console.log(`[DRY-RUN] Simulated: update file ${path}`);
75
+ return;
76
+ }
62
77
  try {
63
78
  let mainTs = fs.readFileSync(path, "utf8");
64
79
  const updatedContent = mainTs.replace(pattern, replacement);
@@ -13,6 +13,12 @@ async function updatePackageJson(inputs, scripts, devDependencies = {}) {
13
13
  const projectPath = process.cwd();
14
14
  const packageJsonPath = path.join(projectPath, "package.json");
15
15
 
16
+ const isDryRun = process.argv.includes("--dry-run");
17
+ if (isDryRun) {
18
+ console.log(`[DRY-RUN] Simulated: update package.json scripts and devDependencies for ${inputs.projectName}`);
19
+ return;
20
+ }
21
+
16
22
  try {
17
23
  // 1. Lire le contenu actuel
18
24
  const fileContent = fs.readFileSync(packageJsonPath, "utf8");
@@ -23,6 +23,12 @@ async function saveProjectConfig(inputs) {
23
23
  generatedAt: new Date().toISOString(),
24
24
  };
25
25
 
26
+ const isDryRun = process.argv.includes("--dry-run");
27
+ if (isDryRun) {
28
+ console.log(`[DRY-RUN] Simulated: save project config to ${configFile}`);
29
+ return;
30
+ }
31
+
26
32
  try {
27
33
  if (!fs.existsSync(configDir)) {
28
34
  fs.mkdirSync(configDir, { recursive: true });
@@ -4,6 +4,7 @@ const inquirer = require("inquirer");
4
4
  const { capitalize } = require("./userInput");
5
5
  const { logWarning } = require("./loggers/logWarning");
6
6
  const { getPackageManager } = require("./utils");
7
+ const { buildEntityInteractive, buildRelationsInteractive } = require("./interactive/entityBuilder");
7
8
  const actualInquirer = inquirer.default || inquirer;
8
9
 
9
10
  async function getFullModeInputs(projectName, flags) {
@@ -334,97 +335,13 @@ async function getFullModeInputs(projectName, flags) {
334
335
 
335
336
  let addEntity = readline.keyInYNStrict(`${info("[?]")} Add an entity?`);
336
337
  while (addEntity) {
337
- let name;
338
- while (true) {
339
- name = readline.question(`\n Entity name : `);
340
- if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
341
- logWarning("Invalid name. Letters, numbers, _ (start with a letter).");
342
- }
343
-
344
- const fields = [];
345
-
346
- console.log(` Fields for "${name}" :`);
347
- while (true) {
348
- let fname = readline.question(" Field name (leave empty to finish) : ");
349
- if (!fname) break;
350
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
351
- logWarning("Invalid field name.");
352
- continue;
353
- }
354
-
355
- const baseTypeChoices = [
356
- "string",
357
- "text",
358
- "number",
359
- "decimal",
360
- "boolean",
361
- "Date",
362
- "uuid",
363
- "json",
364
- "enum",
365
- "array",
366
- "object",
367
- ];
368
-
369
- const typeQuestion = {
370
- type: "list",
371
- name: "ftype",
372
- message: `Type for "${fname}"`,
373
- default: "string",
374
- choices: baseTypeChoices,
375
- transformer: () => "",
376
- };
377
- const typeAnswer = await actualInquirer.prompt([typeQuestion]);
378
- let ftype = typeAnswer.ftype;
379
- process.stdout.write("\x1B[1A");
380
- process.stdout.write("\x1B[K");
381
-
382
- if (ftype === "array") {
383
- const arrayInnerQuestion = {
384
- type: "list",
385
- name: "innerType",
386
- message: `Type of elements for "${fname}[]"`,
387
- default: "string",
388
- choices: baseTypeChoices.filter(
389
- (c) => c !== "array" && c !== "object"
390
- ),
391
- transformer: () => "",
392
- };
393
-
394
- const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
395
- ftype = `${innerAnswer.innerType}[]`;
396
- } else if (ftype === "enum") {
397
- const enumName = capitalize(fname) + "Enum";
398
- console.log(
399
- ` ${info(
400
- "[INFO]"
401
- )} Enum type selected. Consider defining ${enumName} in your code.`
402
- );
403
- ftype = enumName;
404
- } else if (ftype === "object") {
405
- const objectNameQuestion = {
406
- type: "input",
407
- name: "objectName",
408
-
409
- message: `Complex type name (DTO/Class or leave 'json') :`,
410
- default: "json",
411
- transformer: () => "",
412
- };
413
-
414
- const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
415
- ftype = capitalize(objectAnswer.objectName.trim() || "json");
416
- }
417
-
418
- console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
419
-
420
- fields.push({ name: fname, type: ftype });
338
+ const entity = await buildEntityInteractive();
339
+ if (entity) {
340
+ entitiesData.entities.push(entity);
341
+ console.log(
342
+ `${success("[✓]")} Entity "${entity.name}" added with ${entity.fields.length} field(s)`
343
+ );
421
344
  }
422
-
423
- entitiesData.entities.push({ name, fields });
424
- console.log(
425
- `${success("[✓]")} Entity "${name}" added with ${fields.length} field(s)`
426
- );
427
-
428
345
  addEntity = readline.keyInYNStrict(`${info("[?]")} Add another entity?`);
429
346
  }
430
347
 
@@ -432,145 +349,7 @@ async function getFullModeInputs(projectName, flags) {
432
349
  `${info("[?]")} Add relationships between entities?`
433
350
  );
434
351
  if (wantsRelation) {
435
- if (entitiesData.entities.length > 1) {
436
- console.log(`\n${info("[INFO]")} Configuring relationships`);
437
-
438
- let configuring = true;
439
- while (configuring) {
440
- const entityNames = entitiesData.entities.map((e) => e.name);
441
-
442
- // 1. Select entities first
443
- const selection = await actualInquirer.prompt([
444
- {
445
- type: "list",
446
- name: "fromName",
447
- message: "From which entity? (Source)",
448
- choices: entityNames,
449
- },
450
- {
451
- type: "list",
452
- name: "toName",
453
- message: (prev) =>
454
- `To which entity should ${prev.fromName} be linked? (Target)`,
455
- choices: (prev) =>
456
- entityNames.filter((name) => name !== prev.fromName),
457
- },
458
- ]);
459
-
460
- // --- VERIFICATION: Check if link already exists (A->B or B->A) ---
461
- const alreadyExists = entitiesData.relations.find(
462
- (rel) =>
463
- (rel.from === selection.fromName && rel.to === selection.toName) ||
464
- (rel.from === selection.toName && rel.to === selection.fromName)
465
- );
466
-
467
- if (alreadyExists) {
468
- logWarning(
469
- `A relationship already exists between ${selection.fromName} and ${selection.toName} (${alreadyExists.type}).`
470
- );
471
-
472
- const { tryAgain } = await actualInquirer.prompt([
473
- {
474
- type: "confirm",
475
- name: "tryAgain",
476
- message: "Do you want to choose different entities?",
477
- default: true,
478
- },
479
- ]);
480
-
481
- if (!tryAgain) break;
482
- continue; // Restart selection
483
- }
484
-
485
- // 2. Select Relationship type only if verification passed
486
- const typeAnswer = await actualInquirer.prompt([
487
- {
488
- type: "list",
489
- name: "relType",
490
- message: "Relationship type:",
491
- choices: [
492
- {
493
- name: `1-1 (One-to-One) : ${selection.fromName} has one ${selection.toName}`,
494
- value: "1-1",
495
- },
496
- {
497
- name: `1-n (One-to-Many) : ${selection.fromName} has many ${selection.toName}s`,
498
- value: "1-n",
499
- },
500
- {
501
- name: `n-1 (Many-to-One) : Many ${selection.fromName}s belong to one ${selection.toName}`,
502
- value: "n-1",
503
- },
504
- {
505
- name: `n-n (Many-to-Many) : Many ${selection.fromName}s linked to many ${selection.toName}s`,
506
- value: "n-n",
507
- },
508
- ],
509
- },
510
- ]);
511
-
512
- const from = entitiesData.entities.find(
513
- (e) => e.name === selection.fromName
514
- );
515
- const to = entitiesData.entities.find(
516
- (e) => e.name === selection.toName
517
- );
518
- const relType = typeAnswer.relType;
519
-
520
- // Register Relationship
521
- entitiesData.relations.push({
522
- from: from.name,
523
- to: to.name,
524
- type: relType,
525
- });
526
-
527
- const fromLow = from.name.toLowerCase();
528
- const toLow = to.name.toLowerCase();
529
-
530
- // --- Add fields logic ---
531
- if (relType === "1-1") {
532
- from.fields.push(
533
- { name: `${toLow}Id`, type: "string" },
534
- { name: toLow, type: to.name }
535
- );
536
- } else if (relType === "1-n") {
537
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
538
- to.fields.push(
539
- { name: `${fromLow}Id`, type: "string" },
540
- { name: fromLow, type: from.name }
541
- );
542
- } else if (relType === "n-1") {
543
- from.fields.push(
544
- { name: `${toLow}Id`, type: "string" },
545
- { name: toLow, type: to.name }
546
- );
547
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
548
- } else if (relType === "n-n") {
549
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
550
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
551
- }
552
-
553
- console.log(
554
- `\n${success("[✓]")} Relationship added: ${from.name} ${relType} ${
555
- to.name
556
- }`
557
- );
558
-
559
- const { addMore } = await actualInquirer.prompt([
560
- {
561
- type: "confirm",
562
- name: "addMore",
563
- message: "Add another relationship?",
564
- default: false,
565
- },
566
- ]);
567
- configuring = addMore;
568
- }
569
- } else {
570
- logWarning(
571
- "At least two entities are required to configure a relationship."
572
- );
573
- }
352
+ await buildRelationsInteractive(entitiesData);
574
353
  }
575
354
 
576
355
  return {
@@ -88,6 +88,7 @@ async function generateDto(
88
88
  const name = f.name;
89
89
  const cleanType = f.type.toLowerCase().replace("[]", "");
90
90
  const isArray = f.type.endsWith("[]");
91
+ const isOptional = optional || f.nullable;
91
92
 
92
93
  const SCALAR_TYPES = [
93
94
  "string",
@@ -104,7 +105,7 @@ async function generateDto(
104
105
  if (!SCALAR_TYPES.includes(cleanType)) return null;
105
106
 
106
107
  let validators = [];
107
- if (optional) validators.push("@IsOptional()");
108
+ if (isOptional) validators.push("@IsOptional()");
108
109
  if (name.toLowerCase().includes("email")) validators.push("@IsEmail()");
109
110
  else if (name.toLowerCase().includes("password"))
110
111
  validators.push(
@@ -141,7 +142,7 @@ async function generateDto(
141
142
 
142
143
  let swaggerDecorator = "";
143
144
  if (useSwagger && !forceNoSwagger) {
144
- const decorator = optional ? "@ApiPropertyOptional" : "@ApiProperty";
145
+ const decorator = isOptional ? "@ApiPropertyOptional" : "@ApiProperty";
145
146
  const options = JSON.stringify(
146
147
  {
147
148
  example: getExampleForField(f),
@@ -154,7 +155,7 @@ async function generateDto(
154
155
  }
155
156
 
156
157
  return `${swaggerDecorator}${validators.join("\n ")}\n ${name}${
157
- optional ? "?" : "!"
158
+ isOptional ? "?" : "!"
158
159
  }: ${formatType(f.type)};`;
159
160
  };
160
161
 
@@ -169,7 +170,7 @@ async function generateDto(
169
170
 
170
171
  return `import {
171
172
  IsString, IsInt, IsBoolean, IsEmail, IsArray,
172
- IsUUID, IsDateString, MinLength, IsOptional
173
+ IsUUID, IsDateString, MinLength, IsOptional, IsNumber
173
174
  } from 'class-validator';
174
175
  ${useSwagger ? "import { ApiProperty } from '@nestjs/swagger';" : ""}
175
176
 
@@ -200,7 +201,7 @@ export class ${entityName}Dto {
200
201
  if (entityName === "User") swaggerImports.push("ApiHideProperty");
201
202
 
202
203
  return `import {
203
- IsOptional, IsString, IsInt, IsBoolean, IsEmail,
204
+ IsOptional, IsString, IsInt, IsNumber, IsBoolean, IsEmail,
204
205
  IsArray, IsUUID, IsDateString, MinLength, IsEnum
205
206
  } from 'class-validator';
206
207
  ${
@@ -3,6 +3,11 @@ const fs = require("fs");
3
3
  const { updateFile, capitalize } = require("../../userInput");
4
4
 
5
5
  async function patchDtoWithRelation(source, targetName, useSwagger, mode) {
6
+ const isDryRun = process.argv.includes("--dry-run");
7
+ if (isDryRun) {
8
+ console.log(`[DRY-RUN] Simulated: patch DTO for ${source} with relation to ${targetName}`);
9
+ return;
10
+ }
6
11
  let dtoPath;
7
12
  if (mode === "full") {
8
13
  dtoPath = `src/${source.toLowerCase()}/application/dtos/${source.toLowerCase()}.dto.ts`;
@@ -8,6 +8,11 @@ async function updateExistingEntityRelation(
8
8
  relationType,
9
9
  mode,
10
10
  ) {
11
+ const isDryRun = process.argv.includes("--dry-run");
12
+ if (isDryRun) {
13
+ console.log(`[DRY-RUN] Simulated: update existing entity relation between ${targetName} and ${newEntityName}`);
14
+ return;
15
+ }
11
16
  const targetLow = targetName.toLowerCase();
12
17
  const newCap = capitalize(newEntityName);
13
18
  const newLow = newEntityName.toLowerCase();
@@ -2,6 +2,11 @@ const { updateFile } = require("../../userInput");
2
2
 
3
3
  // src/utils/generators/infrastructure/mapperUpdater.js
4
4
  async function patchMapperWithRelation(source, targetName, mode = "full") {
5
+ const isDryRun = process.argv.includes("--dry-run");
6
+ if (isDryRun) {
7
+ console.log(`[DRY-RUN] Simulated: patch mapper for ${source} with relation to ${targetName}`);
8
+ return;
9
+ }
5
10
  const targetLow = targetName.toLowerCase();
6
11
 
7
12
  if (mode === "full") {