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