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