backend-mcp
Version:
Generador automΓ‘tico de backends con Node.js, Express, Prisma y mΓ³dulos configurables. Servidor MCP compatible con npx para agentes IA. Soporta PostgreSQL, MySQL, MongoDB y SQLite.
497 lines (414 loc) β’ 15.7 kB
JavaScript
/**
* MCP Project Validation Script
* Validates project structure, configuration, and dependencies
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
class ProjectValidator {
constructor() {
this.projectPath = process.cwd();
this.errors = [];
this.warnings = [];
this.info = [];
this.validationResults = {
structure: false,
dependencies: false,
configuration: false,
modules: false,
database: false,
security: false
};
}
async validate() {
console.log('π MCP Project Validator');
console.log('========================\n');
try {
await this.validateProjectStructure();
await this.validateDependencies();
await this.validateConfiguration();
await this.validateModules();
await this.validateDatabase();
await this.validateSecurity();
this.generateReport();
} catch (error) {
console.error('β Validation failed:', error.message);
process.exit(1);
}
}
async validateProjectStructure() {
console.log('π Validating project structure...');
const requiredFiles = [
'package.json',
'tsconfig.json',
'.env.example',
'README.md',
'ORCHESTRATION.md'
];
const requiredDirectories = [
'src',
'src/controllers',
'src/services',
'src/middleware',
'src/routes',
'src/types',
'src/utils',
'config',
'tests',
'docs',
'scripts',
'modules'
];
// Check required files
for (const file of requiredFiles) {
const filePath = path.join(this.projectPath, file);
if (!fs.existsSync(filePath)) {
this.errors.push(`Missing required file: ${file}`);
} else {
this.info.push(`β
Found: ${file}`);
}
}
// Check required directories
for (const dir of requiredDirectories) {
const dirPath = path.join(this.projectPath, dir);
if (!fs.existsSync(dirPath)) {
this.errors.push(`Missing required directory: ${dir}`);
} else {
this.info.push(`β
Found: ${dir}/`);
}
}
// Check optional but recommended files
const recommendedFiles = [
'.gitignore',
'docker-compose.yml',
'Dockerfile',
'.github/workflows/ci.yml',
'jest.config.js',
'.eslintrc.js',
'.prettierrc'
];
for (const file of recommendedFiles) {
const filePath = path.join(this.projectPath, file);
if (!fs.existsSync(filePath)) {
this.warnings.push(`Recommended file missing: ${file}`);
}
}
this.validationResults.structure = this.errors.length === 0;
}
async validateDependencies() {
console.log('π¦ Validating dependencies...');
const packageJsonPath = path.join(this.projectPath, 'package.json');
if (!fs.existsSync(packageJsonPath)) {
this.errors.push('package.json not found');
return;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Check required dependencies
const requiredDeps = [
'express',
'cors',
'helmet',
'dotenv',
'bcrypt',
'jsonwebtoken',
'joi',
'winston'
];
const requiredDevDeps = [
'typescript',
'tsx',
'@types/node',
'@types/express',
'jest',
'ts-jest',
'@types/jest'
];
for (const dep of requiredDeps) {
if (!packageJson.dependencies || !packageJson.dependencies[dep]) {
this.errors.push(`Missing required dependency: ${dep}`);
} else {
this.info.push(`β
Dependency: ${dep}`);
}
}
for (const dep of requiredDevDeps) {
if (!packageJson.devDependencies || !packageJson.devDependencies[dep]) {
this.errors.push(`Missing required dev dependency: ${dep}`);
} else {
this.info.push(`β
Dev dependency: ${dep}`);
}
}
// Check if node_modules exists
const nodeModulesPath = path.join(this.projectPath, 'node_modules');
if (!fs.existsSync(nodeModulesPath)) {
this.warnings.push('node_modules not found. Run "npm install"');
}
// Check package scripts
const requiredScripts = [
'dev',
'build',
'start',
'test',
'lint'
];
for (const script of requiredScripts) {
if (!packageJson.scripts || !packageJson.scripts[script]) {
this.warnings.push(`Missing recommended script: ${script}`);
}
}
this.validationResults.dependencies = this.errors.filter(e => e.includes('dependency')).length === 0;
}
async validateConfiguration() {
console.log('βοΈ Validating configuration...');
// Check environment files
const envExamplePath = path.join(this.projectPath, '.env.example');
const envPath = path.join(this.projectPath, '.env');
if (fs.existsSync(envExamplePath)) {
const envExample = fs.readFileSync(envExamplePath, 'utf8');
const requiredEnvVars = [
'NODE_ENV',
'PORT',
'DATABASE_URL',
'JWT_SECRET'
];
for (const envVar of requiredEnvVars) {
if (!envExample.includes(envVar)) {
this.warnings.push(`Missing environment variable in .env.example: ${envVar}`);
}
}
}
if (!fs.existsSync(envPath)) {
this.warnings.push('.env file not found. Copy from .env.example and configure');
} else {
// Check if .env has required variables
const env = fs.readFileSync(envPath, 'utf8');
if (env.includes('your-super-secret-jwt-key-change-this-in-production')) {
this.warnings.push('JWT_SECRET still has default value. Please change it!');
}
if (env.includes('username:password@localhost')) {
this.warnings.push('DATABASE_URL still has default values. Please configure it!');
}
}
// Check TypeScript configuration
const tsconfigPath = path.join(this.projectPath, 'tsconfig.json');
if (fs.existsSync(tsconfigPath)) {
try {
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
if (!tsconfig.compilerOptions) {
this.errors.push('tsconfig.json missing compilerOptions');
} else {
const requiredOptions = ['target', 'module', 'outDir', 'rootDir', 'strict'];
for (const option of requiredOptions) {
if (!tsconfig.compilerOptions[option]) {
this.warnings.push(`tsconfig.json missing recommended option: ${option}`);
}
}
}
} catch (error) {
this.errors.push('Invalid tsconfig.json format');
}
}
this.validationResults.configuration = this.errors.filter(e => e.includes('tsconfig')).length === 0;
}
async validateModules() {
console.log('π§© Validating modules...');
const modulesPath = path.join(this.projectPath, 'modules');
if (!fs.existsSync(modulesPath)) {
this.errors.push('modules directory not found');
return;
}
const modules = fs.readdirSync(modulesPath, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
if (modules.length === 0) {
this.warnings.push('No modules found in modules directory');
return;
}
// Check each module
for (const moduleName of modules) {
const modulePath = path.join(modulesPath, moduleName);
// Check required module files
const requiredModuleFiles = ['init.js', 'manifest.yaml'];
const recommendedModuleFiles = ['README.md'];
const recommendedModuleDirs = ['templates', 'examples', 'schemas'];
for (const file of requiredModuleFiles) {
const filePath = path.join(modulePath, file);
if (!fs.existsSync(filePath)) {
this.errors.push(`Module ${moduleName} missing required file: ${file}`);
}
}
for (const file of recommendedModuleFiles) {
const filePath = path.join(modulePath, file);
if (!fs.existsSync(filePath)) {
this.warnings.push(`Module ${moduleName} missing recommended file: ${file}`);
}
}
for (const dir of recommendedModuleDirs) {
const dirPath = path.join(modulePath, dir);
if (!fs.existsSync(dirPath)) {
this.warnings.push(`Module ${moduleName} missing recommended directory: ${dir}`);
}
}
// Validate manifest.yaml
const manifestPath = path.join(modulePath, 'manifest.yaml');
if (fs.existsSync(manifestPath)) {
try {
const manifestContent = fs.readFileSync(manifestPath, 'utf8');
// Basic YAML validation (check for required fields)
const requiredFields = ['name', 'version', 'description', 'triggers'];
for (const field of requiredFields) {
if (!manifestContent.includes(`${field}:`)) {
this.warnings.push(`Module ${moduleName} manifest missing field: ${field}`);
}
}
} catch (error) {
this.errors.push(`Module ${moduleName} has invalid manifest.yaml`);
}
}
this.info.push(`β
Module validated: ${moduleName}`);
}
this.validationResults.modules = this.errors.filter(e => e.includes('Module')).length === 0;
}
async validateDatabase() {
console.log('ποΈ Validating database configuration...');
// Check for Prisma
const prismaPath = path.join(this.projectPath, 'prisma');
const schemaPath = path.join(prismaPath, 'schema.prisma');
if (fs.existsSync(schemaPath)) {
this.info.push('β
Prisma schema found');
const schema = fs.readFileSync(schemaPath, 'utf8');
// Check for required Prisma components
if (!schema.includes('generator client')) {
this.errors.push('Prisma schema missing generator client');
}
if (!schema.includes('datasource db')) {
this.errors.push('Prisma schema missing datasource');
}
// Check for migrations
const migrationsPath = path.join(prismaPath, 'migrations');
if (!fs.existsSync(migrationsPath)) {
this.warnings.push('No Prisma migrations found. Run "npx prisma migrate dev"');
}
// Check for seeds
const seedsPath = path.join(prismaPath, 'seeds');
if (!fs.existsSync(seedsPath)) {
this.warnings.push('No database seeds found');
}
} else {
this.warnings.push('Prisma schema not found. Database module may not be configured');
}
// Check database module structure
const dbModulePath = path.join(this.projectPath, 'modules', 'database');
if (fs.existsSync(dbModulePath)) {
const postgresPath = path.join(dbModulePath, 'postgres');
const mysqlPath = path.join(dbModulePath, 'mysql');
if (!fs.existsSync(postgresPath) && !fs.existsSync(mysqlPath)) {
this.warnings.push('Database module missing postgres/mysql subdirectories');
}
}
this.validationResults.database = this.errors.filter(e => e.includes('Prisma') || e.includes('database')).length === 0;
}
async validateSecurity() {
console.log('π Validating security configuration...');
// Check for security middleware
const middlewarePath = path.join(this.projectPath, 'src', 'middleware');
if (fs.existsSync(middlewarePath)) {
const middlewareFiles = fs.readdirSync(middlewarePath);
const securityMiddleware = ['auth.ts', 'errorHandler.ts', 'validation.ts'];
for (const middleware of securityMiddleware) {
if (!middlewareFiles.includes(middleware)) {
this.warnings.push(`Security middleware missing: ${middleware}`);
}
}
}
// Check for .env in .gitignore
const gitignorePath = path.join(this.projectPath, '.gitignore');
if (fs.existsSync(gitignorePath)) {
const gitignore = fs.readFileSync(gitignorePath, 'utf8');
if (!gitignore.includes('.env')) {
this.errors.push('.env not in .gitignore - security risk!');
}
if (!gitignore.includes('node_modules')) {
this.warnings.push('node_modules not in .gitignore');
}
} else {
this.warnings.push('.gitignore not found');
}
// Check for sensitive data in package.json
const packageJsonPath = path.join(this.projectPath, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = fs.readFileSync(packageJsonPath, 'utf8');
// Check for common security issues
const securityIssues = [
{ pattern: /password/i, message: 'Possible password in package.json' },
{ pattern: /secret/i, message: 'Possible secret in package.json' },
{ pattern: /token/i, message: 'Possible token in package.json' }
];
for (const issue of securityIssues) {
if (issue.pattern.test(packageJson)) {
this.warnings.push(issue.message);
}
}
}
this.validationResults.security = this.errors.filter(e => e.includes('security') || e.includes('.env')).length === 0;
}
generateReport() {
console.log('\nπ Validation Report');
console.log('===================\n');
// Overall status
const overallScore = Object.values(this.validationResults).filter(Boolean).length;
const totalChecks = Object.keys(this.validationResults).length;
const percentage = Math.round((overallScore / totalChecks) * 100);
console.log(`Overall Score: ${overallScore}/${totalChecks} (${percentage}%)\n`);
// Individual results
for (const [category, passed] of Object.entries(this.validationResults)) {
const status = passed ? 'β
' : 'β';
console.log(`${status} ${category.charAt(0).toUpperCase() + category.slice(1)}: ${passed ? 'PASSED' : 'FAILED'}`);
}
console.log('\n');
// Errors
if (this.errors.length > 0) {
console.log('β Errors:');
this.errors.forEach(error => console.log(` β’ ${error}`));
console.log('');
}
// Warnings
if (this.warnings.length > 0) {
console.log('β οΈ Warnings:');
this.warnings.forEach(warning => console.log(` β’ ${warning}`));
console.log('');
}
// Recommendations
console.log('π‘ Recommendations:');
if (this.errors.length > 0) {
console.log(' β’ Fix all errors before proceeding to production');
}
if (this.warnings.length > 0) {
console.log(' β’ Address warnings to improve project quality');
}
if (percentage < 70) {
console.log(' β’ Project needs significant improvements');
} else if (percentage < 90) {
console.log(' β’ Project is in good shape, minor improvements needed');
} else {
console.log(' β’ Project is well configured!');
}
console.log(' β’ Run "npm test" to validate functionality');
console.log(' β’ Run "npm run lint" to check code quality');
console.log(' β’ Review security settings before deployment');
// Exit code
if (this.errors.length > 0) {
console.log('\nβ Validation failed with errors');
process.exit(1);
} else {
console.log('\nβ
Validation completed successfully');
process.exit(0);
}
}
}
// Run the validator
if (require.main === module) {
const validator = new ProjectValidator();
validator.validate().catch(console.error);
}
module.exports = ProjectValidator;