@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
JavaScript
;
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();