forgestack-os-cli 0.1.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.
Files changed (82) hide show
  1. package/dist/commands/create.d.ts +1 -0
  2. package/dist/commands/create.d.ts.map +1 -0
  3. package/dist/commands/create.js +78 -0
  4. package/dist/commands/create.js.map +1 -0
  5. package/dist/generators/api.d.ts +3 -0
  6. package/dist/generators/api.d.ts.map +1 -0
  7. package/dist/generators/api.js +346 -0
  8. package/dist/generators/api.js.map +1 -0
  9. package/dist/generators/auth.d.ts +2 -0
  10. package/dist/generators/auth.d.ts.map +1 -0
  11. package/dist/generators/auth.js +371 -0
  12. package/dist/generators/auth.js.map +1 -0
  13. package/dist/generators/backend.d.ts +2 -0
  14. package/dist/generators/backend.d.ts.map +1 -0
  15. package/dist/generators/backend.js +875 -0
  16. package/dist/generators/backend.js.map +1 -0
  17. package/dist/generators/common.d.ts +2 -0
  18. package/dist/generators/common.d.ts.map +1 -0
  19. package/dist/generators/common.js +354 -0
  20. package/dist/generators/common.js.map +1 -0
  21. package/dist/generators/database.d.ts +2 -0
  22. package/dist/generators/database.d.ts.map +1 -0
  23. package/dist/generators/database.js +157 -0
  24. package/dist/generators/database.js.map +1 -0
  25. package/dist/generators/docker.d.ts +2 -0
  26. package/dist/generators/docker.d.ts.map +1 -0
  27. package/dist/generators/docker.js +181 -0
  28. package/dist/generators/docker.js.map +1 -0
  29. package/dist/generators/frontend-helpers.d.ts +3 -0
  30. package/dist/generators/frontend-helpers.d.ts.map +1 -0
  31. package/dist/generators/frontend-helpers.js +23 -0
  32. package/dist/generators/frontend-helpers.js.map +1 -0
  33. package/dist/generators/frontend.d.ts +2 -0
  34. package/dist/generators/frontend.d.ts.map +1 -0
  35. package/dist/generators/frontend.js +735 -0
  36. package/dist/generators/frontend.js.map +1 -0
  37. package/dist/generators/index.d.ts +2 -0
  38. package/dist/generators/index.d.ts.map +1 -0
  39. package/dist/generators/index.js +59 -0
  40. package/dist/generators/index.js.map +1 -0
  41. package/dist/generators/nextjs-helpers.d.ts +6 -0
  42. package/dist/generators/nextjs-helpers.d.ts.map +1 -0
  43. package/dist/generators/nextjs-helpers.js +216 -0
  44. package/dist/generators/nextjs-helpers.js.map +1 -0
  45. package/dist/index.d.ts +2 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +24 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/types.d.ts +15 -0
  50. package/dist/types.d.ts.map +1 -0
  51. package/dist/types.js +3 -0
  52. package/dist/types.js.map +1 -0
  53. package/dist/utils/logger.d.ts +9 -0
  54. package/dist/utils/logger.d.ts.map +1 -0
  55. package/dist/utils/logger.js +32 -0
  56. package/dist/utils/logger.js.map +1 -0
  57. package/dist/utils/prompts.d.ts +2 -0
  58. package/dist/utils/prompts.d.ts.map +1 -0
  59. package/dist/utils/prompts.js +107 -0
  60. package/dist/utils/prompts.js.map +1 -0
  61. package/dist/utils/validators.d.ts +2 -0
  62. package/dist/utils/validators.d.ts.map +1 -0
  63. package/dist/utils/validators.js +48 -0
  64. package/dist/utils/validators.js.map +1 -0
  65. package/package.json +49 -0
  66. package/src/commands/create.ts +82 -0
  67. package/src/generators/api.ts +353 -0
  68. package/src/generators/auth.ts +406 -0
  69. package/src/generators/backend.ts +927 -0
  70. package/src/generators/common.ts +377 -0
  71. package/src/generators/database.ts +165 -0
  72. package/src/generators/docker.ts +185 -0
  73. package/src/generators/frontend.ts +783 -0
  74. package/src/generators/index.ts +64 -0
  75. package/src/index.ts +27 -0
  76. package/src/types.ts +16 -0
  77. package/src/utils/logger.ts +31 -0
  78. package/src/utils/prompts.ts +105 -0
  79. package/src/utils/validators.ts +50 -0
  80. package/tests/validators.test.ts +69 -0
  81. package/tsc_output.txt +0 -0
  82. package/tsconfig.json +21 -0
