nestcraftx 0.3.0 → 1.0.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/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.fr.md +74 -0
- package/CHANGELOG.md +74 -0
- package/PROGRESS.md +70 -67
- package/README.fr.md +60 -69
- package/bin/nestcraft.js +8 -0
- package/commands/demo.js +12 -46
- package/commands/generate.js +551 -2
- package/commands/generateConf.js +4 -4
- package/commands/help.js +11 -0
- package/commands/info.js +2 -3
- package/commands/list.js +93 -0
- package/commands/new.js +28 -56
- package/jest.config.js +9 -0
- package/package.json +7 -2
- package/readme.md +59 -68
- package/tests/e2e/generator.spec.js +71 -0
- package/tests/unit/appModuleUpdater.spec.js +111 -0
- package/tests/unit/cliParser.spec.js +74 -0
- package/tests/unit/controllerGenerator.spec.js +145 -0
- package/tests/unit/dtoGenerator.spec.js +87 -0
- package/tests/unit/entityGenerator.spec.js +65 -0
- package/tests/unit/generateCommand.spec.js +100 -0
- package/tests/unit/health.spec.js +69 -0
- package/tests/unit/listCommand.spec.js +91 -0
- package/tests/unit/middlewareGenerator.spec.js +64 -0
- package/tests/unit/newCommand.spec.js +88 -0
- package/tests/unit/relationCommand.spec.js +202 -0
- package/tests/unit/throttler.spec.js +117 -0
- package/utils/app-module.updater.js +6 -0
- package/utils/configs/setupCleanArchitecture.js +1 -1
- package/utils/configs/setupLightArchitecture.js +98 -26
- package/utils/envGenerator.js +9 -1
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +9 -0
- package/utils/fullModeInput.js +12 -229
- package/utils/generators/application/dtoGenerator.js +26 -8
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/cleanModuleGenerator.js +40 -11
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +114 -4
- package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
- package/utils/generators/lightModuleGenerator.js +14 -0
- package/utils/generators/presentation/controllerGenerator.js +70 -19
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/askRelationCommand.js +98 -0
- package/utils/interactive/entityBuilder.js +411 -0
- package/utils/lightModeInput.js +22 -241
- package/utils/setups/orms/typeOrmSetup.js +42 -8
- package/utils/setups/projectSetup.js +88 -10
- package/utils/setups/setupAuth.js +335 -20
- package/utils/setups/setupDatabase.js +4 -1
- package/utils/setups/setupHealth.js +74 -0
- package/utils/setups/setupMongoose.js +84 -32
- package/utils/setups/setupPrisma.js +151 -4
- package/utils/setups/setupSwagger.js +15 -8
- package/utils/setups/setupThrottler.js +92 -0
- package/utils/shell.js +61 -9
|
@@ -47,13 +47,30 @@ async function generateDto(
|
|
|
47
47
|
SWAGGER HELPERS
|
|
48
48
|
=============================== */
|
|
49
49
|
const getFieldDescription = (f) => {
|
|
50
|
+
if (f.description) return f.description;
|
|
51
|
+
|
|
50
52
|
const name = f.name.toLowerCase();
|
|
51
53
|
if (name.includes("email")) return "The official email address of the user";
|
|
52
54
|
if (name.includes("password"))
|
|
53
55
|
return "Must contain at least 8 characters, one letter and one number";
|
|
54
56
|
if (name.includes("token")) return "Authentication token";
|
|
55
|
-
if (name.
|
|
56
|
-
return `
|
|
57
|
+
if (name.endsWith("id") && name !== "id")
|
|
58
|
+
return `UUID of the referenced ${capitalize(f.name.slice(0, -2))}`;
|
|
59
|
+
if (name.endsWith("ids"))
|
|
60
|
+
return `Array of UUIDs of the referenced ${capitalize(f.name.slice(0, -3))}`;
|
|
61
|
+
|
|
62
|
+
const cleanType = f.type.toLowerCase().replace("[]", "");
|
|
63
|
+
if (cleanType === "date" || name.endsWith("date") || name.endsWith("at")) {
|
|
64
|
+
return "ISO 8601 date string (e.g. 2026-06-16T15:00:00Z)";
|
|
65
|
+
}
|
|
66
|
+
if (name.includes("price") || name.includes("amount")) {
|
|
67
|
+
return "Decimal value in USD (e.g. 99.99)";
|
|
68
|
+
}
|
|
69
|
+
if (cleanType === "boolean") {
|
|
70
|
+
const defSuffix = f.default !== undefined && f.default !== null ? ` — default: ${f.default}` : "";
|
|
71
|
+
return `true/false flag${defSuffix}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
57
74
|
return `The ${f.name} of the ${entityNameLower}`;
|
|
58
75
|
};
|
|
59
76
|
|
|
@@ -88,6 +105,7 @@ async function generateDto(
|
|
|
88
105
|
const name = f.name;
|
|
89
106
|
const cleanType = f.type.toLowerCase().replace("[]", "");
|
|
90
107
|
const isArray = f.type.endsWith("[]");
|
|
108
|
+
const isOptional = optional || f.nullable;
|
|
91
109
|
|
|
92
110
|
const SCALAR_TYPES = [
|
|
93
111
|
"string",
|
|
@@ -104,7 +122,7 @@ async function generateDto(
|
|
|
104
122
|
if (!SCALAR_TYPES.includes(cleanType)) return null;
|
|
105
123
|
|
|
106
124
|
let validators = [];
|
|
107
|
-
if (
|
|
125
|
+
if (isOptional) validators.push("@IsOptional()");
|
|
108
126
|
if (name.toLowerCase().includes("email")) validators.push("@IsEmail()");
|
|
109
127
|
else if (name.toLowerCase().includes("password"))
|
|
110
128
|
validators.push(
|
|
@@ -141,7 +159,7 @@ async function generateDto(
|
|
|
141
159
|
|
|
142
160
|
let swaggerDecorator = "";
|
|
143
161
|
if (useSwagger && !forceNoSwagger) {
|
|
144
|
-
const decorator =
|
|
162
|
+
const decorator = isOptional ? "@ApiPropertyOptional" : "@ApiProperty";
|
|
145
163
|
const options = JSON.stringify(
|
|
146
164
|
{
|
|
147
165
|
example: getExampleForField(f),
|
|
@@ -154,7 +172,7 @@ async function generateDto(
|
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
return `${swaggerDecorator}${validators.join("\n ")}\n ${name}${
|
|
157
|
-
|
|
175
|
+
isOptional ? "?" : "!"
|
|
158
176
|
}: ${formatType(f.type)};`;
|
|
159
177
|
};
|
|
160
178
|
|
|
@@ -169,7 +187,7 @@ async function generateDto(
|
|
|
169
187
|
|
|
170
188
|
return `import {
|
|
171
189
|
IsString, IsInt, IsBoolean, IsEmail, IsArray,
|
|
172
|
-
IsUUID, IsDateString, MinLength, IsOptional
|
|
190
|
+
IsUUID, IsDateString, MinLength, IsOptional, IsNumber
|
|
173
191
|
} from 'class-validator';
|
|
174
192
|
${useSwagger ? "import { ApiProperty } from '@nestjs/swagger';" : ""}
|
|
175
193
|
|
|
@@ -200,7 +218,7 @@ export class ${entityName}Dto {
|
|
|
200
218
|
if (entityName === "User") swaggerImports.push("ApiHideProperty");
|
|
201
219
|
|
|
202
220
|
return `import {
|
|
203
|
-
IsOptional, IsString, IsInt, IsBoolean, IsEmail,
|
|
221
|
+
IsOptional, IsString, IsInt, IsNumber, IsBoolean, IsEmail,
|
|
204
222
|
IsArray, IsUUID, IsDateString, MinLength, IsEnum
|
|
205
223
|
} from 'class-validator';
|
|
206
224
|
${
|
|
@@ -228,7 +246,7 @@ export class Create${entityName}Dto {
|
|
|
228
246
|
}
|
|
229
247
|
|
|
230
248
|
/**
|
|
231
|
-
* DTO for updating a ${entityName}
|
|
249
|
+
* DTO for updating a ${entityName} (All fields are optional)
|
|
232
250
|
*/
|
|
233
251
|
${
|
|
234
252
|
useSwagger
|
|
@@ -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`;
|
|
@@ -64,7 +64,7 @@ async function generateCleanModule(name, config, entityData) {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
// 4. Génération du Repository (Implementation)
|
|
67
|
-
await generateRepository(name, config.orm);
|
|
67
|
+
await generateRepository(name, config.orm, entityData);
|
|
68
68
|
|
|
69
69
|
// 5. Génération des Use Cases (Create, Get, Update, Delete)
|
|
70
70
|
await generateUseCases(entityPath, entityNameCapitalized, entityNameLower);
|
|
@@ -83,10 +83,25 @@ async function generateCleanModule(name, config, entityData) {
|
|
|
83
83
|
contente: mapperContent,
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
+
if (entityNameLower === "user") {
|
|
87
|
+
await createDirectory(`${entityPath}/domain/enums`);
|
|
88
|
+
await createFile({
|
|
89
|
+
path: `${entityPath}/domain/enums/role.enum.ts`,
|
|
90
|
+
contente: `// Enumération des rôles utilisateurs
|
|
91
|
+
export enum Role {
|
|
92
|
+
USER = 'USER',
|
|
93
|
+
ADMIN = 'ADMIN',
|
|
94
|
+
SUPER_ADMIN = 'SUPER_ADMIN',
|
|
95
|
+
}
|
|
96
|
+
`,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
// ÉTAPE RELATIONS : Patching des fichiers si une relation existe
|
|
87
101
|
if (
|
|
88
102
|
entityData.relation &&
|
|
89
|
-
|
|
103
|
+
entityData.relation.type !== "1-n" &&
|
|
104
|
+
entityData.relation.type !== "n-n"
|
|
90
105
|
) {
|
|
91
106
|
const { target, type } = entityData.relation;
|
|
92
107
|
logInfo(
|
|
@@ -114,6 +129,7 @@ async function generateCleanModule(name, config, entityData) {
|
|
|
114
129
|
name,
|
|
115
130
|
entityPath,
|
|
116
131
|
config.swagger,
|
|
132
|
+
config.auth || false,
|
|
117
133
|
);
|
|
118
134
|
await createFile({
|
|
119
135
|
path: `${entityPath}/presentation/controllers/${entityNameLower}.controller.ts`,
|
|
@@ -146,6 +162,10 @@ async function generateCleanModule(name, config, entityData) {
|
|
|
146
162
|
// --- HELPERS DE TEMPLATES (Extraits de ton setup original) ---
|
|
147
163
|
|
|
148
164
|
function getRepositoryInterfaceTemplate(cap, low) {
|
|
165
|
+
let findByEmailMethod = "";
|
|
166
|
+
if (low === "user") {
|
|
167
|
+
findByEmailMethod = `\n findByEmail(email: string): Promise<${cap}Entity | null>;`;
|
|
168
|
+
}
|
|
149
169
|
return `import { Create${cap}Dto, Update${cap}Dto } from 'src/${low}/application/dtos/${low}.dto';
|
|
150
170
|
import { ${cap}Entity } from 'src/${low}/domain/entities/${low}.entity';
|
|
151
171
|
|
|
@@ -154,9 +174,9 @@ export const I${cap}RepositoryName = 'I${cap}Repository';
|
|
|
154
174
|
export interface I${cap}Repository {
|
|
155
175
|
create(data: Create${cap}Dto): Promise<${cap}Entity>;
|
|
156
176
|
findById(id: string): Promise<${cap}Entity | null>;
|
|
157
|
-
findAll(): Promise
|
|
177
|
+
findAll(page?: number, limit?: number, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${cap}Entity[], total: number }>;
|
|
158
178
|
update(id: string, data: Update${cap}Dto): Promise<${cap}Entity | null>;
|
|
159
|
-
delete(id: string): Promise<void
|
|
179
|
+
delete(id: string): Promise<void>;${findByEmailMethod}
|
|
160
180
|
}`;
|
|
161
181
|
}
|
|
162
182
|
|
|
@@ -237,11 +257,20 @@ export class GetAll${cap}UseCase {
|
|
|
237
257
|
private readonly repository: I${cap}Repository,
|
|
238
258
|
) {}
|
|
239
259
|
|
|
240
|
-
async execute(): Promise
|
|
241
|
-
this.logger.log(
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
260
|
+
async execute(page: number = 1, limit: number = 10, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${cap}Entity[], meta: { total: number, page: number, limit: number, totalPages: number } }> {
|
|
261
|
+
this.logger.log(\`Requesting page \${page} of ${cap} records (limit: \${limit}, search: \${search}, sortBy: \${sortBy}, sortOrder: \${sortOrder})\`);
|
|
262
|
+
const { data, total } = await this.repository.findAll(page, limit, search, sortBy, sortOrder);
|
|
263
|
+
const totalPages = Math.ceil(total / limit);
|
|
264
|
+
this.logger.log(\`Retrieved \${data.length} of \${total} ${cap}(s)\`);
|
|
265
|
+
return {
|
|
266
|
+
data,
|
|
267
|
+
meta: {
|
|
268
|
+
total,
|
|
269
|
+
page,
|
|
270
|
+
limit,
|
|
271
|
+
totalPages,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
245
274
|
}
|
|
246
275
|
}`;
|
|
247
276
|
break;
|
|
@@ -430,8 +459,8 @@ export class ${entityNameCapitalized}Service {
|
|
|
430
459
|
async getById(id: string): Promise<${entityNameCapitalized}Entity | null> {
|
|
431
460
|
return await this.getByIdUseCase.execute(id);
|
|
432
461
|
}
|
|
433
|
-
async getAll(): Promise
|
|
434
|
-
return await this.getAllUseCase.execute();
|
|
462
|
+
async getAll(page: number = 1, limit: number = 10, search?: string, sortBy?: string, sortOrder?: string): Promise<{ data: ${entityNameCapitalized}Entity[], meta: { total: number, page: number, limit: number, totalPages: number } }> {
|
|
463
|
+
return await this.getAllUseCase.execute(page, limit, search, sortBy, sortOrder);
|
|
435
464
|
}
|
|
436
465
|
async delete(id: string): Promise<void> {
|
|
437
466
|
return await this.deleteUseCase.execute(id);
|
|
@@ -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") {
|