archforge-x 1.0.2 → 1.0.3

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,770 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NestJSGenerator = void 0;
4
+ const base_1 = require("../base");
5
+ class NestJSGenerator extends base_1.BaseGenerator {
6
+ getGeneratorName() {
7
+ return "NestJS";
8
+ }
9
+ async generateProjectStructure(root, arch, options) {
10
+ const archStyle = options.architecture || "clean";
11
+ this.generateCommonFiles(root, options);
12
+ if (archStyle === "clean") {
13
+ this.generateCleanArchitecture(root, options);
14
+ }
15
+ else {
16
+ this.generateStandardArchitecture(root, options);
17
+ }
18
+ this.generateDocker(root, options);
19
+ this.generateCI(root, options);
20
+ }
21
+ generateCommonFiles(root, options) {
22
+ const projectName = options.projectName || "nestjs-app";
23
+ this.writeFile(root, "package.json", JSON.stringify({
24
+ name: projectName,
25
+ version: "0.0.1",
26
+ description: "",
27
+ author: "",
28
+ private: true,
29
+ license: "UNLICENSED",
30
+ scripts: {
31
+ "build": "nest build",
32
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
33
+ "start": "nest start",
34
+ "start:dev": "nest start --watch",
35
+ "start:debug": "nest start --debug --watch",
36
+ "start:prod": "node dist/main",
37
+ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
38
+ "test": "jest",
39
+ "test:watch": "jest --watch",
40
+ "test:cov": "jest --coverage",
41
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
42
+ "test:e2e": "jest --config ./test/jest-e2e.json"
43
+ },
44
+ dependencies: {
45
+ "@nestjs/common": "^10.0.0",
46
+ "@nestjs/core": "^10.0.0",
47
+ "@nestjs/platform-express": "^10.0.0",
48
+ "@nestjs/config": "^3.0.0",
49
+ "@nestjs/terminus": "^10.0.0",
50
+ "reflect-metadata": "^0.1.13",
51
+ "rxjs": "^7.8.1",
52
+ "nestjs-pino": "^3.3.0",
53
+ "pino-http": "^8.3.3",
54
+ "zod": "^3.22.2",
55
+ "ioredis": "^5.3.2",
56
+ "class-validator": "^0.14.0",
57
+ "class-transformer": "^0.5.1",
58
+ ...(options.orm === "prisma" ? { "@prisma/client": "^5.0.0" } : {})
59
+ },
60
+ devDependencies: {
61
+ "@nestjs/cli": "^10.0.0",
62
+ "@nestjs/schematics": "^10.0.0",
63
+ "@nestjs/testing": "^10.0.0",
64
+ "@types/express": "^4.17.17",
65
+ "@types/jest": "^29.5.2",
66
+ "@types/node": "^20.3.1",
67
+ "@types/supertest": "^2.0.12",
68
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
69
+ "@typescript-eslint/parser": "^6.0.0",
70
+ "eslint": "^8.42.0",
71
+ "eslint-config-prettier": "^9.0.0",
72
+ "eslint-plugin-prettier": "^5.0.0",
73
+ "eslint-plugin-import": "^2.27.5",
74
+ "jest": "^29.5.0",
75
+ "prettier": "^3.0.0",
76
+ "pino-pretty": "^10.0.0",
77
+ "source-map-support": "^0.5.21",
78
+ "supertest": "^6.3.3",
79
+ "ts-jest": "^29.1.0",
80
+ "ts-loader": "^9.4.3",
81
+ "ts-node": "^10.9.1",
82
+ "tsconfig-paths": "^4.2.0",
83
+ "typescript": "^5.1.3",
84
+ ...(options.orm === "prisma" ? { "prisma": "^5.0.0" } : {})
85
+ },
86
+ jest: {
87
+ "moduleFileExtensions": ["js", "json", "ts"],
88
+ "rootDir": "src",
89
+ "testRegex": ".*\\.spec\\.ts$",
90
+ "transform": { "^.+\\.(t|j)s$": "ts-jest" },
91
+ "collectCoverageFrom": ["**/*.(t|j)s"],
92
+ "coverageDirectory": "../coverage",
93
+ "testEnvironment": "node"
94
+ }
95
+ }, null, 2));
96
+ this.writeFile(root, "tsconfig.json", JSON.stringify({
97
+ compilerOptions: {
98
+ module: "commonjs",
99
+ declaration: true,
100
+ removeComments: true,
101
+ emitDecoratorMetadata: true,
102
+ experimentalDecorators: true,
103
+ allowSyntheticDefaultImports: true,
104
+ target: "ES2021",
105
+ sourceMap: true,
106
+ outDir: "./dist",
107
+ baseUrl: "./",
108
+ incremental: true,
109
+ skipLibCheck: true,
110
+ strictNullChecks: false,
111
+ noImplicitAny: false,
112
+ strictBindCallApply: false,
113
+ forceConsistentCasingInFileNames: false,
114
+ noFallthroughCasesInSwitch: false
115
+ }
116
+ }, null, 2));
117
+ this.writeFile(root, "nest-cli.json", JSON.stringify({
118
+ "$schema": "https://json.schemastore.org/nest-cli",
119
+ "collection": "@nestjs/schematics",
120
+ "sourceRoot": "src",
121
+ "compilerOptions": {
122
+ "deleteOutDir": true
123
+ }
124
+ }, null, 2));
125
+ // .eslintrc.json (Architecture Guardrails)
126
+ const archStyle = options.architecture || "clean";
127
+ let restrictedPaths = [];
128
+ if (archStyle === "clean") {
129
+ restrictedPaths = [
130
+ {
131
+ target: "./src/domain",
132
+ from: "./src/application",
133
+ message: "Domain layer cannot import from Application layer"
134
+ },
135
+ {
136
+ target: "./src/domain",
137
+ from: "./src/infrastructure",
138
+ message: "Domain layer cannot import from Infrastructure layer"
139
+ },
140
+ {
141
+ target: "./src/domain",
142
+ from: "./src/presentation",
143
+ message: "Domain layer cannot import from Presentation layer"
144
+ },
145
+ {
146
+ target: "./src/application",
147
+ from: "./src/infrastructure",
148
+ message: "Application layer cannot import from Infrastructure layer"
149
+ },
150
+ {
151
+ target: "./src/application",
152
+ from: "./src/presentation",
153
+ message: "Application layer cannot import from Presentation layer"
154
+ },
155
+ {
156
+ target: "./src/infrastructure",
157
+ from: "./src/presentation",
158
+ message: "Infrastructure layer cannot import from Presentation layer"
159
+ }
160
+ ];
161
+ }
162
+ this.writeFile(root, ".eslintrc.json", JSON.stringify({
163
+ "parser": "@typescript-eslint/parser",
164
+ "parserOptions": {
165
+ "project": "tsconfig.json",
166
+ "tsconfigRootDir": "__dirname",
167
+ "sourceType": "module"
168
+ },
169
+ "plugins": ["@typescript-eslint/eslint-plugin", "import"],
170
+ "extends": [
171
+ "plugin:@typescript-eslint/recommended",
172
+ "plugin:prettier/recommended"
173
+ ],
174
+ "root": true,
175
+ "env": {
176
+ "node": true,
177
+ "jest": true
178
+ },
179
+ "ignorePatterns": [".eslintrc.js", ".eslintrc.json"],
180
+ "rules": {
181
+ "@typescript-eslint/interface-name-prefix": "off",
182
+ "@typescript-eslint/explicit-function-return-type": "off",
183
+ "@typescript-eslint/explicit-module-boundary-types": "off",
184
+ "@typescript-eslint/no-explicit-any": "off",
185
+ "import/no-restricted-paths": ["error", {
186
+ "zones": restrictedPaths
187
+ }]
188
+ }
189
+ }, null, 2));
190
+ this.writeFile(root, ".env", `PORT=3000\nNODE_ENV=development\nDATABASE_URL="postgresql://user:password@localhost:5432/appdb?schema=public"\nREDIS_URL="redis://localhost:6379"\n`);
191
+ this.writeFile(root, ".env.example", `PORT=3000\nNODE_ENV=development\nDATABASE_URL="postgresql://user:password@localhost:5432/appdb?schema=public"\nREDIS_URL="redis://localhost:6379"\n`);
192
+ this.writeFile(root, ".gitignore", `node_modules/\ndist/\n.env\n`);
193
+ this.generateConfigModule(root, options);
194
+ this.generateCacheModule(root, options);
195
+ this.generateErrorFilter(root, options);
196
+ this.generateHealthModule(root, options);
197
+ if (options.orm === "prisma") {
198
+ this.generatePrismaSchema(root, options);
199
+ }
200
+ }
201
+ generateHealthModule(root, options) {
202
+ this.writeFile(root, "src/infrastructure/health/health.controller.ts", `
203
+ import { Controller, Get } from '@nestjs/common';
204
+ import { HealthCheckService, HealthCheck, TypeOrmHealthIndicator, MemoryHealthIndicator } from '@nestjs/terminus';
205
+
206
+ @Controller('health')
207
+ export class HealthController {
208
+ constructor(
209
+ private health: HealthCheckService,
210
+ private memory: MemoryHealthIndicator,
211
+ ) {}
212
+
213
+ @Get()
214
+ @HealthCheck()
215
+ check() {
216
+ return this.health.check([
217
+ () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
218
+ ]);
219
+ }
220
+ }
221
+ `);
222
+ this.writeFile(root, "src/infrastructure/health/health.module.ts", `
223
+ import { Module } from '@nestjs/common';
224
+ import { TerminusModule } from '@nestjs/terminus';
225
+ import { HealthController } from './health.controller';
226
+
227
+ @Module({
228
+ imports: [TerminusModule],
229
+ controllers: [HealthController],
230
+ })
231
+ export class HealthModule {}
232
+ `);
233
+ }
234
+ generateConfigModule(root, options) {
235
+ this.writeFile(root, "src/infrastructure/config/env.schema.ts", `
236
+ import { z } from "zod";
237
+
238
+ export const envSchema = z.object({
239
+ PORT: z.string().default("3000"),
240
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
241
+ DATABASE_URL: z.string(),
242
+ REDIS_URL: z.string().default("redis://localhost:6379"),
243
+ });
244
+
245
+ export type Env = z.infer<typeof envSchema>;
246
+ `);
247
+ this.writeFile(root, "src/infrastructure/config/config.module.ts", `
248
+ import { Module } from '@nestjs/common';
249
+ import { ConfigModule as NestConfigModule } from '@nestjs/config';
250
+ import { envSchema } from './env.schema';
251
+
252
+ @Module({
253
+ imports: [
254
+ NestConfigModule.forRoot({
255
+ isGlobal: true,
256
+ validate: (config) => {
257
+ const parsed = envSchema.safeParse(config);
258
+ if (!parsed.success) {
259
+ console.error("❌ Invalid environment variables:", parsed.error.format());
260
+ throw new Error("Invalid environment variables");
261
+ }
262
+ return parsed.data;
263
+ },
264
+ }),
265
+ ],
266
+ })
267
+ export class ConfigModule {}
268
+ `);
269
+ }
270
+ generateCacheModule(root, options) {
271
+ this.writeFile(root, "src/infrastructure/cache/cache.interface.ts", `
272
+ export interface ICache {
273
+ get<T>(key: string): Promise<T | null>;
274
+ set(key: string, value: any, ttlSeconds?: number): Promise<void>;
275
+ delete(key: string): Promise<void>;
276
+ }
277
+ `);
278
+ this.writeFile(root, "src/infrastructure/cache/redis.cache.ts", `
279
+ import { Injectable, OnModuleDestroy } from '@nestjs/common';
280
+ import { ConfigService } from '@nestjs/config';
281
+ import Redis from 'ioredis';
282
+ import { ICache } from './cache.interface';
283
+
284
+ @Injectable()
285
+ export class RedisCache implements ICache, OnModuleDestroy {
286
+ private readonly redis: Redis;
287
+
288
+ constructor(private configService: ConfigService) {
289
+ this.redis = new Redis(this.configService.get<string>('REDIS_URL'));
290
+ }
291
+
292
+ async get<T>(key: string): Promise<T | null> {
293
+ const data = await this.redis.get(key);
294
+ return data ? JSON.parse(data) : null;
295
+ }
296
+
297
+ async set(key: string, value: any, ttlSeconds?: number): Promise<void> {
298
+ const data = JSON.stringify(value);
299
+ if (ttlSeconds) {
300
+ await this.redis.setex(key, ttlSeconds, data);
301
+ } else {
302
+ await this.redis.set(key, data);
303
+ }
304
+ }
305
+
306
+ async delete(key: string): Promise<void> {
307
+ await this.redis.del(key);
308
+ }
309
+
310
+ onModuleDestroy() {
311
+ this.redis.disconnect();
312
+ }
313
+ }
314
+ `);
315
+ this.writeFile(root, "src/infrastructure/cache/cache.module.ts", `
316
+ import { Global, Module } from '@nestjs/common';
317
+ import { RedisCache } from './redis.cache';
318
+
319
+ @Global()
320
+ @Module({
321
+ providers: [
322
+ {
323
+ provide: 'ICache',
324
+ useClass: RedisCache,
325
+ },
326
+ ],
327
+ exports: ['ICache'],
328
+ })
329
+ export class CacheModule {}
330
+ `);
331
+ }
332
+ generateErrorFilter(root, options) {
333
+ this.writeFile(root, "src/infrastructure/filters/http-exception.filter.ts", `
334
+ import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
335
+ import { Request, Response } from 'express';
336
+ import { PinoLogger } from 'nestjs-pino';
337
+
338
+ @Catch()
339
+ export class HttpExceptionFilter implements ExceptionFilter {
340
+ constructor(private readonly logger: PinoLogger) {}
341
+
342
+ catch(exception: any, host: ArgumentsHost) {
343
+ const ctx = host.switchToHttp();
344
+ const response = ctx.getResponse<Response>();
345
+ const request = ctx.getRequest<Request>();
346
+
347
+ const status = exception instanceof HttpException
348
+ ? exception.getStatus()
349
+ : HttpStatus.INTERNAL_SERVER_ERROR;
350
+
351
+ const message = exception instanceof HttpException
352
+ ? exception.getResponse()
353
+ : 'Internal server error';
354
+
355
+ this.logger.error({
356
+ err: exception,
357
+ path: request.url,
358
+ status,
359
+ }, 'Unhandled Exception');
360
+
361
+ response.status(status).json({
362
+ statusCode: status,
363
+ timestamp: new Date().toISOString(),
364
+ path: request.url,
365
+ message: typeof message === 'object' ? (message as any).message : message,
366
+ });
367
+ }
368
+ }
369
+ `);
370
+ }
371
+ generateStandardArchitecture(root, options) {
372
+ this.writeFile(root, "src/main.ts", `
373
+ import { NestFactory } from '@nestjs/core';
374
+ import { ValidationPipe } from '@nestjs/common';
375
+ import { Logger } from 'nestjs-pino';
376
+ import { ConfigService } from '@nestjs/config';
377
+ import { AppModule } from './app.module';
378
+ import { HttpExceptionFilter } from './infrastructure/filters/http-exception.filter';
379
+
380
+ async function bootstrap() {
381
+ const app = await NestFactory.create(AppModule, { bufferLogs: true });
382
+ const logger = app.get(Logger);
383
+ const configService = app.get(ConfigService);
384
+
385
+ app.useLogger(logger);
386
+ app.useGlobalFilters(new HttpExceptionFilter(app.get(Logger)));
387
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
388
+
389
+ const port = configService.get<string>('PORT') || 3000;
390
+ await app.listen(port);
391
+
392
+ logger.log(\`🚀 NestJS Server running on port \${port}\`);
393
+ }
394
+ bootstrap();
395
+ `);
396
+ this.writeFile(root, "src/app.module.ts", `
397
+ import { Module } from '@nestjs/common';
398
+ import { LoggerModule } from 'nestjs-pino';
399
+ import { ConfigModule } from './infrastructure/config/config.module';
400
+ import { CacheModule } from './infrastructure/cache/cache.module';
401
+ import { HealthModule } from './infrastructure/health/health.module';
402
+ ${options.orm === 'prisma' ? "import { PrismaModule } from './infrastructure/prisma/prisma.module';" : ""}
403
+ import { AppController } from './app.controller';
404
+ import { AppService } from './app.service';
405
+
406
+ @Module({
407
+ imports: [
408
+ ConfigModule,
409
+ CacheModule,
410
+ HealthModule,
411
+ LoggerModule.forRoot({
412
+ pinoHttp: {
413
+ transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
414
+ autoLogging: true,
415
+ genReqId: (req) => req.headers['x-correlation-id'] || req.id,
416
+ },
417
+ }),
418
+ ${options.orm === 'prisma' ? "PrismaModule," : ""}
419
+ ],
420
+ controllers: [AppController],
421
+ providers: [AppService],
422
+ })
423
+ export class AppModule {}
424
+ `);
425
+ this.writeFile(root, "src/app.controller.ts", `
426
+ import { Controller, Get } from '@nestjs/common';
427
+ import { AppService } from './app.service';
428
+
429
+ @Controller()
430
+ export class AppController {
431
+ constructor(private readonly appService: AppService) {}
432
+
433
+ @Get()
434
+ getHello(): string {
435
+ return this.appService.getHello();
436
+ }
437
+ }
438
+ `);
439
+ this.writeFile(root, "src/app.service.ts", `
440
+ import { Injectable } from '@nestjs/common';
441
+
442
+ @Injectable()
443
+ export class AppService {
444
+ getHello(): string {
445
+ return 'Hello World!';
446
+ }
447
+ }
448
+ `);
449
+ }
450
+ generateCleanArchitecture(root, options) {
451
+ // Domain
452
+ this.writeFile(root, "src/domain/entities/user.entity.ts", `
453
+ export class User {
454
+ constructor(
455
+ public readonly id: string,
456
+ public readonly name: string,
457
+ public readonly email: string
458
+ ) {}
459
+ }
460
+ `);
461
+ this.writeFile(root, "src/domain/repositories/user.repository.interface.ts", `
462
+ import { User } from "../entities/user.entity";
463
+
464
+ export interface IUserRepository {
465
+ save(user: User): Promise<User>;
466
+ findByEmail(email: string): Promise<User | null>;
467
+ findById(id: string): Promise<User | null>;
468
+ }
469
+ `);
470
+ // Application
471
+ this.writeFile(root, "src/application/use-cases/create-user.use-case.ts", `
472
+ import { Inject, Injectable, BadRequestException } from '@nestjs/common';
473
+ import { User } from '../../domain/entities/user.entity';
474
+ import { IUserRepository } from '../../domain/repositories/user.repository.interface';
475
+
476
+ @Injectable()
477
+ export class CreateUserUseCase {
478
+ constructor(
479
+ @Inject('IUserRepository')
480
+ private readonly userRepository: IUserRepository
481
+ ) {}
482
+
483
+ async execute(name: string, email: string): Promise<User> {
484
+ const existing = await this.userRepository.findByEmail(email);
485
+ if (existing) throw new BadRequestException("User already exists");
486
+
487
+ const user = new User(Date.now().toString(), name, email);
488
+ return this.userRepository.save(user);
489
+ }
490
+ }
491
+ `);
492
+ this.writeFile(root, "src/application/use-cases/get-user.use-case.ts", `
493
+ import { Inject, Injectable, NotFoundException } from '@nestjs/common';
494
+ import { User } from '../../domain/entities/user.entity';
495
+ import { IUserRepository } from '../../domain/repositories/user.repository.interface';
496
+ import { ICache } from '../../infrastructure/cache/cache.interface';
497
+
498
+ @Injectable()
499
+ export class GetUserUseCase {
500
+ constructor(
501
+ @Inject('IUserRepository')
502
+ private readonly userRepository: IUserRepository,
503
+ @Inject('ICache')
504
+ private readonly cache: ICache
505
+ ) {}
506
+
507
+ async execute(id: string): Promise<User> {
508
+ const cacheKey = \`user:\${id}\`;
509
+ const cached = await this.cache.get<User>(cacheKey);
510
+ if (cached) return cached;
511
+
512
+ const user = await this.userRepository.findById(id);
513
+ if (!user) throw new NotFoundException("User not found");
514
+
515
+ await this.cache.set(cacheKey, user, 3600);
516
+ return user;
517
+ }
518
+ }
519
+ `);
520
+ // Infrastructure
521
+ this.writeFile(root, "src/infrastructure/repositories/in-memory-user.repository.ts", `
522
+ import { Injectable } from '@nestjs/common';
523
+ import { User } from '../../domain/entities/user.entity';
524
+ import { IUserRepository } from '../../domain/repositories/user.repository.interface';
525
+
526
+ @Injectable()
527
+ export class InMemoryUserRepository implements IUserRepository {
528
+ private users: User[] = [];
529
+
530
+ async save(user: User): Promise<User> {
531
+ this.users.push(user);
532
+ return user;
533
+ }
534
+
535
+ async findByEmail(email: string): Promise<User | null> {
536
+ return this.users.find(u => u.email === email) || null;
537
+ }
538
+
539
+ async findById(id: string): Promise<User | null> {
540
+ return this.users.find(u => u.id === id) || null;
541
+ }
542
+ }
543
+ `);
544
+ // Presentation (Controllers)
545
+ this.writeFile(root, "src/presentation/controllers/user.controller.ts", `
546
+ import { Controller, Post, Get, Body, Param } from '@nestjs/common';
547
+ import { CreateUserUseCase } from '../../application/use-cases/create-user.use-case';
548
+ import { GetUserUseCase } from '../../application/use-cases/get-user.use-case';
549
+
550
+ @Controller('users')
551
+ export class UserController {
552
+ constructor(
553
+ private readonly createUserUseCase: CreateUserUseCase,
554
+ private readonly getUserUseCase: GetUserUseCase
555
+ ) {}
556
+
557
+ @Post()
558
+ async create(@Body() body: { name: string; email: string }) {
559
+ return await this.createUserUseCase.execute(body.name, body.email);
560
+ }
561
+
562
+ @Get(':id')
563
+ async get(@Param('id') id: string) {
564
+ return await this.getUserUseCase.execute(id);
565
+ }
566
+ }
567
+ `);
568
+ // Modules
569
+ this.writeFile(root, "src/app.module.ts", `
570
+ import { Module } from '@nestjs/common';
571
+ import { LoggerModule } from 'nestjs-pino';
572
+ import { ConfigModule } from './infrastructure/config/config.module';
573
+ import { CacheModule } from './infrastructure/cache/cache.module';
574
+ import { HealthModule } from './infrastructure/health/health.module';
575
+ ${options.orm === 'prisma' ? "import { PrismaModule } from './infrastructure/prisma/prisma.module';" : ""}
576
+ import { UserController } from './presentation/controllers/user.controller';
577
+ import { CreateUserUseCase } from './application/use-cases/create-user.use-case';
578
+ import { GetUserUseCase } from './application/use-cases/get-user.use-case';
579
+ import { InMemoryUserRepository } from './infrastructure/repositories/in-memory-user.repository';
580
+
581
+ @Module({
582
+ imports: [
583
+ ConfigModule,
584
+ CacheModule,
585
+ HealthModule,
586
+ LoggerModule.forRoot({
587
+ pinoHttp: {
588
+ transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
589
+ autoLogging: true,
590
+ genReqId: (req) => req.headers['x-correlation-id'] || req.id,
591
+ },
592
+ }),
593
+ ${options.orm === 'prisma' ? "PrismaModule," : ""}
594
+ ],
595
+ controllers: [UserController],
596
+ providers: [
597
+ CreateUserUseCase,
598
+ GetUserUseCase,
599
+ {
600
+ provide: 'IUserRepository',
601
+ useClass: InMemoryUserRepository
602
+ }
603
+ ],
604
+ })
605
+ export class AppModule {}
606
+ `);
607
+ this.writeFile(root, "src/main.ts", `
608
+ import { NestFactory } from '@nestjs/core';
609
+ import { ValidationPipe } from '@nestjs/common';
610
+ import { Logger } from 'nestjs-pino';
611
+ import { ConfigService } from '@nestjs/config';
612
+ import { AppModule } from './app.module';
613
+ import { HttpExceptionFilter } from './infrastructure/filters/http-exception.filter';
614
+
615
+ async function bootstrap() {
616
+ const app = await NestFactory.create(AppModule, { bufferLogs: true });
617
+ const logger = app.get(Logger);
618
+ const configService = app.get(ConfigService);
619
+
620
+ app.useLogger(logger);
621
+ app.useGlobalFilters(new HttpExceptionFilter(app.get(Logger)));
622
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
623
+
624
+ const port = configService.get<string>('PORT') || 3000;
625
+ await app.listen(port);
626
+
627
+ logger.log(\`🚀 NestJS Clean Architecture Server running on port \${port}\`);
628
+ }
629
+ bootstrap();
630
+ `);
631
+ }
632
+ generateDocker(root, options) {
633
+ const dbService = options.database === "postgresql" ? `
634
+ db:
635
+ image: postgres:15-alpine
636
+ environment:
637
+ - POSTGRES_USER=user
638
+ - POSTGRES_PASSWORD=password
639
+ - POSTGRES_DB=appdb
640
+ ports:
641
+ - "5432:5432"
642
+ ` : options.database === "mysql" ? `
643
+ db:
644
+ image: mysql:8
645
+ environment:
646
+ - MYSQL_ROOT_PASSWORD=password
647
+ - MYSQL_DATABASE=appdb
648
+ ports:
649
+ - "3306:3306"
650
+ ` : "";
651
+ const dbUrl = options.database === "postgresql" ? "postgresql://user:password@db:5432/appdb?schema=public" :
652
+ options.database === "mysql" ? "mysql://root:password@db:3306/appdb" : "";
653
+ this.writeFile(root, "Dockerfile", `
654
+ FROM node:18-alpine AS builder
655
+ WORKDIR /app
656
+ COPY package*.json ./
657
+ RUN npm ci
658
+ COPY . .
659
+ ${options.orm === 'prisma' ? 'RUN npx prisma generate' : ''}
660
+ RUN npm run build
661
+
662
+ FROM node:18-alpine
663
+ WORKDIR /app
664
+ COPY --from=builder /app/dist ./dist
665
+ COPY --from=builder /app/package*.json ./
666
+ ${options.orm === 'prisma' ? 'COPY --from=builder /app/prisma ./prisma' : ''}
667
+ RUN npm ci --production
668
+ ${options.orm === 'prisma' ? 'RUN npx prisma generate' : ''}
669
+ EXPOSE 3000
670
+ CMD ["npm", "run", "start:prod"]
671
+ `);
672
+ this.writeFile(root, "docker-compose.yml", `
673
+ version: '3.8'
674
+ services:
675
+ app:
676
+ build: .
677
+ ports:
678
+ - "3000:3000"
679
+ environment:
680
+ - PORT=3000
681
+ - NODE_ENV=production
682
+ ${dbUrl ? `- DATABASE_URL=\${DATABASE_URL:-${dbUrl}}` : ""}
683
+ - REDIS_URL=redis://cache:6379
684
+ depends_on:
685
+ ${dbService ? "- db" : ""}
686
+ - cache
687
+ restart: always
688
+ ${dbService}
689
+ cache:
690
+ image: redis:7-alpine
691
+ ports:
692
+ - "6379:6379"
693
+ `);
694
+ }
695
+ generateCI(root, options) {
696
+ this.writeFile(root, ".github/workflows/ci.yml", `
697
+ name: CI
698
+
699
+ on:
700
+ push:
701
+ branches: [ main ]
702
+ pull_request:
703
+ branches: [ main ]
704
+
705
+ jobs:
706
+ build:
707
+ runs-on: ubuntu-latest
708
+
709
+ steps:
710
+ - uses: actions/checkout@v3
711
+ - name: Use Node.js 18.x
712
+ uses: actions/setup-node@v3
713
+ with:
714
+ node-version: 18.x
715
+ cache: 'npm'
716
+ - run: npm ci
717
+ ${options.orm === 'prisma' ? '- run: npx prisma generate' : ''}
718
+ - name: Architecture Sync & Validation
719
+ run: npx archforge sync
720
+ - run: npm run lint
721
+ - run: npm test
722
+ - run: npm run build
723
+ `);
724
+ }
725
+ generatePrismaSchema(root, options) {
726
+ const provider = options.database === "mongodb" ? "mongodb" :
727
+ options.database === "mysql" ? "mysql" :
728
+ options.database === "sqlite" ? "sqlite" : "postgresql";
729
+ const url = options.database === "sqlite" ? "file:./dev.db" : "env(\"DATABASE_URL\")";
730
+ this.writeFile(root, "prisma/schema.prisma", `
731
+ generator client {
732
+ provider = "prisma-client-js"
733
+ }
734
+
735
+ datasource db {
736
+ provider = "${provider}"
737
+ url = ${url}
738
+ }
739
+
740
+ model User {
741
+ id String @id @default(uuid())
742
+ email String @unique
743
+ name String?
744
+ }
745
+ `);
746
+ this.writeFile(root, "src/infrastructure/prisma/prisma.service.ts", `
747
+ import { Injectable, OnModuleInit } from '@nestjs/common';
748
+ import { PrismaClient } from '@prisma/client';
749
+
750
+ @Injectable()
751
+ export class PrismaService extends PrismaClient implements OnModuleInit {
752
+ async onModuleInit() {
753
+ await this.$connect();
754
+ }
755
+ }
756
+ `);
757
+ this.writeFile(root, "src/infrastructure/prisma/prisma.module.ts", `
758
+ import { Global, Module } from '@nestjs/common';
759
+ import { PrismaService } from './prisma.service';
760
+
761
+ @Global()
762
+ @Module({
763
+ providers: [PrismaService],
764
+ exports: [PrismaService],
765
+ })
766
+ export class PrismaModule {}
767
+ `);
768
+ }
769
+ }
770
+ exports.NestJSGenerator = NestJSGenerator;