nestcraftx 0.4.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.
Files changed (54) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/CHANGELOG.fr.md +74 -0
  3. package/CHANGELOG.md +74 -0
  4. package/PROGRESS.md +59 -57
  5. package/README.fr.md +60 -69
  6. package/bin/nestcraft.js +8 -0
  7. package/commands/demo.js +10 -0
  8. package/commands/generate.js +551 -2
  9. package/commands/generateConf.js +4 -4
  10. package/commands/help.js +11 -0
  11. package/commands/info.js +2 -3
  12. package/commands/list.js +93 -0
  13. package/commands/new.js +26 -9
  14. package/jest.config.js +9 -0
  15. package/package.json +7 -2
  16. package/readme.md +59 -68
  17. package/tests/e2e/generator.spec.js +71 -0
  18. package/tests/unit/appModuleUpdater.spec.js +111 -0
  19. package/tests/unit/cliParser.spec.js +74 -0
  20. package/tests/unit/controllerGenerator.spec.js +145 -0
  21. package/tests/unit/dtoGenerator.spec.js +87 -0
  22. package/tests/unit/entityGenerator.spec.js +65 -0
  23. package/tests/unit/generateCommand.spec.js +100 -0
  24. package/tests/unit/health.spec.js +69 -0
  25. package/tests/unit/listCommand.spec.js +91 -0
  26. package/tests/unit/middlewareGenerator.spec.js +64 -0
  27. package/tests/unit/newCommand.spec.js +88 -0
  28. package/tests/unit/relationCommand.spec.js +202 -0
  29. package/tests/unit/throttler.spec.js +117 -0
  30. package/utils/configs/setupCleanArchitecture.js +1 -1
  31. package/utils/configs/setupLightArchitecture.js +98 -26
  32. package/utils/envGenerator.js +3 -1
  33. package/utils/file-utils/saveProjectConfig.js +3 -0
  34. package/utils/fullModeInput.js +4 -0
  35. package/utils/generators/application/dtoGenerator.js +20 -3
  36. package/utils/generators/cleanModuleGenerator.js +40 -11
  37. package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
  38. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +78 -1
  39. package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
  40. package/utils/generators/lightModuleGenerator.js +14 -0
  41. package/utils/generators/presentation/controllerGenerator.js +70 -19
  42. package/utils/interactive/askRelationCommand.js +98 -0
  43. package/utils/interactive/entityBuilder.js +11 -0
  44. package/utils/lightModeInput.js +14 -0
  45. package/utils/setups/orms/typeOrmSetup.js +11 -4
  46. package/utils/setups/projectSetup.js +10 -2
  47. package/utils/setups/setupAuth.js +334 -18
  48. package/utils/setups/setupDatabase.js +4 -1
  49. package/utils/setups/setupHealth.js +74 -0
  50. package/utils/setups/setupMongoose.js +4 -1
  51. package/utils/setups/setupPrisma.js +110 -1
  52. package/utils/setups/setupSwagger.js +4 -1
  53. package/utils/setups/setupThrottler.js +92 -0
  54. package/utils/shell.js +21 -4
@@ -281,6 +281,32 @@ async function setupPrisma(inputs) {
281
281
  }
282
282
  }
283
283
 
284
+ if (inputs.useAuth) {
285
+ schemaContent += `
286
+ /**
287
+ * PasswordReset Model
288
+ */
289
+ model PasswordReset {
290
+ id String @id @default(uuid())
291
+ email String
292
+ token String @unique
293
+ expiresAt DateTime
294
+ createdAt DateTime @default(now())
295
+ }
296
+
297
+ /**
298
+ * Otp Model
299
+ */
300
+ model Otp {
301
+ id String @id @default(uuid())
302
+ email String
303
+ otp String
304
+ expiresAt DateTime
305
+ createdAt DateTime @default(now())
306
+ }
307
+ `;
308
+ }
309
+
284
310
  logInfo("Updating schema.prisma");
