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.
- package/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.fr.md +74 -0
- package/CHANGELOG.md +74 -0
- package/PROGRESS.md +59 -57
- package/README.fr.md +60 -69
- package/bin/nestcraft.js +8 -0
- package/commands/demo.js +10 -0
- 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 +26 -9
- 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/configs/setupCleanArchitecture.js +1 -1
- package/utils/configs/setupLightArchitecture.js +98 -26
- package/utils/envGenerator.js +3 -1
- package/utils/file-utils/saveProjectConfig.js +3 -0
- package/utils/fullModeInput.js +4 -0
- package/utils/generators/application/dtoGenerator.js +20 -3
- package/utils/generators/cleanModuleGenerator.js +40 -11
- package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +78 -1
- 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/interactive/askRelationCommand.js +98 -0
- package/utils/interactive/entityBuilder.js +11 -0
- package/utils/lightModeInput.js +14 -0
- package/utils/setups/orms/typeOrmSetup.js +11 -4
- package/utils/setups/projectSetup.js +10 -2
- package/utils/setups/setupAuth.js +334 -18
- package/utils/setups/setupDatabase.js +4 -1
- package/utils/setups/setupHealth.js +74 -0
- package/utils/setups/setupMongoose.js +4 -1
- package/utils/setups/setupPrisma.js +110 -1
- package/utils/setups/setupSwagger.js +4 -1
- package/utils/setups/setupThrottler.js +92 -0
- 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
|
-
|
|
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
|
-
|
|
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 =
|
|
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: ${
|
|
76
|
+
spin.succeed(`[DRY-RUN] Simulated: ${finalCommand}`);
|
|
60
77
|
} else {
|
|
61
|
-
console.log(`[DRY-RUN] Simulated: ${
|
|
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(
|
|
87
|
+
execSync(finalCommand, {
|
|
71
88
|
stdio: spinnerText ? "pipe" : "inherit",
|
|
72
89
|
timeout: timeoutVal,
|
|
73
90
|
});
|