ng-upgrade-orchestrator
Version:
Enterprise-grade Angular Multi-Version Upgrade Orchestrator with automatic npm installation, comprehensive dependency management, and seamless integration of all 9 official Angular migrations. Safely migrate Angular applications across multiple major vers
832 lines (823 loc) • 31.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpgradeReportGenerator = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const chalk_1 = __importDefault(require("chalk"));
class UpgradeReportGenerator {
projectPath;
projectName;
fromVersion;
toVersion;
strategy;
report;
fileChanges = new Map();
dependencyChanges = new Map();
breakingChanges = [];
migrations = [];
validationResults = [];
warnings = [];
errors = [];
successStories = [];
constructor(projectPath, projectName, fromVersion, toVersion, strategy) {
this.projectPath = projectPath;
this.projectName = projectName;
this.fromVersion = fromVersion;
this.toVersion = toVersion;
this.strategy = strategy;
this.report = this.initializeReport();
}
initializeReport() {
return {
projectName: this.projectName,
startTime: new Date(),
endTime: new Date(),
duration: 0,
fromVersion: this.fromVersion,
toVersion: this.toVersion,
strategy: this.strategy,
success: false,
sections: [],
summary: {
totalFiles: 0,
filesModified: 0,
filesCreated: 0,
filesDeleted: 0,
dependenciesUpdated: 0,
breakingChangesResolved: 0,
migrationsRun: 0,
testsRun: 0,
testsPassed: 0,
testsFailed: 0,
warningsCount: 0,
errorsCount: 0,
backupsCreated: 0,
checkpointsCreated: 0
},
metadata: this.collectMetadata()
};
}
collectMetadata() {
return {
nodeVersion: process.version,
npmVersion: this.getNpmVersion(),
angularCliVersion: this.getAngularCliVersion(),
typeScriptVersion: this.getTypeScriptVersion(),
operatingSystem: `${process.platform} ${process.arch}`,
projectPath: this.projectPath,
reportGeneratedAt: new Date(),
reportVersion: '1.0.0'
};
}
getNpmVersion() {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { execSync } = require('child_process');
return execSync('npm --version').toString().trim();
}
catch {
return 'unknown';
}
}
getAngularCliVersion() {
try {
const packageJsonPath = path.join(this.projectPath, 'package.json');
const packageJson = fs.readJsonSync(packageJsonPath);
return packageJson.devDependencies?.['@angular/cli'] ||
packageJson.dependencies?.['@angular/cli'] ||
'unknown';
}
catch {
return 'unknown';
}
}
getTypeScriptVersion() {
try {
const packageJsonPath = path.join(this.projectPath, 'package.json');
const packageJson = fs.readJsonSync(packageJsonPath);
return packageJson.devDependencies?.['typescript'] ||
packageJson.dependencies?.['typescript'] ||
'unknown';
}
catch {
return 'unknown';
}
}
/**
* Track file changes during upgrade
*/
trackFileChange(change) {
this.fileChanges.set(change.path, change);
// Update summary
switch (change.type) {
case 'modified':
this.report.summary.filesModified++;
break;
case 'created':
this.report.summary.filesCreated++;
break;
case 'deleted':
this.report.summary.filesDeleted++;
break;
}
}
/**
* Track dependency updates
*/
trackDependencyChange(change) {
this.dependencyChanges.set(change.name, change);
this.report.summary.dependenciesUpdated++;
}
/**
* Track breaking changes
*/
trackBreakingChange(change) {
this.breakingChanges.push(change);
this.report.summary.breakingChangesResolved++;
}
/**
* Track migrations
*/
trackMigration(migration) {
this.migrations.push(migration);
this.report.summary.migrationsRun++;
}
/**
* Track validation results
*/
trackValidation(result) {
this.validationResults.push(result);
if (result.testResults) {
this.report.summary.testsRun += result.testResults.total || 0;
this.report.summary.testsPassed += result.testResults.passed || 0;
this.report.summary.testsFailed += result.testResults.failed || 0;
}
}
/**
* Add warning
*/
addWarning(warning) {
this.warnings.push(warning);
this.report.summary.warningsCount++;
}
/**
* Add error
*/
addError(error) {
this.errors.push(error);
this.report.summary.errorsCount++;
}
/**
* Add success story
*/
addSuccessStory(story) {
this.successStories.push(story);
}
/**
* Track checkpoint creation
*/
trackCheckpoint(_checkpointId) {
this.report.summary.checkpointsCreated++;
}
/**
* Track backup creation
*/
trackBackup(_backupPath) {
this.report.summary.backupsCreated++;
}
/**
* Finalize and generate the report
*/
async generateReport(success) {
this.report.endTime = new Date();
this.report.duration = this.report.endTime.getTime() - this.report.startTime.getTime();
this.report.success = success;
this.report.summary.totalFiles = this.fileChanges.size;
// Build report sections
this.buildReportSections();
// Generate reports in multiple formats
const reportDir = path.join(this.projectPath, '.ng-upgrade', 'reports');
await fs.ensureDir(reportDir);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const baseFileName = `upgrade-report-${this.fromVersion}-to-${this.toVersion}-${timestamp}`;
// Generate HTML report
const htmlPath = path.join(reportDir, `${baseFileName}.html`);
await this.generateHtmlReport(htmlPath);
// Generate Markdown report
const mdPath = path.join(reportDir, `${baseFileName}.md`);
await this.generateMarkdownReport(mdPath);
// Generate JSON report
const jsonPath = path.join(reportDir, `${baseFileName}.json`);
await this.generateJsonReport(jsonPath);
// Generate summary in console
this.printConsoleSummary();
return htmlPath;
}
buildReportSections() {
// Executive Summary
this.report.sections.push(this.buildExecutiveSummary());
// Upgrade Path
this.report.sections.push(this.buildUpgradePathSection());
// File Changes
if (this.fileChanges.size > 0) {
this.report.sections.push(this.buildFileChangesSection());
}
// Dependency Updates
if (this.dependencyChanges.size > 0) {
this.report.sections.push(this.buildDependencySection());
}
// Breaking Changes
if (this.breakingChanges.length > 0) {
this.report.sections.push(this.buildBreakingChangesSection());
}
// Migrations
if (this.migrations.length > 0) {
this.report.sections.push(this.buildMigrationsSection());
}
// Validation Results
if (this.validationResults.length > 0) {
this.report.sections.push(this.buildValidationSection());
}
// Warnings and Errors
if (this.warnings.length > 0 || this.errors.length > 0) {
this.report.sections.push(this.buildIssuesSection());
}
// Success Stories
if (this.successStories.length > 0) {
this.report.sections.push(this.buildSuccessStoriesSection());
}
// Recommendations
this.report.sections.push(this.buildRecommendationsSection());
}
buildExecutiveSummary() {
const duration = this.formatDuration(this.report.duration);
const status = this.report.success ? 'Successfully Completed' : 'Completed with Issues';
return {
title: 'Executive Summary',
level: this.report.success ? 'success' : 'warning',
content: [
`Project: ${this.report.projectName}`,
`Status: ${status}`,
`Upgrade Path: Angular ${this.fromVersion} → Angular ${this.toVersion}`,
`Strategy: ${this.strategy}`,
`Duration: ${duration}`,
`Files Modified: ${this.report.summary.filesModified}`,
`Dependencies Updated: ${this.report.summary.dependenciesUpdated}`,
`Breaking Changes Resolved: ${this.report.summary.breakingChangesResolved}`,
`Migrations Applied: ${this.report.summary.migrationsRun}`
]
};
}
buildUpgradePathSection() {
const steps = this.calculateUpgradeSteps();
return {
title: 'Upgrade Path',
level: 'info',
content: [
`The upgrade from Angular ${this.fromVersion} to Angular ${this.toVersion} was completed through the following steps:`,
...steps.map((step, index) => `${index + 1}. Angular ${step.from} → Angular ${step.to}`)
]
};
}
buildFileChangesSection() {
const subsections = [];
// Group changes by type
const modified = Array.from(this.fileChanges.values()).filter(f => f.type === 'modified');
const created = Array.from(this.fileChanges.values()).filter(f => f.type === 'created');
const deleted = Array.from(this.fileChanges.values()).filter(f => f.type === 'deleted');
if (modified.length > 0) {
subsections.push({
title: `Modified Files (${modified.length})`,
level: 'info',
content: modified.map(f => `• ${f.path}: ${f.changes.join(', ')}`)
});
}
if (created.length > 0) {
subsections.push({
title: `Created Files (${created.length})`,
level: 'success',
content: created.map(f => `• ${f.path}`)
});
}
if (deleted.length > 0) {
subsections.push({
title: `Deleted Files (${deleted.length})`,
level: 'warning',
content: deleted.map(f => `• ${f.path}`)
});
}
return {
title: 'File Changes',
level: 'info',
content: `Total files affected: ${this.fileChanges.size}`,
subsections
};
}
buildDependencySection() {
const deps = Array.from(this.dependencyChanges.values());
const breaking = deps.filter(d => d.breaking);
const subsections = [];
// Angular packages
const angularDeps = deps.filter(d => d.name.startsWith('@angular/'));
if (angularDeps.length > 0) {
subsections.push({
title: 'Angular Packages',
level: 'info',
content: angularDeps.map(d => `• ${d.name}: ${d.previousVersion} → ${d.newVersion}${d.breaking ? ' ⚠️ Breaking' : ''}`)
});
}
// Third-party packages
const thirdPartyDeps = deps.filter(d => !d.name.startsWith('@angular/'));
if (thirdPartyDeps.length > 0) {
subsections.push({
title: 'Third-Party Packages',
level: 'info',
content: thirdPartyDeps.map(d => `• ${d.name}: ${d.previousVersion} → ${d.newVersion}${d.breaking ? ' ⚠️ Breaking' : ''}`)
});
}
return {
title: 'Dependency Updates',
level: breaking.length > 0 ? 'warning' : 'info',
content: [
`Total dependencies updated: ${deps.length}`,
`Breaking changes: ${breaking.length}`
],
subsections
};
}
buildBreakingChangesSection() {
return {
title: 'Breaking Changes Resolved',
level: 'warning',
content: this.breakingChanges.map(change => `• ${change.description} (${change.type})`)
};
}
buildMigrationsSection() {
return {
title: 'Migrations Applied',
level: 'success',
content: this.migrations.map(migration => `• ${migration.instructions || 'Migration applied'} (${migration.type})`)
};
}
buildValidationSection() {
const totalTests = this.report.summary.testsRun;
const passed = this.report.summary.testsPassed;
const failed = this.report.summary.testsFailed;
const successRate = totalTests > 0 ? ((passed / totalTests) * 100).toFixed(1) : '0';
return {
title: 'Validation Results',
level: failed > 0 ? 'warning' : 'success',
content: [
`Tests Run: ${totalTests}`,
`Tests Passed: ${passed}`,
`Tests Failed: ${failed}`,
`Success Rate: ${successRate}%`
]
};
}
buildIssuesSection() {
const subsections = [];
if (this.errors.length > 0) {
subsections.push({
title: `Errors (${this.errors.length})`,
level: 'error',
content: this.errors.map(e => `• ${e}`)
});
}
if (this.warnings.length > 0) {
subsections.push({
title: `Warnings (${this.warnings.length})`,
level: 'warning',
content: this.warnings.map(w => `• ${w}`)
});
}
return {
title: 'Issues Encountered',
level: this.errors.length > 0 ? 'error' : 'warning',
content: `Total issues: ${this.errors.length + this.warnings.length}`,
subsections
};
}
buildSuccessStoriesSection() {
return {
title: 'Success Highlights',
level: 'success',
content: this.successStories.map(s => `✅ ${s}`)
};
}
buildRecommendationsSection() {
const recommendations = [];
// Build recommendations based on the upgrade results
if (this.report.summary.testsFailed > 0) {
recommendations.push('• Fix failing tests before deploying to production');
}
if (this.errors.length > 0) {
recommendations.push('• Review and resolve all errors listed in the report');
}
if (this.warnings.length > 5) {
recommendations.push('• Address warnings to improve code quality');
}
if (this.report.summary.breakingChangesResolved > 10) {
recommendations.push('• Thoroughly test all features affected by breaking changes');
}
recommendations.push('• Run comprehensive regression testing');
recommendations.push('• Update documentation to reflect Angular version changes');
recommendations.push('• Train team on new Angular features and patterns');
recommendations.push('• Monitor application performance after deployment');
return {
title: 'Next Steps & Recommendations',
level: 'info',
content: recommendations
};
}
async generateHtmlReport(filePath) {
const html = this.generateHtmlContent();
await fs.writeFile(filePath, html);
console.log(chalk_1.default.green(`✓ HTML report generated: ${filePath}`));
}
generateHtmlContent() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Angular Upgrade Report - ${this.fromVersion} to ${this.toVersion}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #334155;
background: #ffffff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow: hidden;
border: 1px solid #e2e8f0;
}
header {
background: white;
color: #1e293b;
padding: 40px;
text-align: center;
border-bottom: 1px solid #e2e8f0;
}
h1 {
font-size: 2.5em;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
position: relative;
z-index: 1;
}
.angular-logo {
width: 50px;
height: 50px;
}
.subtitle {
font-size: 1.2em;
opacity: 0.7;
color: #64748b;
}
.status {
display: inline-block;
padding: 8px 20px;
border-radius: 6px;
margin-top: 15px;
font-weight: 500;
font-size: 0.95em;
letter-spacing: 0.25px;
}
.status.success {
background: #f0f9ff;
color: #0369a1;
border: 1px solid #0ea5e9;
}
.status.warning {
background: #fffbeb;
color: #d97706;
border: 1px solid #f59e0b;
}
.status.error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #ef4444;
}
.content { padding: 40px; }
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 30px 0;
}
.summary-card {
background: white;
padding: 25px;
border-radius: 6px;
text-align: center;
transition: all 0.2s ease;
border: 1px solid #e2e8f0;
}
.summary-card:hover {
border-color: #cbd5e1;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.summary-number {
font-size: 2.2em;
font-weight: 600;
color: #1e293b;
}
.summary-label {
color: #64748b;
margin-top: 8px;
font-size: 0.95em;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
section {
margin: 30px 0;
padding: 30px;
background: #ffffff;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
h2 {
color: #1e293b;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e2e8f0;
font-size: 1.4em;
font-weight: 600;
}
h3 {
color: #475569;
margin: 25px 0 12px 0;
font-size: 1.1em;
font-weight: 600;
}
ul {
list-style: none;
padding-left: 0;
}
li {
padding: 10px 0;
padding-left: 30px;
position: relative;
color: #4b5563;
}
li:before {
content: "→";
position: absolute;
left: 0;
color: #3b82f6;
font-weight: bold;
}
.level-success { border-left: 4px solid #4CAF50; }
.level-warning { border-left: 4px solid #FF9800; }
.level-error { border-left: 4px solid #f44336; }
.level-info { border-left: 4px solid #2196F3; }
.footer {
background: #f5f5f5;
padding: 20px;
text-align: center;
color: #666;
}
.timestamp { font-size: 0.9em; color: #999; }
@media (max-width: 768px) {
.summary-grid { grid-template-columns: 1fr; }
h1 { font-size: 1.8em; }
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>
<svg class="angular-logo" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
<polygon fill="#DD0031" points="125,30 125,30 125,30 31.9,63.2 46.1,186.3 125,230 125,230 125,230 203.9,186.3 218.1,63.2"/>
<polygon fill="#C3002F" points="125,30 125,52.2 125,52.1 125,153.4 125,153.4 125,230 125,230 203.9,186.3 218.1,63.2 125,30"/>
<path fill="#FFFFFF" d="M125,52.1L66.8,182.6h0h21.7h0l11.7-29.2h49.4l11.7,29.2h0h21.7h0L125,52.1L125,52.1L125,52.1L125,52.1L125,52.1z M142,135.4H108l17-40.9L142,135.4z"/>
</svg>
Angular Upgrade Report
</h1>
<div class="subtitle">${this.fromVersion} → ${this.toVersion}</div>
<div class="status ${this.report.success ? 'success' : 'warning'}">
${this.report.success ? 'SUCCESS' : 'COMPLETED WITH ISSUES'}
</div>
</header>
<div class="content">
<div class="summary-grid">
<div class="summary-card">
<div class="summary-number">${this.report.summary.filesModified}</div>
<div class="summary-label">Files Modified</div>
</div>
<div class="summary-card">
<div class="summary-number">${this.report.summary.dependenciesUpdated}</div>
<div class="summary-label">Dependencies Updated</div>
</div>
<div class="summary-card">
<div class="summary-number">${this.report.summary.breakingChangesResolved}</div>
<div class="summary-label">Breaking Changes</div>
</div>
<div class="summary-card">
<div class="summary-number">${this.report.summary.migrationsRun}</div>
<div class="summary-label">Migrations Run</div>
</div>
<div class="summary-card">
<div class="summary-number">${this.formatDuration(this.report.duration)}</div>
<div class="summary-label">Duration</div>
</div>
<div class="summary-card">
<div class="summary-number">${this.report.summary.checkpointsCreated}</div>
<div class="summary-label">Checkpoints</div>
</div>
</div>
${this.report.sections.map(section => this.generateHtmlSection(section)).join('')}
</div>
<div class="footer">
<div class="timestamp">Report generated on ${this.report.metadata.reportGeneratedAt.toLocaleString()}</div>
<div>Angular Multi-Version Upgrade Orchestrator v${this.report.metadata.reportVersion}</div>
</div>
</div>
</body>
</html>`;
}
generateHtmlSection(section) {
const levelClass = `level-${section.level}`;
const content = Array.isArray(section.content)
? `<ul>${section.content.map(item => `<li>${this.escapeHtml(item)}</li>`).join('')}</ul>`
: `<p>${this.escapeHtml(section.content)}</p>`;
const subsections = section.subsections
? section.subsections.map(sub => `
<div style="margin-left: 20px;">
<h3>${this.escapeHtml(sub.title)}</h3>
${Array.isArray(sub.content)
? `<ul>${sub.content.map(item => `<li>${this.escapeHtml(item)}</li>`).join('')}</ul>`
: `<p>${this.escapeHtml(sub.content)}</p>`}
</div>
`).join('')
: '';
return `
<section class="${levelClass}">
<h2>${this.escapeHtml(section.title)}</h2>
${content}
${subsections}
</section>
`;
}
async generateMarkdownReport(filePath) {
const markdown = this.generateMarkdownContent();
await fs.writeFile(filePath, markdown);
console.log(chalk_1.default.green(`✓ Markdown report generated: ${filePath}`));
}
generateMarkdownContent() {
const sections = this.report.sections.map(section => this.generateMarkdownSection(section)).join('\n\n');
return `# Angular Upgrade Report
## ${this.fromVersion} → ${this.toVersion}
**Status:** ${this.report.success ? '✅ Success' : '⚠️ Completed with Issues'}
**Date:** ${this.report.metadata.reportGeneratedAt.toLocaleString()}
**Duration:** ${this.formatDuration(this.report.duration)}
## Summary
| Metric | Value |
|--------|-------|
| Files Modified | ${this.report.summary.filesModified} |
| Dependencies Updated | ${this.report.summary.dependenciesUpdated} |
| Breaking Changes Resolved | ${this.report.summary.breakingChangesResolved} |
| Migrations Run | ${this.report.summary.migrationsRun} |
| Tests Run | ${this.report.summary.testsRun} |
| Tests Passed | ${this.report.summary.testsPassed} |
| Tests Failed | ${this.report.summary.testsFailed} |
| Warnings | ${this.report.summary.warningsCount} |
| Errors | ${this.report.summary.errorsCount} |
${sections}
---
*Generated by Angular Multi-Version Upgrade Orchestrator v${this.report.metadata.reportVersion}*
`;
}
generateMarkdownSection(section, level = 2) {
const heading = '#'.repeat(level) + ' ' + section.title;
const content = Array.isArray(section.content)
? section.content.map(item => `- ${item}`).join('\n')
: section.content;
const subsections = section.subsections
? '\n\n' + section.subsections.map(sub => this.generateMarkdownSection(sub, level + 1)).join('\n\n')
: '';
return `${heading}\n\n${content}${subsections}`;
}
async generateJsonReport(filePath) {
await fs.writeJson(filePath, this.report, { spaces: 2 });
console.log(chalk_1.default.green(`✓ JSON report generated: ${filePath}`));
}
printConsoleSummary() {
console.log('\n' + chalk_1.default.bold.cyan('═'.repeat(60)));
console.log(chalk_1.default.bold.cyan(' UPGRADE SUMMARY'));
console.log(chalk_1.default.bold.cyan('═'.repeat(60)));
const status = this.report.success
? chalk_1.default.green('✓ Successfully Completed')
: chalk_1.default.yellow('⚠ Completed with Issues');
console.log(`\n${chalk_1.default.bold('Status:')} ${status}`);
console.log(`${chalk_1.default.bold('Upgrade:')} Angular ${this.fromVersion} → Angular ${this.toVersion}`);
console.log(`${chalk_1.default.bold('Duration:')} ${this.formatDuration(this.report.duration)}`);
console.log('\n' + chalk_1.default.bold.cyan('Key Metrics:'));
console.log(` • Files Modified: ${chalk_1.default.yellow(this.report.summary.filesModified)}`);
console.log(` • Dependencies Updated: ${chalk_1.default.yellow(this.report.summary.dependenciesUpdated)}`);
console.log(` • Breaking Changes: ${chalk_1.default.yellow(this.report.summary.breakingChangesResolved)}`);
console.log(` • Migrations Applied: ${chalk_1.default.yellow(this.report.summary.migrationsRun)}`);
if (this.report.summary.testsRun > 0) {
const testStatus = this.report.summary.testsFailed > 0
? chalk_1.default.red(`${this.report.summary.testsPassed}/${this.report.summary.testsRun} passed`)
: chalk_1.default.green(`${this.report.summary.testsPassed}/${this.report.summary.testsRun} passed`);
console.log(` • Tests: ${testStatus}`);
}
if (this.report.summary.warningsCount > 0 || this.report.summary.errorsCount > 0) {
console.log('\n' + chalk_1.default.bold.cyan('Issues:'));
if (this.report.summary.errorsCount > 0) {
console.log(` • Errors: ${chalk_1.default.red(this.report.summary.errorsCount)}`);
}
if (this.report.summary.warningsCount > 0) {
console.log(` • Warnings: ${chalk_1.default.yellow(this.report.summary.warningsCount)}`);
}
}
console.log('\n' + chalk_1.default.cyan('═'.repeat(60)) + '\n');
}
formatDuration(ms) {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
}
else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
}
else {
return `${seconds}s`;
}
}
calculateUpgradeSteps() {
const steps = [];
const start = parseInt(this.fromVersion);
const end = parseInt(this.toVersion);
for (let i = start; i < end; i++) {
steps.push({ from: i.toString(), to: (i + 1).toString() });
}
return steps;
}
escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
}
exports.UpgradeReportGenerator = UpgradeReportGenerator;
//# sourceMappingURL=UpgradeReportGenerator.js.map