285
311
  const baseSchema = `
286
312
  generator client {
@@ -782,5 +808,88 @@ model ${capitalizedName} {
782
808
  );
783
809
  }
784
810
 
785
- module.exports = { setupPrisma, updatePrismaSchema };
811
+ /**
812
+ * Ajoute une relation entre deux modèles Prisma EXISTANTS.
813
+ * Contrairement à updatePrismaSchema, ne crée PAS un nouveau modèle —
814
+ * patche uniquement les champs FK/inverse dans les deux modèles déjà présents.
815
+ *
816
+ * @param {string} source - Nom de l'entité source (ex: "post")
817
+ * @param {string} target - Nom de l'entité cible (ex: "user")
818
+ * @param {string} relationType - "n-1" | "1-n" | "1-1" | "n-n"
819
+ */
820
+ async function addPrismaRelation(source, target, relationType) {
821
+ const schemaPath = "prisma/schema.prisma";
822
+ const sourceCap = capitalize(source);
823
+ const targetCap = capitalize(target);
824
+ const sourceLow = source.toLowerCase();
825
+ const targetLow = target.toLowerCase();
826
+
827
+ // 1️⃣ Champs à injecter dans le modèle SOURCE
828
+ let sourceFields = "";
829
+ switch (relationType) {
830
+ case "n-1":
831
+ sourceFields = ` ${targetLow} ${targetCap} @relation(fields: [${targetLow}Id], references: [id])\n ${targetLow}Id String`;
832
+ break;
833
+ case "1-n":
834
+ sourceFields = ` ${targetLow}s ${targetCap}[]`;
835
+ break;
836
+ case "1-1":
837
+ sourceFields = ` ${targetLow} ${targetCap}? @relation(fields: [${targetLow}Id], references: [id])\n ${targetLow}Id String? @unique`;
838
+ break;
839
+ case "n-n":
840
+ sourceFields = ` ${targetLow}s ${targetCap}[]`;
841
+ break;
842
+ default:
843
+ throw new Error(`❌ Type de relation inconnu : ${relationType}`);
844
+ }
845
+
846
+ // 2️⃣ Champs inverses à injecter dans le modèle TARGET
847
+ let targetFields = "";
848
+ switch (relationType) {
849
+ case "n-1":
850
+ targetFields = ` ${sourceLow}s ${sourceCap}[]`;
851
+ break;
852
+ case "1-n":
853
+ targetFields = ` ${sourceLow} ${sourceCap} @relation(fields: [${sourceLow}Id], references: [id])\n ${sourceLow}Id String`;
854
+ break;
855
+ case "1-1":
856
+ targetFields = ` ${sourceLow} ${sourceCap}?`;
857
+ break;
858
+ case "n-n":
859
+ targetFields = ` ${sourceLow}s ${sourceCap}[]`;
860
+ break;
861
+ }
862
+
863
+ // 3️⃣ Patch du modèle SOURCE
864
+ await updateFile({
865
+ path: schemaPath,
866
+ pattern: new RegExp(`model ${sourceCap} \\{([\\s\\S]*?)\\}`, "g"),
867
+ replacement: `model ${sourceCap} {$1\n${sourceFields}\n}`,
868
+ });
869
+
870
+ // 4️⃣ Patch du modèle TARGET (inverse)
871
+ await updateFile({
872
+ path: schemaPath,
873
+ pattern: new RegExp(`model ${targetCap} \\{([\\s\\S]*?)\\}`, "g"),
874
+ replacement: `model ${targetCap} {$1\n${targetFields}\n}`,
875
+ });
876
+
877
+ // 5️⃣ Synchronisation Prisma
878
+ await runCommand(
879
+ "npx prisma format",
880
+ "❌ Failed to format prisma schema — run 'npx prisma format' manually",
881
+ { critical: false },
882
+ );
883
+ await runCommand(
884
+ "npx prisma generate",
885
+ "❌ Failed to generate prisma client — run 'npx prisma generate' manually",
886
+ { critical: false },
887
+ );
888
+ await runCommand(
889
+ `npx prisma migrate dev --name add_relation_${sourceLow}_${targetLow}`,
890
+ `❌ Failed to run prisma migration — run 'npx prisma migrate dev' manually`,
891
+ { critical: false },
892
+ );
893
+ }
786
894
 
895
+ module.exports = { setupPrisma, updatePrismaSchema, addPrismaRelation };
@@ -6,9 +6,12 @@ const { logSuccess } = require("../loggers/logSuccess");
6
6
  async function setupSwagger(inputs) {
7
7
  logInfo("Installation et Configuration de Swagger...");
8
8
 
9
+ const pkgManager = inputs.packageManager || "npm";
10
+ const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
11
+
9
12
  // installation de swagger
10
13
  await runCommand(
11
- "npm install @nestjs/swagger swagger-ui-express",
14
+ `${installCmd} @nestjs/swagger@^7.3.0 swagger-ui-express@^5.0.0`,
12
15
  "Échec de l'installation de Swagger",
13
16
  { spinner: "Installing Swagger..." }
14
17
  );
@@ -0,0 +1,92 @@
1
+ // utils/setups/setupThrottler.js
2
+ //
3
+ // Script d'installation et de configuration de @nestjs/throttler.
4
+ // Injecte le module et le guard dans app.module.ts de manière idempotente.
5
+
6
+ const fs = require("fs");
7
+ const { logInfo } = require("../loggers/logInfo");
8
+ const { logSuccess } = require("../loggers/logSuccess");
9
+ const { runCommand } = require("../shell");
10
+ const { updateFile } = require("../userInput");
11
+
12
+ /**
13
+ * Configure le rate limiting global dans le projet.
14
+ *
15
+ * @param {object} inputs - Configuration du projet
16
+ */
17
+ async function setupThrottler(inputs) {
18
+ logInfo("Installation et Configuration du Rate Limiting (Throttler)...");
19
+
20
+ const pkgManager = inputs.packageManager || "npm";
21
+ const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
22
+
23
+ // Installation de @nestjs/throttler
24
+ await runCommand(
25
+ `${installCmd} @nestjs/throttler@^6.0.0`,
26
+ "Échec de l'installation de @nestjs/throttler",
27
+ { spinner: "Installing @nestjs/throttler..." }
28
+ );
29
+
30
+ const isDryRun = process.argv.includes("--dry-run");
31
+ if (isDryRun) {
32
+ console.log("[DRY-RUN] Simulated: write Throttler configuration to src/app.module.ts");
33
+ logSuccess(" Throttler configuré avec succès !");
34
+ return;
35
+ }
36
+
37
+ const appModulePath = "src/app.module.ts";
38
+ if (!fs.existsSync(appModulePath)) {
39
+ console.warn(`⚠️ Fichier ${appModulePath} introuvable. Skip configuration.`);
40
+ return;
41
+ }
42
+
43
+ let content = fs.readFileSync(appModulePath, "utf8");
44
+
45
+ if (!content.includes("ThrottlerModule")) {
46
+ // 1. Ajouter les imports
47
+ content = content.replace(
48
+ "import { Module } from '@nestjs/common';",
49
+ "import { Module } from '@nestjs/common';\nimport { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';\nimport { APP_GUARD } from '@nestjs/core';"
50
+ );
51
+
52
+ // 2. Ajouter ThrottlerModule.forRoot dans imports: [
53
+ content = content.replace(
54
+ "imports: [",
55
+ `imports: [
56
+ ThrottlerModule.forRoot([{
57
+ ttl: 60000,
58
+ limit: 10,
59
+ }]),`
60
+ );
61
+
62
+ // 3. Ajouter ThrottlerGuard dans providers: [
63
+ if (content.includes("providers: [")) {
64
+ content = content.replace(
65
+ "providers: [",
66
+ `providers: [
67
+ {
68
+ provide: APP_GUARD,
69
+ useClass: ThrottlerGuard,
70
+ },`
71
+ );
72
+ } else {
73
+ content = content.replace(
74
+ "@Module({",
75
+ `@Module({
76
+ providers: [
77
+ {
78
+ provide: APP_GUARD,
79
+ useClass: ThrottlerGuard,
80
+ },
81
+ ],`
82
+ );
83
+ }
84
+
85
+ fs.writeFileSync(appModulePath, content, "utf8");
86
+ logSuccess(" Throttler configuré avec succès dans app.module.ts !");
87
+ } else {
88
+ logInfo(" Throttler déjà présent dans app.module.ts. Skip.");
89
+ }
90
+ }
91
+
92
+ module.exports = { setupThrottler };
package/utils/shell.js CHANGED
@@ -44,21 +44,38 @@ async function runCommand(command, errorMessage, options = {}) {
44
44
  command.includes(" new ") ||
45
45
  command.includes("prisma migrate")
46
46
  ) {
47
- timeoutVal = 300000; // 5 minutes for heavy setup commands
47
+ timeoutVal = 600000; // 10 minutes for heavy setup commands
48
48
  }
49
49
  if (customTimeout !== undefined) {
50
50
  timeoutVal = customTimeout;
51
51
  }
52
52
 
53
+ // Intercept npm installation commands and append --legacy-peer-deps, --no-audit, --no-fund if missing
54
+ let finalCommand = command;
55
+ if (
56
+ command.startsWith("npm ") &&
57
+ (command.includes("install") || command.includes("add") || command.includes(" i "))
58
+ ) {
59
+ if (!command.includes("--legacy-peer-deps")) {
60
+ finalCommand = `${finalCommand} --legacy-peer-deps`;
61
+ }
62
+ if (!command.includes("--no-audit")) {
63
+ finalCommand = `${finalCommand} --no-audit`;
64
+ }
65
+ if (!command.includes("--no-fund")) {
66
+ finalCommand = `${finalCommand} --no-fund`;
67
+ }
68
+ }
69
+
53
70
  // Dry-Run Simulation Check
54
71
  const isDryRun = process.argv.includes("--dry-run");
55
72
  if (isDryRun) {
56
73
  if (spinnerText) {
57
74
  const spin = spinner(spinnerText);
58
75
  spin.start();
59
- spin.succeed(`[DRY-RUN] Simulated: ${command}`);
76
+ spin.succeed(`[DRY-RUN] Simulated: ${finalCommand}`);
60
77
  } else {
61
- console.log(`[DRY-RUN] Simulated: ${command}`);
78
+ console.log(`[DRY-RUN] Simulated: ${finalCommand}`);
62
79
  }
63
80
  return true;
64
81
  }
@@ -67,7 +84,7 @@ async function runCommand(command, errorMessage, options = {}) {
67
84
 
68
85
  try {
69
86
  if (spin) spin.start();
70
- execSync(command, {
87
+ execSync(finalCommand, {
71
88
  stdio: spinnerText ? "pipe" : "inherit",
72
89
  timeout: timeoutVal,
73
90
  });