khushikumarigupta14-express-generator-cli 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/bin/cli.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initProject } from '../lib/commands/init.js';
4
+ import { generateModule } from '../lib/commands/generate.js';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('my-express-gen')
10
+ .description('CLI to scaffold Express apps and modules')
11
+ .version('1.0.0');
12
+
13
+ program.command('init')
14
+ .description('Initialize a new project')
15
+ .argument('<project-name>', 'project name')
16
+ .action(initProject);
17
+
18
+ const generate = program.command('generate')
19
+ .alias('g')
20
+ .description('Generate components');
21
+
22
+ generate.command('module')
23
+ .argument('<module-name>', 'name of the module')
24
+ .action(generateModule);
25
+
26
+ program.parse();
@@ -0,0 +1,456 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ const toCamelCase = (s) => s.replace(/-./g, (x) => x[1].toUpperCase());
6
+ const toPascalCase = (s) => s.charAt(0).toUpperCase() + toCamelCase(s.slice(1));
7
+
8
+ export const generateModule = (moduleName) => {
9
+ if (!moduleName) {
10
+ console.error(chalk.red('Please provide a module name'));
11
+ process.exit(1);
12
+ }
13
+
14
+ const ModuleName = toPascalCase(moduleName);
15
+ const moduleNameCamel = toCamelCase(moduleName);
16
+
17
+ // Assume we are in the project root
18
+ const srcPath = path.join(process.cwd(), 'src');
19
+ const modulesPath = path.join(srcPath, 'modules');
20
+ const modulePath = path.join(modulesPath, moduleName);
21
+
22
+ if (!fs.existsSync(srcPath)) {
23
+ console.error(chalk.red('Error: src directory not found. Are you in the project root?'));
24
+ process.exit(1);
25
+ }
26
+
27
+ if (fs.existsSync(modulePath)) {
28
+ console.error(chalk.red(`Module ${moduleName} already exists`));
29
+ process.exit(1);
30
+ }
31
+
32
+ // Create module directory
33
+ fs.mkdirSync(modulePath, { recursive: true });
34
+
35
+ const templates = {
36
+ docs: `import { create${ModuleName}Schema, update${ModuleName}Schema } from './${moduleName}.validation';
37
+
38
+ export const ${moduleNameCamel}Docs = {
39
+ getAll: {
40
+ summary: 'Get all ${moduleName}s',
41
+ description: 'Retrieve a list of all ${moduleName}s with optional filtering, sorting, and pagination',
42
+ tags: ['${ModuleName}'],
43
+ responses: {
44
+ 200: { description: '${ModuleName}s retrieved successfully' }
45
+ }
46
+ },
47
+ getById: {
48
+ summary: 'Get ${moduleName} by ID',
49
+ description: 'Retrieve a specific ${moduleName} by its ID',
50
+ tags: ['${ModuleName}'],
51
+ responses: {
52
+ 200: { description: '${ModuleName} retrieved successfully' },
53
+ 404: { description: '${ModuleName} not found' }
54
+ }
55
+ },
56
+ create: {
57
+ summary: 'Create a new ${moduleName}',
58
+ description: 'Create a new ${moduleName} with the provided data',
59
+ tags: ['${ModuleName}'],
60
+ request: { body: { schema: create${ModuleName}Schema.shape.body } },
61
+ responses: {
62
+ 201: { description: '${ModuleName} created successfully' },
63
+ 400: { description: 'Invalid request data' }
64
+ }
65
+ },
66
+ update: {
67
+ summary: 'Update ${moduleName}',
68
+ description: 'Update an existing ${moduleName} by ID',
69
+ tags: ['${ModuleName}'],
70
+ request: { body: { schema: update${ModuleName}Schema.shape.body } },
71
+ responses: {
72
+ 200: { description: '${ModuleName} updated successfully' },
73
+ 404: { description: '${ModuleName} not found' }
74
+ }
75
+ },
76
+ softDelete: {
77
+ summary: 'Soft delete ${moduleName}',
78
+ description: 'Soft delete a ${moduleName} (marks as deleted but keeps in database)',
79
+ tags: ['${ModuleName}'],
80
+ responses: {
81
+ 200: { description: '${ModuleName} soft-deleted successfully' },
82
+ 404: { description: '${ModuleName} not found' }
83
+ }
84
+ },
85
+ restore: {
86
+ summary: 'Restore ${moduleName}',
87
+ description: 'Restore a previously soft-deleted ${moduleName}',
88
+ tags: ['${ModuleName}'],
89
+ responses: {
90
+ 200: { description: '${ModuleName} restored successfully' },
91
+ 404: { description: '${ModuleName} not found' }
92
+ }
93
+ },
94
+ toggleActive: {
95
+ summary: 'Toggle ${moduleName} active status',
96
+ description: 'Toggle the active/inactive status of a ${moduleName}',
97
+ tags: ['${ModuleName}'],
98
+ responses: {
99
+ 200: { description: '${ModuleName} status toggled successfully' },
100
+ 404: { description: '${ModuleName} not found' }
101
+ }
102
+ },
103
+ hardDelete: {
104
+ summary: 'Permanently delete ${moduleName}',
105
+ description: 'Permanently delete a ${moduleName} from the database (cannot be undone)',
106
+ tags: ['${ModuleName}'],
107
+ responses: {
108
+ 200: { description: '${ModuleName} permanently deleted' },
109
+ 404: { description: '${ModuleName} not found' }
110
+ }
111
+ }
112
+ };
113
+ `,
114
+ interface: `export interface I${ModuleName} {
115
+ name: string;
116
+ slug: string;
117
+ description?: string;
118
+ image?: string;
119
+ isActive: boolean;
120
+ isDeleted: boolean;
121
+ deletedAt?: Date | null;
122
+ createdAt?: Date;
123
+ updatedAt?: Date;
124
+ }
125
+ `,
126
+ model: `import mongoose, { Schema, Document } from 'mongoose';
127
+ import slugify from 'slugify';
128
+ import { I${ModuleName} } from './${moduleName}.interface';
129
+
130
+ export interface I${ModuleName}Doc extends I${ModuleName}, Document {
131
+ _id: mongoose.Types.ObjectId;
132
+ }
133
+
134
+ const ${ModuleName}Schema = new Schema<I${ModuleName}Doc>(
135
+ {
136
+ name: {
137
+ type: String,
138
+ required: [true, '${ModuleName} name is required'],
139
+ unique: true,
140
+ trim: true,
141
+ },
142
+ slug: {
143
+ type: String,
144
+ lowercase: true,
145
+ index: true,
146
+ },
147
+ description: {
148
+ type: String,
149
+ trim: true,
150
+ },
151
+ image: {
152
+ type: String,
153
+ },
154
+ isActive: {
155
+ type: Boolean,
156
+ default: true,
157
+ },
158
+ isDeleted: {
159
+ type: Boolean,
160
+ default: false,
161
+ index: true,
162
+ },
163
+ deletedAt: {
164
+ type: Date,
165
+ default: null,
166
+ },
167
+ },
168
+ {
169
+ timestamps: true,
170
+ toJSON: { virtuals: true },
171
+ toObject: { virtuals: true },
172
+ }
173
+ );
174
+
175
+ // Slugify before saving
176
+ ${ModuleName}Schema.pre('save', function (next) {
177
+ if (!this.isModified('name')) return next();
178
+ this.slug = slugify(this.name, { lower: true, strict: true });
179
+ next();
180
+ });
181
+
182
+ // Query middleware to filter out deleted documents by default
183
+ ${ModuleName}Schema.pre(/^find/, function (next) {
184
+ (this as any).find({ isDeleted: { $ne: true } });
185
+ next();
186
+ });
187
+
188
+ export const ${ModuleName}Model = mongoose.model<I${ModuleName}Doc>('${ModuleName}', ${ModuleName}Schema);
189
+ `,
190
+ repository: `import { BaseRepository } from '../../database/base.repository';
191
+ import { I${ModuleName} } from './${moduleName}.interface';
192
+ import { ${ModuleName}Model } from './${moduleName}.model';
193
+
194
+ export class ${ModuleName}Repository extends BaseRepository<I${ModuleName}> {
195
+ constructor() {
196
+ super(${ModuleName}Model as any);
197
+ }
198
+ }
199
+ `,
200
+ events: `import { eventBus } from '../../common/eventBus';
201
+ import { I${ModuleName} } from './${moduleName}.interface';
202
+ import { logger } from '../../common/logger';
203
+
204
+ export const emit${ModuleName}Created = (data: I${ModuleName}) => {
205
+ eventBus.emit('${moduleName}.created', data);
206
+ };
207
+
208
+ export const register${ModuleName}Handlers = () => {
209
+ eventBus.on('${moduleName}.created', (data: I${ModuleName}) => {
210
+ logger.info(\`[Event] ${ModuleName} Created: \${data.name}\`);
211
+ });
212
+ };
213
+ `,
214
+ service: `import { ${ModuleName}Repository } from './${moduleName}.repository';
215
+ import { I${ModuleName} } from './${moduleName}.interface';
216
+ import { APIError } from '../../common/APIError';
217
+ import { APIFeatures } from '../../common/APIFeatures';
218
+ import { ${ModuleName}Model } from './${moduleName}.model';
219
+ import { emit${ModuleName}Created } from './${moduleName}.events';
220
+
221
+ class ${ModuleName}Service {
222
+ private repository: ${ModuleName}Repository;
223
+
224
+ constructor() {
225
+ this.repository = new ${ModuleName}Repository();
226
+ }
227
+
228
+ async create(data: Partial<I${ModuleName}>) {
229
+ const doc = await this.repository.create(data);
230
+ emit${ModuleName}Created(doc as I${ModuleName});
231
+ return doc;
232
+ }
233
+
234
+ async findAll(queryParams: any) {
235
+ const features = new APIFeatures(${ModuleName}Model.find(), queryParams)
236
+ .filter()
237
+ .search(['name', 'description'])
238
+ .sort()
239
+ .limitFields()
240
+ .paginate();
241
+
242
+ const docs = await features.query.lean();
243
+ const total = await this.repository.count(features.query.getFilter());
244
+
245
+ return {
246
+ results: docs.length,
247
+ total,
248
+ data: docs,
249
+ };
250
+ }
251
+
252
+ async findOne(id: string) {
253
+ const doc = await this.repository.findById(id);
254
+ if (!doc) throw new APIError('${ModuleName} not found', 404);
255
+ return doc;
256
+ }
257
+
258
+ async update(id: string, data: Partial<I${ModuleName}>) {
259
+ const doc = await this.repository.update(id, data);
260
+ if (!doc) throw new APIError('${ModuleName} not found', 404);
261
+ return doc;
262
+ }
263
+
264
+ async softDelete(id: string) {
265
+ const success = await this.repository.softDelete(id);
266
+ if (!success) throw new APIError('${ModuleName} not found', 404);
267
+ return success;
268
+ }
269
+
270
+ async restore(id: string) {
271
+ const success = await this.repository.restore(id);
272
+ if (!success) throw new APIError('${ModuleName} not found', 404);
273
+ return success;
274
+ }
275
+
276
+ async hardDelete(id: string) {
277
+ const success = await this.repository.delete(id);
278
+ if (!success) throw new APIError('${ModuleName} not found', 404);
279
+ return success;
280
+ }
281
+
282
+ async toggleActive(id: string) {
283
+ const doc = await this.findOne(id);
284
+ return await this.update(id, { isActive: !doc.isActive });
285
+ }
286
+ }
287
+
288
+ export const ${moduleNameCamel}Service = new ${ModuleName}Service();
289
+ `,
290
+ controller: `import { Request, Response } from 'express';
291
+ import { ${moduleNameCamel}Service } from './${moduleName}.service';
292
+ import { asyncHandler } from '../../common/asyncHandler';
293
+ import { sendResponse } from '../../common/responseHandler';
294
+
295
+ export const create${ModuleName} = asyncHandler(async (req: Request, res: Response) => {
296
+ const result = await ${moduleNameCamel}Service.create(req.body);
297
+ sendResponse(res, 201, result, '${ModuleName} created successfully');
298
+ });
299
+
300
+ export const get${ModuleName}s = asyncHandler(async (req: Request, res: Response) => {
301
+ const result = await ${moduleNameCamel}Service.findAll(req.query);
302
+ sendResponse(res, 200, result, '${ModuleName}s retrieved successfully');
303
+ });
304
+
305
+ export const get${ModuleName}ById = asyncHandler(async (req: Request, res: Response) => {
306
+ const result = await ${moduleNameCamel}Service.findOne(req.params.id);
307
+ sendResponse(res, 200, result, '${ModuleName} retrieved successfully');
308
+ });
309
+
310
+ export const update${ModuleName} = asyncHandler(async (req: Request, res: Response) => {
311
+ const result = await ${moduleNameCamel}Service.update(req.params.id, req.body);
312
+ sendResponse(res, 200, result, '${ModuleName} updated successfully');
313
+ });
314
+
315
+ export const delete${ModuleName} = asyncHandler(async (req: Request, res: Response) => {
316
+ await ${moduleNameCamel}Service.softDelete(req.params.id);
317
+ sendResponse(res, 200, null, '${ModuleName} soft-deleted successfully');
318
+ });
319
+
320
+ export const restore${ModuleName} = asyncHandler(async (req: Request, res: Response) => {
321
+ await ${moduleNameCamel}Service.restore(req.params.id);
322
+ sendResponse(res, 200, null, '${ModuleName} restored successfully');
323
+ });
324
+
325
+ export const hardDelete${ModuleName} = asyncHandler(async (req: Request, res: Response) => {
326
+ await ${moduleNameCamel}Service.hardDelete(req.params.id);
327
+ sendResponse(res, 200, null, '${ModuleName} permanently deleted');
328
+ });
329
+
330
+ export const toggle${ModuleName}Active = asyncHandler(async (req: Request, res: Response) => {
331
+ const result = await ${moduleNameCamel}Service.toggleActive(req.params.id);
332
+ sendResponse(res, 200, result, '${ModuleName} status toggled');
333
+ });
334
+ `,
335
+ validation: `import { z } from 'zod';
336
+
337
+ export const create${ModuleName}Schema = z.object({
338
+ body: z.object({
339
+ name: z.string().min(2, 'Name is too short'),
340
+ description: z.string().optional(),
341
+ image: z.string().optional(),
342
+ }),
343
+ });
344
+
345
+ export const update${ModuleName}Schema = z.object({
346
+ body: z.object({
347
+ name: z.string().min(2).optional(),
348
+ description: z.string().optional(),
349
+ image: z.string().optional(),
350
+ isActive: z.boolean().optional(),
351
+ }),
352
+ });
353
+ `,
354
+ routes: `import { createDocRouter } from '../../common/docRouter';
355
+ import * as ${moduleNameCamel}Controller from './${moduleName}.controller';
356
+ import { validateRequest } from '../../common/validateRequest';
357
+ import { create${ModuleName}Schema, update${ModuleName}Schema } from './${moduleName}.validation';
358
+ import { protect, hasPermission } from '../auth/auth.middleware';
359
+ import { PERMISSIONS } from '../../config/roles';
360
+ import { ${moduleNameCamel}Docs } from './${moduleName}.docs';
361
+
362
+ const { router, get, post, put, patch, delete: del } = createDocRouter('/${moduleName}s');
363
+
364
+ // Public routes
365
+ get('/',
366
+ ${moduleNameCamel}Docs.getAll,
367
+ ${moduleNameCamel}Controller.get${ModuleName}s
368
+ );
369
+
370
+ get('/:id',
371
+ ${moduleNameCamel}Docs.getById,
372
+ ${moduleNameCamel}Controller.get${ModuleName}ById
373
+ );
374
+
375
+ // Protected routes
376
+ router.use(protect);
377
+
378
+ post('/',
379
+ ${moduleNameCamel}Docs.create,
380
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_WRITE),
381
+ validateRequest(create${ModuleName}Schema),
382
+ ${moduleNameCamel}Controller.create${ModuleName}
383
+ );
384
+
385
+ put('/:id',
386
+ ${moduleNameCamel}Docs.update,
387
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_WRITE),
388
+ validateRequest(update${ModuleName}Schema),
389
+ ${moduleNameCamel}Controller.update${ModuleName}
390
+ );
391
+
392
+ patch('/:id/restore',
393
+ ${moduleNameCamel}Docs.restore,
394
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_WRITE),
395
+ ${moduleNameCamel}Controller.restore${ModuleName}
396
+ );
397
+
398
+ patch('/:id/toggle-active',
399
+ ${moduleNameCamel}Docs.toggleActive,
400
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_WRITE),
401
+ ${moduleNameCamel}Controller.toggle${ModuleName}Active
402
+ );
403
+
404
+ del('/:id',
405
+ ${moduleNameCamel}Docs.softDelete,
406
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_WRITE),
407
+ ${moduleNameCamel}Controller.delete${ModuleName}
408
+ );
409
+
410
+ // Admin-only (Dangerous) actions
411
+ del('/:id/hard',
412
+ ${moduleNameCamel}Docs.hardDelete,
413
+ hasPermission(PERMISSIONS.${ModuleName.toUpperCase()}S_DELETE),
414
+ ${moduleNameCamel}Controller.hardDelete${ModuleName}
415
+ );
416
+
417
+ export const ${moduleNameCamel}Routes = router;
418
+ `,
419
+
420
+
421
+ index: `export * from './${moduleName}.controller';
422
+ export * from './${moduleName}.docs';
423
+ export * from './${moduleName}.interface';
424
+ export * from './${moduleName}.model';
425
+ export * from './${moduleName}.repository';
426
+ export * from './${moduleName}.routes';
427
+ export * from './${moduleName}.service';
428
+ export * from './${moduleName}.validation';
429
+ export * from './${moduleName}.events';
430
+
431
+ import { register${ModuleName}Handlers } from './${moduleName}.events';
432
+
433
+ // Initialize Module Events
434
+ register${ModuleName}Handlers();
435
+ `
436
+ };
437
+
438
+
439
+ // Write files
440
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.interface.ts`), templates.interface);
441
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.model.ts`), templates.model);
442
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.repository.ts`), templates.repository);
443
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.events.ts`), templates.events);
444
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.service.ts`), templates.service);
445
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.controller.ts`), templates.controller);
446
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.validation.ts`), templates.validation);
447
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.docs.ts`), templates.docs);
448
+ fs.writeFileSync(path.join(modulePath, `${moduleName}.routes.ts`), templates.routes);
449
+ fs.writeFileSync(path.join(modulePath, `index.ts`), templates.index);
450
+
451
+ console.log(chalk.green(`Module ${moduleName} created successfully at ${modulePath}`));
452
+ console.log(chalk.blue(`Next steps:`));
453
+ console.log(`1. Open src/app.ts`);
454
+ console.log(`2. Import ${moduleNameCamel}Routes`);
455
+ console.log(`3. Register the route: app.use('/api/v1/${moduleName}s', ${moduleNameCamel}Routes);`);
456
+ }
@@ -0,0 +1,46 @@
1
+ import shell from 'shelljs';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+
6
+ // TODO: Replace with your actual boilerplate repository URL
7
+ const BOILERPLATE_REPO = 'https://github.com/khushikumarigupta14/repo-pattern-boiler';
8
+
9
+ export const initProject = (projectName) => {
10
+ if (!projectName) {
11
+ console.error(chalk.red('Please provide a project name'));
12
+ process.exit(1);
13
+ }
14
+
15
+ if (fs.existsSync(projectName)) {
16
+ console.error(chalk.red(`Directory ${projectName} already exists`));
17
+ process.exit(1);
18
+ }
19
+
20
+ console.log(chalk.blue(`Initializing project ${projectName}...`));
21
+ console.log(chalk.gray(`Cloning from ${BOILERPLATE_REPO}...`));
22
+
23
+ // Check if git is installed
24
+ if (!shell.which('git')) {
25
+ console.error(chalk.red('Sorry, this script requires git'));
26
+ process.exit(1);
27
+ }
28
+
29
+ if (shell.exec(`git clone ${BOILERPLATE_REPO} ${projectName}`).code !== 0) {
30
+ console.error(chalk.red('Error: Git clone failed'));
31
+ process.exit(1);
32
+ }
33
+
34
+ // Remove .git folder to detach from boilerplate history
35
+ fs.removeSync(path.join(projectName, '.git'));
36
+
37
+ // Initialize new git repo
38
+ shell.cd(projectName);
39
+ shell.exec('git init');
40
+
41
+ console.log(chalk.green(`\nProject ${projectName} initialized successfully!`));
42
+ console.log(chalk.blue(`\nNext steps:`));
43
+ console.log(` cd ${projectName}`);
44
+ console.log(` npm install`);
45
+ console.log(` npm run dev`);
46
+ };
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "khushikumarigupta14-express-generator-cli",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "bin": {
7
+ "my-express-gen": "./bin/cli.js"
8
+ },
9
+ "main": "index.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": [],
14
+ "author": "",
15
+ "license": "ISC",
16
+ "dependencies": {
17
+ "chalk": "^5.6.2",
18
+ "commander": "^14.0.2",
19
+ "fs-extra": "^11.3.3",
20
+ "inquirer": "^13.1.0",
21
+ "shelljs": "^0.10.0"
22
+ }
23
+ }