@morojs/cli 1.0.0 → 1.2.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.
@@ -15,6 +15,9 @@ const ora_1 = __importDefault(require("ora"));
15
15
  const boxen_1 = __importDefault(require("boxen"));
16
16
  const figlet_1 = __importDefault(require("figlet"));
17
17
  const terminal_1 = require("../utils/terminal");
18
+ const child_process_1 = require("child_process");
19
+ const util_1 = require("util");
20
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
18
21
  class ProjectInitializer {
19
22
  constructor() {
20
23
  this.logger = (0, logger_1.createFrameworkLogger)('ProjectInitializer');
@@ -148,16 +151,16 @@ class ProjectInitializer {
148
151
  name: 'features',
149
152
  message: 'Select features to include:',
150
153
  choices: [
151
- { name: 'Authentication & Authorization', value: 'auth', checked: true },
154
+ { name: 'Authentication & Authorization', value: 'auth' },
152
155
  { name: ' CORS & Security Headers', value: 'cors', checked: true },
153
156
  { name: ' Compression & Performance', value: 'compression', checked: true },
154
157
  { name: 'WebSocket Support', value: 'websocket' },
155
- { name: 'API Documentation (OpenAPI)', value: 'docs', checked: true },
156
- { name: 'Rate Limiting', value: 'rate-limit', checked: true },
158
+ { name: 'API Documentation (OpenAPI)', value: 'docs' },
159
+ { name: 'Rate Limiting', value: 'rate-limit' },
157
160
  { name: 'Caching Layer', value: 'cache' },
158
161
  { name: 'Circuit Breaker', value: 'circuit-breaker' },
159
162
  { name: 'Monitoring & Metrics', value: 'monitoring' },
160
- { name: 'Testing Setup', value: 'testing', checked: true },
163
+ { name: 'Testing Setup', value: 'testing' },
161
164
  ],
162
165
  });
163
166
  }
@@ -176,12 +179,12 @@ class ProjectInitializer {
176
179
  name: projectName,
177
180
  version: '1.0.0',
178
181
  description: `MoroJS ${config.template} project`,
179
- main: 'dist/index.js',
180
182
  type: 'module',
183
+ main: 'dist/src/index.js',
181
184
  scripts: {
182
- dev: 'morojs-cli dev',
183
- build: 'morojs-cli build',
184
- start: 'node dist/index.js',
185
+ dev: 'tsx src/index.ts',
186
+ build: 'tsc',
187
+ start: 'node dist/src/index.js',
185
188
  test: 'morojs-cli test',
186
189
  lint: 'morojs-cli lint',
187
190
  'db:migrate': 'morojs-cli db migrate --up',
@@ -193,7 +196,7 @@ class ProjectInitializer {
193
196
  }),
194
197
  },
195
198
  dependencies: {
196
- '@morojs/moro': '^1.0.0',
199
+ '@morojs/moro': await this.getLatestPackageVersion('@morojs/moro'),
197
200
  ...(config.database === 'postgresql' && { pg: '^8.11.3', '@types/pg': '^8.10.9' }),
198
201
  ...(config.database === 'mysql' && { mysql2: '^3.6.5' }),
199
202
  ...(config.database === 'mongodb' && { mongodb: '^6.3.0' }),
@@ -202,13 +205,22 @@ class ProjectInitializer {
202
205
  'drizzle-orm': '^0.29.1',
203
206
  'drizzle-kit': '^0.20.6',
204
207
  }),
205
- ...(config.features.includes('auth') && { jsonwebtoken: '^9.0.2', bcryptjs: '^2.4.3' }),
208
+ ...(config.features.includes('auth') && {
209
+ bcryptjs: '^2.4.3',
210
+ }),
211
+ ...(config.features.includes('docs') && {
212
+ 'swagger-ui-dist': '^5.11.0',
213
+ }),
206
214
  zod: '^3.22.4',
207
215
  },
