crypt-express-app 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.
@@ -0,0 +1,677 @@
1
+ // generate-module.ts
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ // get module name from command line
5
+ const args = process.argv.slice(2);
6
+ // Check that exactly **one argument** is provided
7
+ if (args.length !== 1) {
8
+ console.error('Invalid module name: provide exactly one module name without spaces.');
9
+ console.error('Example: npm run generate-module clientRole');
10
+ process.exit(1);
11
+ }
12
+ const moduleInput = args[0];
13
+ // Reject if input contains spaces
14
+ if (/\s/.test(moduleInput)) {
15
+ console.error('Invalid module name: spaces are not allowed. Use camelCase, kebab-case or snake_case.');
16
+ process.exit(1);
17
+ }
18
+ if (!moduleInput) {
19
+ console.error('Please provide a module name: npm run generate-module <module-name>');
20
+ process.exit(1);
21
+ }
22
+ // Convert any input to camelCase
23
+ const moduleName = moduleInput.includes('-') || moduleInput.includes('_') || moduleInput.includes(' ')
24
+ ? moduleInput
25
+ .toLowerCase()
26
+ .replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase())
27
+ : moduleInput; // preserve existing camelCase
28
+ console.log("moduleName: ", moduleName);
29
+ // Convert any input (camelCase, PascalCase, snake_case, spaces) to kebab-case
30
+ const fileName = moduleName
31
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // add dash before uppercase letters
32
+ .toLowerCase(); // lowercase everything
33
+ console.log("fileName: ", fileName);
34
+ // const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
35
+ // const basePath = path.join(__dirname, '..', 'src', 'modules', fileName);
36
+ const projectRoot = process.cwd();
37
+ const modulesRoot = path.join(projectRoot, "src", "modules");
38
+ const basePath = path.join(modulesRoot, fileName);
39
+ // ensure modules directory exists
40
+ if (!fs.existsSync(modulesRoot)) {
41
+ fs.mkdirSync(modulesRoot, { recursive: true });
42
+ }
43
+ // prevent overwrite
44
+ if (fs.existsSync(basePath)) {
45
+ console.error(`❌ Module '${fileName}' already exists.`);
46
+ process.exit(1);
47
+ }
48
+ fs.mkdirSync(basePath, { recursive: true });
49
+ // Create folder
50
+ if (!fs.existsSync(basePath)) {
51
+ fs.mkdirSync(basePath, { recursive: true });
52
+ }
53
+ // Capitalize first letter
54
+ const ModuleClassName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
55
+ // Service
56
+ const serviceContent = `import { Create${ModuleClassName}Dto, Update${ModuleClassName}Dto, Create${ModuleClassName}Response, Update${ModuleClassName}Response, Get${ModuleClassName}Response, List${ModuleClassName}Response } from "./${fileName}.dto";
57
+ import { Prisma } from "@prisma/client";
58
+ import prisma from '../../prisma/client';
59
+ import { PaginationRequestDto } from "../common/common.dto";
60
+
61
+
62
+ export class ${ModuleClassName}Service {
63
+ constructor() {}
64
+
65
+ async create(payload: Create${ModuleClassName}Dto): Promise<Create${ModuleClassName}Response> {
66
+ try {
67
+ // TODO: create ${ModuleClassName} in database
68
+ return {
69
+ ${moduleName}Id: "generated-${moduleName}Id",
70
+ ...payload,
71
+ createdAt: new Date().toISOString(),
72
+ updatedAt: new Date().toISOString(),
73
+ };
74
+ } catch (error) {
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ async update(${moduleName}Id: string, payload: Update${ModuleClassName}Dto): Promise<Update${ModuleClassName}Response> {
80
+ try {
81
+ // TODO: update ${ModuleClassName} by ${moduleName}Id
82
+ return {
83
+ ${moduleName}Id,
84
+ ...payload,
85
+ updatedAt: new Date().toISOString(),
86
+ };
87
+ } catch (error) {
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ async getById(${moduleName}Id: string): Promise<Get${ModuleClassName}Response> {
93
+ try {
94
+ // TODO: fetch ${ModuleClassName} by ${moduleName}Id
95
+ return {
96
+ ${moduleName}Id,
97
+ name: "Sample ${ModuleClassName}",
98
+ createdAt: new Date().toISOString(),
99
+ updatedAt: new Date().toISOString(),
100
+ };
101
+ } catch (error) {
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ async list({
107
+ offset = 0,
108
+ limit = 20,
109
+ search,
110
+ sortBy = "createdAt",
111
+ sortOrder = "desc",
112
+ }: PaginationRequestDto): Promise<{ items: List${ModuleClassName}Response; total: number }> {
113
+ try {
114
+ // TODO: fetch list of ${ModuleClassName}
115
+ return {
116
+ total: 1,
117
+ items: [
118
+ {
119
+ ${moduleName}Id: "1",
120
+ name: "Sample ${ModuleClassName} 1",
121
+ createdAt: new Date().toISOString(),
122
+ updatedAt: new Date().toISOString(),
123
+ },
124
+ ]
125
+ }
126
+ } catch (error) {
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ async delete(${moduleName}Id: string): Promise<void> {
132
+ try {
133
+ // TODO: delete ${ModuleClassName} by ${moduleName}Id
134
+ return;
135
+ } catch (error) {
136
+ throw error;
137
+ }
138
+ }
139
+ }
140
+
141
+ export default new ${ModuleClassName}Service()
142
+ `;
143
+ fs.writeFileSync(path.join(basePath, `${fileName}.service.ts`), serviceContent);
144
+ // Controller
145
+ const controllerContent = `
146
+ import { Request, Response } from 'express';
147
+ import { ${ModuleClassName}Service } from './${fileName}.service';
148
+ import {
149
+ Create${ModuleClassName}Dto,
150
+ Update${ModuleClassName}Dto,
151
+ Create${ModuleClassName}ResponseDto,
152
+ Update${ModuleClassName}ResponseDto,
153
+ Get${ModuleClassName}ResponseDto,
154
+ List${ModuleClassName}ResponseDto
155
+ } from './${fileName}.dto';
156
+ import { PaginationRequestDto } from '../common/common.dto';
157
+
158
+ export class ${ModuleClassName}Controller {
159
+ constructor(private ${moduleName}Service: ${ModuleClassName}Service) {}
160
+
161
+ async create(req: Request, res: Response) {
162
+ try {
163
+ const payload: Create${ModuleClassName}Dto = req.body;
164
+ const data = await this.${moduleName}Service.create(payload);
165
+ const response: Create${ModuleClassName}ResponseDto = {
166
+ success: true,
167
+ status: 201,
168
+ message: '${ModuleClassName} created successfully',
169
+ data,
170
+ };
171
+ res.status(201).json(response);
172
+ } catch (error: any) {
173
+ res.status(500).json({ success: false, status: 500, message: error.message });
174
+ }
175
+ }
176
+
177
+ async update(req: Request, res: Response) {
178
+ try {
179
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
180
+ const payload: Update${ModuleClassName}Dto = req.body;
181
+ const data = await this.${moduleName}Service.update(${moduleName}Id, payload);
182
+ const response: Update${ModuleClassName}ResponseDto = {
183
+ success: true,
184
+ status: 200,
185
+ message: '${ModuleClassName} updated successfully',
186
+ data,
187
+ };
188
+ res.json(response);
189
+ } catch (error: any) {
190
+ res.status(500).json({ success: false, status: 500, message: error.message });
191
+ }
192
+ }
193
+
194
+ async getById(req: Request, res: Response) {
195
+ try {
196
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
197
+ const data = await this.${moduleName}Service.getById(${moduleName}Id);
198
+ const response: Get${ModuleClassName}ResponseDto = {
199
+ success: true,
200
+ status: 200,
201
+ message: '${ModuleClassName} fetched successfully',
202
+ data,
203
+ };
204
+ res.json(response);
205
+ } catch (error: any) {
206
+ res.status(404).json({ success: false, status: 404, message: error.message });
207
+ }
208
+ }
209
+
210
+ async list(req: Request, res: Response) {
211
+ try {
212
+ const query = req.query as PaginationRequestDto;
213
+
214
+ const offset = query.offset ? Number(query.offset) : 0;
215
+ const limit = query.limit ? Number(query.limit) : 20;
216
+ const search = query.search;
217
+ const sortBy = query.sortBy || "createdAt";
218
+ const sortOrder = query.sortOrder || "desc";
219
+ const { items, total } =
220
+ await this.${moduleName}Service.list({
221
+ offset,
222
+ limit,
223
+ search,
224
+ sortBy,
225
+ sortOrder,
226
+ });
227
+
228
+ const response: List${ModuleClassName}ResponseDto = {
229
+ success: true,
230
+ status: 200,
231
+ message: "${ModuleClassName} list fetched successfully",
232
+ data: {
233
+ items,
234
+ total,
235
+ offset,
236
+ limit,
237
+ },
238
+ };
239
+ res.json(response);
240
+ } catch (error: any) {
241
+ res.status(500).json({ success: false, status: 500, message: error.message });
242
+ }
243
+ }
244
+
245
+ async delete(req: Request, res: Response) {
246
+ try {
247
+ const ${moduleName}Id = req.params.${moduleName}Id as string;
248
+ await this.${moduleName}Service.delete(${moduleName}Id);
249
+ res.json({
250
+ success: true,
251
+ status: 200,
252
+ message: '${ModuleClassName} deleted successfully',
253
+ });
254
+ } catch (error: any) {
255
+ res.status(500).json({ success: false, status: 500, message: error.message });
256
+ }
257
+ }
258
+ }
259
+ `;
260
+ fs.writeFileSync(path.join(basePath, `${fileName}.controller.ts`), controllerContent);
261
+ // Routes
262
+ const routesContent = `
263
+ import express, { Router } from 'express';
264
+ import { ${ModuleClassName}Controller } from './${fileName}.controller';
265
+ import { ${moduleName}Middleware } from './${fileName}.middlewares';
266
+ import { authMiddleware } from '../../middlewares/auth.middleware';
267
+ import ${moduleName}Service from './${fileName}.service';
268
+ import { ActionsType, Resource, ResourceType } from '../../utils/consts';
269
+ import { authorizationMiddleware } from '../../middlewares/authorization.middleware';
270
+
271
+ const router: Router = express.Router();
272
+ const controller = new ${ModuleClassName}Controller(${moduleName}Service);
273
+ router.use(authMiddleware);
274
+ const guard = (actions: ActionsType[]) => authorizationMiddleware({ resource: Resource.COMMON, actions, resourceType: ResourceType.API_ENDPOINT });
275
+
276
+ /**
277
+ * @openapi
278
+ * tags:
279
+ * - name: ${ModuleClassName}
280
+ * description: ${ModuleClassName} management and operations
281
+ */
282
+
283
+ /**
284
+ * @openapi
285
+ * /api/${moduleName}:
286
+ * post:
287
+ * tags: [${ModuleClassName}]
288
+ * summary: Create a new ${ModuleClassName}
289
+ * requestBody:
290
+ * required: true
291
+ * content:
292
+ * application/json:
293
+ * schema:
294
+ * $ref: '#/components/schemas/Create${ModuleClassName}Dto'
295
+ * responses:
296
+ * 201:
297
+ * description: ${ModuleClassName} created successfully
298
+ * content:
299
+ * application/json:
300
+ * schema:
301
+ * $ref: '#/components/schemas/Create${ModuleClassName}ResponseDto'
302
+ */
303
+ router.post('/', ${moduleName}Middleware, guard([ActionsType.CREATE]), controller.create.bind(controller));
304
+
305
+ /**
306
+ * @openapi
307
+ * /api/${moduleName}/{${moduleName}Id}:
308
+ * get:
309
+ * tags: [${ModuleClassName}]
310
+ * summary: Get a ${ModuleClassName} by ID
311
+ * parameters:
312
+ * - name: ${moduleName}Id
313
+ * in: path
314
+ * required: true
315
+ * schema:
316
+ * type: string
317
+ * responses:
318
+ * 200:
319
+ * description: ${ModuleClassName} fetched successfully
320
+ * content:
321
+ * application/json:
322
+ * schema:
323
+ * $ref: '#/components/schemas/Get${ModuleClassName}ResponseDto'
324
+ */
325
+ router.get('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.READ]), controller.getById.bind(controller));
326
+
327
+ /**
328
+ * @openapi
329
+ * /api/${moduleName}/{${moduleName}Id}:
330
+ * put:
331
+ * tags: [${ModuleClassName}]
332
+ * summary: Update a ${ModuleClassName} by ID
333
+ * parameters:
334
+ * - name: ${moduleName}Id
335
+ * in: path
336
+ * required: true
337
+ * schema:
338
+ * type: string
339
+ * requestBody:
340
+ * required: true
341
+ * content:
342
+ * application/json:
343
+ * schema:
344
+ * $ref: '#/components/schemas/Update${ModuleClassName}Dto'
345
+ * responses:
346
+ * 200:
347
+ * description: ${ModuleClassName} updated successfully
348
+ * content:
349
+ * application/json:
350
+ * schema:
351
+ * $ref: '#/components/schemas/Update${ModuleClassName}ResponseDto'
352
+ */
353
+ router.put('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.UPDATE]), controller.update.bind(controller));
354
+
355
+ /**
356
+ * @openapi
357
+ * /api/${moduleName}/{${moduleName}Id}:
358
+ * delete:
359
+ * tags: [${ModuleClassName}]
360
+ * summary: Delete a ${ModuleClassName} by ID
361
+ * parameters:
362
+ * - name: ${moduleName}Id
363
+ * in: path
364
+ * required: true
365
+ * schema:
366
+ * type: string
367
+ * responses:
368
+ * 200:
369
+ * description: ${ModuleClassName} deleted successfully
370
+ * content:
371
+ * application/json:
372
+ * schema:
373
+ * type: object
374
+ * properties:
375
+ * success:
376
+ * type: boolean
377
+ * status:
378
+ * type: integer
379
+ * message:
380
+ * type: string
381
+ */
382
+ router.delete('/:${moduleName}Id', ${moduleName}Middleware, guard([ActionsType.DELETE]), controller.delete.bind(controller));
383
+
384
+ /**
385
+ * @openapi
386
+ * /api/${moduleName}:
387
+ * get:
388
+ * tags: [${ModuleClassName}]
389
+ * summary: List all ${ModuleClassName}s with pagination
390
+ * parameters:
391
+ * - in: query
392
+ * name: offset
393
+ * schema:
394
+ * type: integer
395
+ * description: Offset for pagination
396
+ * - in: query
397
+ * name: limit
398
+ * schema:
399
+ * type: integer
400
+ * description: Limit for pagination
401
+ * - in: query
402
+ * name: search
403
+ * schema:
404
+ * type: string
405
+ * description: Search keyword
406
+ * - in: query
407
+ * name: sortBy
408
+ * schema:
409
+ * type: string
410
+ * description: Field to sort by
411
+ * - in: query
412
+ * name: sortOrder
413
+ * schema:
414
+ * type: string
415
+ * enum: [asc, desc]
416
+ * description: Sort order
417
+ * responses:
418
+ * 200:
419
+ * description: List of ${ModuleClassName}s
420
+ * content:
421
+ * application/json:
422
+ * schema:
423
+ * $ref: '#/components/schemas/List${ModuleClassName}ResponseDto'
424
+ */
425
+ router.get('/', ${moduleName}Middleware, guard([ActionsType.READ_ALL]), controller.list.bind(controller));
426
+
427
+ /**
428
+ * ======================================================
429
+ * Optional: Health Check
430
+ * ======================================================
431
+ */
432
+ router.get('/health/check', (_req, res) => {
433
+ res.json({
434
+ success: true,
435
+ status: 200,
436
+ message: "${ModuleClassName} module is healthy",
437
+ });
438
+ });
439
+
440
+ export default router;
441
+ `;
442
+ fs.writeFileSync(path.join(basePath, `${fileName}.routes.ts`), routesContent);
443
+ const dtoContent = `import { ApiResponse, PaginatedData } from "../common/common.dto";
444
+
445
+ /**
446
+ * Main ${ModuleClassName} entity
447
+ *
448
+ * @openapi
449
+ * components:
450
+ * schemas:
451
+ * ${ModuleClassName}:
452
+ * type: object
453
+ * required:
454
+ * - name
455
+ * properties:
456
+ * ${moduleName}Id:
457
+ * type: string
458
+ * example: "c3f2a9b4-8d21-4f3b-a91c-1a2b3c4d5e6f"
459
+ * description: Unique identifier of the ${ModuleClassName}
460
+ * name:
461
+ * type: string
462
+ * example: Sample ${ModuleClassName}
463
+ * description: Name of the ${ModuleClassName}
464
+ * description:
465
+ * type: string
466
+ * example: Optional description
467
+ * nullable: true
468
+ * description: Description of the ${ModuleClassName}
469
+ * createdAt:
470
+ * type: string
471
+ * format: date-time
472
+ * example: "2025-01-01T10:00:00.000Z"
473
+ * description: Creation timestamp
474
+ * updatedAt:
475
+ * type: string
476
+ * format: date-time
477
+ * example: "2025-01-02T12:00:00.000Z"
478
+ * description: Last update timestamp
479
+ */
480
+ export interface ${ModuleClassName} {
481
+ ${moduleName}Id: string;
482
+ name: string;
483
+ description?: string;
484
+ createdAt: string;
485
+ updatedAt: string;
486
+ }
487
+
488
+ /**
489
+ * @openapi
490
+ * components:
491
+ * schemas:
492
+ * Create${ModuleClassName}Dto:
493
+ * type: object
494
+ * required:
495
+ * - name
496
+ * properties:
497
+ * name:
498
+ * type: string
499
+ * example: Sample ${ModuleClassName}
500
+ * description: Name of the ${ModuleClassName}
501
+ * description:
502
+ * type: string
503
+ * example: Sample description
504
+ */
505
+ export interface Create${ModuleClassName}Dto {
506
+ name: string;
507
+ description?: string;
508
+ }
509
+
510
+ /**
511
+ * @openapi
512
+ * components:
513
+ * schemas:
514
+ * Update${ModuleClassName}Dto:
515
+ * type: object
516
+ * properties:
517
+ * name:
518
+ * type: string
519
+ * example: Updated ${ModuleClassName} Name
520
+ * description:
521
+ * type: string
522
+ * example: Updated description
523
+ */
524
+ export interface Update${ModuleClassName}Dto {
525
+ name?: string;
526
+ description?: string;
527
+ }
528
+
529
+ /**
530
+ * ======================================================
531
+ * Service return types (data only, controller wraps response)
532
+ * ======================================================
533
+ */
534
+
535
+ // data returned from service
536
+ export type Create${ModuleClassName}Response = Partial<${ModuleClassName}>;
537
+ export type Update${ModuleClassName}Response = Partial<${ModuleClassName}>;
538
+ export type Get${ModuleClassName}Response = Partial<${ModuleClassName}>;
539
+ export type List${ModuleClassName}Response = Partial<${ModuleClassName}>[];
540
+
541
+ /**
542
+ * ======================================================
543
+ * API Response DTOs (Swagger documented)
544
+ * ======================================================
545
+ */
546
+
547
+ /**
548
+ * @openapi
549
+ * components:
550
+ * schemas:
551
+ * Create${ModuleClassName}ResponseDto:
552
+ * allOf:
553
+ * - $ref: '#/components/schemas/ApiResponse'
554
+ * - type: object
555
+ * properties:
556
+ * data:
557
+ * $ref: '#/components/schemas/${ModuleClassName}'
558
+ */
559
+ export interface Create${ModuleClassName}ResponseDto
560
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
561
+
562
+ /**
563
+ * @openapi
564
+ * components:
565
+ * schemas:
566
+ * Update${ModuleClassName}ResponseDto:
567
+ * allOf:
568
+ * - $ref: '#/components/schemas/ApiResponse'
569
+ * - type: object
570
+ * properties:
571
+ * data:
572
+ * $ref: '#/components/schemas/${ModuleClassName}'
573
+ */
574
+ export interface Update${ModuleClassName}ResponseDto
575
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
576
+
577
+ /**
578
+ * @openapi
579
+ * components:
580
+ * schemas:
581
+ * Get${ModuleClassName}ResponseDto:
582
+ * allOf:
583
+ * - $ref: '#/components/schemas/ApiResponse'
584
+ * - type: object
585
+ * properties:
586
+ * data:
587
+ * $ref: '#/components/schemas/${ModuleClassName}'
588
+ */
589
+ export interface Get${ModuleClassName}ResponseDto
590
+ extends ApiResponse<Partial<${ModuleClassName}>> {}
591
+
592
+ /**
593
+ * @openapi
594
+ * components:
595
+ * schemas:
596
+ * List${ModuleClassName}ResponseDto:
597
+ * allOf:
598
+ * - $ref: '#/components/schemas/ApiResponse'
599
+ * - type: object
600
+ * properties:
601
+ * data:
602
+ * type: object
603
+ * properties:
604
+ * items:
605
+ * type: array
606
+ * items:
607
+ * $ref: '#/components/schemas/${ModuleClassName}'
608
+ * total:
609
+ * type: integer
610
+ * offset:
611
+ * type: integer
612
+ * limit:
613
+ * type: integer
614
+ */
615
+ export interface List${ModuleClassName}ResponseDto
616
+ extends ApiResponse<PaginatedData<Partial<${ModuleClassName}>>> {}
617
+ `;
618
+ fs.writeFileSync(path.join(basePath, `${fileName}.dto.ts`), dtoContent);
619
+ // Middleware
620
+ const middlewareContent = `
621
+ import { Request, Response, NextFunction } from "express";
622
+ import { cacheService } from "../../utils/redis";
623
+ import { CachedUserProfilePermissionDto } from "../../utils/redis.dto";
624
+
625
+ export async function ${moduleName}Middleware(req: Request, res: Response, next: NextFunction) {
626
+ try {
627
+ const { userId } = req.user!
628
+ const profile = await cacheService.get<CachedUserProfilePermissionDto>(\`user:\${userId}:permissions\`);
629
+ if(!profile) throw new Error("No permission found")
630
+ const userPermissions = profile;
631
+
632
+ const method = req.method;
633
+ const url = req.originalUrl;
634
+ const path = req.path;
635
+ const ${moduleName}Id = req?.params?.${moduleName}Id
636
+
637
+ console.log("---- REALM MIDDLEWARE ----");
638
+ console.log("Method:", method);
639
+ console.log("URL:", url);
640
+ console.log("Path:", path);
641
+
642
+ // Example condition checking
643
+ if (method === "POST" && path === "/") {
644
+ console.log("👉 Creating new realm");
645
+ }
646
+
647
+ if (method === "GET" && path === "/") {
648
+ console.log("👉 Listing realms");
649
+ }
650
+
651
+ if (method === "GET" && ${moduleName}Id) {
652
+ console.log("👉 Getting realm by ID:", ${moduleName}Id);
653
+ }
654
+
655
+ if (method === "PUT") {
656
+ console.log("👉 Updating realm");
657
+ }
658
+
659
+ if (method === "DELETE") {
660
+ console.log("👉 Deleting realm");
661
+ }
662
+
663
+ next();
664
+ } catch (error) {
665
+ return res.status(500).json({
666
+ message: "Authorization error",
667
+ });
668
+ }
669
+ }
670
+ `;
671
+ fs.writeFileSync(path.join(basePath, `${fileName}.middlewares.ts`), middlewareContent);
672
+ const routerIndexPath = path.join(projectRoot, "src", "routes.ts");
673
+ if (fs.existsSync(routerIndexPath)) {
674
+ fs.appendFileSync(routerIndexPath, `\nimport ${moduleName}Routes from "./modules/${fileName}/${fileName}.routes";`);
675
+ fs.appendFileSync(routerIndexPath, `\nrouter.use("/${moduleName}", ${moduleName}Routes);\n`);
676
+ }
677
+ console.log(`Module '${moduleName}' generated successfully at ${basePath}`);