create-wabck 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,387 @@
1
+ import { randomBytes } from 'crypto';
2
+
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+ // Base Templates — app.js/ts, server.js/ts, and all root config files
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+
7
+ const ext = (isTS) => (isTS ? 'js' : 'js'); // ESM always uses .js imports
8
+
9
+ /** Generate src/app.js or src/app.ts */
10
+ export function app(options) {
11
+ const { apiStyle, language } = options;
12
+ const isTS = language === 'typescript';
13
+
14
+ const typeImport = isTS ? `import { Application } from 'express';\n` : '';
15
+ const appType = isTS ? ': Application' : '';
16
+
17
+ const graphqlBlock =
18
+ apiStyle === 'graphql'
19
+ ? `
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // GraphQL — Apollo Server 4 mounted on /graphql
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ import { expressMiddleware } from '@as-integrations/express4';
24
+ import { createApolloServer } from './graphql/server.js';
25
+
26
+ const apolloServer = createApolloServer();
27
+ await apolloServer.start();
28
+ app.use('/graphql', expressMiddleware(apolloServer, {
29
+ context: async ({ req }) => ({ req }),
30
+ }));
31
+ `
32
+ : '';
33
+
34
+ return `${typeImport}import express${isTS ? ', { Application }' : ''} from 'express';
35
+ import cors from 'cors';
36
+ import helmet from 'helmet';
37
+ import compression from 'compression';
38
+ import morgan from 'morgan';
39
+ import cookieParser from 'cookie-parser';
40
+ import { rateLimiter } from './middlewares/rateLimiter.middleware.js';
41
+ import { errorMiddleware } from './middlewares/error.middleware.js';
42
+ import { notFoundMiddleware } from './middlewares/notFound.middleware.js';
43
+ import routes from './routes/index.js';
44
+
45
+ const app${appType} = express();
46
+
47
+ // ─────────────────────────────────────────────────────────────────────────────
48
+ // Security Middleware
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ app.use(helmet());
51
+ app.use(
52
+ cors({
53
+ origin: process.env.CORS_ORIGIN || '*',
54
+ credentials: true,
55
+ })
56
+ );
57
+ app.use(rateLimiter);
58
+
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+ // Body Parsing
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ app.use(express.json({ limit: '16kb' }));
63
+ app.use(express.urlencoded({ extended: true, limit: '16kb' }));
64
+ app.use(cookieParser());
65
+
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+ // Logging & Compression
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+ app.use(compression());
70
+ app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
71
+ ${graphqlBlock}
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+ // API Routes — versioned under /api/v1
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+ app.use('/api/v1', routes);
76
+
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // Error Handling — Order matters: 404 must come before the error handler
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+ app.use(notFoundMiddleware);
81
+ app.use(errorMiddleware);
82
+
83
+ export default app;
84
+ `;
85
+ }
86
+
87
+ /** Generate src/server.js or src/server.ts */
88
+ export function server(options) {
89
+ const { database, language, projectName } = options;
90
+ const isTS = language === 'typescript';
91
+ const hasDB = database !== 'none';
92
+
93
+ const dbImport = hasDB ? `import connectDB from './config/db.js';\n` : '';
94
+ const dbConnect = hasDB ? ` await connectDB();\n` : '';
95
+
96
+ return `import 'dotenv/config';
97
+ import app from './app.js';
98
+ ${dbImport}
99
+ const PORT = process.env.PORT || 5000;
100
+ const HOST = process.env.HOST || '0.0.0.0';
101
+
102
+ // ─────────────────────────────────────────────────────────────────────────────
103
+ // Bootstrap — Connect to DB then start HTTP server
104
+ // ─────────────────────────────────────────────────────────────────────────────
105
+ const startServer = async ()${isTS ? ': Promise<void>' : ''} => {
106
+ try {
107
+ ${dbConnect} const server = app.listen(PORT, HOST, () => {
108
+ console.log(\`\`);
109
+ console.log(\` 🚀 ${projectName} is running!\`);
110
+ console.log(\` ➜ Local: http://localhost:\${PORT}\`);
111
+ console.log(\` ➜ Health: http://localhost:\${PORT}/api/v1/health\`);
112
+ console.log(\`\`);
113
+
114
+ });
115
+
116
+ // ── Graceful Shutdown ──────────────────────────────────────────────────
117
+ const shutdown = (signal${isTS ? ': string' : ''}) => {
118
+ console.log(\`\\n Received \${signal}. Gracefully shutting down…\`);
119
+ server.close(() => {
120
+ console.log(' HTTP server closed.');
121
+ process.exit(0);
122
+ });
123
+ };
124
+
125
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
126
+ process.on('SIGINT', () => shutdown('SIGINT'));
127
+
128
+ } catch (error) {
129
+ console.error('Failed to start server:', error);
130
+ process.exit(1);
131
+ }
132
+ };
133
+
134
+ startServer();
135
+ `;
136
+ }
137
+
138
+ /** Generate .env */
139
+ export function envFile(options) {
140
+ const { database, auth, projectName } = options;
141
+
142
+ // Auto-generate a cryptographically secure JWT secret at scaffold time
143
+ const jwtSecret = randomBytes(32).toString('hex');
144
+
145
+ const dbVars = {
146
+ mongodb: 'MONGODB_URI=mongodb://localhost:27017/${projectName}',
147
+ postgresql: 'DATABASE_URL=postgresql://postgres:password@localhost:5432/${projectName}',
148
+ mysql: `DB_HOST=localhost\nDB_PORT=3306\nDB_USER=root\nDB_PASSWORD=password\nDB_NAME=${projectName}`,
149
+ sqlite: `SQLITE_PATH=./database.sqlite`,
150
+ none: '',
151
+ };
152
+
153
+ const authVars = {
154
+ jwt: `\nJWT_SECRET=${jwtSecret}\nJWT_EXPIRES_IN=7d\nJWT_REFRESH_EXPIRES_IN=30d`,
155
+ clerk: `\nCLERK_SECRET_KEY=sk_test_your-clerk-secret-key`,
156
+ firebase: `\nFIREBASE_PROJECT_ID=your-firebase-project-id\nFIREBASE_PRIVATE_KEY=your-firebase-private-key\nFIREBASE_CLIENT_EMAIL=your-firebase-client-email`,
157
+ none: '',
158
+ };
159
+
160
+ return `# ─────────────────────────────────────────────
161
+ # ${projectName} — Environment Variables
162
+ # ─────────────────────────────────────────────
163
+ # Copy this file to .env and fill in the values.
164
+ # JWT_SECRET below was auto-generated at scaffold time — keep it secret!
165
+
166
+ NODE_ENV=development
167
+ PORT=5000
168
+ HOST=0.0.0.0
169
+ CORS_ORIGIN=*
170
+
171
+ # Database
172
+ ${(dbVars[database] || '').replace('${projectName}', projectName)}
173
+ ${authVars[auth] || ''}
174
+ `;
175
+ }
176
+
177
+ /** Generate .env.example — safe to commit; uses a placeholder instead of the real secret */
178
+ export function envExample(options) {
179
+ const { database, auth, projectName } = options;
180
+
181
+ const dbVars = {
182
+ mongodb: `MONGODB_URI=mongodb://localhost:27017/${projectName}`,
183
+ postgresql: `DATABASE_URL=postgresql://postgres:password@localhost:5432/${projectName}`,
184
+ mysql: `DB_HOST=localhost\nDB_PORT=3306\nDB_USER=root\nDB_PASSWORD=password\nDB_NAME=${projectName}`,
185
+ sqlite: `SQLITE_PATH=./database.sqlite`,
186
+ none: '',
187
+ };
188
+
189
+ const authVars = {
190
+ jwt: `\nJWT_SECRET=change-me-generate-with-openssl-rand-hex-32\nJWT_EXPIRES_IN=7d\nJWT_REFRESH_EXPIRES_IN=30d`,
191
+ clerk: `\nCLERK_SECRET_KEY=sk_test_your-clerk-secret-key`,
192
+ firebase: `\nFIREBASE_PROJECT_ID=your-firebase-project-id\nFIREBASE_PRIVATE_KEY=your-firebase-private-key\nFIREBASE_CLIENT_EMAIL=your-firebase-client-email`,
193
+ none: '',
194
+ };
195
+
196
+ return `# ─────────────────────────────────────────────
197
+ # ${projectName} — Environment Variables (EXAMPLE)
198
+ # ─────────────────────────────────────────────
199
+ # This file is safe to commit. Copy to .env and replace placeholder values.
200
+ # Your actual .env already has a generated JWT_SECRET.
201
+
202
+ NODE_ENV=development
203
+ PORT=5000
204
+ HOST=0.0.0.0
205
+ CORS_ORIGIN=*
206
+
207
+ # Database
208
+ ${(dbVars[database] || '').replace('${projectName}', projectName)}
209
+ ${authVars[auth] || ''}
210
+ `;
211
+ }
212
+
213
+ /** Generate .gitignore for the generated project */
214
+ export function gitignore() {
215
+ return `# ─── Dependencies ──────────────────────────────
216
+ node_modules/
217
+ .pnp
218
+ .pnp.js
219
+
220
+ # ─── Build Outputs ──────────────────────────────
221
+ dist/
222
+ build/
223
+ *.tsbuildinfo
224
+
225
+ # ─── Environment Variables ──────────────────────
226
+ .env
227
+ .env.local
228
+ .env.*.local
229
+ # .env.example is safe to commit
230
+
231
+ # ─── Logs ───────────────────────────────────────
232
+ logs/
233
+ *.log
234
+ npm-debug.log*
235
+ yarn-debug.log*
236
+ yarn-error.log*
237
+ pnpm-debug.log*
238
+
239
+ # ─── OS / Editor ────────────────────────────────
240
+ .DS_Store
241
+ Thumbs.db
242
+ .idea/
243
+ .vscode/
244
+
245
+ # ─── Database ───────────────────────────────────
246
+ *.sqlite
247
+ *.sqlite3
248
+ database.sqlite
249
+
250
+ # ─── Testing ────────────────────────────────────
251
+ coverage/
252
+ `;
253
+ }
254
+
255
+ /** Generate .prettierrc */
256
+ export function prettierrc() {
257
+ return JSON.stringify(
258
+ {
259
+ semi: true,
260
+ singleQuote: true,
261
+ trailingComma: 'es5',
262
+ printWidth: 100,
263
+ tabWidth: 2,
264
+ useTabs: false,
265
+ bracketSpacing: true,
266
+ arrowParens: 'always',
267
+ },
268
+ null,
269
+ 2
270
+ );
271
+ }
272
+
273
+ /** Generate eslint.config.js */
274
+ export function eslintConfig(options) {
275
+ const isTS = options.language === 'typescript';
276
+ return `import js from '@eslint/js';
277
+ import prettier from 'eslint-plugin-prettier';
278
+ import eslintConfigPrettier from 'eslint-config-prettier';
279
+
280
+ export default [
281
+ js.configs.recommended,
282
+ eslintConfigPrettier,
283
+ {
284
+ plugins: { prettier },
285
+ rules: {
286
+ 'prettier/prettier': 'error',
287
+ 'no-console': 'warn',
288
+ 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
289
+ 'no-duplicate-imports': 'error',
290
+ 'prefer-const': 'error',
291
+ 'no-var': 'error',
292
+ },
293
+ languageOptions: {
294
+ ecmaVersion: 2022,
295
+ sourceType: 'module',
296
+ globals: {
297
+ process: 'readonly',
298
+ console: 'readonly',
299
+ Buffer: 'readonly',
300
+ URL: 'readonly',
301
+ setTimeout: 'readonly',
302
+ clearTimeout: 'readonly',
303
+ setInterval: 'readonly',
304
+ clearInterval: 'readonly',
305
+ },
306
+ },
307
+ },
308
+ ];
309
+ `;
310
+ }
311
+
312
+ /** Generate README.md */
313
+ export function readme(options) {
314
+ const { projectName, language, database, auth, validation, apiStyle, docker, packageManager } = options;
315
+ const isTS = language === 'typescript';
316
+ const pm = packageManager;
317
+
318
+ return `# ${projectName}
319
+
320
+ > Production-ready Express.js API — scaffolded by [wabck](https://github.com/wabck/wabck)
321
+
322
+ ## 🚀 Quick Start
323
+
324
+ \`\`\`bash
325
+ # Install dependencies
326
+ ${pm} ${pm === 'yarn' ? '' : 'install'}
327
+
328
+ # Copy environment variables
329
+ cp .env.example .env
330
+
331
+ # Start development server
332
+ ${pm} run dev
333
+ \`\`\`
334
+
335
+ The API will be available at **http://localhost:5000**
336
+
337
+ ## 📁 Project Structure
338
+
339
+ \`\`\`
340
+ ${projectName}/
341
+ ├── src/
342
+ │ ├── config/ # DB connection, env validation
343
+ │ ├── controllers/ # Request handlers
344
+ │ ├── middlewares/ # Error, auth, rate-limiter
345
+ │ ├── models/ # Data models
346
+ │ ├── routes/ # Route definitions
347
+ │ ├── services/ # Business logic
348
+ │ ├── utils/ # asyncHandler, ApiError, ApiResponse
349
+ ${validation !== 'none' ? `│ ├── validators/ # Input validation\n` : ''}│ ├── helpers/ # Token, etc.
350
+ │ ├── constants/ # App-wide constants
351
+ │ ├── app.${isTS ? 'ts' : 'js'} # Express app
352
+ │ └── server.${isTS ? 'ts' : 'js'} # Entry point
353
+ ├── .env.example
354
+ ├── .prettierrc
355
+ └── eslint.config.js
356
+ \`\`\`
357
+
358
+ ## 🛠 Stack
359
+
360
+ | Feature | Choice |
361
+ |---------------|---------------------------|
362
+ | Language | ${isTS ? 'TypeScript' : 'JavaScript (ES Modules)'} |
363
+ | Database | ${database === 'none' ? '—' : database} |
364
+ | Auth | ${auth === 'none' ? '—' : auth} |
365
+ ${validation !== 'none' ? `| Validation | ${validation} |\n` : ''}| API Style | ${apiStyle === 'graphql' ? 'GraphQL (Apollo Server 4)' : 'REST'} |
366
+ | Docker | ${docker ? '✔ Dockerfile + docker-compose' : '—'} |
367
+
368
+ ## 🔗 Endpoints
369
+
370
+ | Method | Path | Description |
371
+ |--------|--------------------|--------------------|
372
+ | GET | \`/\` | Root health ping |
373
+ | GET | \`/api/v1/health\` | Detailed health |
374
+ | GET | \`/api/v1/users\` | List users |
375
+ | POST | \`/api/v1/users\` | Create user |
376
+ | GET | \`/api/v1/users/:id\` | Get user by ID |
377
+ | PUT | \`/api/v1/users/:id\` | Update user |
378
+ | DELETE | \`/api/v1/users/:id\` | Delete user |
379
+ ${auth === 'jwt' ? `| POST | \`/api/v1/auth/register\` | Register |\n| POST | \`/api/v1/auth/login\` | Login |` : ''}
380
+
381
+ ${docker ? `## 🐳 Docker\n\n\`\`\`bash\ndocker compose up --build\n\`\`\`\n` : ''}
382
+ ${isTS ? `## 🏗 Build\n\n\`\`\`bash\n${pm} run build\n${pm} start\n\`\`\`\n` : ''}
383
+ ## 📜 License
384
+
385
+ MIT
386
+ `;
387
+ }
@@ -0,0 +1,271 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Config Templates — db.js, env.js, swagger.js
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/config/db.js or db.ts */
6
+ export function db(options) {
7
+ const { database, language } = options;
8
+ const isTS = language === 'typescript';
9
+
10
+ switch (database) {
11
+ // ── MongoDB / Mongoose ──────────────────────────────────────────────────
12
+ case 'mongodb':
13
+ return `import mongoose from 'mongoose';
14
+
15
+ /**
16
+ * Establishes a connection to MongoDB using Mongoose.
17
+ * Uses DATABASE_URL (or MONGODB_URI) from environment variables.
18
+ * Exits the process if the connection fails.
19
+ */
20
+ const connectDB = async ()${isTS ? ': Promise<void>' : ''} => {
21
+ try {
22
+ const uri = process.env.MONGODB_URI || process.env.DATABASE_URL;
23
+ if (!uri) throw new Error('MONGODB_URI is not defined in .env');
24
+
25
+ const conn = await mongoose.connect(uri, {
26
+ // Mongoose 7+ uses the new connection engine by default
27
+ });
28
+
29
+ console.log(\`✔ MongoDB connected: \${conn.connection.host}\`);
30
+
31
+ // ── Mongoose connection events ────────────────────────────────────────
32
+ mongoose.connection.on('error', (err) => {
33
+ console.error('MongoDB connection error:', err);
34
+ });
35
+
36
+ mongoose.connection.on('disconnected', () => {
37
+ console.warn('MongoDB disconnected');
38
+ });
39
+ } catch (error) {
40
+ console.error('MongoDB connection failed:', error${isTS ? ' as Error' : ''}.message || error);
41
+ process.exit(1);
42
+ }
43
+ };
44
+
45
+ export default connectDB;
46
+ `;
47
+
48
+ // ── PostgreSQL / Prisma ─────────────────────────────────────────────────
49
+ case 'postgresql':
50
+ return `import { PrismaClient } from '@prisma/client';
51
+
52
+ const globalForPrisma = globalThis${isTS ? ' as unknown as { prisma: PrismaClient }' : ''};
53
+
54
+ /**
55
+ * PrismaClient singleton — prevents multiple instances in development
56
+ * due to hot-reloading (Next.js pattern, works for Node as well).
57
+ */
58
+ export const prisma =
59
+ globalForPrisma.prisma ??
60
+ new PrismaClient({
61
+ log: process.env.NODE_ENV === 'development'
62
+ ? ['query', 'info', 'warn', 'error']
63
+ : ['error'],
64
+ });
65
+
66
+ if (process.env.NODE_ENV !== 'production') {
67
+ globalForPrisma.prisma = prisma;
68
+ }
69
+
70
+ /**
71
+ * Establishes a connection to PostgreSQL via Prisma.
72
+ * Exits the process if the connection fails.
73
+ */
74
+ const connectDB = async ()${isTS ? ': Promise<void>' : ''} => {
75
+ try {
76
+ await prisma.$connect();
77
+ console.log('✔ PostgreSQL connected via Prisma');
78
+ } catch (error) {
79
+ console.error('PostgreSQL connection failed:', error${isTS ? ' as Error' : ''}.message || error);
80
+ process.exit(1);
81
+ }
82
+ };
83
+
84
+ export default connectDB;
85
+ `;
86
+
87
+ // ── MySQL / mysql2 ──────────────────────────────────────────────────────
88
+ case 'mysql':
89
+ return `import mysql from 'mysql2/promise';
90
+
91
+ let pool${isTS ? ': mysql.Pool' : ''};
92
+
93
+ /**
94
+ * Creates a mysql2 connection pool and verifies connectivity.
95
+ * Pool is exported for use across the application.
96
+ */
97
+ const connectDB = async ()${isTS ? ': Promise<void>' : ''} => {
98
+ try {
99
+ pool = mysql.createPool({
100
+ host: process.env.DB_HOST || 'localhost',
101
+ port: Number(process.env.DB_PORT) || 3306,
102
+ user: process.env.DB_USER || 'root',
103
+ password: process.env.DB_PASSWORD || '',
104
+ database: process.env.DB_NAME || 'mydb',
105
+ waitForConnections: true,
106
+ connectionLimit: 10,
107
+ queueLimit: 0,
108
+ });
109
+
110
+ // Verify the connection
111
+ await pool.query('SELECT 1');
112
+ console.log(\`✔ MySQL connected — db: \${process.env.DB_NAME || 'mydb'}\`);
113
+ } catch (error) {
114
+ console.error('MySQL connection failed:', error${isTS ? ' as Error' : ''}.message || error);
115
+ process.exit(1);
116
+ }
117
+ };
118
+
119
+ export { pool };
120
+ export default connectDB;
121
+ `;
122
+
123
+ // ── SQLite / better-sqlite3 ─────────────────────────────────────────────
124
+ case 'sqlite':
125
+ return `import Database from 'better-sqlite3';
126
+ import path from 'path';
127
+ import { fileURLToPath } from 'url';
128
+
129
+ const __filename = fileURLToPath(import.meta.url);
130
+ const __dirname = path.dirname(__filename);
131
+
132
+ let db${isTS ? ': Database.Database' : ''};
133
+
134
+ /**
135
+ * Opens the SQLite database file and runs a quick smoke test.
136
+ * better-sqlite3 is synchronous — no await needed for queries.
137
+ */
138
+ const connectDB = async ()${isTS ? ': Promise<void>' : ''} => {
139
+ try {
140
+ const dbPath = process.env.SQLITE_PATH
141
+ ? path.resolve(process.env.SQLITE_PATH)
142
+ : path.join(__dirname, '..', '..', 'database.sqlite');
143
+
144
+ db = new Database(dbPath);
145
+
146
+ // Enable WAL mode for better concurrent read performance
147
+ db.pragma('journal_mode = WAL');
148
+
149
+ // Smoke-test the connection
150
+ db.prepare('SELECT 1').get();
151
+ console.log(\`✔ SQLite connected — \${dbPath}\`);
152
+ } catch (error) {
153
+ console.error('SQLite connection failed:', error${isTS ? ' as Error' : ''}.message || error);
154
+ process.exit(1);
155
+ }
156
+ };
157
+
158
+ export { db };
159
+ export default connectDB;
160
+ `;
161
+
162
+ default:
163
+ return `// No database selected — this file is intentionally empty.\n`;
164
+ }
165
+ }
166
+
167
+ /** Generate src/config/env.js — validates required environment variables */
168
+ export function envConfig(options) {
169
+ const { database, auth, language } = options;
170
+ const isTS = language === 'typescript';
171
+
172
+ const requiredVars = [`'PORT'`, `'NODE_ENV'`];
173
+ if (database === 'mongodb') requiredVars.push(`'MONGODB_URI'`);
174
+ if (database === 'postgresql') requiredVars.push(`'DATABASE_URL'`);
175
+ if (database === 'mysql') {
176
+ requiredVars.push(`'DB_HOST'`, `'DB_USER'`, `'DB_NAME'`);
177
+ }
178
+ if (auth === 'jwt') requiredVars.push(`'JWT_SECRET'`);
179
+ if (auth === 'clerk') requiredVars.push(`'CLERK_SECRET_KEY'`);
180
+ if (auth === 'firebase') requiredVars.push(`'FIREBASE_PROJECT_ID'`, `'FIREBASE_PRIVATE_KEY'`, `'FIREBASE_CLIENT_EMAIL'`);
181
+
182
+ return `/**
183
+ * Environment Configuration & Validation
184
+ *
185
+ * Validates that all required environment variables are present at startup.
186
+ * Import this module early (before any DB or server init).
187
+ */
188
+
189
+ const REQUIRED_VARS = [${requiredVars.join(', ')}];
190
+
191
+ /**
192
+ * Validates environment variables and exits if any are missing.
193
+ */
194
+ export function validateEnv()${isTS ? ': void' : ''} {
195
+ const missing = REQUIRED_VARS.filter((key) => !process.env[key]);
196
+
197
+ if (missing.length > 0) {
198
+ console.error('\\n Missing required environment variables:');
199
+ missing.forEach((key) => console.error(\` ✖ \${key}\`));
200
+ console.error('\\n Check your .env file.\\n');
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Typed environment config object for convenient access.
207
+ */
208
+ export const config = {
209
+ port: Number(process.env.PORT) || 5000,
210
+ host: process.env.HOST || '0.0.0.0',
211
+ nodeEnv: process.env.NODE_ENV || 'development',
212
+ isProd: process.env.NODE_ENV === 'production',
213
+ corsOrigin: process.env.CORS_ORIGIN || '*',
214
+ ${database === 'mongodb' ? " mongoUri: process.env.MONGODB_URI || '',\n" : ''}${database === 'postgresql' ? " databaseUrl: process.env.DATABASE_URL || '',\n" : ''}${auth === 'jwt' ? " jwtSecret: process.env.JWT_SECRET || '',\n jwtExpiresIn: process.env.JWT_EXPIRES_IN || '7d',\n" : ''}};
215
+ `;
216
+ }
217
+
218
+ /** Generate src/config/swagger.js */
219
+ export function swagger(options) {
220
+ const { projectName, language } = options;
221
+ const isTS = language === 'typescript';
222
+
223
+ return `import swaggerJsdoc from 'swagger-jsdoc';
224
+ import { readFileSync } from 'fs';
225
+ import { fileURLToPath } from 'url';
226
+ import path from 'path';
227
+
228
+ const __filename = fileURLToPath(import.meta.url);
229
+ const __dirname = path.dirname(__filename);
230
+
231
+ // Read version from package.json
232
+ let version = '1.0.0';
233
+ try {
234
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8'));
235
+ version = pkg.version;
236
+ } catch { /* use default */ }
237
+
238
+ const options = {
239
+ definition: {
240
+ openapi: '3.0.0',
241
+ info: {
242
+ title: '${projectName} API',
243
+ version,
244
+ description: 'Auto-generated API documentation for ${projectName}',
245
+ license: { name: 'MIT' },
246
+ contact: { name: '${projectName} Support' },
247
+ },
248
+ servers: [
249
+ { url: \`http://localhost:\${process.env.PORT || 5000}/api/v1\`, description: 'Development' },
250
+ ],
251
+ components: {
252
+ securitySchemes: {
253
+ bearerAuth: {
254
+ type: 'http',
255
+ scheme: 'bearer',
256
+ bearerFormat: 'JWT',
257
+ },
258
+ },
259
+ },
260
+ security: [{ bearerAuth: [] }],
261
+ },
262
+ // Pick up JSDoc @swagger annotations from all route files
263
+ apis: [
264
+ path.join(__dirname, '../routes/*.js'),
265
+ path.join(__dirname, '../routes/*.ts'),
266
+ ],
267
+ };
268
+
269
+ export const swaggerSpec = swaggerJsdoc(options);
270
+ `;
271
+ }