UNPKG

stack-performance

Version:

A comprehensive application stack analyzer that evaluates MEAN, MERN, and other Node.js-based applications across 15 performance criteria

310 lines (274 loc) 11.3 kB
const fs = require('fs-extra'); const path = require('path'); const glob = require('glob'); const semver = require('semver'); // Import individual criterion analyzers const ApplicationPerformanceAnalyzer = require('../analyzers/ApplicationPerformanceAnalyzer'); const DeveloperProductivityAnalyzer = require('../analyzers/DeveloperProductivityAnalyzer'); const ApiResponseTimeAnalyzer = require('../analyzers/ApiResponseTimeAnalyzer'); const LearningCurveAnalyzer = require('../analyzers/LearningCurveAnalyzer'); const SecurityFeaturesAnalyzer = require('../analyzers/SecurityFeaturesAnalyzer'); const ToolingEcosystemAnalyzer = require('../analyzers/ToolingEcosystemAnalyzer'); const MongoIntegrationAnalyzer = require('../analyzers/MongoIntegrationAnalyzer'); const ModularityScalabilityAnalyzer = require('../analyzers/ModularityScalabilityAnalyzer'); const PackageEcosystemAnalyzer = require('../analyzers/PackageEcosystemAnalyzer'); const StartupTimeAnalyzer = require('../analyzers/StartupTimeAnalyzer'); const MaintenanceDebuggingAnalyzer = require('../analyzers/MaintenanceDebuggingAnalyzer'); const HostingDeploymentAnalyzer = require('../analyzers/HostingDeploymentAnalyzer'); const MemoryCpuEfficiencyAnalyzer = require('../analyzers/MemoryCpuEfficiencyAnalyzer'); const ConcurrentLoadAnalyzer = require('../analyzers/ConcurrentLoadAnalyzer'); const MonitoringObservabilityAnalyzer = require('../analyzers/MonitoringObservabilityAnalyzer'); /** * Main Stack Analyzer Class * Coordinates analysis across all 15 criteria using advanced algorithms */ class StackAnalyzer { constructor(options = {}) { this.options = options; this.projectPath = null; this.stackInfo = null; this.analyzers = []; this.weights = this.defineWeights(); } /** * Define weights for different criteria based on importance * @returns {Object} Criteria weights */ defineWeights() { return { 'Application Performance': 0.12, 'Developer Productivity': 0.08, 'API Response Time': 0.10, 'Learning Curve': 0.05, 'Security Features': 0.09, 'Tooling & Ecosystem Support': 0.07, 'Integration with MongoDB': 0.06, 'Modularity & Scalability': 0.09, 'Package Ecosystem': 0.06, 'Startup Time': 0.08, 'Maintenance and Debugging': 0.07, 'Hosting and Deployment Flexibility': 0.06, 'Memory & CPU Efficiency': 0.10, 'Performance under Concurrent Load': 0.09, 'Monitoring and Observability': 0.08 }; } /** * Initialize analysis by detecting stack and preparing analyzers * @param {string} projectPath - Path to project */ async initialize(projectPath) { this.projectPath = path.resolve(projectPath); if (!await fs.pathExists(this.projectPath)) { throw new Error(`Project path does not exist: ${this.projectPath}`); } // Detect stack information this.stackInfo = await this.detectStack(); // Initialize all analyzers this.initializeAnalyzers(); } /** * Detect the technology stack of the application * @returns {Object} Stack information */ async detectStack() { const packageJsonPath = path.join(this.projectPath, 'package.json'); let packageJson = {}; let stackType = 'Unknown'; let technologies = []; let nodeVersion = null; try { if (await fs.pathExists(packageJsonPath)) { packageJson = await fs.readJson(packageJsonPath); // Detect Node.js version nodeVersion = packageJson.engines?.node || process.version; // Analyze dependencies to determine stack const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // MEAN Stack Detection if (allDependencies.express && allDependencies.mongoose && (allDependencies.angular || allDependencies['@angular/core'])) { stackType = 'MEAN'; technologies = ['MongoDB', 'Express.js', 'Angular', 'Node.js']; } // MERN Stack Detection else if (allDependencies.express && allDependencies.mongoose && (allDependencies.react || allDependencies['react-dom'])) { stackType = 'MERN'; technologies = ['MongoDB', 'Express.js', 'React', 'Node.js']; } // Other Node.js based stacks else if (allDependencies.express) { stackType = 'Express.js Application'; technologies = ['Express.js', 'Node.js']; if (allDependencies.mongoose || allDependencies.mongodb) { technologies.unshift('MongoDB'); } } else if (allDependencies.fastify) { stackType = 'Fastify Application'; technologies = ['Fastify', 'Node.js']; } else if (allDependencies.koa) { stackType = 'Koa Application'; technologies = ['Koa', 'Node.js']; } else if (allDependencies.react) { stackType = 'React Application'; technologies = ['React', 'Node.js']; } else if (allDependencies.vue) { stackType = 'Vue.js Application'; technologies = ['Vue.js', 'Node.js']; } else if (packageJson.name || Object.keys(allDependencies).length > 0) { stackType = 'Node.js Application'; technologies = ['Node.js']; } } } catch (error) { console.warn(`Warning: Could not read package.json: ${error.message}`); } return { type: stackType, technologies, nodeVersion, packageJson, projectStructure: await this.analyzeProjectStructure() }; } /** * Analyze project structure for additional insights * @returns {Object} Project structure information */ async analyzeProjectStructure() { const structure = { hasTests: false, hasDocumentation: false, hasConfigFiles: false, hasTypeScript: false, directoryCount: 0, fileCount: 0, codeFiles: 0 }; try { // Check for test directories/files const testPatterns = ['**/test/**', '**/tests/**', '**/__tests__/**', '**/*.test.js', '**/*.spec.js']; for (const pattern of testPatterns) { const files = glob.sync(pattern, { cwd: this.projectPath }); if (files.length > 0) { structure.hasTests = true; break; } } // Check for documentation const docPatterns = ['README*', 'DOCS/**', 'docs/**', '*.md']; for (const pattern of docPatterns) { const files = glob.sync(pattern, { cwd: this.projectPath }); if (files.length > 0) { structure.hasDocumentation = true; break; } } // Check for config files const configPatterns = ['.eslintrc*', '.prettierrc*', 'webpack.config.js', 'babel.config.js', 'tsconfig.json']; for (const pattern of configPatterns) { const files = glob.sync(pattern, { cwd: this.projectPath }); if (files.length > 0) { structure.hasConfigFiles = true; break; } } // Check for TypeScript const tsFiles = glob.sync('**/*.ts', { cwd: this.projectPath }); structure.hasTypeScript = tsFiles.length > 0; // Count files and directories const allFiles = glob.sync('**/*', { cwd: this.projectPath }); structure.fileCount = allFiles.length; const codeExtensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.angular']; structure.codeFiles = allFiles.filter(file => codeExtensions.some(ext => file.endsWith(ext)) ).length; } catch (error) { console.warn(`Warning: Could not analyze project structure: ${error.message}`); } return structure; } /** * Initialize all criterion analyzers */ initializeAnalyzers() { this.analyzers = [ new ApplicationPerformanceAnalyzer(this.stackInfo, this.projectPath), new DeveloperProductivityAnalyzer(this.stackInfo, this.projectPath), new ApiResponseTimeAnalyzer(this.stackInfo, this.projectPath), new LearningCurveAnalyzer(this.stackInfo, this.projectPath), new SecurityFeaturesAnalyzer(this.stackInfo, this.projectPath), new ToolingEcosystemAnalyzer(this.stackInfo, this.projectPath), new MongoIntegrationAnalyzer(this.stackInfo, this.projectPath), new ModularityScalabilityAnalyzer(this.stackInfo, this.projectPath), new PackageEcosystemAnalyzer(this.stackInfo, this.projectPath), new StartupTimeAnalyzer(this.stackInfo, this.projectPath), new MaintenanceDebuggingAnalyzer(this.stackInfo, this.projectPath), new HostingDeploymentAnalyzer(this.stackInfo, this.projectPath), new MemoryCpuEfficiencyAnalyzer(this.stackInfo, this.projectPath), new ConcurrentLoadAnalyzer(this.stackInfo, this.projectPath), new MonitoringObservabilityAnalyzer(this.stackInfo, this.projectPath) ]; } /** * Run comprehensive analysis across all criteria * @returns {Object} Complete analysis results */ async runFullAnalysis() { const results = { stackInfo: this.stackInfo, criteria: [], overall: { score: 0, remark: 'Unknown' }, timestamp: new Date().toISOString(), analysisVersion: '1.0.0' }; let totalWeightedScore = 0; // Run each analyzer for (const analyzer of this.analyzers) { try { const criterionResult = await analyzer.analyze(); // Add weight to result const weight = this.weights[criterionResult.name] || (1 / this.analyzers.length); criterionResult.weight = weight; results.criteria.push(criterionResult); totalWeightedScore += criterionResult.score * weight; } catch (error) { console.warn(`Warning: Analysis failed for ${analyzer.constructor.name}: ${error.message}`); // Add failed criterion with default score results.criteria.push({ name: analyzer.criterionName || 'Unknown Criterion', score: 50, remark: 'Average', details: `Analysis failed: ${error.message}`, weight: this.weights[analyzer.criterionName] || (1 / this.analyzers.length) }); totalWeightedScore += 50 * (this.weights[analyzer.criterionName] || (1 / this.analyzers.length)); } } // Calculate overall score results.overall.score = Math.round(totalWeightedScore); results.overall.remark = this.getPerformanceRemark(results.overall.score); return results; } /** * Convert numerical score to performance remark * @param {number} score - Score out of 100 * @returns {string} Performance remark */ getPerformanceRemark(score) { if (score >= 95) return 'Excellent'; if (score >= 85) return 'Best'; if (score >= 75) return 'Good'; if (score >= 60) return 'Average'; return 'Poor'; } } module.exports = StackAnalyzer;