create-coreback 1.0.1

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.
Files changed (96) hide show
  1. package/.github/PUBLISH.md +58 -0
  2. package/.github/workflows/publish.yml +78 -0
  3. package/LICENSE +21 -0
  4. package/README.md +62 -0
  5. package/dist/createProject.d.ts +2 -0
  6. package/dist/createProject.d.ts.map +1 -0
  7. package/dist/createProject.js +70 -0
  8. package/dist/createProject.js.map +1 -0
  9. package/dist/generators/cursorRules.d.ts +2 -0
  10. package/dist/generators/cursorRules.d.ts.map +1 -0
  11. package/dist/generators/cursorRules.js +35 -0
  12. package/dist/generators/cursorRules.js.map +1 -0
  13. package/dist/generators/docker.d.ts +3 -0
  14. package/dist/generators/docker.d.ts.map +1 -0
  15. package/dist/generators/docker.js +119 -0
  16. package/dist/generators/docker.js.map +1 -0
  17. package/dist/generators/envExample.d.ts +3 -0
  18. package/dist/generators/envExample.d.ts.map +1 -0
  19. package/dist/generators/envExample.js +39 -0
  20. package/dist/generators/envExample.js.map +1 -0
  21. package/dist/generators/eslint.d.ts +2 -0
  22. package/dist/generators/eslint.d.ts.map +1 -0
  23. package/dist/generators/eslint.js +32 -0
  24. package/dist/generators/eslint.js.map +1 -0
  25. package/dist/generators/githubActions.d.ts +3 -0
  26. package/dist/generators/githubActions.d.ts.map +1 -0
  27. package/dist/generators/githubActions.js +68 -0
  28. package/dist/generators/githubActions.js.map +1 -0
  29. package/dist/generators/gitignore.d.ts +2 -0
  30. package/dist/generators/gitignore.d.ts.map +1 -0
  31. package/dist/generators/gitignore.js +50 -0
  32. package/dist/generators/gitignore.js.map +1 -0
  33. package/dist/generators/index.d.ts +3 -0
  34. package/dist/generators/index.d.ts.map +1 -0
  35. package/dist/generators/index.js +34 -0
  36. package/dist/generators/index.js.map +1 -0
  37. package/dist/generators/jest.d.ts +2 -0
  38. package/dist/generators/jest.d.ts.map +1 -0
  39. package/dist/generators/jest.js +23 -0
  40. package/dist/generators/jest.js.map +1 -0
  41. package/dist/generators/packageJson.d.ts +3 -0
  42. package/dist/generators/packageJson.d.ts.map +1 -0
  43. package/dist/generators/packageJson.js +80 -0
  44. package/dist/generators/packageJson.js.map +1 -0
  45. package/dist/generators/prettier.d.ts +2 -0
  46. package/dist/generators/prettier.d.ts.map +1 -0
  47. package/dist/generators/prettier.js +14 -0
  48. package/dist/generators/prettier.js.map +1 -0
  49. package/dist/generators/prisma.d.ts +3 -0
  50. package/dist/generators/prisma.d.ts.map +1 -0
  51. package/dist/generators/prisma.js +55 -0
  52. package/dist/generators/prisma.js.map +1 -0
  53. package/dist/generators/sourceFiles.d.ts +3 -0
  54. package/dist/generators/sourceFiles.d.ts.map +1 -0
  55. package/dist/generators/sourceFiles.js +767 -0
  56. package/dist/generators/sourceFiles.js.map +1 -0
  57. package/dist/generators/tsconfig.d.ts +2 -0
  58. package/dist/generators/tsconfig.d.ts.map +1 -0
  59. package/dist/generators/tsconfig.js +32 -0
  60. package/dist/generators/tsconfig.js.map +1 -0
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +42 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/prompts.d.ts +3 -0
  66. package/dist/prompts.d.ts.map +1 -0
  67. package/dist/prompts.js +56 -0
  68. package/dist/prompts.js.map +1 -0
  69. package/dist/types.d.ts +10 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/utils/parseArgs.d.ts +2 -0
  74. package/dist/utils/parseArgs.d.ts.map +1 -0
  75. package/dist/utils/parseArgs.js +6 -0
  76. package/dist/utils/parseArgs.js.map +1 -0
  77. package/package.json +46 -0
  78. package/src/createProject.ts +86 -0
  79. package/src/generators/cursorRules.ts +39 -0
  80. package/src/generators/docker.ts +131 -0
  81. package/src/generators/envExample.ts +49 -0
  82. package/src/generators/eslint.ts +38 -0
  83. package/src/generators/githubActions.ts +79 -0
  84. package/src/generators/gitignore.ts +52 -0
  85. package/src/generators/index.ts +45 -0
  86. package/src/generators/jest.ts +29 -0
  87. package/src/generators/packageJson.ts +89 -0
  88. package/src/generators/prettier.ts +22 -0
  89. package/src/generators/prisma.ts +66 -0
  90. package/src/generators/sourceFiles.ts +840 -0
  91. package/src/generators/tsconfig.ts +34 -0
  92. package/src/index.ts +45 -0
  93. package/src/prompts.ts +62 -0
  94. package/src/types.ts +11 -0
  95. package/src/utils/parseArgs.ts +6 -0
  96. package/tsconfig.json +21 -0