@@ -0,0 +1,927 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { StackConfig } from '../types';
4
+
5
+ export async function generateBackend(config: StackConfig, backendDir: string) {
6
+ switch (config.backend) {
7
+ case 'express':
8
+ await generateExpress(config, backendDir);
9
+ break;
10
+ case 'fastify':
11
+ await generateFastify(config, backendDir);
12
+ break;
13
+ case 'nestjs':
14
+ await generateNestJS(config, backendDir);
15
+ break;
16
+ case 'bun-elysia':
17
+ await generateBunElysia(config, backendDir);
18
+ break;
19
+ case 'go-fiber':
20
+ await generateGoFiber(config, backendDir);
21
+ break;
22
+ default:
23
+ throw new Error(`Unsupported backend: ${config.backend}`);
24
+ }
25
+ }
26
+
27
+ async function generateExpress(config: StackConfig, backendDir: string) {
28
+ // Package.json
29
+ const packageJson = {
30
+ name: `${config.projectName}-backend`,
31
+ version: '0.1.0',
32
+ type: 'module',
33
+ scripts: {
34
+ dev: 'tsx watch src/index.ts',
35
+ build: 'tsc',
36
+ start: 'node dist/index.js',
37
+ 'db:migrate': config.database.includes('prisma') ? 'prisma migrate dev' : undefined,
38
+ 'db:generate': config.database.includes('prisma') ? 'prisma generate' : undefined,
39
+ },
40
+ dependencies: {
41
+ express: '^4.18.2',
42
+ cors: '^2.8.5',
43
+ dotenv: '^16.4.1',
44
+ helmet: '^7.1.0',
45
+ 'express-rate-limit': '^7.1.5',
46
+ zod: '^3.22.4',
47
+ ...(config.auth === 'jwt' && {
48
+ jsonwebtoken: '^9.0.2',
49
+ bcrypt: '^5.1.1',
50
+ }),
51
+ ...(config.database === 'postgresql' && { '@prisma/client': '^5.8.1' }),
52
+ ...(config.database === 'mongodb' && { mongoose: '^8.1.0' }),
53
+ ...(config.database === 'mysql' && { '@prisma/client': '^5.8.1' }),
54
+ ...(config.database === 'sqlite' && { '@prisma/client': '^5.8.1' }),
55
+ },
56
+ devDependencies: {
57
+ '@types/express': '^4.17.21',
58
+ '@types/cors': '^2.8.17',
59
+ '@types/node': '^20.11.5',
60
+ ...(config.auth === 'jwt' && {
61
+ '@types/jsonwebtoken': '^9.0.5',
62
+ '@types/bcrypt': '^5.0.2',
63
+ }),
64
+ typescript: '^5.3.3',
65
+ tsx: '^4.7.0',
66
+ ...(config.database.includes('prisma') && { prisma: '^5.8.1' }),
67
+ },
68
+ };
69
+
70
+ await fs.writeJSON(path.join(backendDir, 'package.json'), packageJson, { spaces: 2 });
71
+
72
+ // TypeScript config
73
+ const tsConfig = {
74
+ compilerOptions: {
75
+ target: 'ES2020',
76
+ module: 'ESNext',
77
+ lib: ['ES2020'],
78
+ outDir: './dist',
79
+ rootDir: './src',
80
+ strict: true,
81
+ esModuleInterop: true,
82
+ skipLibCheck: true,
83
+ forceConsistentCasingInFileNames: true,
84
+ resolveJsonModule: true,
85
+ moduleResolution: 'bundler',
86
+ allowImportingTsExtensions: true,
87
+ },
88
+ include: ['src/**/*'],
89
+ exclude: ['node_modules', 'dist'],
90
+ };
91
+
92
+ await fs.writeJSON(path.join(backendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
93
+
94
+ // Create src directory structure
95
+ const srcDir = path.join(backendDir, 'src');
96
+ await fs.ensureDir(srcDir);
97
+ await fs.ensureDir(path.join(srcDir, 'routes'));
98
+ await fs.ensureDir(path.join(srcDir, 'middleware'));
99
+ await fs.ensureDir(path.join(srcDir, 'controllers'));
100
+ await fs.ensureDir(path.join(srcDir, 'lib'));
101
+
102
+ // Main server file
103
+ const indexTs = getExpressIndex(config);
104
+ await fs.writeFile(path.join(srcDir, 'index.ts'), indexTs);
105
+
106
+ // Routes
107
+ const healthRoute = `import { Router } from 'express';
108
+
109
+ const router = Router();
110
+
111
+ router.get('/health', (req, res) => {
112
+ res.json({
113
+ status: 'ok',
114
+ timestamp: new Date().toISOString(),
115
+ service: '${config.projectName}-backend'
116
+ });
117
+ });
118
+
119
+ export default router;
120
+ `;
121
+
122
+ await fs.writeFile(path.join(srcDir, 'routes', 'health.ts'), healthRoute);
123
+
124
+ // Error handler middleware
125
+ const errorHandler = `import { Request, Response, NextFunction } from 'express';
126
+
127
+ export interface AppError extends Error {
128
+ statusCode?: number;
129
+ }
130
+
131
+ export function errorHandler(
132
+ err: AppError,
133
+ req: Request,
134
+ res: Response,
135
+ next: NextFunction
136
+ ) {
137
+ const statusCode = err.statusCode || 500;
138
+ const message = err.message || 'Internal Server Error';
139
+
140
+ console.error('Error:', err);
141
+
142
+ res.status(statusCode).json({
143
+ error: {
144
+ message,
145
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
146
+ },
147
+ });
148
+ }
149
+ `;
150
+
151
+ await fs.writeFile(path.join(srcDir, 'middleware', 'errorHandler.ts'), errorHandler);
152
+
153
+ // Multi-tenant middleware if enabled
154
+ if (config.multiTenant) {
155
+ const tenantMiddleware = getTenantMiddleware(config);
156
+ await fs.writeFile(path.join(srcDir, 'middleware', 'tenant.ts'), tenantMiddleware);
157
+ }
158
+ }
159
+
160
+ function getExpressIndex(config: StackConfig): string {
161
+ return `import express from 'express';
162
+ import cors from 'cors';
163
+ import helmet from 'helmet';
164
+ import rateLimit from 'express-rate-limit';
165
+ import dotenv from 'dotenv';
166
+ import healthRouter from './routes/health';
167
+ import authRouter from './routes/auth';
168
+ import { errorHandler } from './middleware/errorHandler';
169
+ ${config.multiTenant ? "import { tenantMiddleware } from './middleware/tenant';" : ''}
170
+
171
+ dotenv.config();
172
+
173
+ const app = express();
174
+ const PORT = process.env.PORT || 3000;
175
+
176
+ // Security middleware
177
+ app.use(helmet());
178
+ app.use(cors({
179
+ origin: process.env.FRONTEND_URL || 'http://localhost:5173',
180
+ credentials: true,
181
+ }));
182
+
183
+ // Rate limiting
184
+ const limiter = rateLimit({
185
+ windowMs: 15 * 60 * 1000, // 15 minutes
186
+ max: 100, // limit each IP to 100 requests per windowMs
187
+ });
188
+ app.use('/api', limiter);
189
+
190
+ // Body parsing
191
+ app.use(express.json());
192
+ app.use(express.urlencoded({ extended: true }));
193
+
194
+ ${config.multiTenant ? '// Multi-tenant middleware\napp.use(tenantMiddleware);' : ''}
195
+
196
+ // Routes
197
+ app.use('/api', healthRouter);
198
+ app.use('/api/auth', authRouter);
199
+
200
+ // Error handling
201
+ app.use(errorHandler);
202
+
203
+ app.listen(PORT, () => {
204
+ console.log(\`🚀 Server running on http://localhost:\${PORT}\`);
205
+ console.log(\`📊 Health check: http://localhost:\${PORT}/api/health\`);
206
+ });
207
+ `;
208
+ }
209
+
210
+ function getTenantMiddleware(config: StackConfig): string {
211
+ if (config.auth === 'jwt') {
212
+ return `import { Request, Response, NextFunction } from 'express';
213
+ import jwt from 'jsonwebtoken';
214
+
215
+ export interface TenantRequest extends Request {
216
+ tenantId?: string;
217
+ userId?: string;
218
+ }
219
+
220
+ export function tenantMiddleware(
221
+ req: TenantRequest,
222
+ res: Response,
223
+ next: NextFunction
224
+ ) {
225
+ try {
226
+ const token = req.headers.authorization?.replace('Bearer ', '');
227
+
228
+ if (!token) {
229
+ return next();
230
+ }
231
+
232
+ const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any;
233
+ req.tenantId = decoded.tenantId;
234
+ req.userId = decoded.userId;
235
+
236
+ next();
237
+ } catch (error) {
238
+ next();
239
+ }
240
+ }
241
+ `;
242
+ } else if (config.auth === 'clerk') {
243
+ return `import { Request, Response, NextFunction } from 'express';
244
+
245
+ export interface TenantRequest extends Request {
246
+ tenantId?: string;
247
+ userId?: string;
248
+ }
249
+
250
+ export function tenantMiddleware(
251
+ req: TenantRequest,
252
+ res: Response,
253
+ next: NextFunction
254
+ ) {
255
+ // Clerk organization ID is used as tenant ID
256
+ // Extract from Clerk session token
257
+ const orgId = req.headers['x-clerk-org-id'] as string;
258
+
259
+ if (orgId) {
260
+ req.tenantId = orgId;
261
+ }
262
+
263
+ next();
264
+ }
265
+ `;
266
+ }
267
+
268
+ return `import { Request, Response, NextFunction } from 'express';
269
+
270
+ export interface TenantRequest extends Request {
271
+ tenantId?: string;
272
+ userId?: string;
273
+ }
274
+
275
+ export function tenantMiddleware(
276
+ req: TenantRequest,
277
+ res: Response,
278
+ next: NextFunction
279
+ ) {
280
+ // Extract tenant ID from request
281
+ // Implementation depends on your auth provider
282
+ const tenantId = req.headers['x-tenant-id'] as string;
283
+
284
+ if (tenantId) {
285
+ req.tenantId = tenantId;
286
+ }
287
+
288
+ next();
289
+ }
290
+ `;
291
+ }
292
+
293
+ async function generateFastify(_config: StackConfig, _backendDir: string) {
294
+ throw new Error('Fastify support coming in Phase 2');
295
+ }
296
+
297
+ async function generateNestJS(config: StackConfig, backendDir: string) {
298
+ // Package.json
299
+ const packageJson = {
300
+ name: `${config.projectName}-backend`,
301
+ version: '0.1.0',
302
+ scripts: {
303
+ build: 'nest build',
304
+ format: 'prettier --write "src/**/*.ts"',
305
+ start: 'nest start',
306
+ 'start:dev': 'nest start --watch',
307
+ 'start:debug': 'nest start --debug --watch',
308
+ 'start:prod': 'node dist/main',
309
+ lint: 'eslint "{src,apps,libs,test}/**/*.ts" --fix',
310
+ },
311
+ dependencies: {
312
+ '@nestjs/common': '^10.3.0',
313
+ '@nestjs/core': '^10.3.0',
314
+ '@nestjs/platform-express': '^10.3.0',
315
+ '@nestjs/config': '^3.1.1',
316
+ '@nestjs/jwt': '^10.2.0',
317
+ '@nestjs/passport': '^10.0.3',
318
+ '@nestjs/swagger': '^7.1.17',
319
+ 'passport': '^0.7.0',
320
+ 'passport-jwt': '^4.0.1',
321
+ 'bcrypt': '^5.1.1',
322
+ 'class-validator': '^0.14.1',
323
+ 'class-transformer': '^0.5.1',
324
+ 'reflect-metadata': '^0.2.1',
325
+ 'rxjs': '^7.8.1',
326
+ ...(config.database === 'mongodb' && { '@nestjs/mongoose': '^10.0.2', 'mongoose': '^8.0.4' }),
327
+ ...(config.database !== 'mongodb' && { '@prisma/client': '^5.8.1' }),
328
+ },
329
+ devDependencies: {
330
+ '@nestjs/cli': '^10.2.1',
331
+ '@nestjs/schematics': '^10.0.3',
332
+ '@nestjs/testing': '^10.3.0',
333
+ '@types/express': '^4.17.21',
334
+ '@types/node': '^20.11.5',
335
+ '@types/passport-jwt': '^4.0.0',
336
+ '@types/bcrypt': '^5.0.2',
337
+ '@typescript-eslint/eslint-plugin': '^6.19.0',
338
+ '@typescript-eslint/parser': '^6.19.0',
339
+ 'eslint': '^8.56.0',
340
+ 'prettier': '^3.2.4',
341
+ 'typescript': '^5.3.3',
342
+ ...(config.database !== 'mongodb' && { 'prisma': '^5.8.1' }),
343
+ },
344
+ };
345
+
346
+ await fs.writeJSON(path.join(backendDir, 'package.json'), packageJson, { spaces: 2 });
347
+
348
+ // NestJS CLI config
349
+ const nestCliJson = {
350
+ '$schema': 'https://json.schemastore.org/nest-cli',
351
+ collection: '@nestjs/schematics',
352
+ sourceRoot: 'src',
353
+ compilerOptions: {
354
+ deleteOutDir: true,
355
+ },
356
+ };
357
+
358
+ await fs.writeJSON(path.join(backendDir, 'nest-cli.json'), nestCliJson, { spaces: 2 });
359
+
360
+ // TypeScript config
361
+ const tsConfig = {
362
+ compilerOptions: {
363
+ module: 'commonjs',
364
+ declaration: true,
365
+ removeComments: true,
366
+ emitDecoratorMetadata: true,
367
+ experimentalDecorators: true,
368
+ allowSyntheticDefaultImports: true,
369
+ target: 'ES2021',
370
+ sourceMap: true,
371
+ outDir: './dist',
372
+ baseUrl: './',
373
+ incremental: true,
374
+ skipLibCheck: true,
375
+ strictNullChecks: false,
376
+ noImplicitAny: false,
377
+ strictBindCallApply: false,
378
+ forceConsistentCasingInFileNames: false,
379
+ noFallthroughCasesInSwitch: false,
380
+ },
381
+ };
382
+
383
+ await fs.writeJSON(path.join(backendDir, 'tsconfig.json'), tsConfig, { spaces: 2 });
384
+
385
+ // Create directory structure
386
+ const srcDir = path.join(backendDir, 'src');
387
+ await fs.ensureDir(srcDir);
388
+ await fs.ensureDir(path.join(srcDir, 'auth'));
389
+ await fs.ensureDir(path.join(srcDir, 'auth/guards'));
390
+ await fs.ensureDir(path.join(srcDir, 'auth/strategies'));
391
+ await fs.ensureDir(path.join(srcDir, 'auth/dto'));
392
+ await fs.ensureDir(path.join(srcDir, 'users'));
393
+ await fs.ensureDir(path.join(srcDir, 'users/dto'));
394
+ await fs.ensureDir(path.join(srcDir, 'common/interceptors'));
395
+ await fs.ensureDir(path.join(srcDir, 'common/filters'));
396
+ await fs.ensureDir(path.join(srcDir, 'config'));
397
+ await fs.ensureDir(path.join(srcDir, 'database'));
398
+
399
+ // Main.ts
400
+ const mainTs = `import { NestFactory } from '@nestjs/core';
401
+ import { ValidationPipe } from '@nestjs/common';
402
+ import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
403
+ import { AppModule } from './app.module';
404
+
405
+ async function bootstrap() {
406
+ const app = await NestFactory.create(AppModule);
407
+
408
+ app.enableCors();
409
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
410
+
411
+ const config = new DocumentBuilder()
412
+ .setTitle('${config.projectName} API')
413
+ .setDescription('Generated by ForgeStack OS')
414
+ .setVersion('1.0')
415
+ .addBearerAuth()
416
+ .build();
417
+ const document = SwaggerModule.createDocument(app, config);
418
+ SwaggerModule.setup('api/docs', app, document);
419
+
420
+ await app.listen(process.env.PORT || 3000);
421
+ console.log(\`Application is running on: \${await app.getUrl()}\`);
422
+ }
423
+ bootstrap();
424
+ `;
425
+
426
+ await fs.writeFile(path.join(srcDir, 'main.ts'), mainTs);
427
+
428
+ // App Module
429
+ const appModule = `import { Module } from '@nestjs/common';
430
+ import { ConfigModule } from '@nestjs/config';
431
+ import { AuthModule } from './auth/auth.module';
432
+ import { UsersModule } from './users/users.module';
433
+ import { DatabaseModule } from './database/database.module';
434
+ import configuration from './config/configuration';
435
+
436
+ @Module({
437
+ imports: [
438
+ ConfigModule.forRoot({
439
+ load: [configuration],
440
+ isGlobal: true,
441
+ }),
442
+ DatabaseModule,
443
+ AuthModule,
444
+ UsersModule,
445
+ ],
446
+ })
447
+ export class AppModule {}
448
+ `;
449
+
450
+ await fs.writeFile(path.join(srcDir, 'app.module.ts'), appModule);
451
+
452
+ // Configuration
453
+ const configFile = `export default () => ({
454
+ port: parseInt(process.env.PORT, 10) || 3000,
455
+ database: {
456
+ url: process.env.DATABASE_URL,
457
+ },
458
+ jwt: {
459
+ secret: process.env.JWT_SECRET || 'your-secret-key',
460
+ expiresIn: '7d',
461
+ },
462
+ });
463
+ `;
464
+
465
+ await fs.writeFile(path.join(srcDir, 'config', 'configuration.ts'), configFile);
466
+
467
+ // Database Module
468
+ if (config.database === 'mongodb') {
469
+ const dbModule = `import { Module } from '@nestjs/common';
470
+ import { MongooseModule } from '@nestjs/mongoose';
471
+ import { ConfigService } from '@nestjs/config';
472
+
473
+ @Module({
474
+ imports: [
475
+ MongooseModule.forRootAsync({
476
+ inject: [ConfigService],
477
+ useFactory: (config: ConfigService) => ({
478
+ uri: config.get<string>('database.url'),
479
+ }),
480
+ }),
481
+ ],
482
+ })
483
+ export class DatabaseModule {}
484
+ `;
485
+ await fs.writeFile(path.join(srcDir, 'database', 'database.module.ts'), dbModule);
486
+ } else {
487
+ const prismaService = `import { Injectable, OnModuleInit } from '@nestjs/common';
488
+ import { PrismaClient } from '@prisma/client';
489
+
490
+ @Injectable()
491
+ export class PrismaService extends PrismaClient implements OnModuleInit {
492
+ async onModuleInit() {
493
+ await this.$connect();
494
+ }
495
+ }
496
+ `;
497
+ await fs.writeFile(path.join(srcDir, 'database', 'prisma.service.ts'), prismaService);
498
+
499
+ const dbModule = `import { Module, Global } from '@nestjs/common';
500
+ import { PrismaService } from './prisma.service';
501
+
502
+ @Global()
503
+ @Module({
504
+ providers: [PrismaService],
505
+ exports: [PrismaService],
506
+ })
507
+ export class DatabaseModule {}
508
+ `;
509
+ await fs.writeFile(path.join(srcDir, 'database', 'database.module.ts'), dbModule);
510
+ }
511
+
512
+ // Auth DTOs
513
+ const loginDto = `import { ApiProperty } from '@nestjs/swagger';
514
+ import { IsEmail, IsString, MinLength } from 'class-validator';
515
+
516
+ export class LoginDto {
517
+ @ApiProperty()
518
+ @IsEmail()
519
+ email: string;
520
+
521
+ @ApiProperty()
522
+ @IsString()
523
+ @MinLength(6)
524
+ password: string;
525
+ }
526
+ `;
527
+
528
+ await fs.writeFile(path.join(srcDir, 'auth/dto', 'login.dto.ts'), loginDto);
529
+
530
+ const registerDto = `import { ApiProperty } from '@nestjs/swagger';
531
+ import { IsEmail, IsString, MinLength } from 'class-validator';
532
+
533
+ export class RegisterDto {
534
+ @ApiProperty()
535
+ @IsEmail()
536
+ email: string;
537
+
538
+ @ApiProperty()
539
+ @IsString()
540
+ @MinLength(6)
541
+ password: string;
542
+
543
+ @ApiProperty({ required: false })
544
+ @IsString()
545
+ name?: string;
546
+ }
547
+ `;
548
+
549
+ await fs.writeFile(path.join(srcDir, 'auth/dto', 'register.dto.ts'), registerDto);
550
+
551
+ // JWT Strategy
552
+ const jwtStrategy = `import { Injectable } from '@nestjs/common';
553
+ import { PassportStrategy } from '@nestjs/passport';
554
+ import { ExtractJwt, Strategy } from 'passport-jwt';
555
+ import { ConfigService } from '@nestjs/config';
556
+
557
+ @Injectable()
558
+ export class JwtStrategy extends PassportStrategy(Strategy) {
559
+ constructor(private config: ConfigService) {
560
+ super({
561
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
562
+ ignoreExpiration: false,
563
+ secretOrKey: config.get<string>('jwt.secret'),
564
+ });
565
+ }
566
+
567
+ async validate(payload: any) {
568
+ return { userId: payload.sub, email: payload.email${config.multiTenant ? ', tenantId: payload.tenantId' : ''} };
569
+ }
570
+ }
571
+ `;
572
+
573
+ await fs.writeFile(path.join(srcDir, 'auth/strategies', 'jwt.strategy.ts'), jwtStrategy);
574
+
575
+ // JWT Auth Guard
576
+ const jwtAuthGuard = `import { Injectable } from '@nestjs/common';
577
+ import { AuthGuard } from '@nestjs/passport';
578
+
579
+ @Injectable()
580
+ export class JwtAuthGuard extends AuthGuard('jwt') {}
581
+ `;
582
+
583
+ await fs.writeFile(path.join(srcDir, 'auth/guards', 'jwt-auth.guard.ts'), jwtAuthGuard);
584
+
585
+ // Auth Service
586
+ const authService = getNestJSAuthService(config);
587
+ await fs.writeFile(path.join(srcDir, 'auth', 'auth.service.ts'), authService);
588
+
589
+ // Auth Controller
590
+ const authController = `import { Controller, Post, Body, HttpCode, HttpStatus } from '@nestjs/common';
591
+ import { ApiTags, ApiOperation } from '@nestjs/swagger';
592
+ import { AuthService } from './auth.service';
593
+ import { LoginDto } from './dto/login.dto';
594
+ import { RegisterDto } from './dto/register.dto';
595
+
596
+ @ApiTags('auth')
597
+ @Controller('auth')
598
+ export class AuthController {
599
+ constructor(private authService: AuthService) {}
600
+
601
+ @Post('register')
602
+ @ApiOperation({ summary: 'Register a new user' })
603
+ async register(@Body() dto: RegisterDto) {
604
+ return this.authService.register(dto);
605
+ }
606
+
607
+ @Post('login')
608
+ @HttpCode(HttpStatus.OK)
609
+ @ApiOperation({ summary: 'Login user' })
610
+ async login(@Body() dto: LoginDto) {
611
+ return this.authService.login(dto);
612
+ }
613
+ }
614
+ `;
615
+
616
+ await fs.writeFile(path.join(srcDir, 'auth', 'auth.controller.ts'), authController);
617
+
618
+ // Auth Module
619
+ const authModule = `import { Module } from '@nestjs/common';
620
+ import { JwtModule } from '@nestjs/jwt';
621
+ import { PassportModule } from '@nestjs/passport';
622
+ import { ConfigService } from '@nestjs/config';
623
+ import { AuthService } from './auth.service';
624
+ import { AuthController } from './auth.controller';
625
+ import { JwtStrategy } from './strategies/jwt.strategy';
626
+ import { UsersModule } from '../users/users.module';
627
+
628
+ @Module({
629
+ imports: [
630
+ UsersModule,
631
+ PassportModule,
632
+ JwtModule.registerAsync({
633
+ inject: [ConfigService],
634
+ useFactory: (config: ConfigService) => ({
635
+ secret: config.get<string>('jwt.secret'),
636
+ signOptions: { expiresIn: config.get<string>('jwt.expiresIn') },
637
+ }),
638
+ }),
639
+ ],
640
+ providers: [AuthService, JwtStrategy],
641
+ controllers: [AuthController],
642
+ })
643
+ export class AuthModule {}
644
+ `;
645
+
646
+ await fs.writeFile(path.join(srcDir, 'auth', 'auth.module.ts'), authModule);
647
+
648
+ // Users Module
649
+ const usersService = getNestJSUsersService(config);
650
+ await fs.writeFile(path.join(srcDir, 'users', 'users.service.ts'), usersService);
651
+
652
+ const usersController = `import { Controller, Get, UseGuards, Request } from '@nestjs/common';
653
+ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
654
+ import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
655
+ import { UsersService } from './users.service';
656
+
657
+ @ApiTags('users')
658
+ @Controller('users')
659
+ export class UsersController {
660
+ constructor(private usersService: UsersService) {}
661
+
662
+ @Get('me')
663
+ @UseGuards(JwtAuthGuard)
664
+ @ApiBearerAuth()
665
+ async getProfile(@Request() req) {
666
+ return this.usersService.findById(req.user.userId);
667
+ }
668
+ }
669
+ `;
670
+
671
+ await fs.writeFile(path.join(srcDir, 'users', 'users.controller.ts'), usersController);
672
+
673
+ const usersModule = `import { Module } from '@nestjs/common';
674
+ import { UsersService } from './users.service';
675
+ import { UsersController } from './users.controller';
676
+
677
+ @Module({
678
+ providers: [UsersService],
679
+ controllers: [UsersController],
680
+ exports: [UsersService],
681
+ })
682
+ export class UsersModule {}
683
+ `;
684
+
685
+ await fs.writeFile(path.join(srcDir, 'users', 'users.module.ts'), usersModule);
686
+
687
+ // Tenant Interceptor (if multi-tenant)
688
+ if (config.multiTenant) {
689
+ const tenantInterceptor = `import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
690
+ import { Observable } from 'rxjs';
691
+
692
+ @Injectable()
693
+ export class TenantInterceptor implements NestInterceptor {
694
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
695
+ const request = context.switchToHttp().getRequest();
696
+ const user = request.user;
697
+
698
+ if (user && user.tenantId) {
699
+ request.tenantId = user.tenantId;
700
+ }
701
+
702
+ return next.handle();
703
+ }
704
+ }
705
+ `;
706
+ await fs.writeFile(path.join(srcDir, 'common/interceptors', 'tenant.interceptor.ts'), tenantInterceptor);
707
+ }
708
+
709
+ // .env.example
710
+ const envExample = `PORT=3000
711
+ DATABASE_URL=${config.database === 'mongodb' ? 'mongodb://localhost:27017/myapp' : 'postgresql://user:password@localhost:5432/myapp'}
712
+ JWT_SECRET=your-secret-key-change-in-production
713
+ `;
714
+
715
+ await fs.writeFile(path.join(backendDir, '.env.example'), envExample);
716
+
717
+ // .gitignore
718
+ const gitignore = `# compiled output
719
+ /dist
720
+ /node_modules
721
+
722
+ # Logs
723
+ logs
724
+ *.log
725
+ npm-debug.log*
726
+ pnpm-debug.log*
727
+ yarn-debug.log*
728
+ yarn-error.log*
729
+ lerna-debug.log*
730
+
731
+ # OS
732
+ .DS_Store
733
+
734
+ # Tests
735
+ /coverage
736
+ /.nyc_output
737
+
738
+ # IDEs and editors
739
+ /.idea
740
+ .project
741
+ .classpath
742
+ .c9/
743
+ *.launch
744
+ .settings/
745
+ *.sublime-workspace
746
+
747
+ # IDE - VSCode
748
+ .vscode/*
749
+ !.vscode/settings.json
750
+ !.vscode/tasks.json
751
+ !.vscode/launch.json
752
+ !.vscode/extensions.json
753
+
754
+ # Environment
755
+ .env
756
+ .env.local
757
+ `;
758
+
759
+ await fs.writeFile(path.join(backendDir, '.gitignore'), gitignore);
760
+ }
761
+
762
+ async function generateBunElysia(_config: StackConfig, _backendDir: string) {
763
+ throw new Error('Bun + Elysia support coming in Phase 2');
764
+ }
765
+
766
+ async function generateGoFiber(_config: StackConfig, _backendDir: string) {
767
+ throw new Error('Go + Fiber support coming in Phase 2');
768
+ }
769
+ // NestJS helper functions
770
+
771
+ function getNestJSAuthService(config: StackConfig): string {
772
+ if (config.database === 'mongodb') {
773
+ return `import { Injectable, UnauthorizedException } from '@nestjs/common';
774
+ import { JwtService } from '@nestjs/jwt';
775
+ import { InjectModel } from '@nestjs/mongoose';
776
+ import { Model } from 'mongoose';
777
+ import * as bcrypt from 'bcrypt';
778
+ import { LoginDto } from './dto/login.dto';
779
+ import { RegisterDto } from './dto/register.dto';
780
+
781
+ @Injectable()
782
+ export class AuthService {
783
+ constructor(
784
+ @InjectModel('User') private userModel: Model<any>,
785
+ private jwtService: JwtService,
786
+ ) {}
787
+
788
+ async register(dto: RegisterDto) {
789
+ const hashedPassword = await bcrypt.hash(dto.password, 10);
790
+ const user = new this.userModel({
791
+ email: dto.email,
792
+ password: hashedPassword,
793
+ name: dto.name,
794
+ });
795
+ await user.save();
796
+
797
+ const token = this.generateToken(user);
798
+ return { token, user: { id: user._id, email: user.email, name: user.name } };
799
+ }
800
+
801
+ async login(dto: LoginDto) {
802
+ const user = await this.userModel.findOne({ email: dto.email });
803
+ if (!user) {
804
+ throw new UnauthorizedException('Invalid credentials');
805
+ }
806
+
807
+ const isPasswordValid = await bcrypt.compare(dto.password, user.password);
808
+ if (!isPasswordValid) {
809
+ throw new UnauthorizedException('Invalid credentials');
810
+ }
811
+
812
+ const token = this.generateToken(user);
813
+ return { token, user: { id: user._id, email: user.email, name: user.name } };
814
+ }
815
+
816
+ private generateToken(user: any) {
817
+ const payload = {
818
+ sub: user._id,
819
+ email: user.email${config.multiTenant ? ',\n tenantId: user.tenantId' : ''}
820
+ };
821
+ return this.jwtService.sign(payload);
822
+ }
823
+ }
824
+ `;
825
+ }
826
+
827
+ return `import { Injectable, UnauthorizedException } from '@nestjs/common';
828
+ import { JwtService } from '@nestjs/jwt';
829
+ import * as bcrypt from 'bcrypt';
830
+ import { LoginDto } from './dto/login.dto';
831
+ import { RegisterDto } from './dto/register.dto';
832
+ import { PrismaService } from '../database/prisma.service';
833
+
834
+ @Injectable()
835
+ export class AuthService {
836
+ constructor(
837
+ private prisma: PrismaService,
838
+ private jwtService: JwtService,
839
+ ) {}
840
+
841
+ async register(dto: RegisterDto) {
842
+ const hashedPassword = await bcrypt.hash(dto.password, 10);
843
+ const user = await this.prisma.user.create({
844
+ data: {
845
+ email: dto.email,
846
+ password: hashedPassword,
847
+ name: dto.name,
848
+ },
849
+ });
850
+
851
+ const token = this.generateToken(user);
852
+ return { token, user: { id: user.id, email: user.email, name: user.name } };
853
+ }
854
+
855
+ async login(dto: LoginDto) {
856
+ const user = await this.prisma.user.findUnique({
857
+ where: { email: dto.email },
858
+ });
859
+
860
+ if (!user) {
861
+ throw new UnauthorizedException('Invalid credentials');
862
+ }
863
+
864
+ const isPasswordValid = await bcrypt.compare(dto.password, user.password);
865
+ if (!isPasswordValid) {
866
+ throw new UnauthorizedException('Invalid credentials');
867
+ }
868
+
869
+ const token = this.generateToken(user);
870
+ return { token, user: { id: user.id, email: user.email, name: user.name } };
871
+ }
872
+
873
+ private generateToken(user: any) {
874
+ const payload = {
875
+ sub: user.id,
876
+ email: user.email${config.multiTenant ? ',\n tenantId: user.tenantId' : ''}
877
+ };
878
+ return this.jwtService.sign(payload);
879
+ }
880
+ }
881
+ `;
882
+ }
883
+
884
+ function getNestJSUsersService(config: StackConfig): string {
885
+ if (config.database === 'mongodb') {
886
+ return `import { Injectable } from '@nestjs/common';
887
+ import { InjectModel } from '@nestjs/mongoose';
888
+ import { Model } from 'mongoose';
889
+
890
+ @Injectable()
891
+ export class UsersService {
892
+ constructor(@InjectModel('User') private userModel: Model<any>) {}
893
+
894
+ async findById(id: string) {
895
+ const user = await this.userModel.findById(id).select('-password');
896
+ return user;
897
+ }
898
+
899
+ async findByEmail(email: string) {
900
+ return this.userModel.findOne({ email });
901
+ }
902
+ }
903
+ `;
904
+ }
905
+
906
+ return `import { Injectable } from '@nestjs/common';
907
+ import { PrismaService } from '../database/prisma.service';
908
+
909
+ @Injectable()
910
+ export class UsersService {
911
+ constructor(private prisma: PrismaService) {}
912
+
913
+ async findById(id: string) {
914
+ return this.prisma.user.findUnique({
915
+ where: { id },
916
+ select: { id: true, email: true, name: true, createdAt: true },
917
+ });
918
+ }
919
+
920
+ async findByEmail(email: string) {
921
+ return this.prisma.user.findUnique({
922
+ where: { email },
923
+ });
924
+ }
925
+ }
926
+ `;
927
+ }