nestcraftx 0.2.5 → 0.3.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 (56) hide show
  1. package/AUDIT_ROADMAP.md +798 -0
  2. package/CHANGELOG.fr.md +22 -0
  3. package/CHANGELOG.md +22 -0
  4. package/CLI_USAGE.fr.md +331 -331
  5. package/CLI_USAGE.md +364 -364
  6. package/LICENSE +21 -21
  7. package/PROGRESS.md +258 -0
  8. package/bin/nestcraft.js +84 -64
  9. package/commands/demo.js +337 -330
  10. package/commands/generate.js +94 -0
  11. package/commands/generateConf.js +91 -0
  12. package/commands/help.js +29 -2
  13. package/commands/info.js +48 -48
  14. package/commands/new.js +340 -335
  15. package/commands/start.js +19 -19
  16. package/commands/test.js +7 -7
  17. package/package.json +1 -1
  18. package/utils/app-module.updater.js +96 -0
  19. package/utils/cliParser.js +88 -76
  20. package/utils/colors.js +62 -62
  21. package/utils/configs/configureDocker.js +120 -120
  22. package/utils/configs/setupCleanArchitecture.js +17 -15
  23. package/utils/configs/setupLightArchitecture.js +15 -9
  24. package/utils/file-system.js +75 -0
  25. package/utils/file-utils/saveProjectConfig.js +36 -0
  26. package/utils/fullModeInput.js +607 -607
  27. package/utils/generators/application/dtoGenerator.js +251 -0
  28. package/utils/generators/application/dtoUpdater.js +54 -0
  29. package/utils/generators/cleanModuleGenerator.js +475 -0
  30. package/utils/generators/database/setupDatabase.js +49 -0
  31. package/utils/generators/domain/entityGenerator.js +126 -0
  32. package/utils/generators/domain/entityUpdater.js +78 -0
  33. package/utils/generators/infrastructure/mapperGenerator.js +114 -0
  34. package/utils/generators/infrastructure/mapperUpdater.js +65 -0
  35. package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
  36. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +196 -0
  37. package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
  38. package/utils/generators/lightModuleGenerator.js +131 -0
  39. package/utils/generators/presentation/controllerGenerator.js +142 -0
  40. package/utils/generators/relation/relation.engine.js +64 -0
  41. package/utils/helpers.js +109 -0
  42. package/utils/interactive/askEntityInputs.js +165 -0
  43. package/utils/loggers/logError.js +7 -7
  44. package/utils/loggers/logInfo.js +7 -7
  45. package/utils/loggers/logSuccess.js +7 -7
  46. package/utils/loggers/logWarning.js +7 -7
  47. package/utils/setups/orms/typeOrmSetup.js +630 -630
  48. package/utils/setups/setupAuth.js +33 -20
  49. package/utils/setups/setupDatabase.js +76 -75
  50. package/utils/setups/setupMongoose.js +22 -5
  51. package/utils/setups/setupPrisma.js +748 -630
  52. package/utils/shell.js +112 -32
  53. package/utils/spinner.js +57 -57
  54. package/utils/systemCheck.js +124 -124
  55. package/utils/userInput.js +357 -421
  56. package/utils/utils.js +29 -2164
