@diagramers/cli
Version:
Diagramers CLI - Command-line tools for managing Diagramers projects
1,396 lines (1,276 loc) • 59.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectExtender = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const chalk_1 = __importDefault(require("chalk"));
class ProjectExtender {
constructor() {
this.features = [
{
name: 'auth',
description: 'Authentication system with JWT',
files: ['src/features/auth/**/*'],
dependencies: ['jsonwebtoken', 'bcryptjs']
},
{
name: 'email',
description: 'Email notification system',
files: ['src/features/email/**/*'],
dependencies: ['nodemailer', 'handlebars']
},
{
name: 'socket',
description: 'Real-time WebSocket communication',
files: ['src/features/socket/**/*'],
dependencies: ['socket.io']
},
{
name: 'cron',
description: 'Scheduled task system',
files: ['src/features/cron/**/*'],
dependencies: ['node-cron']
},
{
name: 'audit',
description: 'Audit logging system',
files: ['src/features/audit/**/*']
}
];
}
async listFeatures() {
console.log(chalk_1.default.blue('�� Available features and templates:'));
console.log('');
console.log(chalk_1.default.green('🔧 Features:'));
this.features.forEach(feature => {
console.log(chalk_1.default.green(` ${feature.name}`));
console.log(chalk_1.default.gray(` ${feature.description}`));
if (feature.dependencies && feature.dependencies.length > 0) {
console.log(chalk_1.default.yellow(` Dependencies: ${feature.dependencies.join(', ')}`));
}
console.log('');
});
console.log(chalk_1.default.blue('📦 Module Templates:'));
console.log(chalk_1.default.green(' --module <name> --crud --fields field1,field2'));
console.log(chalk_1.default.gray(' Generate complete module with CRUD operations'));
console.log('');
console.log(chalk_1.default.blue('🗄️ Database Templates:'));
console.log(chalk_1.default.green(' --table <name> --fields field1,field2 --type mongodb'));
console.log(chalk_1.default.gray(' Generate database table/collection schema'));
console.log('');
console.log(chalk_1.default.blue('🔗 Relation Templates:'));
console.log(chalk_1.default.green(' --relation <name> --type mongodb'));
console.log(chalk_1.default.gray(' Generate database relations and references'));
console.log('');
}
async addFeature(featureName) {
const feature = this.features.find(f => f.name === featureName);
if (!feature) {
throw new Error(`Feature '${featureName}' not found. Use --list to see available features.`);
}
const projectPath = process.cwd();
// Check if feature already exists
const featurePath = path.join(projectPath, 'src', 'features', featureName);
if (await fs.pathExists(featurePath)) {
throw new Error(`Feature '${featureName}' already exists in this project.`);
}
// Create feature directory structure
await this.createFeatureStructure(projectPath, feature);
// Add dependencies if any
if (feature.dependencies && feature.dependencies.length > 0) {
await this.addDependencies(feature.dependencies);
}
// Update main configuration to use the extended feature
await this.updateMainConfiguration(projectPath, feature);
console.log(chalk_1.default.green(`✅ Feature '${featureName}' added successfully!`));
console.log(chalk_1.default.blue(`📝 Main configuration updated to include ${featureName} feature`));
}
async generateModule(moduleName, options = {}) {
const projectPath = process.cwd();
// Check if module already exists
const modulePath = path.join(projectPath, 'src', 'modules', moduleName);
if (await fs.pathExists(modulePath)) {
throw new Error(`Module '${moduleName}' already exists in this project.`);
}
console.log(chalk_1.default.blue(`📁 Creating module structure for: ${moduleName}`));
// Create module directory structure
await this.createModuleStructure(projectPath, moduleName, options);
// Generate CRUD operations if requested
if (options.crud) {
await this.generateCRUDOperations(projectPath, moduleName, options);
}
// Update main application to include the module
await this.registerModule(projectPath, moduleName);
console.log(chalk_1.default.green(`✅ Module '${moduleName}' generated successfully!`));
}
async generateTable(tableName, options = {}) {
const projectPath = process.cwd();
console.log(chalk_1.default.blue(`🗄️ Generating table schema for: ${tableName}`));
// Create table schema based on database type
await this.createTableSchema(projectPath, tableName, options);
console.log(chalk_1.default.green(`✅ Table '${tableName}' generated successfully!`));
}
async generateRelations(relationName, options = {}) {
const projectPath = process.cwd();
console.log(chalk_1.default.blue(`🔗 Generating relations for: ${relationName}`));
// Create relation definitions
await this.createRelations(projectPath, relationName, options);
console.log(chalk_1.default.green(`✅ Relations '${relationName}' generated successfully!`));
}
async createFeatureStructure(projectPath, feature) {
const featurePath = path.join(projectPath, 'src', 'features', feature.name);
// Create basic feature structure
const structure = {
'controllers': 'Feature controllers',
'services': 'Feature business logic',
'schemas': 'Feature validation schemas',
'routes': 'Feature API routes',
'types': 'Feature TypeScript types',
'providers': 'Feature providers and implementations'
};
for (const [dir, description] of Object.entries(structure)) {
const dirPath = path.join(featurePath, dir);
await fs.ensureDir(dirPath);
// Create index file
const indexContent = `// ${description} for ${feature.name} feature
export * from './${feature.name}-${dir.slice(0, -1)}';
`;
await fs.writeFile(path.join(dirPath, 'index.ts'), indexContent);
}
// Create main feature file with core functions
const mainFeatureContent = await this.generateFeatureCore(feature.name);
await fs.writeFile(path.join(featurePath, `${feature.name}.feature.ts`), mainFeatureContent);
// Create feature configuration
const configContent = this.generateFeatureConfig(feature.name);
await fs.writeFile(path.join(featurePath, 'config.ts'), configContent);
// Create controllers, services, and other core files
await this.createFeatureCoreFiles(featurePath, feature.name);
// Track path refinements
await this.trackPathRefinements(projectPath, feature.name, 'feature');
}
async createFeatureCoreFiles(featurePath, featureName) {
const capitalizedName = this.capitalizeFirst(featureName);
// Create controller
const controllerContent = this.generateFeatureController(featureName, capitalizedName);
await fs.writeFile(path.join(featurePath, 'controllers', `${featureName}-controller.ts`), controllerContent);
// Create service
const serviceContent = this.generateFeatureService(featureName, capitalizedName);
await fs.writeFile(path.join(featurePath, 'services', `${featureName}-service.ts`), serviceContent);
// Create types
const typesContent = this.generateFeatureTypes(featureName, capitalizedName);
await fs.writeFile(path.join(featurePath, 'types', `${featureName}-types.ts`), typesContent);
// Create schemas
const schemasContent = this.generateFeatureSchemas(featureName, capitalizedName);
await fs.writeFile(path.join(featurePath, 'schemas', `${featureName}-schemas.ts`), schemasContent);
}
generateFeatureController(featureName, capitalizedName) {
switch (featureName) {
case 'auth':
return `import { Request, Response } from 'express';
import { AuthService } from '../services/auth-service';
import { Result } from '../../../shared/types/result';
import { logger } from '../../../core/logging';
export class AuthController {
constructor(private authService: AuthService) {}
async login(req: Request, res: Response): Promise<void> {
try {
const { email, password } = req.body;
const result = await this.authService.login(email, password);
res.json(result);
} catch (error) {
logger.error('Login error:', error);
res.status(500).json(Result.error('Login failed'));
}
}
async register(req: Request, res: Response): Promise<void> {
try {
const userData = req.body;
const result = await this.authService.register(userData);
res.json(result);
} catch (error) {
logger.error('Registration error:', error);
res.status(500).json(Result.error('Registration failed'));
}
}
async logout(req: Request, res: Response): Promise<void> {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
const result = await this.authService.logout(token);
res.json(result);
} catch (error) {
logger.error('Logout error:', error);
res.status(500).json(Result.error('Logout failed'));
}
}
async refreshToken(req: Request, res: Response): Promise<void> {
try {
const { refreshToken } = req.body;
const result = await this.authService.refreshToken(refreshToken);
res.json(result);
} catch (error) {
logger.error('Token refresh error:', error);
res.status(500).json(Result.error('Token refresh failed'));
}
}
async forgotPassword(req: Request, res: Response): Promise<void> {
try {
const { email } = req.body;
const result = await this.authService.forgotPassword(email);
res.json(result);
} catch (error) {
logger.error('Forgot password error:', error);
res.status(500).json(Result.error('Password reset failed'));
}
}
async resetPassword(req: Request, res: Response): Promise<void> {
try {
const { token, newPassword } = req.body;
const result = await this.authService.resetPassword(token, newPassword);
res.json(result);
} catch (error) {
logger.error('Reset password error:', error);
res.status(500).json(Result.error('Password reset failed'));
}
}
async verifyEmail(req: Request, res: Response): Promise<void> {
try {
const { token } = req.body;
const result = await this.authService.verifyEmail(token);
res.json(result);
} catch (error) {
logger.error('Email verification error:', error);
res.status(500).json(Result.error('Email verification failed'));
}
}
}`;
case 'email':
return `import { Request, Response } from 'express';
import { EmailService } from '../services/email-service';
import { Result } from '../../../shared/types/result';
import { logger } from '../../../core/logging';
export class EmailController {
constructor(private emailService: EmailService) {}
async sendEmail(req: Request, res: Response): Promise<void> {
try {
const { to, subject, content, template } = req.body;
const result = await this.emailService.sendEmail({ to, subject, content, template });
res.json(result);
} catch (error) {
logger.error('Send email error:', error);
res.status(500).json(Result.error('Email sending failed'));
}
}
async sendTemplateEmail(req: Request, res: Response): Promise<void> {
try {
const { to, templateName, data } = req.body;
const result = await this.emailService.sendTemplateEmail(to, templateName, data);
res.json(result);
} catch (error) {
logger.error('Template email error:', error);
res.status(500).json(Result.error('Template email failed'));
}
}
async getTemplates(req: Request, res: Response): Promise<void> {
try {
const result = await this.emailService.getTemplates();
res.json(result);
} catch (error) {
logger.error('Get templates error:', error);
res.status(500).json(Result.error('Failed to get templates'));
}
}
async createTemplate(req: Request, res: Response): Promise<void> {
try {
const templateData = req.body;
const result = await this.emailService.createTemplate(templateData);
res.json(result);
} catch (error) {
logger.error('Create template error:', error);
res.status(500).json(Result.error('Template creation failed'));
}
}
}`;
default:
return `import { Request, Response } from 'express';
import { ${capitalizedName}Service } from '../services/${featureName}-service';
import { Result } from '../../../shared/types/result';
import { logger } from '../../../core/logging';
export class ${capitalizedName}Controller {
constructor(private ${featureName}Service: ${capitalizedName}Service) {}
async index(req: Request, res: Response): Promise<void> {
try {
const result = await this.${featureName}Service.getStatus();
res.json(result);
} catch (error) {
logger.error('${capitalizedName} index error:', error);
res.status(500).json(Result.error('${capitalizedName} service error'));
}
}
// Add your custom controller methods here
async customMethod(req: Request, res: Response): Promise<void> {
try {
const result = await this.${featureName}Service.customMethod(req.body);
res.json(result);
} catch (error) {
logger.error('${capitalizedName} custom method error:', error);
res.status(500).json(Result.error('Custom method failed'));
}
}
}`;
}
}
generateFeatureService(featureName, capitalizedName) {
switch (featureName) {
case 'auth':
return `import { Result } from '../../../shared/types/result';
import { logger } from '../../../core/logging';
import { AuthConfig } from '../config';
export class AuthService {
constructor(private config: AuthConfig) {}
async login(email: string, password: string): Promise<Result> {
try {
// Implement login logic here
logger.info('Login attempt for:', email);
// Example implementation
if (!email || !password) {
return Result.badRequest('Email and password are required');
}
// Add your authentication logic here
// - Validate credentials
// - Generate JWT tokens
// - Return user data and tokens
return Result.success({ message: 'Login successful' });
} catch (error) {
logger.error('Login service error:', error);
return Result.error('Login failed');
}
}
async register(userData: any): Promise<Result> {
try {
logger.info('Registration attempt for:', userData.email);
// Add your registration logic here
// - Validate user data
// - Hash password
// - Create user record
// - Send verification email
return Result.success({ message: 'Registration successful' });
} catch (error) {
logger.error('Registration service error:', error);
return Result.error('Registration failed');
}
}
async logout(token: string): Promise<Result> {
try {
// Add your logout logic here
// - Invalidate token
// - Clear session
return Result.success({ message: 'Logout successful' });
} catch (error) {
logger.error('Logout service error:', error);
return Result.error('Logout failed');
}
}
async refreshToken(refreshToken: string): Promise<Result> {
try {
// Add your token refresh logic here
// - Validate refresh token
// - Generate new access token
return Result.success({ message: 'Token refreshed' });
} catch (error) {
logger.error('Token refresh service error:', error);
return Result.error('Token refresh failed');
}
}
async forgotPassword(email: string): Promise<Result> {
try {
// Add your forgot password logic here
// - Validate email
// - Generate reset token
// - Send reset email
return Result.success({ message: 'Password reset email sent' });
} catch (error) {
logger.error('Forgot password service error:', error);
return Result.error('Password reset failed');
}
}
async resetPassword(token: string, newPassword: string): Promise<Result> {
try {
// Add your password reset logic here
// - Validate token
// - Update password
return Result.success({ message: 'Password reset successful' });
} catch (error) {
logger.error('Reset password service error:', error);
return Result.error('Password reset failed');
}
}
async verifyEmail(token: string): Promise<Result> {
try {
// Add your email verification logic here
// - Validate token
// - Mark email as verified
return Result.success({ message: 'Email verified' });
} catch (error) {
logger.error('Email verification service error:', error);
return Result.error('Email verification failed');
}
}
}`;
default:
return `import { Result } from '../../../shared/types/result';
import { logger } from '../../../core/logging';
import { ${capitalizedName}Config } from '../config';
export class ${capitalizedName}Service {
constructor(private config: ${capitalizedName}Config) {}
async getStatus(): Promise<Result> {
try {
return Result.success({
status: 'ok',
feature: '${featureName}',
enabled: this.config.enabled
});
} catch (error) {
logger.error('${capitalizedName} service error:', error);
return Result.error('${capitalizedName} service error');
}
}
async customMethod(data: any): Promise<Result> {
try {
// Add your custom business logic here
logger.info('${capitalizedName} custom method called with:', data);
return Result.success({ message: 'Custom method executed' });
} catch (error) {
logger.error('${capitalizedName} custom method error:', error);
return Result.error('Custom method failed');
}
}
// Add more service methods as needed
}`;
}
}
generateFeatureTypes(featureName, capitalizedName) {
return `// TypeScript types for ${featureName} feature
export interface ${capitalizedName}Config {
enabled: boolean;
// Add your configuration interface properties here
}
export interface ${capitalizedName}Data {
// Add your data interface properties here
}
export interface ${capitalizedName}Options {
// Add your options interface properties here
}
// Add more type definitions as needed
`;
}
generateFeatureSchemas(featureName, capitalizedName) {
return `// Validation schemas for ${featureName} feature
import Joi from 'joi';
export const ${featureName}ValidationSchema = Joi.object({
// Add your validation schema here
// Example:
// name: Joi.string().required(),
// email: Joi.string().email().required(),
});
export const ${featureName}UpdateSchema = Joi.object({
// Add your update validation schema here
});
// Add more validation schemas as needed
`;
}
async generateFeatureCore(featureName) {
const capitalizedName = this.capitalizeFirst(featureName);
switch (featureName) {
case 'auth':
return `import { Router } from 'express';
import { AuthController } from './controllers';
import { AuthService } from './services';
import { AuthConfig } from './config';
export class ${capitalizedName}Feature {
private router: Router;
private controller: AuthController;
private service: AuthService;
private config: AuthConfig;
constructor() {
this.router = Router();
this.config = new AuthConfig();
this.service = new AuthService();
this.controller = new AuthController(this.service);
this.setupRoutes();
}
private setupRoutes(): void {
// Authentication routes
this.router.post('/login', this.controller.login.bind(this.controller));
this.router.post('/register', this.controller.register.bind(this.controller));
this.router.post('/logout', this.controller.logout.bind(this.controller));
this.router.post('/refresh', this.controller.refreshToken.bind(this.controller));
this.router.post('/forgot-password', this.controller.forgotPassword.bind(this.controller));
this.router.post('/reset-password', this.controller.resetPassword.bind(this.controller));
this.router.post('/verify-email', this.controller.verifyEmail.bind(this.controller));
}
getRouter(): Router {
return this.router;
}
getService(): AuthService {
return this.service;
}
getConfig(): AuthConfig {
return this.config;
}
}`;
case 'email':
return `import { Router } from 'express';
import { EmailController } from './controllers';
import { EmailService } from './services';
import { EmailConfig } from './config';
export class ${capitalizedName}Feature {
private router: Router;
private controller: EmailController;
private service: EmailService;
private config: EmailConfig;
constructor() {
this.router = Router();
this.config = new EmailConfig();
this.service = new EmailService(this.config);
this.controller = new EmailController(this.service);
this.setupRoutes();
}
private setupRoutes(): void {
// Email routes
this.router.post('/send', this.controller.sendEmail.bind(this.controller));
this.router.post('/send-template', this.controller.sendTemplateEmail.bind(this.controller));
this.router.get('/templates', this.controller.getTemplates.bind(this.controller));
this.router.post('/templates', this.controller.createTemplate.bind(this.controller));
}
getRouter(): Router {
return this.router;
}
getService(): EmailService {
return this.service;
}
getConfig(): EmailConfig {
return this.config;
}
}`;
case 'socket':
return `import { Server as SocketIOServer } from 'socket.io';
import { SocketController } from './controllers';
import { SocketService } from './services';
import { SocketConfig } from './config';
export class ${capitalizedName}Feature {
private io: SocketIOServer;
private controller: SocketController;
private service: SocketService;
private config: SocketConfig;
constructor(io: SocketIOServer) {
this.io = io;
this.config = new SocketConfig();
this.service = new SocketService(this.io, this.config);
this.controller = new SocketController(this.service);
this.setupEvents();
}
private setupEvents(): void {
this.io.on('connection', (socket) => {
this.controller.handleConnection(socket);
// Register event handlers
socket.on('join-room', this.controller.handleJoinRoom.bind(this.controller, socket));
socket.on('leave-room', this.controller.handleLeaveRoom.bind(this.controller, socket));
socket.on('send-message', this.controller.handleSendMessage.bind(this.controller, socket));
socket.on('disconnect', this.controller.handleDisconnect.bind(this.controller, socket));
});
}
getService(): SocketService {
return this.service;
}
getConfig(): SocketConfig {
return this.config;
}
}`;
case 'cron':
return `import { CronController } from './controllers';
import { CronService } from './services';
import { CronConfig } from './config';
export class ${capitalizedName}Feature {
private controller: CronController;
private service: CronService;
private config: CronConfig;
constructor() {
this.config = new CronConfig();
this.service = new CronService(this.config);
this.controller = new CronController(this.service);
this.initializeJobs();
}
private initializeJobs(): void {
// Initialize scheduled jobs
this.service.initializeJobs();
}
getService(): CronService {
return this.service;
}
getConfig(): CronConfig {
return this.config;
}
// Method to manually trigger jobs
async triggerJob(jobName: string): Promise<void> {
await this.service.triggerJob(jobName);
}
}`;
case 'audit':
return `import { Router } from 'express';
import { AuditController } from './controllers';
import { AuditService } from './services';
import { AuditConfig } from './config';
export class ${capitalizedName}Feature {
private router: Router;
private controller: AuditController;
private service: AuditService;
private config: AuditConfig;
constructor() {
this.router = Router();
this.config = new AuditConfig();
this.service = new AuditService(this.config);
this.controller = new AuditController(this.service);
this.setupRoutes();
}
private setupRoutes(): void {
// Audit routes
this.router.get('/logs', this.controller.getLogs.bind(this.controller));
this.router.post('/logs', this.controller.createLog.bind(this.controller));
this.router.get('/logs/:id', this.controller.getLogById.bind(this.controller));
this.router.delete('/logs/:id', this.controller.deleteLog.bind(this.controller));
}
getRouter(): Router {
return this.router;
}
getService(): AuditService {
return this.service;
}
getConfig(): AuditConfig {
return this.config;
}
// Method to log audit events
async logEvent(event: any): Promise<void> {
await this.service.logEvent(event);
}
}`;
default:
return `import { Router } from 'express';
import { ${capitalizedName}Controller } from './controllers';
import { ${capitalizedName}Service } from './services';
import { ${capitalizedName}Config } from './config';
export class ${capitalizedName}Feature {
private router: Router;
private controller: ${capitalizedName}Controller;
private service: ${capitalizedName}Service;
private config: ${capitalizedName}Config;
constructor() {
this.router = Router();
this.config = new ${capitalizedName}Config();
this.service = new ${capitalizedName}Service(this.config);
this.controller = new ${capitalizedName}Controller(this.service);
this.setupRoutes();
}
private setupRoutes(): void {
// Add your feature routes here
this.router.get('/', this.controller.index.bind(this.controller));
}
getRouter(): Router {
return this.router;
}
getService(): ${capitalizedName}Service {
return this.service;
}
getConfig(): ${capitalizedName}Config {
return this.config;
}
}`;
}
}
generateFeatureConfig(featureName) {
const capitalizedName = this.capitalizeFirst(featureName);
return `export class ${capitalizedName}Config {
constructor() {
// Initialize configuration from environment variables
this.loadFromEnv();
}
private loadFromEnv(): void {
// Load configuration from environment variables
// Override this method to add specific configuration loading
}
// Add getter methods for configuration values
get enabled(): boolean {
return process.env.${featureName.toUpperCase()}_ENABLED === 'true';
}
}`;
}
async trackPathRefinements(projectPath, featureName, type) {
const refinementsPath = path.join(projectPath, '.diagramers-refinements.json');
let refinements = {};
if (await fs.pathExists(refinementsPath)) {
refinements = JSON.parse(await fs.readFile(refinementsPath, 'utf8'));
}
if (!refinements.paths) {
refinements.paths = [];
}
refinements.paths.push({
type: type,
name: featureName,
path: `src/features/${featureName}`,
addedAt: new Date().toISOString(),
version: '1.0.0'
});
await fs.writeFile(refinementsPath, JSON.stringify(refinements, null, 2));
}
async createModuleStructure(projectPath, moduleName, options) {
const modulePath = path.join(projectPath, 'src', 'modules', moduleName);
// Create module directory structure
const structure = {
'entities': 'Data models and interfaces',
'schemas': 'Database schemas and validation',
'services': 'Business logic and data access',
'controllers': 'HTTP request handlers',
'routes': 'API route definitions'
};
for (const [dir, description] of Object.entries(structure)) {
const dirPath = path.join(modulePath, dir);
await fs.ensureDir(dirPath);
// Create index file with correct file extensions
let fileName = '';
switch (dir) {
case 'entities':
fileName = `${moduleName}.entity`;
break;
case 'schemas':
fileName = `${moduleName}.schema`;
break;
case 'services':
fileName = `${moduleName}.service`;
break;
case 'controllers':
fileName = `${moduleName}.controller`;
break;
case 'routes':
fileName = `${moduleName}.routes`;
break;
default:
fileName = `${moduleName}.${dir.slice(0, -1)}`;
}
const indexContent = `// ${description} for ${moduleName} module
export * from './${fileName}';
`;
await fs.writeFile(path.join(dirPath, 'index.ts'), indexContent);
}
// Create main entity
await this.createEntity(modulePath, moduleName, options);
// Create schema
await this.createSchema(modulePath, moduleName, options);
// Create service
await this.createService(modulePath, moduleName, options);
// Create controller
await this.createController(modulePath, moduleName, options);
// Create routes
await this.createRoutes(modulePath, moduleName, options);
}
async createEntity(modulePath, moduleName, options) {
const entityName = this.capitalizeFirst(moduleName);
const fields = options.fields || ['name', 'description'];
const entityContent = `import { BaseEntity } from '../../../shared/types/base-entity';
export interface I${entityName} extends BaseEntity {
${fields.map((field) => ` ${field}: string;`).join('\n')}
}
export interface I${entityName}Create {
${fields.map((field) => ` ${field}: string;`).join('\n')}
}
export interface I${entityName}Update {
${fields.map((field) => ` ${field}?: string;`).join('\n')}
}
`;
await fs.writeFile(path.join(modulePath, 'entities', `${moduleName}.entity.ts`), entityContent);
}
async createSchema(modulePath, moduleName, options) {
const entityName = this.capitalizeFirst(moduleName);
const fields = options.fields || ['name', 'description'];
const schemaContent = `import mongoose, { Schema, Document } from 'mongoose';
import { I${entityName} } from '../entities/${moduleName}.entity';
export interface I${entityName}Document extends Omit<I${entityName}, '_id'>, Document {}
const ${moduleName}Schema = new Schema({
${fields.map((field) => ` ${field}: {
type: String,
required: true,
trim: true
}`).join(',\n')}
}, {
timestamps: true,
toJSON: {
transform: function(doc, ret) {
return ret;
}
}
});
// Indexes
${fields.map((field) => `${moduleName}Schema.index({ ${field}: 1 });`).join('\n')}
export const ${entityName}Model = mongoose.model<I${entityName}Document>('${entityName}', ${moduleName}Schema);
`;
await fs.writeFile(path.join(modulePath, 'schemas', `${moduleName}.schema.ts`), schemaContent);
}
async createService(modulePath, moduleName, options) {
const entityName = this.capitalizeFirst(moduleName);
const serviceContent = `import { Result } from '../../../shared/types/result';
import { ${entityName}Model, I${entityName}Document } from '../schemas/${moduleName}.schema';
import { I${entityName}, I${entityName}Create, I${entityName}Update } from '../entities/${moduleName}.entity';
export class ${entityName}Service {
/**
* Get all ${moduleName}s
*/
async getAll(): Promise<Result<I${entityName}[]>> {
try {
const ${moduleName}s = await ${entityName}Model.find({ status: 1 }).lean();
const converted${entityName}s = ${moduleName}s.map(${moduleName} => ({
...${moduleName},
_id: ${moduleName}._id?.toString() || ''
})) as I${entityName}[];
return Result.success(converted${entityName}s);
} catch (error: any) {
return Result.error('Failed to fetch ${moduleName}s', error.message);
}
}
/**
* Get ${moduleName} by ID
*/
async getById(id: string): Promise<Result<I${entityName} | null>> {
try {
const ${moduleName} = await ${entityName}Model.findOne({ _id: id, status: 1 });
if (!${moduleName}) {
return Result.error('${entityName} not found', '${entityName} with the specified ID does not exist');
}
const ${moduleName}Obj = ${moduleName}.toObject();
const converted${entityName} = {
...${moduleName}Obj,
_id: ${moduleName}Obj._id?.toString() || ''
} as I${entityName};
return Result.success(converted${entityName});
} catch (error: any) {
return Result.error('Failed to fetch ${moduleName}', error.message);
}
}
/**
* Create new ${moduleName}
*/
async create(${moduleName}Data: I${entityName}Create): Promise<Result<I${entityName}>> {
try {
const ${moduleName} = new ${entityName}Model({
...${moduleName}Data,
status: 1
});
const saved${entityName} = await ${moduleName}.save();
const ${moduleName}Obj = saved${entityName}.toObject();
const converted${entityName} = {
...${moduleName}Obj,
_id: ${moduleName}Obj._id?.toString() || ''
} as I${entityName};
return Result.success(converted${entityName});
} catch (error: any) {
return Result.error('Failed to create ${moduleName}', error.message);
}
}
/**
* Update ${moduleName}
*/
async update(id: string, ${moduleName}Data: I${entityName}Update): Promise<Result<I${entityName} | null>> {
try {
const ${moduleName} = await ${entityName}Model.findOne({ _id: id, status: 1 });
if (!${moduleName}) {
return Result.error('${entityName} not found', '${entityName} with the specified ID does not exist');
}
Object.assign(${moduleName}, ${moduleName}Data);
const updated${entityName} = await ${moduleName}.save();
const ${moduleName}Obj = updated${entityName}.toObject();
const converted${entityName} = {
...${moduleName}Obj,
_id: ${moduleName}Obj._id?.toString() || ''
} as I${entityName};
return Result.success(converted${entityName});
} catch (error: any) {
return Result.error('Failed to update ${moduleName}', error.message);
}
}
/**
* Delete ${moduleName} (soft delete)
*/
async delete(id: string): Promise<Result<boolean>> {
try {
const ${moduleName} = await ${entityName}Model.findOne({ _id: id, status: 1 });
if (!${moduleName}) {
return Result.error('${entityName} not found', '${entityName} with the specified ID does not exist');
}
${moduleName}.status = 0;
await ${moduleName}.save();
return Result.success(true);
} catch (error: any) {
return Result.error('Failed to delete ${moduleName}', error.message);
}
}
}
`;
await fs.writeFile(path.join(modulePath, 'services', `${moduleName}.service.ts`), serviceContent);
}
async createController(modulePath, moduleName, options) {
const entityName = this.capitalizeFirst(moduleName);
const controllerContent = `import { Request, Response } from 'express';
import { ${entityName}Service } from '../services/${moduleName}.service';
import { I${entityName}Create, I${entityName}Update } from '../entities/${moduleName}.entity';
/**
* @swagger
* components:
* schemas:
* ${entityName}:
* type: object
* properties:
* _id:
* type: string
* name:
* type: string
* description:
* type: string
* createdAt:
* type: string
* format: date-time
* updatedAt:
* type: string
* format: date-time
*/
export class ${entityName}Controller {
constructor(private service: ${entityName}Service) {}
/**
* @swagger
* /api/${moduleName}s:
* get:
* summary: Get all ${moduleName}s
* tags: [${entityName}s]
* responses:
* 200:
* description: List of ${moduleName}s
* content:
* application/json:
* schema:
* type: object
* properties:
* Data:
* type: array
* items:
* $ref: '#/components/schemas/${entityName}'
* StatusCode:
* type: number
* Message:
* type: string
*/
async getAll(req: Request, res: Response): Promise<void> {
try {
const result = await this.service.getAll();
res.status(result.StatusCode === 1000 ? 200 : 400).json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
/**
* @swagger
* /api/${moduleName}s/{id}:
* get:
* summary: Get ${moduleName} by ID
* tags: [${entityName}s]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* responses:
* 200:
* description: ${entityName} details
* content:
* application/json:
* schema:
* type: object
* properties:
* Data:
* $ref: '#/components/schemas/${entityName}'
* StatusCode:
* type: number
* Message:
* type: string
*/
async getById(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const result = await this.service.getById(id);
res.status(result.StatusCode === 1000 ? 200 : 400).json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
/**
* @swagger
* /api/${moduleName}s:
* post:
* summary: Create new ${moduleName}
* tags: [${entityName}s]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description:
* type: string
* responses:
* 201:
* description: ${entityName} created successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* Data:
* $ref: '#/components/schemas/${entityName}'
* StatusCode:
* type: number
* Message:
* type: string
*/
async create(req: Request, res: Response): Promise<void> {
try {
const ${moduleName}Data: I${entityName}Create = req.body;
const result = await this.service.create(${moduleName}Data);
res.status(result.StatusCode === 1000 ? 201 : 400).json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
/**
* @swagger
* /api/${moduleName}s/{id}:
* put:
* summary: Update ${moduleName}
* tags: [${entityName}s]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* description:
* type: string
* responses:
* 200:
* description: ${entityName} updated successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* Data:
* $ref: '#/components/schemas/${entityName}'
* StatusCode:
* type: number
* Message:
* type: string
*/
async update(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const ${moduleName}Data: I${entityName}Update = req.body;
const result = await this.service.update(id, ${moduleName}Data);
res.status(result.StatusCode === 1000 ? 200 : 400).json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
/**
* @swagger
* /api/${moduleName}s/{id}:
* delete:
* summary: Delete ${moduleName}
* tags: [${entityName}s]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* responses:
* 200:
* description: ${entityName} deleted successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* Data:
* type: boolean
* StatusCode:
* type: number
* Message:
* type: string
*/
async delete(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const result = await this.service.delete(id);
res.status(result.StatusCode === 1000 ? 200 : 400).json(result);
} catch (error: any) {
res.status(500).json({ error: error.message });
}
}
}
`;
await fs.writeFile(path.join(modulePath, 'controllers', `${moduleName}.controller.ts`), controllerContent);
}
async createRoutes(modulePath, moduleName, options) {
const entityName = this.capitalizeFirst(moduleName);
const routesContent = `import { Router } from 'express';
import { ${entityName}Controller } from '../controllers/${moduleName}.controller';
import { ${entityName}Service } from '../services/${moduleName}.service';
const router = Router();
const service = new ${entityName}Service();
const controller = new ${entityName}Controller(service);
// ${entityName} routes
router.get('/', controller.getAll.bind(controller));
router.get('/:id', controller.getById.bind(controller));
router.post('/', controller.create.bind(controller));
router.put('/:id', controller.update.bind(controller));
router.delete('/:id', controller.delete.bind(controller));
export default router;
`;
await fs.writeFile(path.join(modulePath, 'routes', `${moduleName}.routes.ts`), routesContent);
}
async generateCRUDOperations(projectPath, moduleName, options) {
console.log(chalk_1.default.blue(`🔄 Generating CRUD operations for ${moduleName}...`));
// CRUD operations are already included in the service and controller generation
}
async registerModule(projectPath, moduleName) {
console.log(chalk_1.default.blue(`📝 Registering ${moduleName} module in main application...`));
// Update main routes index to include the new module
const routesIndexPath = path.join(projectPath, 'src', 'modules', 'index.ts');
if (await fs.pathExists(routesIndexPath)) {
let content = await fs.readFile(routesIndexPath, 'utf8');
// Add import for the new module
const importStatement = `import ${moduleName}Routes from './${moduleName}/routes/${moduleName}.routes';`;
if (!content.includes(importStatement)) {
// Find the last import statement and add after it
const importRegex = /import.*from.*['"];?\s*$/gm;
const matches = [...content.matchAll(importRegex)];
if (matches.length > 0) {
const lastImport = matches[matches.length - 1];
const insertIndex = lastImport.index + lastImport[0].length;
content = content.slice(0, insertIndex) + '\n' + importStatement + content.slice(insertIndex);
}
}
// Add route registration
const routeRegistration = `app.use('/api/${moduleName}s', ${moduleName}Routes);`;
if (!content.includes(routeRegistration)) {
// Find where routes are registered and add after
const routeRegex = /app\.use\(.*\);?\s*$/gm;
const matches = [...content.matchAll(routeRegex)];
if (matches.length > 0) {
const lastRoute = matches[matches.length - 1];
const insertIndex = lastRoute.index + lastRoute[0].length;
content = content.slice(0, insertIndex) + '\n' + routeRegistration + content.slice(insertIndex);
}
}
await fs.writeFile(routesIndexPath, content);
}
}
async createTableSchema(projectPath, tableName, options) {
const entityName = this.capitalizeFirst(tableName);
const fields = options.fields || ['name', 'description'];
const dbType = options.type || 'mongodb';
if (dbType === 'mongodb') {
await this.createMongoDBSchema(projectPath, tableName, fields);
}
else {
await this.createSQLSchema(projectPath, tableName, fields, dbType);
}
}
async createMongoDBSchema(projectPath, tableName, fields) {
const schemaPath = path.join(projectPath, 'src', 'schemas', `${tableName}.schema.ts`);
await fs.ensureDir(path.dirname(schemaPath));
const entityName = this.capitalizeFirst(tableName);
const schemaContent = `import mongoose, { Schema, Document } from 'mongoose';
export interface I${entityName}Document extends Document {
${fields.map(field => ` ${field}: string;`).join('\n')}
createdAt: Date;
updatedAt: Date;
}
const ${tableName}Schema = new Schema({
${fields.map(field => ` ${field}: {
type: String,
required: true,
trim: true
}`).join(',\n')}
}, {
timestamps: true
});
// Indexes
${fields.map(field => `${tableName}Schema.index({ ${field}: 1 });`).join('\n')}
export const ${entityName}Model = mongoose.model<I${entityName}Document>('${entityName}', ${tableName}Schema);
`;
await fs.writeFile(schemaPath, schemaContent);
}
async createSQLSchema(projectPath, tableName, fields, dbType) {
const schemaPath = path.join(projectPath, 'src', 'schemas', `${tableName}.sql`);
await fs.ensureDir(path.dirname(schemaPath));
const entityName = this.capitalizeFirst(tableName);
const schemaContent = `-- ${entityName} table schema for ${dbType.toUpperCase()}
CREATE TABLE ${tableName}s (
id ${dbType === 'postgres' ? 'SERIAL' : 'INT AUTO_INCREMENT'} PRIMARY KEY,
${fields.map(field => ` ${field} VARCHAR(255) NOT NULL,`).join('\n')}
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDAT