@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.
- package/LICENSE +0 -0
- package/README.md +18 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -14
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.js +8 -8
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +4 -23
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +610 -153
- package/dist/commands/init.js.map +1 -1
- package/dist/module-stub-generator.d.ts.map +1 -1
- package/dist/module-stub-generator.js +39 -35
- package/dist/module-stub-generator.js.map +1 -1
- package/package.json +34 -20
package/dist/commands/init.js
CHANGED
|
@@ -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'
|
|
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'
|
|
156
|
-
{ name: 'Rate Limiting', value: 'rate-limit'
|
|
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'
|
|
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: '
|
|
183
|
-
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':
|
|
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') && {
|
|
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:
|
|
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
|
-
//
|
|
292
|
+
// Auth setup
|
|
280
293
|
${config.features.includes('auth') ? `await setupAuth(app);` : ''}
|
|
281
294
|
|
|
282
|
-
//
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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 =
|
|
320
|
-
const HOST =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
432
|
-
enableTimestamp:
|
|
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:
|
|
438
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
887
|
-
|
|
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
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
|
917
|
-
app.database
|
|
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:'
|
|
922
|
-
|
|
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
|
-
|
|
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
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
967
|
-
app.get('/
|
|
968
|
-
if (!req.
|
|
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
|
-
|
|
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.
|
|
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
|