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
JavaScript
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}`;
}
}
}