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
@@ -162,12 +162,39 @@ async function setupTypeORM(inputs) {
162
162
  columnOptions.push("default: Role.USER");
163
163
  }
164
164
 
165
- if (field.optional) {
165
+ if (field.unique || field.name.toLowerCase() === "email") {
166
+ columnOptions.push("unique: true");
167
+ }
168
+
169
+ const isNullable = field.nullable || field.optional;
170
+ if (isNullable) {
166
171
  columnOptions.push("nullable: true");
167
172
  }
168
173
 
169
- if (field.name.toLowerCase() === "email") {
170
- columnOptions.push("unique: true");
174
+ if (field.default !== undefined && field.default !== null && field.default !== "") {
175
+ let defaultVal = field.default;
176
+ if (typeof defaultVal === "string") {
177
+ if (defaultVal === "true") defaultVal = true;
178
+ else if (defaultVal === "false") defaultVal = false;
179
+ else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
180
+ defaultVal = "CURRENT_TIMESTAMP";
181
+ } else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
182
+ defaultVal = Number(defaultVal);
183
+ }
184
+ }
185
+ if (defaultVal === "CURRENT_TIMESTAMP") {
186
+ columnOptions.push(`default: () => 'CURRENT_TIMESTAMP'`);
187
+ } else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
188
+ columnOptions.push(`default: ${defaultVal}`);
189
+ } else if (mapping.type === "enum") {
190
+ if (mapping.tsType && /^[A-Z]/.test(mapping.tsType)) {
191
+ columnOptions.push(`default: ${mapping.tsType}.${defaultVal.toUpperCase()}`);
192
+ } else {
193
+ columnOptions.push(`default: '${defaultVal}'`);
194
+ }
195
+ } else {
196
+ columnOptions.push(`default: '${defaultVal}'`);
197
+ }
171
198
  }
172
199
 
173
200
  // 2. Gestion des Imports d'Enums Custom
@@ -186,7 +213,7 @@ async function setupTypeORM(inputs) {
186
213
  // 3. Construction du champ
187
214
  fieldsContent += `
188
215
  @Column({ ${columnOptions.join(", ")} })
189
- ${field.name}${field.optional ? "?" : ""}: ${mapping.tsType};
216
+ ${field.name}${isNullable ? "?" : ""}: ${mapping.tsType};
190
217
  `;
191
218
  }
192
219
 
@@ -1,8 +1,22 @@
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 { setupDatabase } = require("./setupDatabase");
15
+ const { configureDocker } = require("../configs/configureDocker");
16
+ const { setupBootstrapLogger } = require("./setupLogger");
17
+ const { generateEnvFile, writeEnvFile } = require("../envGenerator");
18
+ const { saveProjectConfig } = require("../file-utils/saveProjectConfig");
19
+ const { printSetupWarnings } = require("../shell");
6
20
 
7
21
  async function createProject(inputs) {
8
22
  if (fs.existsSync(inputs.projectName)) {
@@ -13,34 +27,90 @@ async function createProject(inputs) {
13
27
  );
14
28
 
15
29
  if (confirmation) {
16
- // confirmation is true if the user presses 'y'
17
30
  console.log("Deleting existing project...");
18
31
  fs.rmSync(inputs.projectName, { recursive: true, force: true });
19
32
  } else {
20
33
  console.log("Operation cancelled by user. Exiting.");
21
- return; // Stop function execution if the user cancels
34
+ throw new Error("Operation cancelled by user.");
22
35
  }
23
36
  }
24
37
 
25
38
  logInfo(`Creating NestJS project: ${inputs.projectName}`);
26
39
 
27
- // Remains asynchronous as runCommand likely involves external processes (npm/npx)
28
40
  await runCommand(
29
41
  `npx @nestjs/cli new ${inputs.projectName} --package-manager ${inputs.packageManager}`,
30
42
  "Failed to create NestJS project"
31
43
  );
32
44
 
33
45
  // Changing the current process directory to the project root
34
- process.chdir(inputs.projectName);
46
+ const isDryRun = process.argv.includes("--dry-run");
47
+ if (!isDryRun) {
48
+ process.chdir(inputs.projectName);
49
+ } else {
50
+ console.log(`[DRY-RUN] Simulated: change directory to ${inputs.projectName}`);
51
+ }
35
52
 
36
53
  // Installing dependencies
37
54
  logInfo("Installing dependencies...");
38
55
 
39
- // Remains asynchronous as runCommand likely involves external processes (npm/npx)
40
56
  await runCommand(
41
57
  `${inputs.packageManager} add @nestjs/config class-validator class-transformer dotenv`,
42
58
  "Failed to install dependencies"
43
59
  );
44
60
  }