@@ -0,0 +1,840 @@
1
+ import { ProjectConfig } from '../types.js';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+
5
+ export async function generateSourceFiles(
6
+ projectPath: string,
7
+ config: ProjectConfig
8
+ ): Promise<void> {
9
+ const srcDir = path.join(projectPath, 'src');
10
+ const configDir = path.join(srcDir, 'config');
11
+ const controllersDir = path.join(srcDir, 'controllers');
12
+ const servicesDir = path.join(srcDir, 'services');
13
+ const repositoriesDir = path.join(srcDir, 'repositories');
14
+ const middlewaresDir = path.join(srcDir, 'middlewares');
15
+ const routesDir = path.join(srcDir, 'routes');
16
+ const typesDir = path.join(srcDir, 'types');
17
+ const utilsDir = path.join(srcDir, 'utils');
18
+ const validatorsDir = path.join(srcDir, 'validators');
19
+ const testsDir = path.join(projectPath, 'tests');
20
+ const unitTestsDir = path.join(testsDir, 'unit');
21
+ const integrationTestsDir = path.join(testsDir, 'integration');
22
+
23
+ // Create directories
24
+ await Promise.all([
25
+ fs.ensureDir(configDir),
26
+ fs.ensureDir(controllersDir),
27
+ fs.ensureDir(servicesDir),
28
+ fs.ensureDir(repositoriesDir),
29
+ fs.ensureDir(middlewaresDir),
30
+ fs.ensureDir(routesDir),
31
+ fs.ensureDir(typesDir),
32
+ fs.ensureDir(utilsDir),
33
+ fs.ensureDir(validatorsDir),
34
+ fs.ensureDir(unitTestsDir),
35
+ fs.ensureDir(integrationTestsDir),
36
+ ]);
37
+
38
+ // Generate files
39
+ await generateIndex(srcDir, config);
40
+ await generateConfigFiles(configDir, config);
41
+ await generateMiddlewares(middlewaresDir, config);
42
+ await generateUtils(utilsDir);
43
+ await generateRoutes(routesDir, config);
44
+ await generateTypes(typesDir);
45
+ await generateValidators(validatorsDir, config);
46
+ await generateControllers(controllersDir, config);
47
+ await generateServices(servicesDir, config);
48
+ await generateRepositories(repositoriesDir, config);
49
+ await generateTests(unitTestsDir, integrationTestsDir, config);
50
+ }
51
+
52
+ async function generateIndex(srcDir: string, config: ProjectConfig): Promise<void> {
53
+ const indexContent = `import express from 'express';
54
+ import { config } from './config/env.js';
55
+ import { setupMiddlewares } from './config/middlewares.js';
56
+ import { setupRoutes } from './routes/index.js';
57
+ import { setupSwagger } from './config/swagger.js';
58
+ import { logger } from './utils/logger.js';
59
+ import { errorHandler } from './middlewares/errorHandler.js';
60
+
61
+ const app = express();
62
+
63
+ setupMiddlewares(app);
64
+ setupSwagger(app);
65
+ setupRoutes(app);
66
+ app.use(errorHandler);
67
+
68
+ const server = app.listen(config.PORT, () => {
69
+ logger.info(\`🚀 Server: http://localhost:\${config.PORT}\`);
70
+ logger.info(\`📚 Docs: http://localhost:\${config.PORT}/api-docs\`);
71
+ });
72
+
73
+ process.on('SIGTERM', () => {
74
+ server.close();
75
+ process.exit(0);
76
+ });
77
+ `;
78
+
79
+ await fs.writeFile(path.join(srcDir, 'index.ts'), indexContent);
80
+ }
81
+
82
+ async function generateConfigFiles(configDir: string, config: ProjectConfig): Promise<void> {
83
+ // env.ts
84
+ const envContent = `import { z } from 'zod';
85
+ import dotenv from 'dotenv';
86
+
87
+ dotenv.config();
88
+
89
+ const envSchema = z.object({
90
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
91
+ PORT: z.coerce.number().default(3000),
92
+ DATABASE_URL: z.string(),
93
+ ${config.includeAuth ? ` JWT_SECRET: z.string(),
94
+ JWT_EXPIRES_IN: z.string().default('7d'),` : ''}
95
+ RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900000),
96
+ RATE_LIMIT_MAX: z.coerce.number().default(100),
97
+ });
98
+
99
+ export const config = envSchema.parse(process.env);
100
+ `;
101
+
102
+ await fs.writeFile(path.join(configDir, 'env.ts'), envContent);
103
+
104
+ // database.ts
105
+ const databaseContent = `import { PrismaClient } from '@prisma/client';
106
+ import { logger } from '../utils/logger.js';
107
+
108
+ const prisma = new PrismaClient({
109
+ log: [
110
+ { level: 'query', emit: 'event' },
111
+ { level: 'error', emit: 'stdout' },
112
+ { level: 'warn', emit: 'stdout' },
113
+ ],
114
+ });
115
+
116
+ prisma.$on('query', (e) => {
117
+ logger.debug('Query: ' + e.query);
118
+ logger.debug('Params: ' + e.params);
119
+ logger.debug('Duration: ' + e.duration + 'ms');
120
+ });
121
+
122
+ export { prisma };
123
+ `;
124
+
125
+ await fs.writeFile(path.join(configDir, 'database.ts'), databaseContent);
126
+
127
+ // middlewares.ts
128
+ const middlewaresContent = `import express, { Express } from 'express';
129
+ import cors from 'cors';
130
+ import helmet from 'helmet';
131
+ import compression from 'compression';
132
+ import rateLimit from 'express-rate-limit';
133
+ import { config } from './env.js';
134
+
135
+ export function setupMiddlewares(app: Express): void {
136
+ app.use(helmet());
137
+ app.use(cors());
138
+ app.use(compression());
139
+ app.use(express.json());
140
+ app.use(express.urlencoded({ extended: true }));
141
+
142
+ const limiter = rateLimit({
143
+ windowMs: config.RATE_LIMIT_WINDOW_MS,
144
+ max: config.RATE_LIMIT_MAX,
145
+ message: 'Too many requests from this IP, please try again later.',
146
+ });
147
+
148
+ app.use('/api/', limiter);
149
+ }
150
+ `;
151
+
152
+ await fs.writeFile(path.join(configDir, 'middlewares.ts'), middlewaresContent);
153
+
154
+ // swagger.ts
155
+ const swaggerContent = `import { Express } from 'express';
156
+ import swaggerJsdoc from 'swagger-jsdoc';
157
+ import swaggerUi from 'swagger-ui-express';
158
+ import { config } from './env.js';
159
+
160
+ const options: swaggerJsdoc.Options = {
161
+ definition: {
162
+ openapi: '3.0.0',
163
+ info: {
164
+ title: 'CoreBack API',
165
+ version: '1.0.0',
166
+ description: 'Production-ready backend API',
167
+ },
168
+ servers: [
169
+ {
170
+ url: \`http://localhost:\${config.PORT}\`,
171
+ description: 'Development server',
172
+ },
173
+ ],
174
+ },
175
+ apis: ['./src/routes/**/*.ts', './src/controllers/**/*.ts'],
176
+ };
177
+
178
+ const swaggerSpec = swaggerJsdoc(options);
179
+
180
+ export function setupSwagger(app: Express): void {
181
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
182
+ }
183
+ `;
184
+
185
+ await fs.writeFile(path.join(configDir, 'swagger.ts'), swaggerContent);
186
+ }
187
+
188
+ async function generateMiddlewares(middlewaresDir: string, config: ProjectConfig): Promise<void> {
189
+ // errorHandler.ts
190
+ const errorHandlerContent = `import { Request, Response, NextFunction } from 'express';
191
+ import { ZodError } from 'zod';
192
+ import { logger } from '../utils/logger.js';
193
+
194
+ export class AppError extends Error {
195
+ constructor(
196
+ public statusCode: number,
197
+ message: string,
198
+ public isOperational = true
199
+ ) {
200
+ super(message);
201
+ Object.setPrototypeOf(this, AppError.prototype);
202
+ }
203
+ }
204
+
205
+ export const errorHandler = (
206
+ err: Error,
207
+ req: Request,
208
+ res: Response,
209
+ next: NextFunction
210
+ ) => {
211
+ logger.error(err);
212
+
213
+ if (err instanceof ZodError) {
214
+ return res.status(400).json({
215
+ status: 'error',
216
+ message: 'Validation error',
217
+ errors: err.errors,
218
+ });
219
+ }
220
+
221
+ if (err instanceof AppError) {
222
+ return res.status(err.statusCode).json({
223
+ status: 'error',
224
+ message: err.message,
225
+ });
226
+ }
227
+
228
+ res.status(500).json({
229
+ status: 'error',
230
+ message: 'Internal server error',
231
+ });
232
+ };
233
+ `;
234
+
235
+ await fs.writeFile(path.join(middlewaresDir, 'errorHandler.ts'), errorHandlerContent);
236
+
237
+ // validator.ts
238
+ const validatorContent = `import { Request, Response, NextFunction } from 'express';
239
+ import { ZodSchema, ZodError } from 'zod';
240
+
241
+ export const validate = (schema: ZodSchema) => {
242
+ return (req: Request, res: Response, next: NextFunction) => {
243
+ try {
244
+ schema.parse({
245
+ body: req.body,
246
+ query: req.query,
247
+ params: req.params,
248
+ });
249
+ next();
250
+ } catch (error) {
251
+ if (error instanceof ZodError) {
252
+ return res.status(400).json({
253
+ status: 'error',
254
+ message: 'Validation error',
255
+ errors: error.errors,
256
+ });
257
+ }
258
+ next(error);
259
+ }
260
+ };
261
+ };
262
+ `;
263
+
264
+ await fs.writeFile(path.join(middlewaresDir, 'validator.ts'), validatorContent);
265
+
266
+ if (config.includeAuth) {
267
+ // auth.ts
268
+ const authContent = `import { Request, Response, NextFunction } from 'express';
269
+ import jwt from 'jsonwebtoken';
270
+ import { config } from '../config/env.js';
271
+ import { AppError } from './errorHandler.js';
272
+
273
+ export interface AuthRequest extends Request {
274
+ user?: {
275
+ id: string;
276
+ email: string;
277
+ };
278
+ }
279
+
280
+ export const authenticate = (
281
+ req: AuthRequest,
282
+ res: Response,
283
+ next: NextFunction
284
+ ) => {
285
+ try {
286
+ const authHeader = req.headers.authorization;
287
+
288
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
289
+ throw new AppError(401, 'Authentication required');
290
+ }
291
+
292
+ const token = authHeader.substring(7);
293
+ const decoded = jwt.verify(token, config.JWT_SECRET) as {
294
+ id: string;
295
+ email: string;
296
+ };
297
+
298
+ req.user = decoded;
299
+ next();
300
+ } catch (error) {
301
+ if (error instanceof jwt.JsonWebTokenError) {
302
+ next(new AppError(401, 'Invalid token'));
303
+ } else {
304
+ next(error);
305
+ }
306
+ }
307
+ };
308
+ `;
309
+
310
+ await fs.writeFile(path.join(middlewaresDir, 'auth.ts'), authContent);
311
+ }
312
+ }
313
+
314
+ async function generateUtils(utilsDir: string): Promise<void> {
315
+ const loggerContent = `import winston from 'winston';
316
+ import { config } from '../config/env.js';
317
+ import fs from 'fs-extra';
318
+ import path from 'path';
319
+
320
+ // Ensure logs directory exists
321
+ const logsDir = path.join(process.cwd(), 'logs');
322
+ fs.ensureDirSync(logsDir);
323
+
324
+ const logFormat = winston.format.combine(
325
+ winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
326
+ winston.format.errors({ stack: true }),
327
+ winston.format.splat(),
328
+ winston.format.json()
329
+ );
330
+
331
+ export const logger = winston.createLogger({
332
+ level: config.NODE_ENV === 'production' ? 'info' : 'debug',
333
+ format: logFormat,
334
+ defaultMeta: { service: 'coreback-api' },
335
+ transports: [
336
+ new winston.transports.File({ filename: path.join(logsDir, 'error.log'), level: 'error' }),
337
+ new winston.transports.File({ filename: path.join(logsDir, 'combined.log') }),
338
+ ],
339
+ });
340
+
341
+ if (config.NODE_ENV !== 'production') {
342
+ logger.add(
343
+ new winston.transports.Console({
344
+ format: winston.format.combine(
345
+ winston.format.colorize(),
346
+ winston.format.simple()
347
+ ),
348
+ })
349
+ );
350
+ }
351
+ `;
352
+
353
+ await fs.writeFile(path.join(utilsDir, 'logger.ts'), loggerContent);
354
+ }
355
+
356
+ async function generateRoutes(routesDir: string, config: ProjectConfig): Promise<void> {
357
+ // index.ts
358
+ let indexContent = `import { Express } from 'express';
359
+ import { healthRoutes } from './health.routes.js';
360
+ `;
361
+
362
+ if (config.includeAuth) {
363
+ indexContent += `import { authRoutes } from './auth.routes.js';
364
+ `;
365
+ }
366
+
367
+ indexContent += `
368
+ export function setupRoutes(app: Express): void {
369
+ app.use('/api/health', healthRoutes);
370
+ `;
371
+
372
+ if (config.includeAuth) {
373
+ indexContent += ` app.use('/api/auth', authRoutes);
374
+ `;
375
+ }
376
+
377
+ indexContent += `}
378
+ `;
379
+
380
+ await fs.writeFile(path.join(routesDir, 'index.ts'), indexContent);
381
+
382
+ // health.routes.ts
383
+ const healthRoutesContent = `import { Router } from 'express';
384
+ import { healthController } from '../controllers/health.controller.js';
385
+
386
+ /**
387
+ * @swagger
388
+ * tags:
389
+ * name: Health
390
+ * description: Health check endpoints
391
+ */
392
+
393
+ const router = Router();
394
+
395
+ /**
396
+ * @swagger
397
+ * /api/health:
398
+ * get:
399
+ * summary: Health check endpoint
400
+ * tags: [Health]
401
+ * responses:
402
+ * 200:
403
+ * description: Service is healthy
404
+ * content:
405
+ * application/json:
406
+ * schema:
407
+ * type: object
408
+ * properties:
409
+ * status:
410
+ * type: string
411
+ * example: ok
412
+ * timestamp:
413
+ * type: string
414
+ * example: 2024-01-01T00:00:00.000Z
415
+ */
416
+ router.get('/', healthController.check);
417
+
418
+ export { router as healthRoutes };
419
+ `;
420
+
421
+ await fs.writeFile(path.join(routesDir, 'health.routes.ts'), healthRoutesContent);
422
+
423
+ if (config.includeAuth) {
424
+ // auth.routes.ts
425
+ const authRoutesContent = `import { Router } from 'express';
426
+ import { authController } from '../controllers/auth.controller.js';
427
+ import { validate } from '../middlewares/validator.js';
428
+ import { registerSchema, loginSchema } from '../validators/auth.validator.js';
429
+ import { authenticate } from '../middlewares/auth.js';
430
+
431
+ /**
432
+ * @swagger
433
+ * tags:
434
+ * name: Auth
435
+ * description: Authentication endpoints
436
+ */
437
+
438
+ const router = Router();
439
+
440
+ /**
441
+ * @swagger
442
+ * /api/auth/register:
443
+ * post:
444
+ * summary: Register a new user
445
+ * tags: [Auth]
446
+ * requestBody:
447
+ * required: true
448
+ * content:
449
+ * application/json:
450
+ * schema:
451
+ * type: object
452
+ * required:
453
+ * - email
454
+ * - password
455
+ * properties:
456
+ * email:
457
+ * type: string
458
+ * format: email
459
+ * password:
460
+ * type: string
461
+ * minLength: 8
462
+ * name:
463
+ * type: string
464
+ * responses:
465
+ * 201:
466
+ * description: User created successfully
467
+ * 400:
468
+ * description: Validation error
469
+ */
470
+ router.post('/register', validate(registerSchema), authController.register);
471
+
472
+ /**
473
+ * @swagger
474
+ * /api/auth/login:
475
+ * post:
476
+ * summary: Login user
477
+ * tags: [Auth]
478
+ * requestBody:
479
+ * required: true
480
+ * content:
481
+ * application/json:
482
+ * schema:
483
+ * type: object
484
+ * required:
485
+ * - email
486
+ * - password
487
+ * properties:
488
+ * email:
489
+ * type: string
490
+ * format: email
491
+ * password:
492
+ * type: string
493
+ * responses:
494
+ * 200:
495
+ * description: Login successful
496
+ * 401:
497
+ * description: Invalid credentials
498
+ */
499
+ router.post('/login', validate(loginSchema), authController.login);
500
+
501
+ /**
502
+ * @swagger
503
+ * /api/auth/me:
504
+ * get:
505
+ * summary: Get current user
506
+ * tags: [Auth]
507
+ * security:
508
+ * - bearerAuth: []
509
+ * responses:
510
+ * 200:
511
+ * description: Current user information
512
+ * 401:
513
+ * description: Unauthorized
514
+ */
515
+ router.get('/me', authenticate, authController.me);
516
+
517
+ export { router as authRoutes };
518
+ `;
519
+
520
+ await fs.writeFile(path.join(routesDir, 'auth.routes.ts'), authRoutesContent);
521
+ }
522
+ }
523
+
524
+ async function generateTypes(typesDir: string): Promise<void> {
525
+ const indexContent = `export interface ApiResponse<T = unknown> {
526
+ status: 'success' | 'error';
527
+ data?: T;
528
+ message?: string;
529
+ errors?: unknown[];
530
+ }
531
+ `;
532
+
533
+ await fs.writeFile(path.join(typesDir, 'index.ts'), indexContent);
534
+ }
535
+
536
+ async function generateValidators(validatorsDir: string, config: ProjectConfig): Promise<void> {
537
+ if (config.includeAuth) {
538
+ const authValidatorContent = `import { z } from 'zod';
539
+
540
+ export const registerSchema = z.object({
541
+ body: z.object({
542
+ email: z.string().email('Invalid email format'),
543
+ password: z.string().min(8, 'Password must be at least 8 characters'),
544
+ name: z.string().optional(),
545
+ }),
546
+ });
547
+
548
+ export const loginSchema = z.object({
549
+ body: z.object({
550
+ email: z.string().email('Invalid email format'),
551
+ password: z.string().min(1, 'Password is required'),
552
+ }),
553
+ });
554
+ `;
555
+
556
+ await fs.writeFile(path.join(validatorsDir, 'auth.validator.ts'), authValidatorContent);
557
+ }
558
+ }
559
+
560
+ async function generateControllers(controllersDir: string, config: ProjectConfig): Promise<void> {
561
+ // health.controller.ts
562
+ const healthControllerContent = `import { Request, Response } from 'express';
563
+
564
+ export const healthController = {
565
+ check: (_req: Request, res: Response) => {
566
+ res.json({
567
+ status: 'ok',
568
+ timestamp: new Date().toISOString(),
569
+ });
570
+ },
571
+ };
572
+ `;
573
+
574
+ await fs.writeFile(
575
+ path.join(controllersDir, 'health.controller.ts'),
576
+ healthControllerContent
577
+ );
578
+
579
+ if (config.includeAuth) {
580
+ // auth.controller.ts
581
+ const authControllerContent = `import { Response } from 'express';
582
+ import { AuthRequest } from '../middlewares/auth.js';
583
+ import { authService } from '../services/auth.service.js';
584
+ import { AppError } from '../middlewares/errorHandler.js';
585
+
586
+ export const authController = {
587
+ register: async (req: AuthRequest, res: Response) => {
588
+ try {
589
+ const { email, password, name } = req.body;
590
+ const user = await authService.register(email, password, name);
591
+ res.status(201).json({
592
+ status: 'success',
593
+ data: user,
594
+ });
595
+ } catch (error) {
596
+ if (error instanceof AppError) {
597
+ throw error;
598
+ }
599
+ throw new AppError(500, 'Failed to register user');
600
+ }
601
+ },
602
+
603
+ login: async (req: AuthRequest, res: Response) => {
604
+ try {
605
+ const { email, password } = req.body;
606
+ const result = await authService.login(email, password);
607
+ res.json({
608
+ status: 'success',
609
+ data: result,
610
+ });
611
+ } catch (error) {
612
+ if (error instanceof AppError) {
613
+ throw error;
614
+ }
615
+ throw new AppError(500, 'Failed to login');
616
+ }
617
+ },
618
+
619
+ me: async (req: AuthRequest, res: Response) => {
620
+ try {
621
+ const user = await authService.getUserById(req.user!.id);
622
+ res.json({
623
+ status: 'success',
624
+ data: user,
625
+ });
626
+ } catch (error) {
627
+ if (error instanceof AppError) {
628
+ throw error;
629
+ }
630
+ throw new AppError(500, 'Failed to get user');
631
+ }
632
+ },
633
+ };
634
+ `;
635
+
636
+ await fs.writeFile(
637
+ path.join(controllersDir, 'auth.controller.ts'),
638
+ authControllerContent
639
+ );
640
+ }
641
+ }
642
+
643
+ async function generateServices(servicesDir: string, config: ProjectConfig): Promise<void> {
644
+ if (config.includeAuth) {
645
+ const authServiceContent = `import bcrypt from 'bcrypt';
646
+ import jwt from 'jsonwebtoken';
647
+ import { config } from '../config/env.js';
648
+ import { userRepository } from '../repositories/user.repository.js';
649
+ import { AppError } from '../middlewares/errorHandler.js';
650
+
651
+ export const authService = {
652
+ async register(email: string, password: string, name?: string) {
653
+ const existingUser = await userRepository.findByEmail(email);
654
+
655
+ if (existingUser) {
656
+ throw new AppError(400, 'User already exists');
657
+ }
658
+
659
+ const hashedPassword = await bcrypt.hash(password, 10);
660
+ const user = await userRepository.create({
661
+ email,
662
+ password: hashedPassword,
663
+ name,
664
+ });
665
+
666
+ const token = jwt.sign(
667
+ { id: user.id, email: user.email },
668
+ config.JWT_SECRET,
669
+ { expiresIn: config.JWT_EXPIRES_IN }
670
+ );
671
+
672
+ return {
673
+ user: {
674
+ id: user.id,
675
+ email: user.email,
676
+ name: user.name,
677
+ },
678
+ token,
679
+ };
680
+ },
681
+
682
+ async login(email: string, password: string) {
683
+ const user = await userRepository.findByEmail(email);
684
+
685
+ if (!user) {
686
+ throw new AppError(401, 'Invalid credentials');
687
+ }
688
+
689
+ const isValidPassword = await bcrypt.compare(password, user.password);
690
+
691
+ if (!isValidPassword) {
692
+ throw new AppError(401, 'Invalid credentials');
693
+ }
694
+
695
+ const token = jwt.sign(
696
+ { id: user.id, email: user.email },
697
+ config.JWT_SECRET,
698
+ { expiresIn: config.JWT_EXPIRES_IN }
699
+ );
700
+
701
+ return {
702
+ user: {
703
+ id: user.id,
704
+ email: user.email,
705
+ name: user.name,
706
+ },
707
+ token,
708
+ };
709
+ },
710
+
711
+ async getUserById(id: string) {
712
+ const user = await userRepository.findById(id);
713
+
714
+ if (!user) {
715
+ throw new AppError(404, 'User not found');
716
+ }
717
+
718
+ return {
719
+ id: user.id,
720
+ email: user.email,
721
+ name: user.name,
722
+ createdAt: user.createdAt,
723
+ updatedAt: user.updatedAt,
724
+ };
725
+ },
726
+ };
727
+ `;
728
+
729
+ await fs.writeFile(
730
+ path.join(servicesDir, 'auth.service.ts'),
731
+ authServiceContent
732
+ );
733
+ }
734
+ }
735
+
736
+ async function generateRepositories(repositoriesDir: string, config: ProjectConfig): Promise<void> {
737
+ if (config.includeAuth) {
738
+ const userRepositoryContent = `import { prisma } from '../config/database.js';
739
+
740
+ export const userRepository = {
741
+ async findByEmail(email: string) {
742
+ return prisma.user.findUnique({
743
+ where: { email },
744
+ });
745
+ },
746
+
747
+ async findById(id: string) {
748
+ return prisma.user.findUnique({
749
+ where: { id },
750
+ select: {
751
+ id: true,
752
+ email: true,
753
+ name: true,
754
+ createdAt: true,
755
+ updatedAt: true,
756
+ },
757
+ });
758
+ },
759
+
760
+ async create(data: { email: string; password: string; name?: string }) {
761
+ return prisma.user.create({
762
+ data,
763
+ select: {
764
+ id: true,
765
+ email: true,
766
+ name: true,
767
+ createdAt: true,
768
+ updatedAt: true,
769
+ },
770
+ });
771
+ },
772
+ };
773
+ `;
774
+
775
+ await fs.writeFile(
776
+ path.join(repositoriesDir, 'user.repository.ts'),
777
+ userRepositoryContent
778
+ );
779
+ }
780
+ }
781
+
782
+ async function generateTests(
783
+ unitTestsDir: string,
784
+ integrationTestsDir: string,
785
+ config: ProjectConfig
786
+ ): Promise<void> {
787
+ // Unit test example
788
+ const unitTestContent = `import { healthController } from '../../src/controllers/health.controller.js';
789
+
790
+ describe('Health Controller', () => {
791
+ it('should return ok status', () => {
792
+ const mockReq = {} as any;
793
+ const mockRes = {
794
+ json: jest.fn(),
795
+ } as any;
796
+
797
+ healthController.check(mockReq, mockRes);
798
+
799
+ expect(mockRes.json).toHaveBeenCalledWith({
800
+ status: 'ok',
801
+ timestamp: expect.any(String),
802
+ });
803
+ });
804
+ });
805
+ `;
806
+
807
+ await fs.writeFile(
808
+ path.join(unitTestsDir, 'health.controller.test.ts'),
809
+ unitTestContent
810
+ );
811
+
812
+ // Integration test example
813
+ const integrationTestContent = `import request from 'supertest';
814
+ import express from 'express';
815
+ import { setupRoutes } from '../../src/routes/index.js';
816
+ import { setupMiddlewares } from '../../src/config/middlewares.js';
817
+ import { errorHandler } from '../../src/middlewares/errorHandler.js';
818
+
819
+ const app = express();
820
+ setupMiddlewares(app);
821
+ setupRoutes(app);
822
+ app.use(errorHandler);
823
+
824
+ describe('Health API', () => {
825
+ it('GET /api/health should return 200', async () => {
826
+ const response = await request(app).get('/api/health');
827
+
828
+ expect(response.status).toBe(200);
829
+ expect(response.body).toHaveProperty('status', 'ok');
830
+ expect(response.body).toHaveProperty('timestamp');
831
+ });
832
+ });
833
+ `;
834
+
835
+ await fs.writeFile(
836
+ path.join(integrationTestsDir, 'health.api.test.ts'),
837
+ integrationTestContent
838
+ );
839
+ }
840
+