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