208
216
  devDependencies: {
209
217
  '@morojs/cli': '^1.0.0',
210
218
  '@types/node': '^20.10.0',
211
219
  typescript: '^5.3.2',
220
+ tsx: '^4.7.0',
221
+ ...(config.features.includes('auth') && {
222
+ '@types/bcryptjs': '^2.4.0',
223
+ }),
212
224
  ...(config.features.includes('testing') && {
213
225
  jest: '^29.7.0',
214
226
  '@types/jest': '^29.5.8',
@@ -234,7 +246,6 @@ class ProjectInitializer {
234
246
  skipLibCheck: true,
235
247
  forceConsistentCasingInFileNames: true,
236
248
  outDir: './dist',
237
- rootDir: './src',
238
249
  declaration: true,
239
250
  declarationMap: true,
240
251
  sourceMap: true,
@@ -244,7 +255,7 @@ class ProjectInitializer {
244
255
  resolveJsonModule: true,
245
256
  allowSyntheticDefaultImports: true,
246
257
  },
247
- include: ['src/**/*'],
258
+ include: ['src/**/*', 'moro.config.ts'],
248
259
  exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts'],
249
260
  };
250
261
  await (0, promises_1.writeFile)((0, path_1.join)(projectPath, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
@@ -258,69 +269,188 @@ class ProjectInitializer {
258
269
  'cloudflare-workers': 'createAppWorker',
259
270
  };
260
271
  const appContent = `// ${config.template.toUpperCase()} MoroJS Application
261
- import { ${runtimeImports[config.runtime]}, logger } from '@morojs/moro';
262
- ${config.database !== 'none' ? `import { setupDatabase } from './database';` : ''}
263
- ${config.features.includes('auth') ? `import { setupAuth } from './middleware/auth';` : ''}
272
+ import { ${runtimeImports[config.runtime]}, logger, initializeConfig } from '@morojs/moro';
273
+ ${config.database !== 'none' ? `import { setupDatabase } from './database/index.js';` : ''}
274
+ ${config.features.includes('auth') ? `import { setupAuth } from './middleware/auth.js';` : ''}
275
+
276
+ // Initialize configuration from moro.config.js and environment variables
277
+ const appConfig = initializeConfig();
264
278
 
265
279
  // Create MoroJS application with ${config.runtime} runtime
266
280
  const app = ${runtimeImports[config.runtime]}({
267
- runtime: { type: '${config.runtime}' },
268
281
  ${config.features.includes('cors') ? `cors: true,` : ''}
269
282
  ${config.features.includes('compression') ? `compression: true,` : ''}
270
283
  logger: {
271
- level: process.env.LOG_LEVEL || 'info',
272
- format: 'pretty'
284
+ level: appConfig.logging?.level || 'info',
285
+ format: appConfig.logging?.format || 'pretty'
273
286
  }
274
287
  });
275
288
 
276
289
  // Database setup
277
290
  ${config.database !== 'none' ? `await setupDatabase(app);` : ''}
278
291
 
279
- // Middleware setup
292
+ // Auth setup
280
293
  ${config.features.includes('auth') ? `await setupAuth(app);` : ''}
281
294
 
282
- // API Documentation
295
+ // Health check endpoint
296
+ app.get('/health')
297
+ .describe('Health check endpoint to verify API status')
298
+ .tag('System')
299
+ .handler(async (req: any, res: any) => {
300
+ return {
301
+ success: true,
302
+ status: 'healthy',
303
+ timestamp: new Date().toISOString(),
304
+ runtime: '${config.runtime}',
305
+ version: '1.0.0'
306
+ };
307
+ });
308
+
309
+ // Welcome endpoint
310
+ app.get('/')
311
+ .describe('Welcome endpoint with API information and available routes')
312
+ .tag('General')
313
+ .handler(async (req: any, res: any) => {
314
+ return {
315
+ message: 'Welcome to your MoroJS ${config.template}!',
316
+ docs: '/docs',
317
+ health: '/health',
318
+ auth: {
319
+ login: '/auth/login',
320
+ register: '/auth/register',
321
+ profile: '/auth/profile'
322
+ }
323
+ };
324
+ });
325
+
326
+ // Auth endpoints
327
+ app.post('/auth/login')
328
+ .describe('Login with email and password')
329
+ .tag('Auth')
330
+ .handler(async (req: any, res: any) => {
331
+ const { email, password } = req.body;
332
+ if (!email || !password) {
333
+ res.status(400);
334
+ return {
335
+ success: false,
336
+ error: 'Email and password are required'
337
+ };
338
+ }
339
+
340
+ try {
341
+ console.log('Login attempt:', { email });
342
+ const result = await req.auth.signIn('credentials', { email, password });
343
+ console.log('Login result:', { success: !!result });
344
+
345
+ if (!result) {
346
+ res.status(401);
347
+ return {
348
+ success: false,
349
+ error: 'Invalid credentials'
350
+ };
351
+ }
352
+
353
+ const token = await req.auth.createToken(result);
354
+ await req.auth.setSession({ user: result, token });
355
+
356
+ return {
357
+ success: true,
358
+ data: {
359
+ user: result,
360
+ token
361
+ }
362
+ };
363
+ } catch (error) {
364
+ console.error('Auth error:', error);
365
+ res.status(401);
366
+ return {
367
+ success: false,
368
+ error: 'Authentication failed'
369
+ };
370
+ }
371
+ });
372
+
373
+ app.post('/auth/register')
374
+ .describe('Register a new user')
375
+ .tag('Auth')
376
+ .handler(async (req: any, res: any) => {
377
+ const { email, password, name } = req.body;
378
+ if (!email || !password || !name) {
379
+ res.status(400);
380
+ return {
381
+ success: false,
382
+ error: 'Email, password, and name are required'
383
+ };
384
+ }
385
+
386
+ // In a real app, you would hash the password and store in a database
387
+ return {
388
+ success: true,
389
+ message: 'Registration successful. Please login.'
390
+ };
391
+ });
392
+
393
+ app.get('/auth/profile')
394
+ .describe('Get authenticated user profile')
395
+ .tag('Auth')
396
+ .handler(async (req: any, res: any) => {
397
+ const user = await req.auth.getUser();
398
+ if (!user) {
399
+ res.status(401);
400
+ return {
401
+ success: false,
402
+ error: 'Not authenticated'
403
+ };
404
+ }
405
+
406
+ return {
407
+ success: true,
408
+ data: {
409
+ user,
410
+ session: req.auth.session
411
+ }
412
+ };
413
+ });
414
+
283
415
  ${config.features.includes('docs')
284
416
  ? `
417
+ // API Documentation
285
418
  app.enableDocs({
286
419
  title: '${config.template.charAt(0).toUpperCase() + config.template.slice(1)} API',
287
420
  description: 'MoroJS ${config.template} application',
288
421
  version: '1.0.0',
289
- basePath: '/docs'
290
- });`
291
- : ''}
292
-
293
- // Health check endpoint
294
- app.get('/health', async (req, res) => {
295
- return {
296
- success: true,
297
- status: 'healthy',
298
- timestamp: new Date().toISOString(),
299
- runtime: '${config.runtime}',
300
- version: '1.0.0'
301
- };
302
- });
303
-
304
- // Welcome endpoint
305
- app.get('/', async (req, res) => {
306
- return {
307
- message: 'Welcome to your MoroJS ${config.template}!',
308
- docs: '/docs',
309
- health: '/health'
310
- };
422
+ basePath: '/docs',
423
+ swaggerUI: {
424
+ enableTryItOut: false,
425
+ enableFilter: false,
426
+ enableDeepLinking: false,
427
+ customCss: \`
428
+ .swagger-ui .topbar { display: none }
429
+ .swagger-ui .scheme-container { display: none }
430
+ .swagger-ui .info { margin: 20px 0 }
431
+ .swagger-ui .info .title { font-size: 24px }
432
+ .swagger-ui .info .title small { display: none }
433
+ .swagger-ui .info .title span { display: none }
434
+ .swagger-ui .information-container { padding: 0 }
435
+ .swagger-ui section.models { display: none }
436
+ .swagger-ui .auth-wrapper { display: none }
437
+ .swagger-ui .try-out { display: none }
438
+ \`
439
+ },
311
440
  });
312
-
441
+ `
442
+ : ''}
313
443
  // Auto-discover and load modules
314
444
  // Modules will be automatically loaded from ./modules directory
315
445
 
316
446
  ${config.runtime === 'node'
317
447
  ? `
318
448
  // Start server (Node.js only)
319
- const PORT = process.env.PORT || 3000;
320
- const HOST = process.env.HOST || 'localhost';
449
+ const PORT = appConfig.server?.port || 3000;
450
+ const HOST = appConfig.server?.host || 'localhost';
321
451
 
322
452
  app.listen(PORT, HOST, () => {
323
- logger.info(\`\${config.template.charAt(0).toUpperCase() + config.template.slice(1)} server running!\`);
453
+ logger.info(\`${config.template.charAt(0).toUpperCase() + config.template.slice(1)} server running!\`);
324
454
  logger.info(\`HTTP: http://\${HOST}:\${PORT}\`);
325
455
  ${config.features.includes('websocket') ? `logger.info(\`🔌 WebSocket: ws://\${HOST}:\${PORT}\`);` : ''}
326
456
  ${config.features.includes('docs') ? `logger.info(\`Docs: http://\${HOST}:\${PORT}/docs\`);` : ''}
@@ -407,64 +537,222 @@ CLOUDFLARE_API_TOKEN=`
407
537
  }
408
538
  async generateMoroConfig(projectPath, config) {
409
539
  const configContent = `// MoroJS Configuration
410
- import { z } from 'zod';
540
+ // Generated based on selected features: ${config.features.join(', ')}
541
+ // Reference: https://morojs.com/docs/configuration
411
542
 
412
- export default {
543
+ export default async () => {
544
+ const os = await import('os');
545
+ return {
546
+ // Server Configuration
413
547
  server: {
414
- port: parseInt(process.env.PORT || '3000'),
415
- host: process.env.HOST || 'localhost',
416
- environment: process.env.NODE_ENV || 'development'
417
- },
418
-
419
- ${config.database !== 'none'
420
- ? `database: {
421
- ${config.database === 'postgresql' ? `url: process.env.DATABASE_URL` : ''}
422
- ${config.database === 'mysql' ? `url: process.env.DATABASE_URL` : ''}
423
- ${config.database === 'mongodb' ? `url: process.env.MONGODB_URI` : ''}
424
- ${config.database === 'redis' ? `redis: { url: process.env.REDIS_URL }` : ''}
425
- },`
548
+ port: parseInt(process.env.PORT || process.env.MORO_PORT || '3000'),
549
+ host: process.env.HOST || process.env.MORO_HOST || 'localhost',
550
+ environment: process.env.NODE_ENV || process.env.MORO_ENV || 'development'${config.runtime === 'node'
551
+ ? `,
552
+ maxConnections: parseInt(process.env.MAX_CONNECTIONS || process.env.MORO_MAX_CONNECTIONS || '1000'),
553
+ timeout: parseInt(process.env.REQUEST_TIMEOUT || process.env.MORO_TIMEOUT || '30000')`
426
554
  : ''}
427
-
555
+ },
556
+
557
+ ${config.database !== 'none' && config.database !== 'sqlite'
558
+ ? ` // Database Configuration
559
+ database: {${config.database === 'postgresql'
560
+ ? `
561
+ url: process.env.DATABASE_URL || process.env.MORO_DATABASE_URL`
562
+ : ''}${config.database === 'mysql'
563
+ ? `
564
+ mysql: {
565
+ host: process.env.MYSQL_HOST || process.env.MORO_MYSQL_HOST || 'localhost',
566
+ port: parseInt(process.env.MYSQL_PORT || process.env.MORO_MYSQL_PORT || '3306'),
567
+ database: process.env.MYSQL_DATABASE || process.env.MORO_MYSQL_DB,
568
+ username: process.env.MYSQL_USERNAME || process.env.MORO_MYSQL_USER,
569
+ password: process.env.MYSQL_PASSWORD || process.env.MORO_MYSQL_PASS,
570
+ connectionLimit: parseInt(process.env.MYSQL_CONNECTION_LIMIT || process.env.MORO_MYSQL_CONNECTIONS || '10'),
571
+ acquireTimeout: parseInt(process.env.MYSQL_ACQUIRE_TIMEOUT || '60000'),
572
+ timeout: parseInt(process.env.MYSQL_TIMEOUT || '60000')
573
+ }`
574
+ : ''}${config.database === 'mongodb'
575
+ ? `
576
+ url: process.env.DATABASE_URL || process.env.MONGODB_URI || process.env.MORO_DATABASE_URL || 'mongodb://localhost:27017/database'`
577
+ : ''}${config.database === 'redis' || config.features.includes('cache')
578
+ ? `,
579
+ redis: {
580
+ url: process.env.REDIS_URL || process.env.MORO_REDIS_URL || 'redis://localhost:6379',
581
+ maxRetries: parseInt(process.env.REDIS_MAX_RETRIES || process.env.MORO_REDIS_RETRIES || '3'),
582
+ retryDelay: parseInt(process.env.REDIS_RETRY_DELAY || process.env.MORO_REDIS_DELAY || '1000'),
583
+ keyPrefix: process.env.REDIS_KEY_PREFIX || process.env.MORO_REDIS_PREFIX || 'moro:'
584
+ }`
585
+ : ''}
586
+ },
587
+
588
+ `
589
+ : ''} // Logging Configuration
428
590
  logging: {
429
- level: process.env.LOG_LEVEL || 'info',
430
- format: 'pretty',
431
- enableColors: true,
432
- enableTimestamp: true
591
+ level: process.env.LOG_LEVEL || process.env.MORO_LOG_LEVEL || 'info',
592
+ format: process.env.LOG_FORMAT || process.env.MORO_LOG_FORMAT || (process.env.NODE_ENV === 'production' ? 'json' : 'pretty'),
593
+ enableColors: process.env.LOG_COLORS !== 'false' && process.env.NO_COLOR !== '1' && process.env.NODE_ENV !== 'production',
594
+ enableTimestamp: process.env.LOG_TIMESTAMP !== 'false',
595
+ enableContext: process.env.LOG_CONTEXT !== 'false'${config.features.includes('monitoring')
596
+ ? `,
597
+ outputs: {
598
+ console: true,
599
+ file: {
600
+ enabled: process.env.LOG_FILE_ENABLED === 'true' || process.env.MORO_LOG_FILE === 'true' || process.env.NODE_ENV === 'production',
601
+ path: process.env.LOG_FILE_PATH || process.env.MORO_LOG_PATH || './logs/moro.log',
602
+ maxSize: '10MB',
603
+ maxFiles: 5
604
+ }${config.features.includes('webhook-logging')
605
+ ? `,
606
+ webhook: {
607
+ enabled: !!process.env.LOG_WEBHOOK_URL || !!process.env.MORO_LOG_WEBHOOK_URL,
608
+ url: process.env.LOG_WEBHOOK_URL || process.env.MORO_LOG_WEBHOOK_URL,
609
+ headers: {
610
+ 'Authorization': process.env.LOG_WEBHOOK_AUTH,
611
+ 'Content-Type': 'application/json'
612
+ }
613
+ }`
614
+ : ''}
615
+ }`
616
+ : ''}
433
617
  },
434
-
618
+
619
+ // Security Configuration
435
620
  security: {
436
621
  cors: {
437
- enabled: true,
438
- origin: process.env.NODE_ENV === 'production' ? false : '*'
622
+ enabled: ${config.features.includes('cors') ? "process.env.CORS_ENABLED !== 'false'" : 'false'},${config.features.includes('cors')
623
+ ? `
624
+ origin: process.env.NODE_ENV === 'production'
625
+ ? (process.env.CORS_ORIGIN || process.env.MORO_CORS_ORIGIN || '*').split(',')
626
+ : '*',
627
+ methods: (process.env.CORS_METHODS || process.env.MORO_CORS_METHODS || 'GET,POST,PUT,DELETE,PATCH,OPTIONS').split(','),
628
+ allowedHeaders: (process.env.CORS_HEADERS || process.env.MORO_CORS_HEADERS || 'Content-Type,Authorization').split(','),
629
+ credentials: process.env.CORS_CREDENTIALS === 'true'`
630
+ : ''}
439
631
  },
440
632
  helmet: {
441
- enabled: true
442
- }
633
+ enabled: process.env.HELMET_ENABLED !== 'false',
634
+ contentSecurityPolicy: process.env.NODE_ENV === 'production',
635
+ hsts: process.env.NODE_ENV === 'production',
636
+ noSniff: true,
637
+ frameguard: true
638
+ }${config.features.includes('rate-limit')
639
+ ? `,
640
+ rateLimit: {
641
+ enabled: process.env.GLOBAL_RATE_LIMIT_ENABLED === 'true',
642
+ requests: parseInt(process.env.GLOBAL_RATE_LIMIT_REQUESTS || process.env.MORO_GLOBAL_RATE_REQUESTS || '1000'),
643
+ window: 60000 // 1 minute window
644
+ }`
645
+ : ''}
443
646
  },
444
-
445
- performance: {
647
+
648
+ ${config.features.includes('compression') ||
649
+ config.features.includes('circuit-breaker') ||
650
+ config.runtime === 'node'
651
+ ? ` // Performance Configuration
652
+ performance: {${config.features.includes('compression')
653
+ ? `
446
654
  compression: {
447
- enabled: true,
448
- level: 6
449
- },
655
+ enabled: process.env.COMPRESSION_ENABLED !== 'false',
656
+ level: parseInt(process.env.COMPRESSION_LEVEL || process.env.MORO_COMPRESSION_LEVEL || '6'),
657
+ threshold: parseInt(process.env.COMPRESSION_THRESHOLD || process.env.MORO_COMPRESSION_THRESHOLD || '1024')
658
+ }${config.features.includes('circuit-breaker') || config.runtime === 'node' ? ',' : ''}`
659
+ : ''}${config.features.includes('circuit-breaker')
660
+ ? `
450
661
  circuitBreaker: {
451
- enabled: ${config.features.includes('circuit-breaker')}
452
- }
662
+ enabled: process.env.CIRCUIT_BREAKER_ENABLED !== 'false',
663
+ failureThreshold: parseInt(process.env.CIRCUIT_BREAKER_THRESHOLD || process.env.MORO_CB_THRESHOLD || '5'),
664
+ resetTimeout: parseInt(process.env.CIRCUIT_BREAKER_RESET_TIMEOUT || '60000'),
665
+ monitoringPeriod: parseInt(process.env.CIRCUIT_BREAKER_MONITORING_PERIOD || '10000')
666
+ }${config.runtime === 'node' ? ',' : ''}`
667
+ : ''}${config.runtime === 'node'
668
+ ? `
669
+ clustering: {
670
+ enabled: process.env.CLUSTERING_ENABLED === 'true',
671
+ workers: parseInt(process.env.CLUSTER_WORKERS || process.env.MORO_WORKERS || '0') || os.cpus().length
672
+ }`
673
+ : ''}
453
674
  },
454
-
455
- modules: {
675
+
676
+ `
677
+ : ''}${config.features.some((f) => ['cache', 'rate-limit', 'validation'].includes(f))
678
+ ? `
679
+ // Module Configuration
680
+ modules: {${config.features.includes('cache')
681
+ ? `
456
682
  cache: {
457
- enabled: ${config.features.includes('cache')},
458
- defaultTtl: 300
459
- },
683
+ enabled: process.env.CACHE_ENABLED !== 'false' || process.env.MORO_CACHE_ENABLED !== 'false',
684
+ defaultTtl: parseInt(process.env.DEFAULT_CACHE_TTL || process.env.MORO_CACHE_TTL || '300'),
685
+ maxSize: parseInt(process.env.CACHE_MAX_SIZE || process.env.MORO_CACHE_SIZE || '1000'),
686
+ strategy: process.env.CACHE_STRATEGY || process.env.MORO_CACHE_STRATEGY || 'lru'
687
+ }${config.features.includes('rate-limit') || config.features.includes('validation') ? ',' : ''}`
688
+ : ''}${config.features.includes('rate-limit')
689
+ ? `
460
690
  rateLimit: {
461
- enabled: ${config.features.includes('rate-limit')},
462
- defaultRequests: 100,
463
- defaultWindow: 60000
464
- }
465
- }
691
+ enabled: process.env.RATE_LIMIT_ENABLED !== 'false' || process.env.MORO_RATE_LIMIT_ENABLED !== 'false',
692
+ defaultRequests: parseInt(process.env.DEFAULT_RATE_LIMIT_REQUESTS || process.env.MORO_RATE_LIMIT_REQUESTS || '100'),
693
+ defaultWindow: parseInt(process.env.DEFAULT_RATE_LIMIT_WINDOW || process.env.MORO_RATE_LIMIT_WINDOW || '60000'),
694
+ skipSuccessfulRequests: process.env.RATE_LIMIT_SKIP_SUCCESS === 'true',
695
+ skipFailedRequests: process.env.RATE_LIMIT_SKIP_FAILED === 'true'
696
+ }${config.features.includes('validation') ? ',' : ''}`
697
+ : ''}${config.features.includes('validation')
698
+ ? `
699
+ validation: {
700
+ enabled: process.env.VALIDATION_ENABLED !== 'false' || process.env.MORO_VALIDATION_ENABLED !== 'false',
701
+ stripUnknown: process.env.VALIDATION_STRIP_UNKNOWN !== 'false',
702
+ abortEarly: process.env.VALIDATION_ABORT_EARLY === 'true'
703
+ }`
704
+ : ''}
705
+ }`
706
+ : ''}${config.features.includes('service-discovery')
707
+ ? `,
708
+
709
+ // Service Discovery Configuration
710
+ serviceDiscovery: {
711
+ enabled: process.env.SERVICE_DISCOVERY_ENABLED === 'true' || process.env.MORO_SERVICE_DISCOVERY === 'true',
712
+ type: process.env.DISCOVERY_TYPE || process.env.MORO_DISCOVERY_TYPE || 'memory',
713
+ consulUrl: process.env.CONSUL_URL || process.env.MORO_CONSUL_URL || 'http://localhost:8500',
714
+ kubernetesNamespace: process.env.K8S_NAMESPACE || process.env.MORO_K8S_NAMESPACE || 'default',
715
+ healthCheckInterval: parseInt(process.env.HEALTH_CHECK_INTERVAL || process.env.MORO_HEALTH_INTERVAL || '30000'),
716
+ retryAttempts: parseInt(process.env.DISCOVERY_RETRY_ATTEMPTS || process.env.MORO_DISCOVERY_RETRIES || '3')
717
+ }`
718
+ : ''}${config.features.some((f) => ['stripe', 'paypal', 'smtp', 'email'].includes(f))
719
+ ? `,
720
+
721
+ // External Services Configuration
722
+ external: {${config.features.includes('stripe')
723
+ ? `
724
+ // Stripe Configuration (uncomment and configure)
725
+ // stripe: {
726
+ // secretKey: process.env.STRIPE_SECRET_KEY || process.env.MORO_STRIPE_SECRET,
727
+ // publishableKey: process.env.STRIPE_PUBLISHABLE_KEY || process.env.MORO_STRIPE_PUBLIC,
728
+ // webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || process.env.MORO_STRIPE_WEBHOOK,
729
+ // apiVersion: process.env.STRIPE_API_VERSION || process.env.MORO_STRIPE_VERSION || '2023-10-16'
730
+ // }${config.features.includes('paypal') || config.features.includes('smtp') ? ',' : ''}`
731
+ : ''}${config.features.includes('paypal')
732
+ ? `
733
+ // PayPal Configuration (uncomment and configure)
734
+ // paypal: {
735
+ // clientId: process.env.PAYPAL_CLIENT_ID || process.env.MORO_PAYPAL_CLIENT,
736
+ // clientSecret: process.env.PAYPAL_CLIENT_SECRET || process.env.MORO_PAYPAL_SECRET,
737
+ // webhookId: process.env.PAYPAL_WEBHOOK_ID,
738
+ // environment: process.env.PAYPAL_ENVIRONMENT || process.env.MORO_PAYPAL_ENV || 'sandbox'
739
+ // }${config.features.includes('smtp') ? ',' : ''}`
740
+ : ''}${config.features.includes('smtp') || config.features.includes('email')
741
+ ? `
742
+ // SMTP Configuration (uncomment and configure)
743
+ // smtp: {
744
+ // host: process.env.SMTP_HOST || process.env.MORO_SMTP_HOST,
745
+ // port: parseInt(process.env.SMTP_PORT || process.env.MORO_SMTP_PORT || '587'),
746
+ // secure: process.env.SMTP_SECURE === 'true',
747
+ // username: process.env.SMTP_USERNAME || process.env.MORO_SMTP_USER,
748
+ // password: process.env.SMTP_PASSWORD || process.env.MORO_SMTP_PASS
749
+ // }`
750
+ : ''}
751
+ }`
752
+ : ''}
753
+ };
466
754
  };`;
467
- await (0, promises_1.writeFile)((0, path_1.join)(projectPath, 'moro.config.js'), configContent);
755
+ await (0, promises_1.writeFile)((0, path_1.join)(projectPath, 'moro.config.ts'), configContent);
468
756
  }
469
757
  async generateGitignore(projectPath) {
470
758
  const gitignoreContent = `# Dependencies
@@ -621,6 +909,28 @@ npm start
621
909
  - **Welcome**: \`GET /\`
622
910
  ${config.features.includes('docs') ? `- **API Docs**: \`GET /docs\`` : ''}
623
911
 
912
+ ## Configuration
913
+
914
+ This project includes a comprehensive configuration system:
915
+
916
+ - **\`moro.config.ts\`** - Feature-based configuration with production-ready defaults
917
+ - **\`.env\`** - Environment variables for development
918
+ - **\`.env.example\`** - Environment variables template
919
+
920
+ ### Production Setup
921
+
922
+ 1. Set up production environment variables:
923
+ \`\`\`bash
924
+ cp .env.example .env.production
925
+ # Edit .env.production with your production values
926
+ \`\`\`
927
+
928
+ 2. The configuration automatically adapts to production when \`NODE_ENV=production\`:
929
+ - **Performance**: Compression, clustering, circuit breakers
930
+ - **Security**: CORS, Helmet, rate limiting, SSL
931
+ - **Monitoring**: Structured logging, file outputs
932
+ - **Scalability**: Redis caching, connection pooling
933
+
624
934
  ## Database
625
935
 
626
936
  ${config.database !== 'none'
@@ -883,94 +1193,223 @@ volumes:
883
1193
  }
884
1194
  }
885
1195
  async generateDatabaseSetup(projectPath, config) {
886
- const dbSetupContent = `// Database Setup and Configuration
887
- import { ${config.database.charAt(0).toUpperCase() + config.database.slice(1)}Adapter } from '@morojs/moro';
1196
+ // Fix the adapter name for PostgreSQL (should be PostgreSQLAdapter, not PostgresqlAdapter)
1197
+ const getAdapterName = (dbType) => {
1198
+ if (dbType === 'postgresql')
1199
+ return 'PostgreSQLAdapter';
1200
+ return dbType.charAt(0).toUpperCase() + dbType.slice(1) + 'Adapter';
1201
+ };
1202
+ const adapterName = getAdapterName(config.database);
1203
+ const dbSetupContent = config.database === 'postgresql'
1204
+ ? `// Database Setup and Configuration
1205
+ import pg from 'pg';
888
1206
  import { createFrameworkLogger } from '@morojs/moro';
889
1207
 
890
1208
  const logger = createFrameworkLogger('Database');
891
1209
 
892
1210
  export async function setupDatabase(app: any): Promise<void> {
893
1211
  try {
894
- const adapter = new ${config.database.charAt(0).toUpperCase() + config.database.slice(1)}Adapter({
895
- ${config.database === 'postgresql' || config.database === 'mysql'
896
- ? `
897
- url: process.env.DATABASE_URL,
898
- host: process.env.${config.database.toUpperCase()}_HOST,
899
- port: parseInt(process.env.${config.database.toUpperCase()}_PORT || '${config.database === 'postgresql' ? '5432' : '3306'}'),
900
- username: process.env.${config.database.toUpperCase()}_USER,
901
- password: process.env.${config.database.toUpperCase()}_PASSWORD,
902
- database: process.env.${config.database.toUpperCase()}_${config.database === 'postgresql' ? 'DB' : 'DATABASE'}`
903
- : ''}
904
- ${config.database === 'mongodb'
905
- ? `
906
- url: process.env.MONGODB_URI`
907
- : ''}
908
- ${config.database === 'redis'
909
- ? `
910
- url: process.env.REDIS_URL,
911
- host: process.env.REDIS_HOST,
912
- port: parseInt(process.env.REDIS_PORT || '6379')`
913
- : ''}
1212
+ const pool = new pg.Pool({
1213
+ host: process.env.POSTGRESQL_HOST || 'localhost',
1214
+ port: parseInt(process.env.POSTGRESQL_PORT || '5432'),
1215
+ user: process.env.POSTGRESQL_USER || 'postgres',
1216
+ password: process.env.POSTGRESQL_PASSWORD || 'postgres',
1217
+ database: process.env.POSTGRESQL_DATABASE || 'postgres'
914
1218
  });
915
1219
 
916
- await adapter.connect();
917
- app.database(adapter);
918
-
1220
+ await pool.connect();
1221
+ app.database = pool;
1222
+
919
1223
  logger.info('✅ Database connected successfully', 'Database');
920
1224
  } catch (error) {
921
- logger.error('❌ Database connection failed:', error, 'Database');
922
- throw error;
1225
+ logger.error('❌ Database connection failed: ' + String(error), 'Database');
1226
+ logger.warn('⚠️ App will continue without database connection', 'Database');
1227
+ // Don't throw - let the app continue without database
923
1228
  }
924
- }`;
1229
+ }`
1230
+ : config.database === 'mysql'
1231
+ ? `// Database Setup and Configuration
1232
+ import mysql from 'mysql2/promise';
1233
+ import { createFrameworkLogger } from '@morojs/moro';
1234
+
1235
+ const logger = createFrameworkLogger('Database');
1236
+
1237
+ export async function setupDatabase(app: any): Promise<void> {
1238
+ try {
1239
+ const pool = await mysql.createPool({
1240
+ host: process.env.MYSQL_HOST || 'localhost',
1241
+ port: parseInt(process.env.MYSQL_PORT || '3306'),
1242
+ user: process.env.MYSQL_USER || 'mysql',
1243
+ password: process.env.MYSQL_PASSWORD || 'mysql',
1244
+ database: process.env.MYSQL_DATABASE || 'mysql'
1245
+ });
1246
+
1247
+ app.database = pool;
1248
+ logger.info('✅ Database connected successfully', 'Database');
1249
+ } catch (error) {
1250
+ logger.error('❌ Database connection failed: ' + String(error), 'Database');
1251
+ logger.warn('⚠️ App will continue without database connection', 'Database');
1252
+ // Don't throw - let the app continue without database
1253
+ }
1254
+ }`
1255
+ : config.database === 'mongodb'
1256
+ ? `// Database Setup and Configuration
1257
+ import { MongoClient } from 'mongodb';
1258
+ import { createFrameworkLogger } from '@morojs/moro';
1259
+
1260
+ const logger = createFrameworkLogger('Database');
1261
+
1262
+ export async function setupDatabase(app: any): Promise<void> {
1263
+ try {
1264
+ const client = new MongoClient(process.env.MONGODB_URI || 'mongodb://localhost:27017');
1265
+ await client.connect();
1266
+ app.database = client.db(process.env.MONGODB_DATABASE || 'test');
1267
+ logger.info('✅ Database connected successfully', 'Database');
1268
+ } catch (error) {
1269
+ logger.error('❌ Database connection failed: ' + String(error), 'Database');
1270
+ logger.warn('⚠️ App will continue without database connection', 'Database');
1271
+ // Don't throw - let the app continue without database
1272
+ }
1273
+ }`
1274
+ : '';
925
1275
  await (0, promises_1.writeFile)((0, path_1.join)(projectPath, 'src', 'database', 'index.ts'), dbSetupContent);
926
1276
  }
927
1277
  async generateAuthMiddleware(projectPath) {
928
- const authContent = `// Authentication Middleware
929
- import { auth } from '@morojs/moro';
930
- import jwt from 'jsonwebtoken';
1278
+ const authContent = `// Authentication Middleware for MoroJS
931
1279
  import bcrypt from 'bcryptjs';
932
1280
 
933
- const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
1281
+ // Mock auth middleware that creates the auth object on requests
1282
+ function createAuthMiddleware() {
1283
+ return (req: any, res: any, next: any) => {
1284
+ // Initialize auth object
1285
+ req.auth = {
1286
+ isAuthenticated: false,
1287
+ user: null,
1288
+ session: null,
1289
+ async getSession() {
1290
+ return req.auth.session;
1291
+ },
1292
+ async getUser() {
1293
+ return req.auth.user;
1294
+ },
1295
+ async signIn(provider: string, credentials: any) {
1296
+ console.log(\`🔐 Sign in attempt with \${provider}:\`, { email: credentials.email });
1297
+
1298
+ // Mock authentication logic
1299
+ if (provider === 'credentials') {
1300
+ if (credentials.email === 'admin@example.com' && credentials.password === 'password') {
1301
+ const user = {
1302
+ id: '1',
1303
+ name: 'Admin User',
1304
+ email: 'admin@example.com',
1305
+ role: 'admin'
1306
+ };
1307
+
1308
+ req.auth.isAuthenticated = true;
1309
+ req.auth.user = user;
1310
+ req.auth.session = {
1311
+ user,
1312
+ expires: new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), // 8 hours
1313
+ customData: {
1314
+ lastActivity: new Date(),
1315
+ sessionId: 'session_' + Math.random().toString(36).substr(2, 9),
1316
+ provider: 'credentials',
1317
+ },
1318
+ };
934
1319
 
935
- export async function setupAuth(app: any): Promise<void> {
936
- // JWT Authentication middleware
937
- app.use(auth({
938
- secret: JWT_SECRET,
939
- algorithms: ['HS256'],
940
- optional: true // Make auth optional by default
941
- }));
942
-
943
- // Login endpoint
944
- app.post('/auth/login', async (req: any, res: any) => {
945
- const { email, password } = req.body;
946
-
947
- // TODO: Implement user lookup from database
948
- // const user = await getUserByEmail(email);
949
- // const isValid = await bcrypt.compare(password, user.password);
950
-
951
- // Mock implementation
952
- if (email === 'admin@example.com' && password === 'password') {
953
- const token = jwt.sign(
954
- { userId: 1, email, roles: ['admin'] },
955
- JWT_SECRET,
956
- { expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
957
- );
958
-
959
- return { success: true, token, user: { id: 1, email, roles: ['admin'] } };
1320
+ console.log('🔐 Authentication successful');
1321
+ return user;
1322
+ }
1323
+ }
1324
+
1325
+ console.log('🔐 Authentication failed');
1326
+ return null;
1327
+ },
1328
+ signOut(options?: any) {
1329
+ console.log('🚪 Sign out initiated');
1330
+ req.auth.isAuthenticated = false;
1331
+ req.auth.user = null;
1332
+ req.auth.session = null;
1333
+ return { url: options?.callbackUrl || '/' };
1334
+ },
1335
+ createToken(user: any) {
1336
+ // Mock token creation
1337
+ return 'jwt_' + Math.random().toString(36).substr(2, 20);
1338
+ },
1339
+ setSession(sessionData: any) {
1340
+ req.auth.session = sessionData.session || sessionData;
1341
+ req.auth.user = sessionData.user;
1342
+ req.auth.isAuthenticated = true;
1343
+ return Promise.resolve();
1344
+ }
1345
+ };
1346
+
1347
+ // Check for existing authentication via Authorization header
1348
+ const authHeader = req.headers.authorization;
1349
+ if (authHeader?.startsWith('Bearer ')) {
1350
+ const token = authHeader.split(' ')[1];
1351
+
1352
+ // Mock token validation
1353
+ if (token.startsWith('jwt_') || token === 'admin-token') {
1354
+ req.auth.isAuthenticated = true;
1355
+ req.auth.user = {
1356
+ id: '1',
1357
+ name: 'Admin User',
1358
+ email: 'admin@example.com',
1359
+ role: 'admin'
1360
+ };
1361
+ req.auth.session = {
1362
+ user: req.auth.user,
1363
+ expires: new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(),
1364
+ customData: {
1365
+ lastActivity: new Date(),
1366
+ sessionId: 'session_from_token',
1367
+ provider: 'credentials',
1368
+ },
1369
+ };
1370
+ }
960
1371
  }
961
-
1372
+
1373
+ next();
1374
+ };
1375
+ }
1376
+
1377
+ export async function setupAuth(app: any): Promise<void> {
1378
+ // Install the auth middleware
1379
+ app.use(createAuthMiddleware());
1380
+
1381
+ // Protected route example
1382
+ app.get('/api/profile', async (req: any, res: any) => {
1383
+ // Auth.js automatically adds auth object to request
1384
+ if (!req.auth?.isAuthenticated) {
962
1385
  res.status(401);
963
- return { success: false, error: 'Invalid credentials' };
1386
+ return { success: false, error: 'Authentication required' };
1387
+ }
1388
+
1389
+ return {
1390
+ success: true,
1391
+ user: req.auth.user,
1392
+ session: req.auth.session
1393
+ };
964
1394
  });
965
1395
 
966
- // Protected route example
967
- app.get('/auth/profile', async (req: any, res: any) => {
968
- if (!req.user) {
1396
+ // Admin-only route example
1397
+ app.get('/api/admin', async (req: any, res: any) => {
1398
+ if (!req.auth?.isAuthenticated) {
969
1399
  res.status(401);
970
1400
  return { success: false, error: 'Authentication required' };
971
1401
  }
972
-
973
- return { success: true, user: req.user };
1402
+
1403
+ if (req.auth.user?.role !== 'admin') {
1404
+ res.status(403);
1405
+ return { success: false, error: 'Admin access required' };
1406
+ }
1407
+
1408
+ return {
1409
+ success: true,
1410
+ message: 'Admin access granted',
1411
+ user: req.auth.user
1412
+ };
974
1413
  });
975
1414
  }`;
