nestcraftx 0.2.6 → 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.
- package/AUDIT_ROADMAP.md +798 -0
- package/CHANGELOG.fr.md +22 -0
- package/CHANGELOG.md +22 -0
- package/PROGRESS.md +258 -0
- package/commands/demo.js +4 -0
- package/commands/generate.js +5 -4
- package/commands/help.js +29 -2
- package/commands/new.js +8 -6
- package/package.json +1 -1
- package/utils/app-module.updater.js +96 -0
- package/utils/cliParser.js +0 -45
- package/utils/configs/setupCleanArchitecture.js +2 -2
- package/utils/file-system.js +75 -0
- package/utils/generators/application/dtoGenerator.js +251 -0
- package/utils/generators/database/setupDatabase.js +32 -14
- package/utils/generators/domain/entityGenerator.js +126 -0
- package/utils/generators/infrastructure/mapperGenerator.js +114 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +196 -0
- package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
- package/utils/generators/presentation/controllerGenerator.js +142 -0
- package/utils/helpers.js +109 -0
- package/utils/setups/setupAuth.js +5 -5
- package/utils/setups/setupDatabase.js +2 -1
- package/utils/setups/setupMongoose.js +22 -5
- package/utils/setups/setupPrisma.js +78 -132
- package/utils/shell.js +88 -8
- package/utils/userInput.js +37 -101
- package/utils/utils.js +29 -2164
package/utils/helpers.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/helpers.js
|
|
3
|
+
*
|
|
4
|
+
* Fonctions utilitaires de manipulation de chaînes et de types.
|
|
5
|
+
* Partagées entre les générateurs full-mode et light-mode.
|
|
6
|
+
*
|
|
7
|
+
* Ces fonctions sont purement stateless : aucune dépendance externe,
|
|
8
|
+
* aucun effet de bord, aucune logique métier.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Met en majuscule la première lettre d'une chaîne.
|
|
13
|
+
* @param {string} str
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
function capitalize(str) {
|
|
17
|
+
if (!str) return str;
|
|
18
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Met en minuscule la première lettre d'une chaîne.
|
|
23
|
+
* @param {string} str
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function decapitalize(str) {
|
|
27
|
+
if (!str) return str;
|
|
28
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Retourne le pluriel simple d'un nom.
|
|
33
|
+
* - si finit par 'y' → remplace par 'ies' (category → categories)
|
|
34
|
+
* - si finit déjà par 's' → retourne tel quel
|
|
35
|
+
* - sinon → ajoute 's'
|
|
36
|
+
* @param {string} name
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function pluralize(name) {
|
|
40
|
+
if (name.endsWith("y")) {
|
|
41
|
+
return name.slice(0, -1) + "ies";
|
|
42
|
+
} else if (name.endsWith("s")) {
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
return name + "s";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mappe le type de champ interne (sélectionné par l'utilisateur) au type
|
|
50
|
+
* TypeScript correspondant pour la génération de code.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} type - Le type de base (string, number, boolean, Date, json, etc.)
|
|
53
|
+
* @returns {string} Le type formaté TypeScript
|
|
54
|
+
*/
|
|
55
|
+
function formatType(type) {
|
|
56
|
+
switch (type.toLowerCase()) {
|
|
57
|
+
case "number":
|
|
58
|
+
case "decimal":
|
|
59
|
+
return "number";
|
|
60
|
+
|
|
61
|
+
case "boolean":
|
|
62
|
+
return "boolean";
|
|
63
|
+
|
|
64
|
+
case "date":
|
|
65
|
+
return "Date";
|
|
66
|
+
|
|
67
|
+
case "json":
|
|
68
|
+
return "Record<string, any>";
|
|
69
|
+
|
|
70
|
+
case "string":
|
|
71
|
+
case "text":
|
|
72
|
+
case "uuid":
|
|
73
|
+
return "string";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Gère les tableaux (ex: 'string[]', 'number[]')
|
|
77
|
+
if (type.endsWith("[]")) {
|
|
78
|
+
return `${formatType(type.slice(0, -2))}[]`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Laisse passer les types complexes (DTO, Enum, classe custom)
|
|
82
|
+
if (type.match(/^[A-Za-z][A-Za-z0-9_]*$/)) {
|
|
83
|
+
return type;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return "any";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Gère les cas spéciaux avant l'application de formatType.
|
|
91
|
+
* Ex: le champ 'role' de type string est traité comme le type 'Role' (enum).
|
|
92
|
+
*
|
|
93
|
+
* @param {object} field - L'objet champ { name, type }
|
|
94
|
+
* @returns {string} Le type final formaté
|
|
95
|
+
*/
|
|
96
|
+
function getFormattedType(field) {
|
|
97
|
+
if (field.name === "role" && field.type.toLowerCase().startsWith("string")) {
|
|
98
|
+
return "Role";
|
|
99
|
+
}
|
|
100
|
+
return formatType(field.type);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
capitalize,
|
|
105
|
+
decapitalize,
|
|
106
|
+
pluralize,
|
|
107
|
+
formatType,
|
|
108
|
+
getFormattedType,
|
|
109
|
+
};
|
|
@@ -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
|
-
|
|
20
|
+
`${installCmd} @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
|
|
18
21
|
"Erreur install deps auth",
|
|
19
22
|
);
|
|
20
23
|
await runCommand(
|
|
21
|
-
|
|
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
|
}
|
|
@@ -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,
|
|
@@ -68,11 +69,27 @@ async function setupMongoose(inputs) {
|
|
|
68
69
|
);
|
|
69
70
|
|
|
70
71
|
// --- Base Configuration (app.module.ts and .env) --- // Generating the .env file
|
|
71
|
-
const
|
|
72
|
-
MONGO_URI=${inputs.dbConfig.MONGO_URI}
|
|
73
|
-
MONGO_DB=${inputs.dbConfig.MONGO_DB}
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const envPath = ".env";
|
|
73
|
+
const mongoUriLine = `MONGO_URI=${inputs.dbConfig.MONGO_URI}`;
|
|
74
|
+
const mongoDbLine = `MONGO_DB=${inputs.dbConfig.MONGO_DB}`;
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(envPath)) {
|
|
77
|
+
const envContent = `${mongoUriLine}\n${mongoDbLine}\n`;
|
|
78
|
+
await createFile({ path: envPath, contente: envContent });
|
|
79
|
+
} else {
|
|
80
|
+
let content = fs.readFileSync(envPath, "utf8");
|
|
81
|
+
if (content.includes("MONGO_URI=")) {
|
|
82
|
+
content = content.replace(/MONGO_URI=.*/g, mongoUriLine);
|
|
83
|
+
} else {
|
|
84
|
+
content += `\n${mongoUriLine}`;
|
|
85
|
+
}
|
|
86
|
+
if (content.includes("MONGO_DB=")) {
|
|
87
|
+
content = content.replace(/MONGO_DB=.*/g, mongoDbLine);
|
|
88
|
+
} else {
|
|
89
|
+
content += `\n${mongoDbLine}`;
|
|
90
|
+
}
|
|
91
|
+
fs.writeFileSync(envPath, content, "utf8");
|
|
92
|
+
}
|
|
76
93
|
|
|
77
94
|
const appModulePath = path.join("src", "app.module.ts");
|
|
78
95
|
const mongooseImport = `import { MongooseModule } from '@nestjs/mongoose';`;
|
|
@@ -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`;
|
|
@@ -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) {
|
|
@@ -645,18 +655,18 @@ async function updatePrismaSchema(entityData) {
|
|
|
645
655
|
|
|
646
656
|
switch (relation.type) {
|
|
647
657
|
case "n-1": // "Many to One" : Le nouveau module appartient à une cible
|
|
648
|
-
relationFields += `\n ${targetLow} ${
|
|
658
|
+
relationFields += `\n ${targetLow} ${targetCap} @relation(fields: [${targetLow}Id], references: [id])`;
|
|
649
659
|
relationFields += `\n ${targetLow}Id String`;
|
|
650
660
|
break;
|
|
651
661
|
case "1-n": // "One to Many" : Le nouveau module possède plusieurs cibles
|
|
652
|
-
relationFields += `\n ${targetLow}s ${
|
|
662
|
+
relationFields += `\n ${targetLow}s ${targetCap}[]`;
|
|
653
663
|
break;
|
|
654
664
|
case "1-1":
|
|
655
|
-
relationFields += `\n ${targetLow} ${
|
|
665
|
+
relationFields += `\n ${targetLow} ${targetCap}? @relation(fields: [${targetLow}Id], references: [id])`;
|
|
656
666
|
relationFields += `\n ${targetLow}Id String? @unique`;
|
|
657
667
|
break;
|
|
658
668
|
case "n-n":
|
|
659
|
-
relationFields += `\n ${targetLow}s ${
|
|
669
|
+
relationFields += `\n ${targetLow}s ${targetCap}[]`;
|
|
660
670
|
break;
|
|
661
671
|
}
|
|
662
672
|
}
|
|
@@ -665,7 +675,7 @@ async function updatePrismaSchema(entityData) {
|
|
|
665
675
|
let modelBlock = `\n/**
|
|
666
676
|
* ${capitalizedName} Model
|
|
667
677
|
*/
|
|
668
|
-
model ${
|
|
678
|
+
model ${capitalizedName} {
|
|
669
679
|
id String @id @default(uuid())
|
|
670
680
|
createdAt DateTime @default(now())
|
|
671
681
|
updatedAt DateTime @updatedAt${relationFields}`;
|
|
@@ -686,117 +696,53 @@ model ${lowercasedName} {
|
|
|
686
696
|
// 4️⃣ Injection de l'INVERSE dans le modèle cible (Target)
|
|
687
697
|
if (relation) {
|
|
688
698
|
const targetCap = capitalize(relation.target);
|
|
689
|
-
const targetLow =
|
|
699
|
+
const targetLow = relation.target.toLowerCase();
|
|
690
700
|
const newCap = capitalizedName;
|
|
691
701
|
const newLow = name.toLowerCase();
|
|
692
702
|
|
|
693
703
|
let inverseField = "";
|
|
694
704
|
switch (relation.type) {
|
|
695
705
|
case "n-1": // Inverse d'un Many-to-One est un One-to-Many
|
|
696
|
-
inverseField = ` ${newLow}s ${
|
|
706
|
+
inverseField = ` ${newLow}s ${newCap}[]`;
|
|
697
707
|
break;
|
|
698
708
|
case "1-n": // Inverse d'un One-to-Many est un Many-to-One
|
|
699
|
-
inverseField = ` ${newLow} ${
|
|
709
|
+
inverseField = ` ${newLow} ${newCap} @relation(fields: [${newLow}Id], references: [id])\n ${newLow}Id String`;
|
|
700
710
|
break;
|
|
701
711
|
case "1-1":
|
|
702
|
-
inverseField = ` ${newLow} ${
|
|
712
|
+
inverseField = ` ${newLow} ${newCap}?`;
|
|
703
713
|
break;
|
|
704
714
|
case "n-n":
|
|
705
|
-
inverseField = ` ${newLow}s ${
|
|
715
|
+
inverseField = ` ${newLow}s ${newCap}[]`;
|
|
706
716
|
break;
|
|
707
717
|
}
|
|
708
718
|
|
|
709
719
|
// On utilise une RegEx pour trouver le bloc du modèle cible et insérer avant la fermeture "}"
|
|
710
720
|
await updateFile({
|
|
711
721
|
path: schemaPath,
|
|
712
|
-
pattern: new RegExp(`model ${
|
|
713
|
-
replacement: `model ${
|
|
722
|
+
pattern: new RegExp(`model ${targetCap} \\{([\\s\\S]*?)\\}`, "g"),
|
|
723
|
+
replacement: `model ${targetCap} {$1\n${inverseField}\n}`,
|
|
714
724
|
});
|
|
715
725
|
}
|
|
716
726
|
|
|
717
727
|
// 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
|
-
});
|
|
728
|
+
await runCommand(
|
|
729
|
+
"npx prisma format",
|
|
730
|
+
"❌ Failed to format prisma schema — run 'npx prisma format' manually",
|
|
731
|
+
{ critical: false }
|
|
732
|
+
);
|
|
778
733
|
|
|
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
|
-
}
|
|
734
|
+
await runCommand(
|
|
735
|
+
"npx prisma generate",
|
|
736
|
+
"❌ Failed to generate prisma client — run 'npx prisma generate' manually",
|
|
737
|
+
{ critical: false }
|
|
738
|
+
);
|
|
798
739
|
|
|
799
|
-
|
|
800
|
-
}
|
|
740
|
+
await runCommand(
|
|
741
|
+
`npx prisma migrate dev --name add_module_${entityData.name} --force`,
|
|
742
|
+
`❌ Failed to run prisma migration — run 'npx prisma migrate dev --name add_module_${entityData.name}' manually`,
|
|
743
|
+
{ critical: false }
|
|
744
|
+
);
|
|
745
|
+
}
|
|
801
746
|
|
|
802
747
|
module.exports = { setupPrisma, updatePrismaSchema };
|
|
748
|
+
|