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.
- package/AUDIT_ROADMAP.md +798 -0
- package/CHANGELOG.fr.md +22 -0
- package/CHANGELOG.md +22 -0
- package/PROGRESS.md +259 -0
- package/commands/demo.js +2 -42
- package/commands/generate.js +5 -4
- package/commands/help.js +29 -2
- package/commands/new.js +6 -49
- package/package.json +1 -1
- package/utils/app-module.updater.js +102 -0
- package/utils/cliParser.js +0 -45
- package/utils/configs/setupCleanArchitecture.js +2 -2
- package/utils/envGenerator.js +6 -0
- package/utils/file-system.js +90 -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 +252 -0
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/database/setupDatabase.js +32 -14
- package/utils/generators/domain/entityGenerator.js +126 -0
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperGenerator.js +114 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +229 -0
- package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
- package/utils/generators/presentation/controllerGenerator.js +142 -0
- package/utils/helpers.js +115 -0
- 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 +6 -7
- package/utils/setups/setupDatabase.js +2 -1
- package/utils/setups/setupMongoose.js +96 -30
- package/utils/setups/setupPrisma.js +118 -134
- package/utils/setups/setupSwagger.js +12 -8
- package/utils/shell.js +125 -10
- package/utils/userInput.js +37 -101
- package/utils/utils.js +29 -2164
|
@@ -86,36 +86,41 @@ async function setupPrisma(inputs) {
|
|
|
86
86
|
for (const relation of inputs.entitiesData.relations) {
|
|
87
87
|
const fromLower = relation.from.toLowerCase();
|
|
88
88
|
const toLower = relation.to.toLowerCase();
|
|
89
|
-
const fromCapitalized = capitalize(relation.from);
|
|
90
|
-
const toCapitalized = capitalize(relation.to); // 'from' side (source)
|
|
91
89
|
|
|
92
90
|
if (relation.type === "1-n") {
|
|
93
91
|
// 'One' side: exclude the name of the other entity's list (e.g., 'articles')
|
|
94
92
|
fieldsToExcludeMap.get(fromLower).push(`${toLower}s`);
|
|
95
|
-
} else if (relation.type === "n-1") {
|
|
96
|
-
// 'Many' side: exclude the foreign key (e.g., 'articleId') and the relation name (e.g., 'article')
|
|
97
|
-
fieldsToExcludeMap.get(fromLower).push(`${toLower}id`, toLower);
|
|
98
|
-
} // Add other relation types (1-1, n-n) if necessary here... // 'to' side (target)
|
|
99
|
-
if (relation.type === "1-n") {
|
|
100
93
|
// 'Many' side: exclude the foreign key (e.g., 'userId') and the relation name (e.g., 'user')
|
|
101
94
|
fieldsToExcludeMap.get(toLower).push(`${fromLower}id`, fromLower);
|
|
102
95
|
} else if (relation.type === "n-1") {
|
|
96
|
+
// 'Many' side: exclude the foreign key (e.g., 'articleId') and the relation name (e.g., 'article')
|
|
97
|
+
fieldsToExcludeMap.get(fromLower).push(`${toLower}id`, toLower);
|
|
103
98
|
// 'One' side: exclude the name of the other entity's list (e.g., 'comments')
|
|
104
99
|
fieldsToExcludeMap.get(toLower).push(`${fromLower}s`);
|
|
100
|
+
} else if (relation.type === "1-1") {
|
|
101
|
+
// 'from' side (source of relation, holds the FK): exclude foreign key and relation name
|
|
102
|
+
fieldsToExcludeMap.get(fromLower).push(`${toLower}id`, toLower);
|
|
103
|
+
// 'to' side (target of relation): exclude relation name
|
|
104
|
+
fieldsToExcludeMap.get(toLower).push(fromLower);
|
|
105
|
+
} else if (relation.type === "n-n") {
|
|
106
|
+
// Both sides hold list of other entity: exclude plurals
|
|
107
|
+
fieldsToExcludeMap.get(fromLower).push(`${toLower}s`);
|
|
108
|
+
fieldsToExcludeMap.get(toLower).push(`${fromLower}s`);
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
// 2. Initial generation of models WITHOUT incorrect relationship fields
|
|
109
113
|
for (const entity of inputs.entitiesData.entities) {
|
|
110
114
|
const entityNameLower = entity.name.toLowerCase();
|
|
115
|
+
const entityNameCap = capitalize(entity.name);
|
|
111
116
|
// On utilise un Set pour suivre les noms de champs déjà écrits dans ce modèle
|
|
112
117
|
const addedFields = new Set(["id", "createdat", "updatedat"]);
|
|
113
118
|
|
|
114
119
|
schemaContent += `
|
|
115
120
|
/**
|
|
116
|
-
* ${
|
|
121
|
+
* ${entityNameCap} Model
|
|
117
122
|
*/
|
|
118
|
-
model ${
|
|
123
|
+
model ${entityNameCap} {
|
|
119
124
|
id String @id @default(uuid())
|
|
120
125
|
createdAt DateTime @default(now())
|
|
121
126
|
updatedAt DateTime @updatedAt`;
|
|
@@ -142,7 +147,7 @@ async function setupPrisma(inputs) {
|
|
|
142
147
|
!fieldsToExclude.includes(fieldNameLower) &&
|
|
143
148
|
!addedFields.has(fieldNameLower)
|
|
144
149
|
) {
|
|
145
|
-
schemaContent += `\n ${field.name} ${
|
|
150
|
+
schemaContent += `\n ${field.name} ${formatPrismaField(field)}`;
|
|
146
151
|
addedFields.add(fieldNameLower);
|
|
147
152
|
}
|
|
148
153
|
}
|
|
@@ -159,30 +164,35 @@ async function setupPrisma(inputs) {
|
|
|
159
164
|
const to = relation.to;
|
|
160
165
|
const type = relation.type;
|
|
161
166
|
|
|
167
|
+
const fromCap = capitalize(from);
|
|
168
|
+
const toCap = capitalize(to);
|
|
169
|
+
const fromLow = from.toLowerCase();
|
|
170
|
+
const toLow = to.toLowerCase();
|
|
171
|
+
|
|
162
172
|
// The replacement must be done on the entire generated schemaContent // Using a replacement function to update the content of `schemaContent`
|
|
163
173
|
if (type === "1-n") {
|
|
164
174
|
// "One" side (source): adds the list (to[])
|
|
165
175
|
schemaContent = schemaContent.replace(
|
|
166
|
-
new RegExp(`model ${
|
|
176
|
+
new RegExp(`model ${fromCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
167
177
|
(match, content) => {
|
|
168
|
-
const fieldLine = ` ${
|
|
178
|
+
const fieldLine = ` ${toLow}s ${toCap}[]`;
|
|
169
179
|
return match.includes(fieldLine)
|
|
170
180
|
? match
|
|
171
|
-
: `model ${
|
|
181
|
+
: `model ${fromCap} {${content}\n${fieldLine}\n}`;
|
|
172
182
|
},
|
|
173
183
|
);
|
|
174
184
|
|
|
175
185
|
// "Many" side (target): adds the relation and the foreign key
|
|
176
186
|
schemaContent = schemaContent.replace(
|
|
177
|
-
new RegExp(`model ${
|
|
187
|
+
new RegExp(`model ${toCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
178
188
|
(match, content) => {
|
|
179
|
-
const relationLine = ` ${
|
|
180
|
-
const fkLine = ` ${
|
|
189
|
+
const relationLine = ` ${fromLow} ${fromCap} @relation(fields: [${fromLow}Id], references: [id])`;
|
|
190
|
+
const fkLine = ` ${fromLow}Id String`;
|
|
181
191
|
let result = match.includes(relationLine)
|
|
182
192
|
? content
|
|
183
193
|
: `${content}\n${relationLine}`;
|
|
184
194
|
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
185
|
-
return `model ${
|
|
195
|
+
return `model ${toCap} {${result}\n}`;
|
|
186
196
|
},
|
|
187
197
|
);
|
|
188
198
|
}
|
|
@@ -192,27 +202,26 @@ async function setupPrisma(inputs) {
|
|
|
192
202
|
|
|
193
203
|
// "Many" side (source = from): adds the relation and the foreign key
|
|
194
204
|
schemaContent = schemaContent.replace(
|
|
195
|
-
new RegExp(`model ${
|
|
205
|
+
new RegExp(`model ${fromCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
196
206
|
(match, content) => {
|
|
197
|
-
const relationLine = ` ${
|
|
198
|
-
const fkLine = ` ${
|
|
207
|
+
const relationLine = ` ${toLow} ${toCap} @relation(fields: [${toLow}Id], references: [id])`;
|
|
208
|
+
const fkLine = ` ${toLow}Id String`;
|
|
199
209
|
let result = match.includes(relationLine)
|
|
200
210
|
? content
|
|
201
211
|
: `${content}\n${relationLine}`;
|
|
202
212
|
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
203
|
-
return `model ${
|
|
213
|
+
return `model ${fromCap} {${result}\n}`;
|
|
204
214
|
},
|
|
205
215
|
);
|
|
206
216
|
|
|
207
217
|
// "One" side (target = to): adds the list (from[])
|
|
208
218
|
schemaContent = schemaContent.replace(
|
|
209
|
-
new RegExp(`model ${
|
|
219
|
+
new RegExp(`model ${toCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
210
220
|
(match, content) => {
|
|
211
|
-
const
|
|
212
|
-
const fieldLine = ` ${from}s ${from}[]`;
|
|
221
|
+
const fieldLine = ` ${fromLow}s ${fromCap}[]`;
|
|
213
222
|
return match.includes(fieldLine)
|
|
214
223
|
? match
|
|
215
|
-
: `model ${
|
|
224
|
+
: `model ${toCap} {${content}\n${fieldLine}\n}`;
|
|
216
225
|
},
|
|
217
226
|
);
|
|
218
227
|
}
|
|
@@ -220,28 +229,28 @@ async function setupPrisma(inputs) {
|
|
|
220
229
|
if (type === "1-1") {
|
|
221
230
|
// 'from' side (source): adds the relation, foreign key, and @unique attribute
|
|
222
231
|
schemaContent = schemaContent.replace(
|
|
223
|
-
new RegExp(`model ${
|
|
232
|
+
new RegExp(`model ${fromCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
224
233
|
(match, content) => {
|
|
225
|
-
const relationLine = ` ${
|
|
226
|
-
const fkLine = ` ${
|
|
234
|
+
const relationLine = ` ${toLow} ${toCap}? @relation(fields: [${toLow}Id], references: [id])`; // The foreign key must be unique in a 1-1 relationship, and optional for flexibility
|
|
235
|
+
const fkLine = ` ${toLow}Id String? @unique`;
|
|
227
236
|
|
|
228
237
|
let result = match.includes(relationLine)
|
|
229
238
|
? content
|
|
230
239
|
: `${content}\n${relationLine}`;
|
|
231
240
|
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
232
|
-
return `model ${
|
|
241
|
+
return `model ${fromCap} {${result}\n}`;
|
|
233
242
|
},
|
|
234
243
|
);
|
|
235
244
|
|
|
236
245
|
// 'to' side (target): adds the inverse relation (optional)
|
|
237
246
|
schemaContent = schemaContent.replace(
|
|
238
|
-
new RegExp(`model ${
|
|
247
|
+
new RegExp(`model ${toCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
239
248
|
(match, content) => {
|
|
240
249
|
// Inverse relation (optional because 'from' holds the FK)
|
|
241
|
-
const fieldLine = ` ${
|
|
250
|
+
const fieldLine = ` ${fromLow} ${fromCap}?`;
|
|
242
251
|
return match.includes(fieldLine)
|
|
243
252
|
? match
|
|
244
|
-
: `model ${
|
|
253
|
+
: `model ${toCap} {${content}\n${fieldLine}\n}`;
|
|
245
254
|
},
|
|
246
255
|
);
|
|
247
256
|
}
|
|
@@ -249,23 +258,23 @@ async function setupPrisma(inputs) {
|
|
|
249
258
|
if (type === "n-n") {
|
|
250
259
|
// 'from' side (source): adds the list (to[])
|
|
251
260
|
schemaContent = schemaContent.replace(
|
|
252
|
-
new RegExp(`model ${
|
|
261
|
+
new RegExp(`model ${fromCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
253
262
|
(match, content) => {
|
|
254
|
-
const fieldLine = ` ${
|
|
263
|
+
const fieldLine = ` ${toLow}s ${toCap}[]`;
|
|
255
264
|
return match.includes(fieldLine)
|
|
256
265
|
? match
|
|
257
|
-
: `model ${
|
|
266
|
+
: `model ${fromCap} {${content}\n${fieldLine}\n}`;
|
|
258
267
|
},
|
|
259
268
|
);
|
|
260
269
|
|
|
261
270
|
// 'to' side (target): adds the list (from[])
|
|
262
271
|
schemaContent = schemaContent.replace(
|
|
263
|
-
new RegExp(`model ${
|
|
272
|
+
new RegExp(`model ${toCap} \\{([\\s\\S]*?)\\n\\}`, "m"),
|
|
264
273
|
(match, content) => {
|
|
265
|
-
const fieldLine = ` ${
|
|
274
|
+
const fieldLine = ` ${fromLow}s ${fromCap}[]`;
|
|
266
275
|
return match.includes(fieldLine)
|
|
267
276
|
? match
|
|
268
|
-
: `model ${
|
|
277
|
+
: `model ${toCap} {${content}\n${fieldLine}\n}`;
|
|
269
278
|
},
|
|
270
279
|
);
|
|
271
280
|
}
|
|
@@ -289,7 +298,7 @@ async function setupPrisma(inputs) {
|
|
|
289
298
|
path: schemaPath,
|
|
290
299
|
contente: baseSchema,
|
|
291
300
|
});
|
|
292
|
-
await runCommand(`npx prisma format`, "❌ Failed to format prisma schema");
|
|
301
|
+
await runCommand(`npx prisma format`, "❌ Failed to format prisma schema — run 'npx prisma format' manually", { critical: false });
|
|
293
302
|
|
|
294
303
|
// 📁 Step 6: Creating the `src/prisma` structure
|
|
295
304
|
const defaultPatch = "src/prisma";
|
|
@@ -344,6 +353,7 @@ async function setupPrisma(inputs) {
|
|
|
344
353
|
await runCommand(
|
|
345
354
|
`${inputs.packageManager} add dotenv`,
|
|
346
355
|
"❌ Failed to install dotenv",
|
|
356
|
+
{ critical: false },
|
|
347
357
|
);
|
|
348
358
|
|
|
349
359
|
// Creating prisma.config.ts file to load environment variables
|
|
@@ -362,7 +372,7 @@ async function setupPrisma(inputs) {
|
|
|
362
372
|
}
|
|
363
373
|
|
|
364
374
|
// Step 7: Generating the Prisma client
|
|
365
|
-
await runCommand("npx prisma generate", "❌ Prisma generation failed");
|
|
375
|
+
await runCommand("npx prisma generate", "❌ Prisma client generation failed — run 'npx prisma generate' manually", { critical: false });
|
|
366
376
|
|
|
367
377
|
// Step 8: Migration (ONLY in 'new' mode)
|
|
368
378
|
if (inputs.isDemo) {
|
|
@@ -421,6 +431,44 @@ function mapTypeToPrisma(type) {
|
|
|
421
431
|
}
|
|
422
432
|
}
|
|
423
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Formats a field definition for the Prisma schema including constraints.
|
|
436
|
+
* @param {object} field
|
|
437
|
+
* @returns {string} Formatted field line content (e.g. "String? @unique")
|
|
438
|
+
*/
|
|
439
|
+
function formatPrismaField(field) {
|
|
440
|
+
let prismaType = mapTypeToPrisma(field.type);
|
|
441
|
+
if (field.nullable) {
|
|
442
|
+
if (!prismaType.endsWith("?") && !prismaType.endsWith("[]")) {
|
|
443
|
+
prismaType += "?";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let attributes = "";
|
|
447
|
+
if (field.unique) {
|
|
448
|
+
attributes += " @unique";
|
|
449
|
+
}
|
|
450
|
+
if (field.default !== undefined && field.default !== null && field.default !== "") {
|
|
451
|
+
let defaultVal = field.default;
|
|
452
|
+
if (typeof defaultVal === "string") {
|
|
453
|
+
if (defaultVal === "true") defaultVal = true;
|
|
454
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
455
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
456
|
+
defaultVal = "now()";
|
|
457
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
458
|
+
defaultVal = Number(defaultVal);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (defaultVal === "now()") {
|
|
462
|
+
attributes += ` @default(now())`;
|
|
463
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
464
|
+
attributes += ` @default(${defaultVal})`;
|
|
465
|
+
} else {
|
|
466
|
+
attributes += ` @default("${defaultVal}")`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return `${prismaType}${attributes}`;
|
|
470
|
+
}
|
|
471
|
+
|
|
424
472
|
async function setupPrismaSeeding(inputs) {
|
|
425
473
|
logInfo("⚙️ Configuring seeding for Prisma...");
|
|
426
474
|
|
|
@@ -645,18 +693,18 @@ async function updatePrismaSchema(entityData) {
|
|
|
645
693
|
|
|
646
694
|
switch (relation.type) {
|
|
647
695
|
case "n-1": // "Many to One" : Le nouveau module appartient à une cible
|
|
648
|
-
relationFields += `\n ${targetLow} ${
|
|
696
|
+
relationFields += `\n ${targetLow} ${targetCap} @relation(fields: [${targetLow}Id], references: [id])`;
|
|
649
697
|
relationFields += `\n ${targetLow}Id String`;
|
|
650
698
|
break;
|
|
651
699
|
case "1-n": // "One to Many" : Le nouveau module possède plusieurs cibles
|
|
652
|
-
relationFields += `\n ${targetLow}s ${
|
|
700
|
+
relationFields += `\n ${targetLow}s ${targetCap}[]`;
|
|
653
701
|
break;
|
|
654
702
|
case "1-1":
|
|
655
|
-
relationFields += `\n ${targetLow} ${
|
|
703
|
+
relationFields += `\n ${targetLow} ${targetCap}? @relation(fields: [${targetLow}Id], references: [id])`;
|
|
656
704
|
relationFields += `\n ${targetLow}Id String? @unique`;
|
|
657
705
|
break;
|
|
658
706
|
case "n-n":
|
|
659
|
-
relationFields += `\n ${targetLow}s ${
|
|
707
|
+
relationFields += `\n ${targetLow}s ${targetCap}[]`;
|
|
660
708
|
break;
|
|
661
709
|
}
|
|
662
710
|
}
|
|
@@ -665,13 +713,13 @@ async function updatePrismaSchema(entityData) {
|
|
|
665
713
|
let modelBlock = `\n/**
|
|
666
714
|
* ${capitalizedName} Model
|
|
667
715
|
*/
|
|
668
|
-
model ${
|
|
716
|
+
model ${capitalizedName} {
|
|
669
717
|
id String @id @default(uuid())
|
|
670
718
|
createdAt DateTime @default(now())
|
|
671
719
|
updatedAt DateTime @updatedAt${relationFields}`;
|
|
672
720
|
|
|
673
721
|
fields.forEach((field) => {
|
|
674
|
-
modelBlock += `\n ${field.name} ${
|
|
722
|
+
modelBlock += `\n ${field.name} ${formatPrismaField(field)}`;
|
|
675
723
|
});
|
|
676
724
|
|
|
677
725
|
modelBlock += `\n}\n`;
|
|
@@ -686,117 +734,53 @@ model ${lowercasedName} {
|
|
|
686
734
|
// 4️⃣ Injection de l'INVERSE dans le modèle cible (Target)
|
|
687
735
|
if (relation) {
|
|
688
736
|
const targetCap = capitalize(relation.target);
|
|
689
|
-
const targetLow =
|
|
737
|
+
const targetLow = relation.target.toLowerCase();
|
|
690
738
|
const newCap = capitalizedName;
|
|
691
739
|
const newLow = name.toLowerCase();
|
|
692
740
|
|
|
693
741
|
let inverseField = "";
|
|
694
742
|
switch (relation.type) {
|
|
695
743
|
case "n-1": // Inverse d'un Many-to-One est un One-to-Many
|
|
696
|
-
inverseField = ` ${newLow}s ${
|
|
744
|
+
inverseField = ` ${newLow}s ${newCap}[]`;
|
|
697
745
|
break;
|
|
698
746
|
case "1-n": // Inverse d'un One-to-Many est un Many-to-One
|
|
699
|
-
inverseField = ` ${newLow} ${
|
|
747
|
+
inverseField = ` ${newLow} ${newCap} @relation(fields: [${newLow}Id], references: [id])\n ${newLow}Id String`;
|
|
700
748
|
break;
|
|
701
749
|
case "1-1":
|
|
702
|
-
inverseField = ` ${newLow} ${
|
|
750
|
+
inverseField = ` ${newLow} ${newCap}?`;
|
|
703
751
|
break;
|
|
704
752
|
case "n-n":
|
|
705
|
-
inverseField = ` ${newLow}s ${
|
|
753
|
+
inverseField = ` ${newLow}s ${newCap}[]`;
|
|
706
754
|
break;
|
|
707
755
|
}
|
|
708
756
|
|
|
709
757
|
// On utilise une RegEx pour trouver le bloc du modèle cible et insérer avant la fermeture "}"
|
|
710
758
|
await updateFile({
|
|
711
759
|
path: schemaPath,
|
|
712
|
-
pattern: new RegExp(`model ${
|
|
713
|
-
replacement: `model ${
|
|
760
|
+
pattern: new RegExp(`model ${targetCap} \\{([\\s\\S]*?)\\}`, "g"),
|
|
761
|
+
replacement: `model ${targetCap} {$1\n${inverseField}\n}`,
|
|
714
762
|
});
|
|
715
763
|
}
|
|
716
764
|
|
|
717
765
|
// 3️⃣ Prisma Commands Sync (meilleur sur Windows)
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
execSync("npx prisma generate", { stdio: "pipe" });
|
|
724
|
-
|
|
725
|
-
console.log(info("Running: prisma migrate dev..."));
|
|
726
|
-
execSync(
|
|
727
|
-
`npx prisma migrate dev --name add_module_${entityData.name} --force`,
|
|
728
|
-
{
|
|
729
|
-
stdio: "pipe",
|
|
730
|
-
},
|
|
731
|
-
);
|
|
732
|
-
|
|
733
|
-
// console.log(success("🎉 Prisma migration completed successfully!"));
|
|
734
|
-
} catch (err) {
|
|
735
|
-
// 4️⃣ Gestion spécifique Windows EPERM
|
|
736
|
-
if (
|
|
737
|
-
err.message.includes("operation not permitted") ||
|
|
738
|
-
err.message.includes("EPERM") ||
|
|
739
|
-
err.message.includes("EBUSY")
|
|
740
|
-
) {
|
|
741
|
-
logWarning("System Lock Detected!");
|
|
742
|
-
console.log(
|
|
743
|
-
`${info("Prisma Client binary is locked because your server is running.")}`,
|
|
744
|
-
);
|
|
745
|
-
console.log(
|
|
746
|
-
`${info("ACTION:")} Stop NestJS (Ctrl+C) and run manually:\n`,
|
|
747
|
-
);
|
|
748
|
-
console.log(`${success("npx prisma generate")}`);
|
|
749
|
-
console.log(`${success("npx prisma migrate dev")}\n`);
|
|
750
|
-
} else {
|
|
751
|
-
console.log(error("❌ Prisma error: " + err.message));
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/* async function updatePrismaSchema(entityData) {
|
|
757
|
-
const schemaPath = "prisma/schema.prisma";
|
|
758
|
-
|
|
759
|
-
// Construction du bloc modèle
|
|
760
|
-
let modelBlock = `\nmodel ${capitalize(entityData.name)} {
|
|
761
|
-
id String @id @default(uuid())
|
|
762
|
-
createdAt DateTime @default(now())
|
|
763
|
-
updatedAt DateTime @updatedAt`;
|
|
764
|
-
|
|
765
|
-
entityData.fields.forEach((field) => {
|
|
766
|
-
// Note: On réutilise ta fonction mapTypeToPrisma ici
|
|
767
|
-
modelBlock += `\n ${field.name} ${mapTypeToPrisma(field.type)}`;
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
modelBlock += `\n}\n`;
|
|
771
|
-
|
|
772
|
-
// Injection à la fin du fichier
|
|
773
|
-
await updateFile({
|
|
774
|
-
path: schemaPath,
|
|
775
|
-
pattern: /$/, // On ajoute à la fin
|
|
776
|
-
replacement: modelBlock,
|
|
777
|
-
});
|
|
766
|
+
await runCommand(
|
|
767
|
+
"npx prisma format",
|
|
768
|
+
"❌ Failed to format prisma schema — run 'npx prisma format' manually",
|
|
769
|
+
{ critical: false }
|
|
770
|
+
);
|
|
778
771
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
`npx prisma migrate dev --name add_module_${entityData.name}`,
|
|
785
|
-
);
|
|
786
|
-
} catch (err) {
|
|
787
|
-
if (err.message.includes("EPERM")) {
|
|
788
|
-
console.log(
|
|
789
|
-
`\n${warning("⚠️ System Lock:")} Could not update Prisma Client binary because your app is currently running.`,
|
|
790
|
-
);
|
|
791
|
-
console.log(
|
|
792
|
-
`${info("ACTION REQUIRED:")} Stop your dev server (Ctrl+C) and run: ${success("| npx prisma generate | and | npx prisma migrate dev |")}\n`,
|
|
793
|
-
);
|
|
794
|
-
} else {
|
|
795
|
-
logError("Prisma error: " + err.message);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
772
|
+
await runCommand(
|
|
773
|
+
"npx prisma generate",
|
|
774
|
+
"❌ Failed to generate prisma client — run 'npx prisma generate' manually",
|
|
775
|
+
{ critical: false }
|
|
776
|
+
);
|
|
798
777
|
|
|
799
|
-
|
|
800
|
-
}
|
|
778
|
+
await runCommand(
|
|
779
|
+
`npx prisma migrate dev --name add_module_${entityData.name}`,
|
|
780
|
+
`❌ Failed to run prisma migration — run 'npx prisma migrate dev --name add_module_${entityData.name}' manually`,
|
|
781
|
+
{ critical: false }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
801
784
|
|
|
802
785
|
module.exports = { setupPrisma, updatePrismaSchema };
|
|
786
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
-
const {
|
|
2
|
+
const { runCommand } = require("../shell");
|
|
3
3
|
const { logInfo } = require("../loggers/logInfo");
|
|
4
4
|
const { logSuccess } = require("../loggers/logSuccess");
|
|
5
5
|
|
|
@@ -7,13 +7,17 @@ async function setupSwagger(inputs) {
|
|
|
7
7
|
logInfo("Installation et Configuration de Swagger...");
|
|
8
8
|
|
|
9
9
|
// installation de swagger
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
await runCommand(
|
|
11
|
+
"npm install @nestjs/swagger swagger-ui-express",
|
|
12
|
+
"Échec de l'installation de Swagger",
|
|
13
|
+
{ spinner: "Installing Swagger..." }
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
17
|
+
if (isDryRun) {
|
|
18
|
+
console.log("[DRY-RUN] Simulated: write Swagger configuration to src/main.ts");
|
|
19
|
+
logSuccess(" Swagger configuré avec succès !");
|
|
20
|
+
return;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
// Modification de main.ts pour intégrer Swagger
|
package/utils/shell.js
CHANGED
|
@@ -1,32 +1,147 @@
|
|
|
1
1
|
const { execSync } = require("child_process");
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const { logError } = require("./loggers/logError");
|
|
4
|
+
const { logWarning } = require("./loggers/logWarning");
|
|
4
5
|
const { spinner } = require("./spinner");
|
|
6
|
+
const { warning, info } = require("./colors");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Accumulated warnings collected during a generation session.
|
|
10
|
+
* Displayed as a summary at the end via printSetupWarnings().
|
|
11
|
+
*/
|
|
12
|
+
const _warnings = [];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Runs a shell command synchronously.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} command - The shell command to execute.
|
|
18
|
+
* @param {string} errorMessage - Human-readable error message shown on failure.
|
|
19
|
+
* @param {object} [options] - Optional configuration.
|
|
20
|
+
* @param {boolean} [options.critical=true] - If true (default), a failure calls process.exit(1).
|
|
21
|
+
* If false, the failure is logged as a warning and execution continues.
|
|
22
|
+
* @param {string|null} [options.spinner] - Optional spinner label shown during execution.
|
|
23
|
+
* @param {number} [options.timeout] - Custom timeout in milliseconds.
|
|
24
|
+
* @returns {Promise<boolean>} Resolves to true on success, false on non-critical failure.
|
|
25
|
+
*/
|
|
26
|
+
async function runCommand(command, errorMessage, options = {}) {
|
|
27
|
+
let critical = true;
|
|
28
|
+
let spinnerText = null;
|
|
29
|
+
let customTimeout = undefined;
|
|
30
|
+
|
|
31
|
+
if (typeof options === "string") {
|
|
32
|
+
spinnerText = options;
|
|
33
|
+
} else if (typeof options === "object" && options !== null) {
|
|
34
|
+
critical = options.critical !== false;
|
|
35
|
+
spinnerText = options.spinner || null;
|
|
36
|
+
customTimeout = options.timeout;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Determine Timeout
|
|
40
|
+
let timeoutVal = 30000; // default 30 seconds
|
|
41
|
+
if (
|
|
42
|
+
command.includes("install") ||
|
|
43
|
+
command.includes("add") ||
|
|
44
|
+
command.includes(" new ") ||
|
|
45
|
+
command.includes("prisma migrate")
|
|
46
|
+
) {
|
|
47
|
+
timeoutVal = 300000; // 5 minutes for heavy setup commands
|
|
48
|
+
}
|
|
49
|
+
if (customTimeout !== undefined) {
|
|
50
|
+
timeoutVal = customTimeout;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Dry-Run Simulation Check
|
|
54
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
55
|
+
if (isDryRun) {
|
|
56
|
+
if (spinnerText) {
|
|
57
|
+
const spin = spinner(spinnerText);
|
|
58
|
+
spin.start();
|
|
59
|
+
spin.succeed(`[DRY-RUN] Simulated: ${command}`);
|
|
60
|
+
} else {
|
|
61
|
+
console.log(`[DRY-RUN] Simulated: ${command}`);
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
5
65
|
|
|
6
|
-
async function runCommand(command, errorMessage, spinnerText = null) {
|
|
7
66
|
const spin = spinnerText ? spinner(spinnerText) : null;
|
|
8
67
|
|
|
9
68
|
try {
|
|
10
69
|
if (spin) spin.start();
|
|
11
|
-
execSync(command, {
|
|
70
|
+
execSync(command, {
|
|
71
|
+
stdio: spinnerText ? "pipe" : "inherit",
|
|
72
|
+
timeout: timeoutVal,
|
|
73
|
+
});
|
|
12
74
|
if (spin) spin.succeed(spinnerText);
|
|
75
|
+
return true;
|
|
13
76
|
} catch (error) {
|
|
14
77
|
if (spin) spin.fail(errorMessage);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
|
|
78
|
+
|
|
79
|
+
let errorDetail = error.message;
|
|
80
|
+
if (error.code === "ETIMEDOUT" || error.signal === "SIGTERM") {
|
|
81
|
+
errorDetail = `Command timed out after ${timeoutVal / 1000}s`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const logLine = `[${new Date().toISOString()}] ${errorMessage}: ${errorDetail}\n`;
|
|
85
|
+
fs.appendFileSync("setup.log", logLine);
|
|
86
|
+
|
|
87
|
+
if (critical) {
|
|
88
|
+
logError(`${errorMessage}`);
|
|
89
|
+
logError(`Run: ${command}`);
|
|
90
|
+
logError(`Detail: ${errorDetail}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
} else {
|
|
93
|
+
logWarning(`${errorMessage} (non-fatal, continuing...)`);
|
|
94
|
+
logWarning(` Command: ${command}`);
|
|
95
|
+
logWarning(` Detail: ${errorDetail}`);
|
|
96
|
+
logWarning(` See setup.log for details`);
|
|
97
|
+
_warnings.push({ command, errorMessage });
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
21
100
|
}
|
|
22
101
|
}
|
|
23
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Silent command — returns stdout string or null on failure. Never exits.
|
|
105
|
+
* @param {string} command
|
|
106
|
+
* @returns {string|null}
|
|
107
|
+
*/
|
|
24
108
|
async function runCommandSilent(command) {
|
|
25
109
|
try {
|
|
26
|
-
return execSync(command, { stdio: "pipe" }).toString();
|
|
110
|
+
return execSync(command, { stdio: "pipe", timeout: 10000 }).toString();
|
|
27
111
|
} catch (error) {
|
|
28
112
|
return null;
|
|
29
113
|
}
|
|
30
114
|
}
|
|
31
115
|
|
|
32
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Prints a summary of all non-critical warnings accumulated during the session.
|
|
118
|
+
* Call this at the very end of the generation pipeline.
|
|
119
|
+
*/
|
|
120
|
+
function printSetupWarnings() {
|
|
121
|
+
if (_warnings.length === 0) return;
|
|
122
|
+
|
|
123
|
+
console.log(
|
|
124
|
+
`\n${warning("⚠️ Setup completed with")} ${warning(String(_warnings.length))} ${warning("warning(s):")}`,
|
|
125
|
+
);
|
|
126
|
+
_warnings.forEach((w, i) => {
|
|
127
|
+
console.log(` ${info(`${i + 1}.`)} ${w.errorMessage}`);
|
|
128
|
+
console.log(` ${info("Command:")} ${w.command}`);
|
|
129
|
+
});
|
|
130
|
+
console.log(
|
|
131
|
+
`\n ${info("➜")} Full details in: ${info("setup.log")}\n`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clears the warnings accumulator (useful between test runs).
|
|
137
|
+
*/
|
|
138
|
+
function clearSetupWarnings() {
|
|
139
|
+
_warnings.length = 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
runCommand,
|
|
144
|
+
runCommandSilent,
|
|
145
|
+
printSetupWarnings,
|
|
146
|
+
clearSetupWarnings,
|
|
147
|
+
};
|