package/commands/start.js CHANGED
@@ -1,19 +1,19 @@
1
- const { execSync } = require("child_process");
2
- const path = require("path");
3
-
4
- // 🔐 Forcer UTF-8 uniquement sur Windows
5
- if (process.platform === "win32") {
6
- try {
7
- execSync("chcp 65001", { stdio: "ignore" }); // Changer l'encodage de la console
8
- } catch (err) {
9
- const message = `⚠️ Impossible de forcer UTF-8 dans le terminal Windows: ${err}`;
10
- console.warn(message);
11
- }
12
- }
13
-
14
- module.exports = function () {
15
- console.log("🚀 Lancement de la génération NestJS...");
16
-
17
- const setupPath = path.join(__dirname, "..", "setup.js");
18
- require(setupPath);
19
- };
1
+ const { execSync } = require("child_process");
2
+ const path = require("path");
3
+
4
+ // 🔐 Forcer UTF-8 uniquement sur Windows
5
+ if (process.platform === "win32") {
6
+ try {
7
+ execSync("chcp 65001", { stdio: "ignore" }); // Changer l'encodage de la console
8
+ } catch (err) {
9
+ const message = `⚠️ Impossible de forcer UTF-8 dans le terminal Windows: ${err}`;
10
+ console.warn(message);
11
+ }
12
+ }
13
+
14
+ module.exports = function () {
15
+ console.log("🚀 Lancement de la génération NestJS...");
16
+
17
+ const setupPath = path.join(__dirname, "..", "setup.js");
18
+ require(setupPath);
19
+ };
package/commands/test.js CHANGED
@@ -1,7 +1,7 @@
1
- const { displaySystemCheck } = require('../utils/systemCheck');
2
-
3
- async function testCommand() {
4
- displaySystemCheck();
5
- }
6
-
7
- module.exports = testCommand;
1
+ const { displaySystemCheck } = require('../utils/systemCheck');
2
+
3
+ async function testCommand() {
4
+ displaySystemCheck();
5
+ }
6
+
7
+ module.exports = testCommand;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestcraftx",
3
- "version": "0.2.5",
3
+ "version": "0.3.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": {
@@ -0,0 +1,96 @@
1
+ /**
2
+ * utils/app-module.updater.js
3
+ *
4
+ * Gestion de la mise à jour sécurisée du fichier app.module.ts.
5
+ * Extrait de userInput.js pour isoler cette responsabilité spécifique.
6
+ *
7
+ * Les fichiers qui importent `safeUpdateAppModule` depuis userInput.js
8
+ * continuent de fonctionner sans modification grâce aux ré-exports.
9
+ */
10
+
11
+ const fs = require("fs");
12
+ const { updateFile } = require("./file-system");
13
+ const { logInfo } = require("./loggers/logInfo");
14
+ const { capitalize } = require("./helpers");
15
+
16
+ /**
17
+ * Ajoute un module NestJS dans le fichier `src/app.module.ts` de manière idempotente.
18
+ *
19
+ * - Ajoute la ligne `import { XModule } from 'src/x/x.module'` si absente
20
+ * - Ajoute `XModule` dans le tableau `imports: []` du décorateur @Module si absent
21
+ * - Utilise un parsing par comptage de crochets pour gérer les imports imbriqués
22
+ * (ex: ConfigModule.forRoot({ ... }))
23
+ *
24
+ * @param {string} entity - Le nom de l'entité en minuscule (ex: "user", "post")
25
+ */
26
+ async function safeUpdateAppModule(entity) {
27
+ const filePath = "src/app.module.ts";
28
+ const moduleName = `${capitalize(entity)}Module`;
29
+ const importLine = `import { ${moduleName} } from 'src/${entity}/${entity}.module';`;
30
+
31
+ let content = fs.readFileSync(filePath, "utf-8");
32
+
33
+ // Étape 1 : Ajout de l'import en haut du fichier si nécessaire
34
+ if (!content.includes(importLine)) {
35
+ const importMarker = `import { ConfigModule } from '@nestjs/config';`;
36
+
37
+ if (content.includes(importMarker)) {
38
+ await updateFile({
39
+ path: filePath,
40
+ pattern: importMarker,
41
+ replacement: `${importMarker}\n${importLine}`,
42
+ });
43
+ content = fs.readFileSync(filePath, "utf-8");
44
+ } else {
45
+ logInfo(
46
+ " Impossible de trouver le point d'insertion de l'import (ConfigModule manquant)",
47
+ );
48
+ }
49
+ }
50
+
51
+ // Étape 2 : Vérifier le bloc des imports du @Module avec gestion des crochets imbriqués
52
+ const importsHeaderMatch = content.match(/imports:\s*\[/);
53
+ if (!importsHeaderMatch) {
54
+ logInfo(" Impossible de trouver le bloc 'imports' dans AppModule.");
55
+ return;
56
+ }
57
+
58
+ const startIndex =
59
+ importsHeaderMatch.index + importsHeaderMatch[0].length - 1; // index du '['
60
+ let depth = 1;
61
+ let endIndex = -1;
62
+ for (let i = startIndex + 1; i < content.length; i++) {
63
+ if (content[i] === "[") {
64
+ depth++;
65
+ } else if (content[i] === "]") {
66
+ depth--;
67
+ if (depth === 0) {
68
+ endIndex = i;
69
+ break;
70
+ }
71
+ }
72
+ }
73
+
74
+ if (endIndex === -1) {
75
+ logInfo(
76
+ " Impossible de trouver le crochet de fermeture du bloc 'imports' dans AppModule.",
77
+ );
78
+ return;
79
+ }
80
+
81
+ const currentImportsBlock = content.substring(startIndex + 1, endIndex);
82
+ const isAlreadyImportedInModule = currentImportsBlock.includes(moduleName);
83
+
84
+ if (!isAlreadyImportedInModule) {
85
+ const updatedBlock = currentImportsBlock.trim().endsWith(",")
86
+ ? `${currentImportsBlock.trim()} ${moduleName},`
87
+ : `${currentImportsBlock.trim()}, ${moduleName},`;
88
+
89
+ const beforeBlock = content.substring(0, startIndex + 1);
90
+ const afterBlock = content.substring(endIndex);
91
+ const newContent = beforeBlock + updatedBlock + afterBlock;
92
+ fs.writeFileSync(filePath, newContent, "utf-8");
93
+ }
94
+ }
95
+
96
+ module.exports = { safeUpdateAppModule };
@@ -1,76 +1,88 @@
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
-
47
- function parseFlag(arg) {
48
- if (arg.includes('=')) {
49
- const [key, ...valueParts] = arg.slice(2).split('=');
50
- return [key, valueParts.join('=')];
51
- }
52
- return [arg.slice(2), null];
53
- }
54
-
55
- function isValidProjectName(name) {
56
- return /^[a-zA-Z0-9_-]+$/.test(name);
57
- }
58
-
59
- function validateFlags(parsed) {
60
- const validOrms = ['prisma', 'typeorm', 'mongoose'];
61
- const validModes = ['full', 'light'];
62
-
63
- if (parsed.flags.orm && !validOrms.includes(parsed.flags.orm)) {
64
- parsed.errors.push(`ORM invalide: "${parsed.flags.orm}". Valeurs acceptées: ${validOrms.join(', ')}`);
65
- }
66
-
67
- if (parsed.flags.mode && !validModes.includes(parsed.flags.mode)) {
68
- parsed.errors.push(`Mode invalide: "${parsed.flags.mode}". Valeurs acceptées: ${validModes.join(', ')}`);
69
- }
70
-
71
- if (parsed.flags.full && parsed.flags.light) {
72
- parsed.errors.push('Les flags --full et --light sont mutuellement exclusifs.');
73
- }
74
- }
75
-
76
- module.exports = { parseCliArgs };
1
+
2
+ function parseCliArgs(args) {
3
+ const parsed = {
4
+ command: null,
5
+ projectName: null, // Pour 'new'
6
+ subCommand: null, // Pour 'generate' (module, auth, etc.)
7
+ targetName: null, // Pour 'generate' (User, Product, etc.)
8
+ flags: {},
9
+ positional: [],
10
+ errors: [],
11
+ };
12
+
13
+ for (let i = 2; i < args.length; i++) {
14
+ const arg = args[i];
15
+
16
+ // Commande principale (new, g, info...)
17
+ if (i === 2 && !arg.startsWith("--")) {
18
+ parsed.command = arg;
19
+ continue;
20
+ }
21
+
22
+ if (arg.startsWith("--")) {
23
+ const [key, value] = parseFlag(arg);
24
+ // ... logique des flags inchangée ...
25
+ const nextArg = args[i + 1];
26
+ if (value !== null) {
27
+ parsed.flags[key] = value;
28
+ } else if (nextArg && !nextArg.startsWith("--")) {
29
+ parsed.flags[key] = nextArg;
30
+ i++;
31
+ } else {
32
+ parsed.flags[key] = true;
33
+ }
34
+ } else {
35
+ parsed.positional.push(arg);
36
+ }
37
+ }
38
+
39
+ // Distribution des arguments selon la commande
40
+ if (parsed.command === "new") {
41
+ parsed.projectName = parsed.positional[0] || null;
42
+ if (parsed.projectName && !isValidProjectName(parsed.projectName)) {
43
+ parsed.errors.push(`Nom de projet invalide: "${parsed.projectName}".`);
44
+ }
45
+ } else if (parsed.command === "generate" || parsed.command === "g") {
46
+ parsed.subCommand = parsed.positional[0] || null; // ex: module
47
+ parsed.targetName = parsed.positional[1] || null; // ex: User
48
+ }
49
+
50
+ validateFlags(parsed);
51
+ return parsed;
52
+ }
53
+ function parseFlag(arg) {
54
+ if (arg.includes("=")) {
55
+ const [key, ...valueParts] = arg.slice(2).split("=");
56
+ return [key, valueParts.join("=")];
57
+ }
58
+ return [arg.slice(2), null];
59
+ }
60
+
61
+ function isValidProjectName(name) {
62
+ return /^[a-zA-Z0-9_-]+$/.test(name);
63
+ }
64
+
65
+ function validateFlags(parsed) {
66
+ const validOrms = ["prisma", "typeorm", "mongoose"];
67
+ const validModes = ["full", "light"];
68
+
69
+ if (parsed.flags.orm && !validOrms.includes(parsed.flags.orm)) {
70
+ parsed.errors.push(
71
+ `ORM invalide: "${parsed.flags.orm}". Valeurs acceptées: ${validOrms.join(", ")}`,
72
+ );
73
+ }
74
+
75
+ if (parsed.flags.mode && !validModes.includes(parsed.flags.mode)) {
76
+ parsed.errors.push(
77
+ `Mode invalide: "${parsed.flags.mode}". Valeurs acceptées: ${validModes.join(", ")}`,
78
+ );
79
+ }
80
+
81
+ if (parsed.flags.full && parsed.flags.light) {
82
+ parsed.errors.push(
83
+ "Les flags --full et --light sont mutuellement exclusifs.",
84
+ );
85
+ }
86
+ }
87
+
88
+ module.exports = { parseCliArgs };
package/utils/colors.js CHANGED
@@ -1,62 +1,62 @@
1
- const colors = {
2
- reset: '\x1b[0m',
3
- bold: '\x1b[1m',
4
- dim: '\x1b[2m',
5
-
6
- black: '\x1b[30m',
7
- red: '\x1b[31m',
8
- green: '\x1b[32m',
9
- yellow: '\x1b[33m',
10
- blue: '\x1b[34m',
11
- magenta: '\x1b[35m',
12
- cyan: '\x1b[36m',
13
- white: '\x1b[37m',
14
-
15
- bgBlack: '\x1b[40m',
16
- bgRed: '\x1b[41m',
17
- bgGreen: '\x1b[42m',
18
- bgYellow: '\x1b[43m',
19
- bgBlue: '\x1b[44m',
20
- bgMagenta: '\x1b[45m',
21
- bgCyan: '\x1b[46m',
22
- bgWhite: '\x1b[47m'
23
- };
24
-
25
- function colorize(text, color) {
26
- return `${colors[color]}${text}${colors.reset}`;
27
- }
28
-
29
- function success(text) {
30
- return colorize(text, 'green');
31
- }
32
-
33
- function error(text) {
34
- return colorize(text, 'red');
35
- }
36
-
37
- function warning(text) {
38
- return colorize(text, 'yellow');
39
- }
40
-
41
- function info(text) {
42
- return colorize(text, 'cyan');
43
- }
44
-
45
- function bold(text) {
46
- return `${colors.bold}${text}${colors.reset}`;
47
- }
48
-
49
- function dim(text) {
50
- return `${colors.dim}${text}${colors.reset}`;
51
- }
52
-
53
- module.exports = {
54
- colors,
55
- colorize,
56
- success,
57
- error,
58
- warning,
59
- info,
60
- bold,
61
- dim
62
- };
1
+ const colors = {
2
+ reset: '\x1b[0m',
3
+ bold: '\x1b[1m',
4
+ dim: '\x1b[2m',
5
+
6
+ black: '\x1b[30m',
7
+ red: '\x1b[31m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ blue: '\x1b[34m',
11
+ magenta: '\x1b[35m',
12
+ cyan: '\x1b[36m',
13
+ white: '\x1b[37m',
14
+
15
+ bgBlack: '\x1b[40m',
16
+ bgRed: '\x1b[41m',
17
+ bgGreen: '\x1b[42m',
18
+ bgYellow: '\x1b[43m',
19
+ bgBlue: '\x1b[44m',
20
+ bgMagenta: '\x1b[45m',
21
+ bgCyan: '\x1b[46m',
22
+ bgWhite: '\x1b[47m'
23
+ };
24
+
25
+ function colorize(text, color) {
26
+ return `${colors[color]}${text}${colors.reset}`;
27
+ }
28
+
29
+ function success(text) {
30
+ return colorize(text, 'green');
31
+ }
32
+
33
+ function error(text) {
34
+ return colorize(text, 'red');
35
+ }
36
+
37
+ function warning(text) {
38
+ return colorize(text, 'yellow');
39
+ }
40
+
41
+ function info(text) {
42
+ return colorize(text, 'cyan');
43
+ }
44
+
45
+ function bold(text) {
46
+ return `${colors.bold}${text}${colors.reset}`;
47
+ }
48
+
49
+ function dim(text) {
50
+ return `${colors.dim}${text}${colors.reset}`;
51
+ }
52
+
53
+ module.exports = {
54
+ colors,
55
+ colorize,
56
+ success,
57
+ error,
58
+ warning,
59
+ info,
60
+ bold,
61
+ dim
62
+ };