nestcraftx 0.3.0 → 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.
- package/PROGRESS.md +13 -12
- package/commands/demo.js +2 -46
- package/commands/new.js +2 -47
- package/package.json +1 -1
- package/utils/app-module.updater.js +6 -0
- package/utils/envGenerator.js +6 -0
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +6 -0
- package/utils/fullModeInput.js +8 -229
- package/utils/generators/application/dtoGenerator.js +6 -5
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +36 -3
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/entityBuilder.js +400 -0
- package/utils/lightModeInput.js +13 -246
- package/utils/setups/orms/typeOrmSetup.js +31 -4
- package/utils/setups/projectSetup.js +78 -8
- package/utils/setups/setupAuth.js +1 -2
- package/utils/setups/setupMongoose.js +80 -31
- package/utils/setups/setupPrisma.js +41 -3
- package/utils/setups/setupSwagger.js +12 -8
- package/utils/shell.js +44 -9
package/PROGRESS.md
CHANGED
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
| Phase | Statut | Tickets | Complétés | Progression |
|
|
10
10
|
|---|---|---|---|---|
|
|
11
11
|
| Phase 0 — Stabilisation (v0.3.0) | ✅ Terminé | 17 | 17 | 100% |
|
|
12
|
-
| Phase 1 — Refactorisation (v0.4.0) |
|
|
12
|
+
| Phase 1 — Refactorisation (v0.4.0) | ✅ Terminé | 18 | 18 | 100% |
|
|
13
13
|
| Phase 2 — Qualité & Sécurité (v0.5.0) | ⏸️ En attente | 10 | 0 | 0% |
|
|
14
14
|
| Phase 3 — Tests & CI/CD (v0.6.0) | ⏸️ En attente | 15 | 0 | 0% |
|
|
15
15
|
| Phase 4 — Features (v0.7–0.9) | ⏸️ En attente | 14 | 0 | 0% |
|
|
16
16
|
| Phase 5 — Documentation (v0.9.x) | ⏸️ En attente | 8 | 0 | 0% |
|
|
17
17
|
| Phase 6 — Release v1.0 | ⏸️ En attente | 7 | 0 | 0% |
|
|
18
|
-
| **TOTAL** | | **89** | **
|
|
18
|
+
| **TOTAL** | | **89** | **35** | **39%** |
|
|
19
19
|
|
|
20
|
-
> Mis à jour : 2026-06-
|
|
20
|
+
> Mis à jour : 2026-06-16
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -79,20 +79,20 @@
|
|
|
79
79
|
|
|
80
80
|
| Ticket | Description | Fichier(s) | Statut | Date |
|
|
81
81
|
|---|---|---|---|---|
|
|
82
|
-
| PROD-04a | Étendre modèle de champ : `unique`, `nullable`, `default` | `utils/fullModeInput.js`, `utils/lightModeInput.js` |
|
|
83
|
-
| PROD-05 | Écran de révision d'entité (modifier, supprimer, renommer) | `utils/fullModeInput.js`, `utils/lightModeInput.js` |
|
|
84
|
-
| PROD-04b | Refonte state-based : `EntityBuilder` navigable | `utils/fullModeInput.js` |
|
|
85
|
-
| MP-02 | Dédupliquer `addCustomEntities` et `addRelations` | `utils/fullModeInput.js`, `utils/lightModeInput.js` |
|
|
82
|
+
| PROD-04a | Étendre modèle de champ : `unique`, `nullable`, `default` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | ✅ Terminé | 2026-06-14 |
|
|
83
|
+
| PROD-05 | Écran de révision d'entité (modifier, supprimer, renommer) | `utils/fullModeInput.js`, `utils/lightModeInput.js` | ✅ Terminé | 2026-06-14 |
|
|
84
|
+
| PROD-04b | Refonte state-based : `EntityBuilder` navigable | `utils/fullModeInput.js` | ✅ Terminé | 2026-06-14 |
|
|
85
|
+
| MP-02 | Dédupliquer `addCustomEntities` et `addRelations` | `utils/fullModeInput.js`, `utils/lightModeInput.js` | ✅ Terminé | 2026-06-14 |
|
|
86
86
|
|
|
87
87
|
### 1C — Architecture générale
|
|
88
88
|
|
|
89
89
|
| Ticket | Description | Fichier(s) | Statut | Date |
|
|
90
90
|
|---|---|---|---|---|
|
|
91
|
-
| MA-05 | Extraire flow commun `demo.js` → partager avec `new.js` | `commands/demo.js`, `commands/new.js` |
|
|
92
|
-
| MP-04 | Remplacer `execSync` par `execa` avec timeout | `utils/shell.js` |
|
|
93
|
-
| MA-01 | Mécanisme de rapport final (accumuler warnings, afficher en bloc) | `utils/shell.js`, `commands/new.js` |
|
|
94
|
-
| MA-02 | Isoler `process.chdir` — vérifier CWD avant chaque I/O | `utils/setups/projectSetup.js` |
|
|
95
|
-
| DRY-01 | Ajouter `--dry-run` flag (simuler sans écrire) | `bin/nestcraft.js`, `commands/new.js` |
|
|
91
|
+
| MA-05 | Extraire flow commun `demo.js` → partager avec `new.js` | `commands/demo.js`, `commands/new.js` | ✅ Terminé | 2026-06-16 |
|
|
92
|
+
| MP-04 | Remplacer `execSync` par `execa` avec timeout | `utils/shell.js` | ✅ Terminé | 2026-06-16 |
|
|
93
|
+
| MA-01 | Mécanisme de rapport final (accumuler warnings, afficher en bloc) | `utils/shell.js`, `commands/new.js` | ✅ Terminé | 2026-06-16 |
|
|
94
|
+
| MA-02 | Isoler `process.chdir` — vérifier CWD avant chaque I/O | `utils/setups/projectSetup.js` | ✅ Terminé | 2026-06-16 |
|
|
95
|
+
| DRY-01 | Ajouter `--dry-run` flag (simuler sans écrire) | `bin/nestcraft.js`, `commands/new.js` | ✅ Terminé | 2026-06-16 |
|
|
96
96
|
|
|
97
97
|
---
|
|
98
98
|
|
|
@@ -239,6 +239,7 @@
|
|
|
239
239
|
| 2026-06-05 | 0.3.0-wip | PROD-03, PROD-01a, PROD-01b, PROD-01c, BUG-09 | `runCommand` refactorisé (PROD-03). Prisma relations : capitalisation forcée des modèles et types, déduplication complète des clés étrangères (PROD-01a, b, c). Correction de la boucle asynchrone des use cases clean architecture (BUG-09). |
|
|
240
240
|
| 2026-06-06 | 0.3.0-wip | BUG-01, BUG-03, BUG-06 | Unification du module system en CommonJS (BUG-01). Validation non-interactive des flags (BUG-03). Utilisation de inputs.packageManager dynamique dans `setupAuth.js` au lieu de npm en dur (BUG-06). |
|
|
241
241
|
| 2026-06-07 | 0.3.0 | BUG-07, BUG-04, BUG-08, BUG-12, MP-07a, MP-07b, BUG-02, BUG-11, BUG-12b | Clôture de la Phase 0. Mongoose .env préservé. createFile supporte overwrite. safeUpdateAppModule robuste aux crochets imbriqués. help.js dynamic. Nettoyage de code mort et debug logs. |
|
|
242
|
+
| 2026-06-14 | 0.3.0-wip | PROD-04a, PROD-04b, PROD-05, MP-02 | Refonte state-based interactive de la saisie d'entités avec EntityBuilder. Support des contraintes unique, nullable et valeurs par défaut. Alignement et déduplication des générateurs (Prisma, TypeORM, Mongoose, DTOs, et Domain Entities). |
|
|
242
243
|
|
|
243
244
|
---
|
|
244
245
|
|
package/commands/demo.js
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
const inquirer = require("inquirer");
|
|
2
2
|
const { logInfo } = require("../utils/loggers/logInfo");
|
|
3
3
|
const { logSuccess } = require("../utils/loggers/logSuccess");
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
setupCleanArchitecture,
|
|
7
|
-
} = require("../utils/configs/setupCleanArchitecture");
|
|
8
|
-
const {
|
|
9
|
-
setupLightArchitecture,
|
|
10
|
-
} = require("../utils/configs/setupLightArchitecture");
|
|
11
|
-
const { setupAuth } = require("../utils/setups/setupAuth");
|
|
12
|
-
const { setupSwagger } = require("../utils/setups/setupSwagger");
|
|
13
|
-
const { setupDatabase } = require("../utils/setups/setupDatabase");
|
|
14
|
-
const { configureDocker } = require("../utils/configs/configureDocker");
|
|
15
|
-
const { generateEnvFile, writeEnvFile } = require("../utils/envGenerator");
|
|
16
|
-
const { setupBootstrapLogger } = require("../utils/setups/setupLogger");
|
|
17
|
-
const { saveProjectConfig } = require("../utils/file-utils/saveProjectConfig");
|
|
18
|
-
const { printSetupWarnings } = require("../utils/shell");
|
|
4
|
+
const { runProjectSetupPipeline } = require("../utils/setups/projectSetup");
|
|
19
5
|
const actualInquirer = inquirer.default || inquirer;
|
|
20
6
|
|
|
21
7
|
async function demoCommand(flags = {}) {
|
|
@@ -210,37 +196,7 @@ async function demoCommand(flags = {}) {
|
|
|
210
196
|
dbConfig,
|
|
211
197
|
};
|
|
212
198
|
|
|
213
|
-
await
|
|
214
|
-
|
|
215
|
-
if (isLight) {
|
|
216
|
-
await setupLightArchitecture(demoInputs);
|
|
217
|
-
} else {
|
|
218
|
-
await setupCleanArchitecture(demoInputs);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (useAuth) {
|
|
222
|
-
await setupAuth(demoInputs);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (useSwagger) {
|
|
226
|
-
await setupSwagger(demoInputs.swaggerInputs);
|
|
227
|
-
} else {
|
|
228
|
-
await setupBootstrapLogger();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (useDocker) {
|
|
232
|
-
await configureDocker(demoInputs);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
await setupDatabase(demoInputs);
|
|
236
|
-
|
|
237
|
-
const envContent = await generateEnvFile(demoInputs);
|
|
238
|
-
writeEnvFile(envContent);
|
|
239
|
-
|
|
240
|
-
await saveProjectConfig(demoInputs);
|
|
241
|
-
|
|
242
|
-
// Show any non-critical warnings accumulated during generation
|
|
243
|
-
printSetupWarnings();
|
|
199
|
+
await runProjectSetupPipeline(demoInputs);
|
|
244
200
|
|
|
245
201
|
console.log("\n" + "=".repeat(60));
|
|
246
202
|
logSuccess("Demonstration project created successfully!");
|
package/commands/new.js
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
const { getFullModeInputs } = require("../utils/fullModeInput");
|
|
2
2
|
const { getLightModeInputs } = require("../utils/lightModeInput");
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
setupCleanArchitecture,
|
|
6
|
-
} = require("../utils/configs/setupCleanArchitecture");
|
|
7
|
-
const {
|
|
8
|
-
setupLightArchitecture,
|
|
9
|
-
} = require("../utils/configs/setupLightArchitecture");
|
|
10
|
-
const { setupAuth } = require("../utils/setups/setupAuth");
|
|
11
|
-
const { setupSwagger } = require("../utils/setups/setupSwagger");
|
|
12
|
-
const { setupDatabase } = require("../utils/setups/setupDatabase");
|
|
13
|
-
const { configureDocker } = require("../utils/configs/configureDocker");
|
|
14
|
-
const { setupBootstrapLogger } = require("../utils/setups/setupLogger");
|
|
3
|
+
const { runProjectSetupPipeline } = require("../utils/setups/projectSetup");
|
|
15
4
|
const { logSuccess } = require("../utils/loggers/logSuccess");
|
|
16
5
|
const { logInfo } = require("../utils/loggers/logInfo");
|
|
17
6
|
const { logError } = require("../utils/loggers/logError");
|
|
18
|
-
const { generateEnvFile, writeEnvFile } = require("../utils/envGenerator");
|
|
19
7
|
const readline = require("readline-sync");
|
|
20
8
|
const { info, warning } = require("../utils/colors");
|
|
21
9
|
const { logWarning } = require("../utils/loggers/logWarning");
|
|
22
|
-
const { saveProjectConfig } = require("../utils/file-utils/saveProjectConfig");
|
|
23
|
-
const { printSetupWarnings } = require("../utils/shell");
|
|
24
10
|
|
|
25
11
|
async function newCommand(projectName, flags = {}) {
|
|
26
12
|
if (!projectName) {
|
|
@@ -240,40 +226,9 @@ function buildFullModeFromFlags(projectName, flags) {
|
|
|
240
226
|
|
|
241
227
|
async function executeProjectSetup(inputs) {
|
|
242
228
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await createProject(inputs);
|
|
246
|
-
|
|
247
|
-
if (inputs.mode === "light") {
|
|
248
|
-
await setupLightArchitecture(inputs);
|
|
249
|
-
} else {
|
|
250
|
-
await setupCleanArchitecture(inputs);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (inputs.useAuth) await setupAuth(inputs);
|
|
254
|
-
|
|
255
|
-
if (inputs.useSwagger) {
|
|
256
|
-
await setupSwagger(inputs.swaggerInputs);
|
|
257
|
-
} else {
|
|
258
|
-
setupBootstrapLogger();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (inputs.useDocker) await configureDocker(inputs);
|
|
262
|
-
|
|
263
|
-
await setupDatabase(inputs);
|
|
264
|
-
|
|
265
|
-
const envContent = await generateEnvFile(inputs);
|
|
266
|
-
writeEnvFile(envContent);
|
|
267
|
-
|
|
268
|
-
await saveProjectConfig(inputs);
|
|
269
|
-
|
|
270
|
-
// Show any non-critical warnings accumulated during generation
|
|
271
|
-
printSetupWarnings();
|
|
272
|
-
|
|
229
|
+
await runProjectSetupPipeline(inputs);
|
|
273
230
|
printSuccessSummary(inputs);
|
|
274
231
|
} catch (error) {
|
|
275
|
-
// Erreur lors de la creation du projet: ${error.message}
|
|
276
|
-
logError(`Error during project creation: ${error.message}`);
|
|
277
232
|
throw error;
|
|
278
233
|
}
|
|
279
234
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestcraftx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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": {
|
|
@@ -24,6 +24,12 @@ const { capitalize } = require("./helpers");
|
|
|
24
24
|
* @param {string} entity - Le nom de l'entité en minuscule (ex: "user", "post")
|
|
25
25
|
*/
|
|
26
26
|
async function safeUpdateAppModule(entity) {
|
|
27
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
28
|
+
if (isDryRun) {
|
|
29
|
+
console.log(`[DRY-RUN] Simulated: safe update app.module.ts with ${capitalize(entity)}Module`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
const filePath = "src/app.module.ts";
|
|
28
34
|
const moduleName = `${capitalize(entity)}Module`;
|
|
29
35
|
const importLine = `import { ${moduleName} } from 'src/${entity}/${entity}.module';`;
|
package/utils/envGenerator.js
CHANGED
|
@@ -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");
|
package/utils/file-system.js
CHANGED
|
@@ -15,6 +15,11 @@ const fs = require("fs");
|
|
|
15
15
|
* @param {string} directoryPath - Le chemin du répertoire à créer.
|
|
16
16
|
*/
|
|
17
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
|
+
}
|
|
18
23
|
try {
|
|
19
24
|
if (!fs.existsSync(directoryPath)) {
|
|
20
25
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
@@ -36,6 +41,11 @@ async function createDirectory(directoryPath) {
|
|
|
36
41
|
* @param {{ path: string, contente: string, overwrite?: boolean }} fileData
|
|
37
42
|
*/
|
|
38
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
|
+
}
|
|
39
49
|
try {
|
|
40
50
|
if (!fs.existsSync(fileData.path)) {
|
|
41
51
|
fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
|
|
@@ -59,6 +69,11 @@ async function createFile(fileData) {
|
|
|
59
69
|
* @param {{ path: string, pattern: string|RegExp, replacement: string }} options
|
|
60
70
|
*/
|
|
61
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
|
+
}
|
|
62
77
|
try {
|
|
63
78
|
let mainTs = fs.readFileSync(path, "utf8");
|
|
64
79
|
const updatedContent = mainTs.replace(pattern, replacement);
|
|
@@ -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 });
|
package/utils/fullModeInput.js
CHANGED
|
@@ -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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -88,6 +88,7 @@ async function generateDto(
|
|
|
88
88
|
const name = f.name;
|
|
89
89
|
const cleanType = f.type.toLowerCase().replace("[]", "");
|
|
90
90
|
const isArray = f.type.endsWith("[]");
|
|
91
|
+
const isOptional = optional || f.nullable;
|
|
91
92
|
|
|
92
93
|
const SCALAR_TYPES = [
|
|
93
94
|
"string",
|
|
@@ -104,7 +105,7 @@ async function generateDto(
|
|
|
104
105
|
if (!SCALAR_TYPES.includes(cleanType)) return null;
|
|
105
106
|
|
|
106
107
|
let validators = [];
|
|
107
|
-
if (
|
|
108
|
+
if (isOptional) validators.push("@IsOptional()");
|
|
108
109
|
if (name.toLowerCase().includes("email")) validators.push("@IsEmail()");
|
|
109
110
|
else if (name.toLowerCase().includes("password"))
|
|
110
111
|
validators.push(
|
|
@@ -141,7 +142,7 @@ async function generateDto(
|
|
|
141
142
|
|
|
142
143
|
let swaggerDecorator = "";
|
|
143
144
|
if (useSwagger && !forceNoSwagger) {
|
|
144
|
-
const decorator =
|
|
145
|
+
const decorator = isOptional ? "@ApiPropertyOptional" : "@ApiProperty";
|
|
145
146
|
const options = JSON.stringify(
|
|
146
147
|
{
|
|
147
148
|
example: getExampleForField(f),
|
|
@@ -154,7 +155,7 @@ async function generateDto(
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
return `${swaggerDecorator}${validators.join("\n ")}\n ${name}${
|
|
157
|
-
|
|
158
|
+
isOptional ? "?" : "!"
|
|
158
159
|
}: ${formatType(f.type)};`;
|
|
159
160
|
};
|
|
160
161
|
|
|
@@ -169,7 +170,7 @@ async function generateDto(
|
|
|
169
170
|
|
|
170
171
|
return `import {
|
|
171
172
|
IsString, IsInt, IsBoolean, IsEmail, IsArray,
|
|
172
|
-
IsUUID, IsDateString, MinLength, IsOptional
|
|
173
|
+
IsUUID, IsDateString, MinLength, IsOptional, IsNumber
|
|
173
174
|
} from 'class-validator';
|
|
174
175
|
${useSwagger ? "import { ApiProperty } from '@nestjs/swagger';" : ""}
|
|
175
176
|
|
|
@@ -200,7 +201,7 @@ export class ${entityName}Dto {
|
|
|
200
201
|
if (entityName === "User") swaggerImports.push("ApiHideProperty");
|
|
201
202
|
|
|
202
203
|
return `import {
|
|
203
|
-
IsOptional, IsString, IsInt, IsBoolean, IsEmail,
|
|
204
|
+
IsOptional, IsString, IsInt, IsNumber, IsBoolean, IsEmail,
|
|
204
205
|
IsArray, IsUUID, IsDateString, MinLength, IsEnum
|
|
205
206
|
} from 'class-validator';
|
|
206
207
|
${
|
|
@@ -3,6 +3,11 @@ const fs = require("fs");
|
|
|
3
3
|
const { updateFile, capitalize } = require("../../userInput");
|
|
4
4
|
|
|
5
5
|
async function patchDtoWithRelation(source, targetName, useSwagger, mode) {
|
|
6
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
7
|
+
if (isDryRun) {
|
|
8
|
+
console.log(`[DRY-RUN] Simulated: patch DTO for ${source} with relation to ${targetName}`);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
6
11
|
let dtoPath;
|
|
7
12
|
if (mode === "full") {
|
|
8
13
|
dtoPath = `src/${source.toLowerCase()}/application/dtos/${source.toLowerCase()}.dto.ts`;
|
|
@@ -8,6 +8,11 @@ async function updateExistingEntityRelation(
|
|
|
8
8
|
relationType,
|
|
9
9
|
mode,
|
|
10
10
|
) {
|
|
11
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
12
|
+
if (isDryRun) {
|
|
13
|
+
console.log(`[DRY-RUN] Simulated: update existing entity relation between ${targetName} and ${newEntityName}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
11
16
|
const targetLow = targetName.toLowerCase();
|
|
12
17
|
const newCap = capitalize(newEntityName);
|
|
13
18
|
const newLow = newEntityName.toLowerCase();
|
|
@@ -2,6 +2,11 @@ const { updateFile } = require("../../userInput");
|
|
|
2
2
|
|
|
3
3
|
// src/utils/generators/infrastructure/mapperUpdater.js
|
|
4
4
|
async function patchMapperWithRelation(source, targetName, mode = "full") {
|
|
5
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
6
|
+
if (isDryRun) {
|
|
7
|
+
console.log(`[DRY-RUN] Simulated: patch mapper for ${source} with relation to ${targetName}`);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
5
10
|
const targetLow = targetName.toLowerCase();
|
|
6
11
|
|
|
7
12
|
if (mode === "full") {
|