UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

262 lines (261 loc) 9.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startupTester = exports.StartupRegressionTester = void 0; /** * Startup time regression testing framework */ const child_process_1 = require("child_process"); const perf_hooks_1 = require("perf_hooks"); const fs_1 = require("fs"); const path_1 = require("path"); const os_1 = require("os"); class StartupRegressionTester { constructor() { this.baselines = new Map(); // Performance thresholds this.REGRESSION_THRESHOLD = 10; // 10% slower is a regression this.WARNING_THRESHOLD = 5; // 5% slower is a warning const cacheDir = (0, path_1.join)((0, os_1.homedir)(), '.re-shell', 'benchmarks'); if (!(0, fs_1.existsSync)(cacheDir)) { (0, fs_1.mkdirSync)(cacheDir, { recursive: true }); } this.benchmarksPath = (0, path_1.join)(cacheDir, 'startup-benchmarks.json'); this.loadBaselines(); } static getInstance() { if (!StartupRegressionTester.instance) { StartupRegressionTester.instance = new StartupRegressionTester(); } return StartupRegressionTester.instance; } /** * Measure startup time for a command */ async measureStartupTime(command, args = [], iterations = 5) { const measurements = []; let cpuUsage; let memoryUsage; for (let i = 0; i < iterations; i++) { const startTime = perf_hooks_1.performance.now(); const startCpu = process.cpuUsage(); const startMemory = process.memoryUsage(); await this.runCommand(command, args); const endTime = perf_hooks_1.performance.now(); const endCpu = process.cpuUsage(startCpu); const endMemory = process.memoryUsage(); measurements.push(endTime - startTime); // Use measurements from the last iteration if (i === iterations - 1) { cpuUsage = endCpu; memoryUsage = endMemory; } // Small delay between iterations await new Promise(resolve => setTimeout(resolve, 100)); } // Remove outliers and calculate median measurements.sort((a, b) => a - b); const median = measurements[Math.floor(measurements.length / 2)]; return { command: `${command} ${args.join(' ')}`.trim(), timestamp: Date.now(), duration: median, version: this.getVersion(), nodeVersion: process.version, platform: process.platform, cpuUsage, memoryUsage }; } /** * Run a command and measure its execution time */ async runCommand(command, args) { return new Promise((resolve, reject) => { const child = (0, child_process_1.spawn)(command, args, { stdio: 'ignore', cwd: process.cwd() }); const timeout = setTimeout(() => { child.kill(); reject(new Error('Command timeout')); }, 10000); // 10 second timeout child.on('close', (code) => { clearTimeout(timeout); if (code === 0) { resolve(); } else { reject(new Error(`Command failed with code ${code}`)); } }); child.on('error', (error) => { clearTimeout(timeout); reject(error); }); }); } /** * Set baseline for a command */ async setBaseline(command, args = []) { const benchmark = await this.measureStartupTime(command, args); const key = benchmark.command; this.baselines.set(key, benchmark); this.saveBaselines(); console.log(`Baseline set for "${key}": ${benchmark.duration.toFixed(2)}ms`); } /** * Test for regressions against baseline */ async testRegression(command, args = []) { const current = await this.measureStartupTime(command, args); const key = current.command; const baseline = this.baselines.get(key); if (!baseline) { throw new Error(`No baseline found for command: ${key}`); } const regression = ((current.duration - baseline.duration) / baseline.duration) * 100; let status; if (regression >= this.REGRESSION_THRESHOLD) { status = 'fail'; } else if (regression >= this.WARNING_THRESHOLD) { status = 'warning'; } else { status = 'pass'; } return { baseline, current, regression, status, threshold: this.REGRESSION_THRESHOLD }; } /** * Run comprehensive regression test suite */ async runTestSuite() { const testCommands = [ ['node', ['dist/index.js', '--version']], ['node', ['dist/index.js', '--help']], ['node', ['dist/index.js', 'init', '--help']], ['node', ['dist/index.js', 'plugin', 'list', '--help']] ]; const reports = []; for (const [command, args] of testCommands) { try { const report = await this.testRegression(command, args); reports.push(report); } catch (error) { console.error(`Failed to test ${command} ${Array.isArray(args) ? args.join(' ') : args || ''}: ${error}`); } } return reports; } /** * Generate performance report */ generateReport(reports) { const lines = [ '# Re-Shell CLI Startup Performance Report', `Generated: ${new Date().toISOString()}`, '', '## Summary', '' ]; const passed = reports.filter(r => r.status === 'pass').length; const warnings = reports.filter(r => r.status === 'warning').length; const failed = reports.filter(r => r.status === 'fail').length; lines.push(`- ✅ Passed: ${passed}`); lines.push(`- ⚠️ Warnings: ${warnings}`); lines.push(`- ❌ Failed: ${failed}`); lines.push(''); lines.push('## Detailed Results'); lines.push(''); for (const report of reports) { const icon = report.status === 'pass' ? '✅' : report.status === 'warning' ? '⚠️' : '❌'; lines.push(`### ${icon} ${report.current.command}`); lines.push(''); lines.push(`| Metric | Baseline | Current | Change |`); lines.push(`|--------|----------|---------|--------|`); lines.push(`| Duration | ${report.baseline.duration.toFixed(2)}ms | ${report.current.duration.toFixed(2)}ms | ${report.regression > 0 ? '+' : ''}${report.regression.toFixed(1)}% |`); if (report.baseline.memoryUsage && report.current.memoryUsage) { const memoryChange = ((report.current.memoryUsage.heapUsed - report.baseline.memoryUsage.heapUsed) / report.baseline.memoryUsage.heapUsed) * 100; lines.push(`| Memory | ${(report.baseline.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB | ${(report.current.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB | ${memoryChange > 0 ? '+' : ''}${memoryChange.toFixed(1)}% |`); } lines.push(''); } return lines.join('\n'); } /** * Continuous monitoring setup */ setupContinuousMonitoring() { // Run tests periodically during development if (process.env.NODE_ENV === 'development') { setInterval(async () => { try { const reports = await this.runTestSuite(); const failed = reports.filter(r => r.status === 'fail'); if (failed.length > 0) { console.warn(`\n⚠️ Performance regression detected in ${failed.length} command(s):`); failed.forEach(report => { console.warn(` - ${report.current.command}: +${report.regression.toFixed(1)}% slower`); }); } } catch (error) { // Ignore monitoring errors } }, 60000); // Check every minute } } /** * Load baselines from disk */ loadBaselines() { if ((0, fs_1.existsSync)(this.benchmarksPath)) { try { const data = JSON.parse((0, fs_1.readFileSync)(this.benchmarksPath, 'utf-8')); Object.entries(data).forEach(([key, benchmark]) => { this.baselines.set(key, benchmark); }); } catch { // Ignore load errors } } } /** * Save baselines to disk */ saveBaselines() { try { const data = {}; this.baselines.forEach((benchmark, key) => { data[key] = benchmark; }); (0, fs_1.writeFileSync)(this.benchmarksPath, JSON.stringify(data, null, 2)); } catch { // Ignore save errors } } /** * Get current CLI version */ getVersion() { try { return require('../../package.json').version; } catch { return 'unknown'; } } } exports.StartupRegressionTester = StartupRegressionTester; exports.startupTester = StartupRegressionTester.getInstance();