nestcraftx 0.2.6 → 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.
Files changed (42) hide show
  1. package/AUDIT_ROADMAP.md +798 -0
  2. package/CHANGELOG.fr.md +22 -0
  3. package/CHANGELOG.md +22 -0
  4. package/PROGRESS.md +259 -0
  5. package/commands/demo.js +2 -42
  6. package/commands/generate.js +5 -4
  7. package/commands/help.js +29 -2
  8. package/commands/new.js +6 -49
  9. package/package.json +1 -1
  10. package/utils/app-module.updater.js +102 -0
  11. package/utils/cliParser.js +0 -45
  12. package/utils/configs/setupCleanArchitecture.js +2 -2
  13. package/utils/envGenerator.js +6 -0
  14. package/utils/file-system.js +90 -0
  15. package/utils/file-utils/packageJsonUtils.js +6 -0
  16. package/utils/file-utils/saveProjectConfig.js +6 -0
  17. package/utils/fullModeInput.js +8 -229
  18. package/utils/generators/application/dtoGenerator.js +252 -0
  19. package/utils/generators/application/dtoUpdater.js +5 -0
  20. package/utils/generators/database/setupDatabase.js +32 -14
  21. package/utils/generators/domain/entityGenerator.js +126 -0
  22. package/utils/generators/domain/entityUpdater.js +5 -0
  23. package/utils/generators/infrastructure/mapperGenerator.js +114 -0
  24. package/utils/generators/infrastructure/mapperUpdater.js +5 -0
  25. package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
  26. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +229 -0
  27. package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
  28. package/utils/generators/presentation/controllerGenerator.js +142 -0
  29. package/utils/helpers.js +115 -0
  30. package/utils/interactive/askEntityInputs.js +5 -68
  31. package/utils/interactive/entityBuilder.js +400 -0
  32. package/utils/lightModeInput.js +13 -246
  33. package/utils/setups/orms/typeOrmSetup.js +31 -4
  34. package/utils/setups/projectSetup.js +78 -8
  35. package/utils/setups/setupAuth.js +6 -7
  36. package/utils/setups/setupDatabase.js +2 -1
  37. package/utils/setups/setupMongoose.js +96 -30
  38. package/utils/setups/setupPrisma.js +118 -134
  39. package/utils/setups/setupSwagger.js +12 -8
  40. package/utils/shell.js +125 -10
  41. package/utils/userInput.js +37 -101
  42. package/utils/utils.js +29 -2164
@@ -1,48 +1,3 @@
1
- /* function parseCliArgs(args) {
2
- const parsed = {
3
- command: null,
4
- projectName: null,
5
- flags: {},
6
- positional: [],
7
- errors: []
8
- };
9
-
10
- for (let i = 2; i < args.length; i++) {
11
- const arg = args[i];
12
-
13
- if (i === 2 && !arg.startsWith('--')) {
14
- parsed.command = arg;
15
- continue;
16
- }
17
-
18
- if (i === 3 && !arg.startsWith('--') && parsed.command === 'new') {
19
- if (!isValidProjectName(arg)) {
20
- parsed.errors.push(`Nom de projet invalide: "${arg}". Utilisez uniquement des lettres, chiffres, tirets et underscores.`);
21
- }
22
- parsed.projectName = arg;
23
- continue;
24
- }
25
-
26
- if (arg.startsWith('--')) {
27
- const [key, value] = parseFlag(arg);
28
- const nextArg = args[i + 1];
29
-
30
- if (value !== null) {
31
- parsed.flags[key] = value;
32
- } else if (nextArg && !nextArg.startsWith('--')) {
33
- parsed.flags[key] = nextArg;
34
- i++;
35
- } else {
36
- parsed.flags[key] = true;
37
- }
38
- } else {
39
- parsed.positional.push(arg);
40
- }
41
- }
42
-
43
- validateFlags(parsed);
44
- return parsed;
45
- } */
46
1
 
47
2
  function parseCliArgs(args) {
48
3
  const parsed = {
@@ -128,7 +128,7 @@ async function setupCleanArchitecture(inputs) {
128
128
 
129
129
  // 4. Use Cases
130
130
  const useCases = ["Create", "GetById", "GetAll", "Update", "Delete"];
131
- useCases.forEach(async (useCase) => {
131
+ for (const useCase of useCases) {
132
132
  let content = "";
133
133
  const entityName = capitalize(entity.name);
134
134
  const entityNameLower = decapitalize(entity.name);
@@ -302,7 +302,7 @@ export class Delete${entityName}UseCase {
302
302
  }.use-case.ts`,
303
303
  contente: content.trim(),
304
304
  });
305
- });
305
+ }
306
306
 
307
307
  // 5. DTOs
308
308
  const DtoFileContent = await generateDto(entity, useSwagger);
@@ -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");
@@ -0,0 +1,90 @@
1
+ /**
2
+ * utils/file-system.js
3
+ *
4
+ * Fonctions d'accès au système de fichiers utilisées par tous les générateurs.
5
+ * Extraites de userInput.js pour centraliser les opérations I/O.
6
+ *
7
+ * Les fichiers qui importent depuis userInput.js continuent de fonctionner
8
+ * sans modification grâce aux ré-exports dans userInput.js.
9
+ */
10
+
11
+ const fs = require("fs");
12
+
13
+ /**
14
+ * Crée un répertoire de manière récursive s'il n'existe pas déjà.
15
+ * @param {string} directoryPath - Le chemin du répertoire à créer.
16
+ */
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
+ }
23
+ try {
24
+ if (!fs.existsSync(directoryPath)) {
25
+ fs.mkdirSync(directoryPath, { recursive: true });
26
+ }
27
+ } catch (error) {
28
+ console.error(
29
+ `Erreur lors de la création du dossier ${directoryPath}:`,
30
+ error,
31
+ );
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Crée un fichier avec le contenu fourni.
37
+ * Si le fichier existe déjà :
38
+ * - Si `fileData.overwrite === false` : skip silencieusement
39
+ * - Sinon : écrase le fichier existant
40
+ *
41
+ * @param {{ path: string, contente: string, overwrite?: boolean }} fileData
42
+ */
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
+ }
49
+ try {
50
+ if (!fs.existsSync(fileData.path)) {
51
+ fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
52
+ } else {
53
+ if (fileData.overwrite === false) {
54
+ console.log(`File already exists, skipping: ${fileData.path}`);
55
+ return;
56
+ }
57
+ console.log(`Existing file : ${fileData.path}`);
58
+ fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
59
+ }
60
+ } catch (error) {
61
+ console.error(`Erreur creating file ${fileData.path}:`, error);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Met à jour un fichier existant en remplaçant un pattern par un remplacement.
67
+ * Utilise `String.prototype.replace` : supporte les chaînes et les RegExp.
68
+ *
69
+ * @param {{ path: string, pattern: string|RegExp, replacement: string }} options
70
+ */
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
+ }
77
+ try {
78
+ let mainTs = fs.readFileSync(path, "utf8");
79
+ const updatedContent = mainTs.replace(pattern, replacement);
80
+ fs.writeFileSync(path, updatedContent, "utf-8");
81
+ } catch (error) {
82
+ console.error(` Error updating file ${path}:`, error);
83
+ }
84
+ }
85
+
86
+ module.exports = {
87
+ createDirectory,
88
+ createFile,
89
+ updateFile,
90
+ };
@@ -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 {