976
1415
  await (0, promises_1.writeFile)((0, path_1.join)(projectPath, 'src', 'middleware', 'auth.ts'), authContent);
@@ -1034,7 +1473,7 @@ Project Structure:
1034
1473
  │ └── types/ # TypeScript types
1035
1474
  ├── package.json
1036
1475
  ├── tsconfig.json
1037
- ├── moro.config.js
1476
+ ├── moro.config.ts
1038
1477
  └── README.md
1039
1478
 
1040
1479
  Next Steps:
@@ -1056,6 +1495,24 @@ ${config.features.map((f) => ` • ${f}`).join('\n')}
1056
1495
  borderColor: 'green',
1057
1496
  }));
1058
1497
  }
1498
+ /**
1499
+ * Fetch the latest version of a package from NPM registry
1500
+ */
1501
+ async getLatestPackageVersion(packageName) {
1502
+ try {
1503
+ const { stdout } = await execAsync(`npm view ${packageName} version`);
1504
+ const version = stdout.trim();
1505
+ return `^${version}`;
1506
+ }
1507
+ catch (error) {
1508
+ this.logger.warn(`Failed to fetch latest version for ${packageName}, using fallback`, 'Init');
1509
+ // Fallback to a reasonable default if npm view fails
1510
+ if (packageName === '@morojs/moro') {
1511
+ return '^1.1.0';
1512
+ }
1513
+ return '^1.1.0';
1514
+ }
1515
+ }
1059
1516
  }
1060
1517
  exports.ProjectInitializer = ProjectInitializer;
1061
1518
  //# sourceMappingURL=init.js.map