UNPKG

modurize

Version:

Intelligent CLI tool to scaffold dynamic, context-aware modules for Node.js apps with smart CRUD generation and database integration

601 lines (527 loc) 20 kB
import { ProjectAnalyzer } from './analyzer.js'; export class CRUDGenerator { constructor(moduleName, analysis) { this.moduleName = moduleName; this.analysis = analysis; this.capName = this.capitalize(moduleName); this.pluralName = this.pluralize(moduleName); } capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } pluralize(str) { if (str.endsWith('y')) return str.slice(0, -1) + 'ies'; if (str.endsWith('s') || str.endsWith('sh') || str.endsWith('ch') || str.endsWith('x') || str.endsWith('z')) { return str + 'es'; } return str + 's'; } generateFields() { // Common field patterns based on module name const commonFields = { id: { type: 'string', required: true, primary: true }, createdAt: { type: 'date', required: true, default: 'now' }, updatedAt: { type: 'date', required: true, default: 'now' } }; // Dynamic fields based on module type const dynamicFields = this.getDynamicFields(); return { ...commonFields, ...dynamicFields }; } getDynamicFields() { const fields = {}; // User-related fields if (this.moduleName.includes('user')) { fields.email = { type: 'string', required: true, unique: true }; fields.name = { type: 'string', required: true }; fields.password = { type: 'string', required: true, hidden: true }; fields.role = { type: 'string', required: true, default: 'user' }; fields.isActive = { type: 'boolean', required: true, default: true }; } // Product-related fields else if (this.moduleName.includes('product')) { fields.name = { type: 'string', required: true }; fields.description = { type: 'string', required: false }; fields.price = { type: 'number', required: true }; fields.category = { type: 'string', required: true }; fields.stock = { type: 'number', required: true, default: 0 }; fields.isActive = { type: 'boolean', required: true, default: true }; } // Order-related fields else if (this.moduleName.includes('order')) { fields.userId = { type: 'string', required: true, ref: 'User' }; fields.items = { type: 'array', required: true }; fields.total = { type: 'number', required: true }; fields.status = { type: 'string', required: true, default: 'pending' }; fields.paymentMethod = { type: 'string', required: false }; } // Category-related fields else if (this.moduleName.includes('category')) { fields.name = { type: 'string', required: true, unique: true }; fields.description = { type: 'string', required: false }; fields.slug = { type: 'string', required: true, unique: true }; fields.isActive = { type: 'boolean', required: true, default: true }; } // Default fields for unknown modules else { fields.name = { type: 'string', required: true }; fields.description = { type: 'string', required: false }; fields.isActive = { type: 'boolean', required: true, default: true }; } return fields; } generateController(useTypeScript = false, useClass = false) { const fields = this.generateFields(); const fieldNames = Object.keys(fields).filter(f => f !== 'id' && f !== 'createdAt' && f !== 'updatedAt'); if (useClass) { return this.generateClassController(fields, fieldNames, useTypeScript); } else { return this.generateFunctionController(fields, fieldNames, useTypeScript); } } generateClassController(fields, fieldNames, useTypeScript) { const tsImports = useTypeScript ? `import { Request, Response } from 'express';\n` : ''; const tsTypes = useTypeScript ? ': Request, res: Response' : ''; const tsReturn = useTypeScript ? ': Promise<Response>' : ''; const tsServiceType = useTypeScript ? `import { ${this.capName}Service } from './${this.moduleName}.service.js';\n` : ''; return `// ${this.moduleName}.controller.${useTypeScript ? 'ts' : 'js'} ${tsImports}${tsServiceType} export class ${this.capName}Controller { // Get all ${this.pluralName} static async getAll(req${tsTypes}, res${tsTypes})${tsReturn} { try { const ${this.pluralName} = await ${this.capName}Service.findAll(req.query); return res.json({ success: true, data: ${this.pluralName}, count: ${this.pluralName}.length }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } } // Get ${this.moduleName} by ID static async getById(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const ${this.moduleName} = await ${this.capName}Service.findById(id); if (!${this.moduleName}) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, data: ${this.moduleName} }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } } // Create new ${this.moduleName} static async create(req${tsTypes}, res${tsTypes})${tsReturn} { try { const ${this.moduleName}Data = req.body; const new${this.capName} = await ${this.capName}Service.create(${this.moduleName}Data); return res.status(201).json({ success: true, data: new${this.capName} }); } catch (error) { return res.status(400).json({ success: false, error: error.message }); } } // Update ${this.moduleName} static async update(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const updateData = req.body; const updated${this.capName} = await ${this.capName}Service.update(id, updateData); if (!updated${this.capName}) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, data: updated${this.capName} }); } catch (error) { return res.status(400).json({ success: false, error: error.message }); } } // Delete ${this.moduleName} static async delete(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const deleted = await ${this.capName}Service.delete(id); if (!deleted) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, message: '${this.capName} deleted successfully' }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } } }`; } generateFunctionController(fields, fieldNames, useTypeScript) { const tsImports = useTypeScript ? `import { Request, Response } from 'express';\n` : ''; const tsTypes = useTypeScript ? ': Request, res: Response' : ''; const tsReturn = useTypeScript ? ': Promise<Response>' : ''; const tsServiceType = useTypeScript ? `import * as ${this.capName}Service from './${this.moduleName}.service.js';\n` : ''; return `// ${this.moduleName}.controller.${useTypeScript ? 'ts' : 'js'} ${tsImports}${tsServiceType} // Get all ${this.pluralName} export async function getAll(req${tsTypes}, res${tsTypes})${tsReturn} { try { const ${this.pluralName} = await ${this.capName}Service.findAll(req.query); return res.json({ success: true, data: ${this.pluralName}, count: ${this.pluralName}.length }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } } // Get ${this.moduleName} by ID export async function getById(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const ${this.moduleName} = await ${this.capName}Service.findById(id); if (!${this.moduleName}) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, data: ${this.moduleName} }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } } // Create new ${this.moduleName} export async function create(req${tsTypes}, res${tsTypes})${tsReturn} { try { const ${this.moduleName}Data = req.body; const new${this.capName} = await ${this.capName}Service.create(${this.moduleName}Data); return res.status(201).json({ success: true, data: new${this.capName} }); } catch (error) { return res.status(400).json({ success: false, error: error.message }); } } // Update ${this.moduleName} export async function update(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const updateData = req.body; const updated${this.capName} = await ${this.capName}Service.update(id, updateData); if (!updated${this.capName}) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, data: updated${this.capName} }); } catch (error) { return res.status(400).json({ success: false, error: error.message }); } } // Delete ${this.moduleName} export async function delete${this.capName}(req${tsTypes}, res${tsTypes})${tsReturn} { try { const { id } = req.params; const deleted = await ${this.capName}Service.delete(id); if (!deleted) { return res.status(404).json({ success: false, error: '${this.capName} not found' }); } return res.json({ success: true, message: '${this.capName} deleted successfully' }); } catch (error) { return res.status(500).json({ success: false, error: error.message }); } }`; } generateService(useTypeScript = false, useClass = false) { const fields = this.generateFields(); const fieldNames = Object.keys(fields).filter(f => f !== 'id' && f !== 'createdAt' && f !== 'updatedAt'); if (useClass) { return this.generateClassService(fields, fieldNames, useTypeScript); } else { return this.generateFunctionService(fields, fieldNames, useTypeScript); } } generateClassService(fields, fieldNames, useTypeScript) { const tsImports = useTypeScript ? `import { ${this.capName}Model } from './${this.moduleName}.model.js';\n` : ''; const tsTypes = useTypeScript ? `: any` : ''; const database = this.analysis.database; return `// ${this.moduleName}.service.${useTypeScript ? 'ts' : 'js'} ${tsImports} export class ${this.capName}Service { // Find all ${this.pluralName} with optional filters static async findAll(filters${tsTypes} = {}) { try { ${this.getDatabaseQuery('findAll', database)} } catch (error) { throw new Error(\`Failed to fetch ${this.pluralName}: \${error.message}\`); } } // Find ${this.moduleName} by ID static async findById(id${tsTypes}) { try { ${this.getDatabaseQuery('findById', database)} } catch (error) { throw new Error(\`Failed to fetch ${this.moduleName}: \${error.message}\`); } } // Create new ${this.moduleName} static async create(data${tsTypes}) { try { ${this.getDatabaseQuery('create', database)} } catch (error) { throw new Error(\`Failed to create ${this.moduleName}: \${error.message}\`); } } // Update ${this.moduleName} static async update(id${tsTypes}, data${tsTypes}) { try { ${this.getDatabaseQuery('update', database)} } catch (error) { throw new Error(\`Failed to update ${this.moduleName}: \${error.message}\`); } } // Delete ${this.moduleName} static async delete(id${tsTypes}) { try { ${this.getDatabaseQuery('delete', database)} } catch (error) { throw new Error(\`Failed to delete ${this.moduleName}: \${error.message}\`); } } }`; } generateFunctionService(fields, fieldNames, useTypeScript) { const tsImports = useTypeScript ? `import { ${this.capName}Model } from './${this.moduleName}.model.js';\n` : ''; const tsTypes = useTypeScript ? `: any` : ''; const database = this.analysis.database; return `// ${this.moduleName}.service.${useTypeScript ? 'ts' : 'js'} ${tsImports} // Find all ${this.pluralName} with optional filters export async function findAll(filters${tsTypes} = {}) { try { ${this.getDatabaseQuery('findAll', database)} } catch (error) { throw new Error(\`Failed to fetch ${this.pluralName}: \${error.message}\`); } } // Find ${this.moduleName} by ID export async function findById(id${tsTypes}) { try { ${this.getDatabaseQuery('findById', database)} } catch (error) { throw new Error(\`Failed to fetch ${this.moduleName}: \${error.message}\`); } } // Create new ${this.moduleName} export async function create(data${tsTypes}) { try { ${this.getDatabaseQuery('create', database)} } catch (error) { throw new Error(\`Failed to create ${this.moduleName}: \${error.message}\`); } } // Update ${this.moduleName} export async function update(id${tsTypes}, data${tsTypes}) { try { ${this.getDatabaseQuery('update', database)} } catch (error) { throw new Error(\`Failed to update ${this.moduleName}: \${error.message}\`); } } // Delete ${this.moduleName} export async function delete${this.capName}(id${tsTypes}) { try { ${this.getDatabaseQuery('delete', database)} } catch (error) { throw new Error(\`Failed to delete ${this.moduleName}: \${error.message}\`); } }`; } generateRoutes(useTypeScript = false, useClass = false) { const ctrlName = this.capName + (useClass ? 'Controller' : ''); const handlers = useClass ? { getAll: `${ctrlName}.getAll`, getById: `${ctrlName}.getById`, create: `${ctrlName}.create`, update: `${ctrlName}.update`, delete: `${ctrlName}.delete` } : { getAll: 'getAll', getById: 'getById', create: 'create', update: 'update', delete: `delete${this.capName}` }; const tsImports = useTypeScript ? `import { Router } from 'express';\n` : ''; const importStatement = useClass ? `import { ${ctrlName} } from './${this.moduleName}.controller.${useTypeScript ? 'ts' : 'js'}';\n` : `import { getAll, getById, create, update, delete${this.capName} } from './${this.moduleName}.controller.${useTypeScript ? 'ts' : 'js'}';\n`; return `// ${this.moduleName}.routes.${useTypeScript ? 'ts' : 'js'} import express from 'express'; ${tsImports}${importStatement} import { validate${this.capName}Input } from './${this.moduleName}.validator.${useTypeScript ? 'ts' : 'js'}'; const router${useTypeScript ? ': Router' : ''} = express.Router(); // GET /${this.pluralName} - Get all ${this.pluralName} router.get('/', ${handlers.getAll}); // GET /${this.pluralName}/:id - Get ${this.moduleName} by ID router.get('/:id', ${handlers.getById}); // POST /${this.pluralName} - Create new ${this.moduleName} router.post('/', validate${this.capName}Input, ${handlers.create}); // PUT /${this.pluralName}/:id - Update ${this.moduleName} router.put('/:id', validate${this.capName}Input, ${handlers.update}); // DELETE /${this.pluralName}/:id - Delete ${this.moduleName} router.delete('/:id', ${handlers.delete}); export default router;`; } getDatabaseQuery(operation, database) { const modelName = `${this.capName}Model`; switch (database) { case 'mongodb': return this.getMongoQuery(operation, modelName); case 'postgresql': case 'mysql': return this.getSequelizeQuery(operation, modelName); case 'prisma': return this.getPrismaQuery(operation); default: return this.getGenericQuery(operation, modelName); } } getMongoQuery(operation, modelName) { switch (operation) { case 'findAll': return `return await ${modelName}.find(filters).lean();`; case 'findById': return `return await ${modelName}.findById(id).lean();`; case 'create': return `return await ${modelName}.create(data);`; case 'update': return `return await ${modelName}.findByIdAndUpdate(id, data, { new: true }).lean();`; case 'delete': return `return await ${modelName}.findByIdAndDelete(id);`; default: return `// MongoDB query for ${operation}`; } } getSequelizeQuery(operation, modelName) { switch (operation) { case 'findAll': return `return await ${modelName}.findAll({ where: filters });`; case 'findById': return `return await ${modelName}.findByPk(id);`; case 'create': return `return await ${modelName}.create(data);`; case 'update': return `const [updated] = await ${modelName}.update(data, { where: { id } }); return updated ? await ${modelName}.findByPk(id) : null;`; case 'delete': return `const deleted = await ${modelName}.destroy({ where: { id } }); return deleted > 0;`; default: return `// Sequelize query for ${operation}`; } } getPrismaQuery(operation) { const prismaModel = this.moduleName.charAt(0).toLowerCase() + this.moduleName.slice(1); switch (operation) { case 'findAll': return `return await prisma.${prismaModel}.findMany({ where: filters });`; case 'findById': return `return await prisma.${prismaModel}.findUnique({ where: { id } });`; case 'create': return `return await prisma.${prismaModel}.create({ data });`; case 'update': return `return await prisma.${prismaModel}.update({ where: { id }, data });`; case 'delete': return `return await prisma.${prismaModel}.delete({ where: { id } });`; default: return `// Prisma query for ${operation}`; } } getGenericQuery(operation, modelName) { switch (operation) { case 'findAll': return `// Generic implementation - replace with your database logic // Example: return await ${modelName}.findAll(filters); return [];`; case 'findById': return `// Generic implementation - replace with your database logic // Example: return await ${modelName}.findById(id); return null;`; case 'create': return `// Generic implementation - replace with your database logic // Example: return await ${modelName}.create(data); return { id: 'new-id', ...data, createdAt: new Date(), updatedAt: new Date() };`; case 'update': return `// Generic implementation - replace with your database logic // Example: return await ${modelName}.update(id, data); return { id, ...data, updatedAt: new Date() };`; case 'delete': return `// Generic implementation - replace with your database logic // Example: return await ${modelName}.delete(id); return true;`; default: return `// Generic query for ${operation}`; } } }