45
61
 
46
- module.exports = { createProject };
62
+ /**
63
+ * Runs the complete project scaffolding and setup pipeline in sequence.
64
+ * Handles CWD tracking and restores the working directory on failure.
65
+ * @param {object} inputs
66
+ */
67
+ async function runProjectSetupPipeline(inputs) {
68
+ const originalCwd = process.cwd();
69
+ try {
70
+ logInfo("Starting project generation...");
71
+
72
+ await createProject(inputs);
73
+
74
+ if (inputs.mode === "light") {
75
+ await setupLightArchitecture(inputs);
76
+ } else {
77
+ await setupCleanArchitecture(inputs);
78
+ }
79
+
80
+ if (inputs.useAuth) {
81
+ await setupAuth(inputs);
82
+ }
83
+
84
+ if (inputs.useSwagger) {
85
+ await setupSwagger(inputs.swaggerInputs);
86
+ } else {
87
+ setupBootstrapLogger();
88
+ }
89
+
90
+ if (inputs.useDocker) {
91
+ await configureDocker(inputs);
92
+ }
93
+
94
+ await setupDatabase(inputs);
95
+
96
+ const envContent = await generateEnvFile(inputs);
97
+ writeEnvFile(envContent);
98
+
99
+ await saveProjectConfig(inputs);
100
+
101
+ // Show warnings
102
+ printSetupWarnings();
103
+
104
+ } catch (error) {
105
+ logError(`Error during project creation: ${error.message}`);
106
+ if (process.cwd() !== originalCwd) {
107
+ process.chdir(originalCwd);
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ module.exports = {
114
+ createProject,
115
+ runProjectSetupPipeline,
116
+ };
@@ -13,12 +13,15 @@ async function setupAuth(inputs) {
13
13
  const isFull = mode === "full";
14
14
 
15
15
  // 1. INSTALLATION DES DÉPENDANCES
16
+ const pkgManager = inputs.packageManager || "npm";
17
+ const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
18
+
16
19
  await runCommand(
17
- `npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
20
+ `${installCmd} @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
18
21
  "Erreur install deps auth",
19
22
  );
20
23
  await runCommand(
21
- `npm install -D @types/passport-jwt @types/bcrypt @types/uuid`,
24
+ `${installCmd} -D @types/passport-jwt @types/bcrypt @types/uuid`,
22
25
  "Erreur install dev-deps auth",
23
26
  );
24
27
 
@@ -773,9 +776,6 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
773
776
  const request = context.switchToHttp().getRequest();
774
777
  const user = request.user;
775
778
 
776
- console.log('🔍 Required Roles:', requiredRoles);
777
- console.log('👤 User Role:', user?.role);
778
-
779
779
  // Check if the user has one of the required roles
780
780
  return user && user.role && requiredRoles.includes(user.role);
781
781
  }
@@ -805,8 +805,7 @@ import { Session, SessionSchema } from '${schemaRelativePath}';`;
805
805
  }
806
806
 
807
807
  if (inputs.mode == "full") {
808
- dbImports =
809
- +"import { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';";
808
+ dbImports += `\nimport { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';`;
810
809
  }
811
810
 
812
811
  await createFile({
@@ -9,7 +9,8 @@ async function setupDatabase(inputs) {
9
9
 
10
10
  await runCommand(
11
11
  "npm install dotenv",
12
- "Error installing dotenv automatically, please run it manually now"
12
+ "Error installing dotenv automatically, please run it manually now",
13
+ { critical: false },
13
14
  );
14
15
 
15
16
  switch (inputs.selectedDB) {
@@ -1,5 +1,6 @@
1
1
  const { runCommand } = require("../shell");
2
2
  const path = require("path");
3
+ const fs = require("fs");
3
4
  const {
4
5
  createFile,
5
6
  updateFile,
@@ -67,12 +68,33 @@ async function setupMongoose(inputs) {
67
68
  "Mongoose and its dependencies successfully installed!"
68
69
  );
69
70
 
71
+ const isDryRun = process.argv.includes("--dry-run");
72
+ if (isDryRun) {
73
+ console.log("[DRY-RUN] Simulated: write Mongoose configuration, app.module.ts config, and schemas");
74
+ logSuccess(
75
+ "Mongoose configuration complete. Schemas are generated in src/schemas!"
76
+ );
77
+ return;
78
+ }
79
+
70
80
  // --- Base Configuration (app.module.ts and .env) --- // Generating the .env file
71
- const envContent = `
72
- MONGO_URI=${inputs.dbConfig.MONGO_URI}
73
- MONGO_DB=${inputs.dbConfig.MONGO_DB}
74
- `.trim();
75
- await createFile({ path: ".env", contente: envContent });
81
+ const envPath = ".env";
82
+ const mongoUriLine = `MONGO_URI=${inputs.dbConfig.MONGO_URI}`;
83
+ const mongoDbLine = `MONGO_DB=${inputs.dbConfig.MONGO_DB}`;
84
+
85
+ if (!fs.existsSync(envPath)) {
86
+ const envContent = `${mongoUriLine}\n${mongoDbLine}\n`;
87
+ await createFile({ path: envPath, contente: envContent });
88
+ } else {
89
+ let content = fs.readFileSync(envPath, "utf8");
90
+ if (!content.includes("MONGO_URI=")) {
91
+ content += `\n${mongoUriLine}`;
92
+ }
93
+ if (!content.includes("MONGO_DB=")) {
94
+ content += `\n${mongoDbLine}`;
95
+ }
96
+ fs.writeFileSync(envPath, content, "utf8");
97
+ }
76
98
 
77
99
  const appModulePath = path.join("src", "app.module.ts");
78
100
  const mongooseImport = `import { MongooseModule } from '@nestjs/mongoose';`;
@@ -81,21 +103,30 @@ async function setupMongoose(inputs) {
81
103
  dbName: process.env.MONGO_DB,
82
104
  }),`;
83
105
 
84
- // 1. Adding MongooseModule import
85
- await updateFile({
86
- path: appModulePath,
87
- pattern: /import {[\s\S]*?} from '@nestjs\/config';/,
88
- replacement: (match) => `${match}\n${mongooseImport}`,
89
- });
106
+ if (fs.existsSync(appModulePath)) {
107
+ let appModuleContent = fs.readFileSync(appModulePath, "utf8");
108
+
109
+ // 1. Adding MongooseModule import
110
+ if (!appModuleContent.includes("@nestjs/mongoose")) {
111
+ await updateFile({
112
+ path: appModulePath,
113
+ pattern: /import {[\s\S]*?} from '@nestjs\/config';/,
114
+ replacement: (match) => `${match}\n${mongooseImport}`,
115
+ });
116
+ appModuleContent = fs.readFileSync(appModulePath, "utf8");
117
+ }
90
118
 
91
- // 2. Adding MongooseModule.forRoot() configuration
92
- const importsPattern =
93
- /imports:\s*\[[\s\S]*?ConfigModule\.forRoot\([\s\S]*?\),/;
94
- await updateFile({
95
- path: appModulePath,
96
- pattern: importsPattern,
97
- replacement: (match) => `${match}${mongooseForRoot}`,
98
- });
119
+ // 2. Adding MongooseModule.forRoot() configuration
120
+ if (!appModuleContent.includes("MongooseModule.forRoot")) {
121
+ const importsPattern =
122
+ /imports:\s*\[[\s\S]*?ConfigModule\.forRoot\([\s\S]*?\),/;
123
+ await updateFile({
124
+ path: appModulePath,
125
+ pattern: importsPattern,
126
+ replacement: (match) => `${match}${mongooseForRoot}`,
127
+ });
128
+ }
129
+ }
99
130
 
100
131
  // --- Generating Mongoose Entities (Schemas) ---
101
132
  logInfo("📁 Generating Mongoose schemas (src/schemas)...");
@@ -144,10 +175,34 @@ role: Role;
144
175
  const mongooseType = mapTypeToMongoose(field.type);
145
176
  const tsType = field.type;
146
177
 
147
- const isRequired =
148
- field.name.toLowerCase() !== "isactive" &&
149
- field.name.toLowerCase() !== "password";
150
- const requiredOption = isRequired ? ", required: true" : "";
178
+ const isRequired = field.nullable !== undefined ? !field.nullable : (field.name.toLowerCase() !== "isactive" && field.name.toLowerCase() !== "password");
179
+ let propOpts = [`type: ${mongooseType}`];
180
+ if (isRequired) {
181
+ propOpts.push("required: true");
182
+ }
183
+ if (field.unique) {
184
+ propOpts.push("unique: true");
185
+ }
186
+ if (field.default !== undefined && field.default !== null && field.default !== "") {
187
+ let defaultVal = field.default;
188
+ if (typeof defaultVal === "string") {
189
+ if (defaultVal === "true") defaultVal = true;
190
+ else if (defaultVal === "false") defaultVal = false;
191
+ else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
192
+ defaultVal = "Date.now";
193
+ } else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
194
+ defaultVal = Number(defaultVal);
195
+ }
196
+ }
197
+ if (defaultVal === "Date.now") {
198
+ propOpts.push("default: Date.now");
199
+ } else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
200
+ propOpts.push(`default: ${defaultVal}`);
201
+ } else {
202
+ propOpts.push(`default: '${defaultVal}'`);
203
+ }
204
+ }
205
+ const propOptionsStr = propOpts.join(", ");
151
206
 
152
207
  // Import des enums partagés si nécessaire
153
208
  if (
@@ -159,8 +214,8 @@ role: Role;
159
214
  }
160
215
 
161
216
  fieldsContent += `
162
- @Prop({ type: ${mongooseType}${requiredOption} })
163
- ${field.name}: ${tsType};
217
+ @Prop({ ${propOptionsStr} })
218
+ ${field.name}${field.nullable ? "?" : ""}: ${tsType};
164
219
  `;
165
220
  }
166
221
 
@@ -186,11 +241,22 @@ role: Role;
186
241
  ", "
187
242
  )}]),`;
188
243
 
189
- await updateFile({
190
- path: appModulePath,
191
- pattern: new RegExp(mongooseForRoot.trim().replace(/[\n\r]/g, "\\s*"), "g"),
192
- replacement: (match) => `${match}\n\t${forFeatureBlock}`,
193
- });
244
+ if (fs.existsSync(appModulePath)) {
245
+ const appModuleContent = fs.readFileSync(appModulePath, "utf8");
246
+ if (appModuleContent.includes("MongooseModule.forFeature")) {
247
+ await updateFile({
248
+ path: appModulePath,
249
+ pattern: /MongooseModule\.forFeature\(\[[\s\S]*?\]\),/,
250
+ replacement: forFeatureBlock,
251
+ });
252
+ } else {
253
+ await updateFile({
254
+ path: appModulePath,
255
+ pattern: new RegExp(mongooseForRoot.trim().replace(/[\n\r]/g, "\\s*"), "g"),
256
+ replacement: (match) => `${match}\n\t${forFeatureBlock}`,
257
+ });
258
+ }
259
+ }
194
260
 
195
261
  if (inputs.isDemo) {
196
262
  // The generateSampleData function must be adapted for 'new Date()' usage and Mongoose types