UNPKG

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
#!/usr/bin/env node /** * 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;