smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
669 lines (659 loc) âĸ 27.8 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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReportGenerator = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs/promises"));
const Logger_1 = require("../utils/Logger");
class ReportGenerator {
constructor(changeTracker, verbose = false) {
this.changeTracker = changeTracker;
this.verbose = verbose;
}
/**
* Generate comprehensive migration report
*/
async generateReport(report, options) {
try {
const outputPath = options.outputPath || this.generateDefaultOutputPath(report, options.format);
if (this.verbose)
Logger_1.logger.debug(`Generating ${options.format.toUpperCase()} report: ${outputPath}`);
let result;
switch (options.format) {
case 'json':
result = await this.generateJSONReport(report, outputPath);
break;
case 'markdown':
result = await this.generateMarkdownReport(report, outputPath, options);
break;
case 'html':
result = await this.generateHTMLReport(report, outputPath, options);
break;
case 'pdf':
result = await this.generatePDFReport(report, outputPath, options);
break;
default:
throw new Error(`Unsupported report format: ${options.format}`);
}
if (this.verbose)
Logger_1.logger.debug(`Report generated successfully: ${result.outputPath} (${result.size} bytes)`);
return result;
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
Logger_1.logger.error(`Failed to generate report: ${errorMessage}`);
return {
success: false,
outputPath: '',
format: options.format,
size: 0,
error: errorMessage
};
}
}
/**
* Generate multiple report formats
*/
async generateMultipleFormats(report, baseOutputPath, formats = ['json', 'markdown', 'html']) {
const results = [];
for (const format of formats) {
const options = {
format: format,
outputPath: this.generateFormatSpecificPath(baseOutputPath, format),
includeDetails: true,
includeCodeDiffs: true,
includeRecommendations: true,
includeValidationResults: true
};
const result = await this.generateReport(report, options);
results.push(result);
}
return results;
}
/**
* Generate executive summary report
*/
async generateExecutiveSummary(report, outputPath) {
const summary = this.createExecutiveSummary(report);
const markdown = this.formatExecutiveSummary(summary);
try {
await fs.writeFile(outputPath, markdown, 'utf-8');
const stats = await fs.stat(outputPath);
return {
success: true,
outputPath,
format: 'markdown',
size: stats.size
};
}
catch (error) {
return {
success: false,
outputPath: '',
format: 'markdown',
size: 0,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
/**
* Generate detailed change log
*/
async generateChangeLog(report, outputPath) {
const changeLog = this.createDetailedChangeLog(report);
const markdown = this.formatChangeLog(changeLog);
try {
await fs.writeFile(outputPath, markdown, 'utf-8');
const stats = await fs.stat(outputPath);
return {
success: true,
outputPath,
format: 'markdown',
size: stats.size
};
}
catch (error) {
return {
success: false,
outputPath: '',
format: 'markdown',
size: 0,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// Private methods
async generateJSONReport(report, outputPath) {
const jsonContent = JSON.stringify(report, null, 2);
await fs.writeFile(outputPath, jsonContent, 'utf-8');
const stats = await fs.stat(outputPath);
return {
success: true,
outputPath,
format: 'json',
size: stats.size
};
}
async generateMarkdownReport(report, outputPath, options) {
const markdown = this.createMarkdownReport(report, options);
await fs.writeFile(outputPath, markdown, 'utf-8');
const stats = await fs.stat(outputPath);
return {
success: true,
outputPath,
format: 'markdown',
size: stats.size
};
}
async generateHTMLReport(report, outputPath, options) {
const html = this.createHTMLReport(report, options);
await fs.writeFile(outputPath, html, 'utf-8');
const stats = await fs.stat(outputPath);
return {
success: true,
outputPath,
format: 'html',
size: stats.size
};
}
async generatePDFReport(report, outputPath, options) {
// For now, generate HTML and note that PDF conversion would require additional libraries
const html = this.createHTMLReport(report, options);
const htmlPath = outputPath.replace('.pdf', '.html');
await fs.writeFile(htmlPath, html, 'utf-8');
const stats = await fs.stat(htmlPath);
return {
success: true,
outputPath: htmlPath,
format: 'html',
size: stats.size,
error: 'PDF conversion not implemented. Generated HTML version instead.'
};
}
generateDefaultOutputPath(report, format) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const projectName = path.basename(report.metadata.projectPath);
return `migration-report-${projectName}-${timestamp}.${format}`;
}
generateFormatSpecificPath(basePath, format) {
const ext = path.extname(basePath);
const base = basePath.replace(ext, '');
return `${base}.${format}`;
}
createMarkdownReport(report, options) {
const { summary, fileChanges, dependencyChanges, scriptChanges, validationResults, recommendations, metadata } = report;
let markdown = `# SmartUI Migration Report\n\n`;
// Header
markdown += `**Migration Date:** ${metadata.migrationDate.toLocaleString()}\n`;
markdown += `**Tool Version:** ${metadata.toolVersion}\n`;
markdown += `**Project:** ${metadata.projectPath}\n`;
markdown += `**Platform:** ${metadata.platform} | **Framework:** ${metadata.framework} | **Language:** ${metadata.language}\n\n`;
// Executive Summary
markdown += `## đ Executive Summary\n\n`;
markdown += `| Metric | Value |\n`;
markdown += `|--------|-------|\n`;
markdown += `| Total Files | ${summary.totalFiles} |\n`;
markdown += `| Files Modified | ${summary.filesModified} |\n`;
markdown += `| Files Created | ${summary.filesCreated} |\n`;
markdown += `| Snapshots Migrated | ${summary.snapshotsMigrated} |\n`;
markdown += `| Dependencies Changed | ${summary.dependenciesChanged} |\n`;
markdown += `| Migration Time | ${Math.round(summary.migrationTime / 1000)}s |\n`;
markdown += `| Success Rate | ${summary.successRate}% |\n\n`;
// File Changes
if (fileChanges.length > 0 && options.includeDetails) {
markdown += `## đ File Changes\n\n`;
fileChanges.forEach(fileChange => {
markdown += `### ${fileChange.filePath}\n`;
markdown += `- **Type:** ${fileChange.changeType}\n`;
markdown += `- **Risk Level:** ${this.getRiskEmoji(fileChange.riskLevel)} ${fileChange.riskLevel}\n`;
markdown += `- **Changes:** ${fileChange.changes.length}\n`;
if (fileChange.snapshotCount)
markdown += `- **Snapshots:** ${fileChange.snapshotCount}\n`;
if (fileChange.dependencyCount)
markdown += `- **Dependencies:** ${fileChange.dependencyCount}\n`;
markdown += `- **Summary:** ${fileChange.summary}\n\n`;
if (options.includeCodeDiffs && fileChange.changes.length > 0) {
markdown += `#### Detailed Changes\n\n`;
fileChange.changes.forEach(change => {
markdown += `- **Line ${change.lineNumber || 'N/A'}:** ${change.description}\n`;
if (change.originalContent && change.newContent) {
markdown += ` - **Before:** \`${change.originalContent.trim()}\`\n`;
markdown += ` - **After:** \`${change.newContent.trim()}\`\n`;
}
});
markdown += `\n`;
}
});
}
// Dependency Changes
if (dependencyChanges.length > 0) {
markdown += `## đĻ Dependency Changes\n\n`;
markdown += `| Type | Package | Old Version | New Version | Reason |\n`;
markdown += `|------|---------|-------------|-------------|--------|\n`;
dependencyChanges.forEach(depChange => {
markdown += `| ${depChange.type} | ${depChange.package} | ${depChange.oldVersion || 'N/A'} | ${depChange.newVersion || 'N/A'} | ${depChange.reason} |\n`;
});
markdown += `\n`;
}
// Script Changes
if (scriptChanges.length > 0) {
markdown += `## đ§ Script Changes\n\n`;
scriptChanges.forEach(scriptChange => {
markdown += `### ${scriptChange.filePath} - ${scriptChange.scriptName}\n`;
markdown += `- **Type:** ${scriptChange.changeType}\n`;
markdown += `- **Description:** ${scriptChange.description}\n`;
if (scriptChange.oldScript && scriptChange.newScript) {
markdown += `- **Before:** \`${scriptChange.oldScript}\`\n`;
markdown += `- **After:** \`${scriptChange.newScript}\`\n`;
}
markdown += `\n`;
});
}
// Validation Results
if (validationResults.length > 0 && options.includeValidationResults) {
markdown += `## â
Validation Results\n\n`;
const successCount = validationResults.filter(vr => vr.type === 'SUCCESS').length;
const warningCount = validationResults.filter(vr => vr.type === 'WARNING').length;
const errorCount = validationResults.filter(vr => vr.type === 'ERROR').length;
markdown += `- â
**Success:** ${successCount}\n`;
markdown += `- â ī¸ **Warnings:** ${warningCount}\n`;
markdown += `- â **Errors:** ${errorCount}\n\n`;
if (warningCount > 0 || errorCount > 0) {
markdown += `### Issues Found\n\n`;
validationResults
.filter(vr => vr.type !== 'SUCCESS')
.forEach(result => {
markdown += `- **${this.getValidationEmoji(result.type)} ${result.type}:** ${result.message}\n`;
if (result.filePath)
markdown += ` - *File:* ${result.filePath}\n`;
if (result.suggestion)
markdown += ` - *Suggestion:* ${result.suggestion}\n`;
});
markdown += `\n`;
}
}
// Recommendations
if (recommendations.length > 0 && options.includeRecommendations) {
markdown += `## đĄ Recommendations\n\n`;
recommendations.forEach(rec => {
markdown += `### ${this.getRecommendationEmoji(rec.type)} ${rec.title} (${rec.priority})\n`;
markdown += `${rec.description}\n`;
if (rec.action)
markdown += `\n**Action:** ${rec.action}\n`;
markdown += `\n`;
});
}
// Next Steps
markdown += `## đ Next Steps\n\n`;
markdown += `1. **Install SmartUI Dependencies:** Run \`npm install @lambdatest/smartui-cli\` or equivalent for your package manager\n`;
markdown += `2. **Configure SmartUI:** Set up your SmartUI credentials and project configuration\n`;
markdown += `3. **Update Environment Variables:** Configure your CI/CD environment with SmartUI tokens\n`;
markdown += `4. **Test Migration:** Run your migrated tests to verify functionality\n`;
markdown += `5. **Check SmartUI Dashboard:** Verify test results in the SmartUI dashboard\n\n`;
markdown += `## đ Support\n\n`;
markdown += `- **Documentation:** https://github.com/lambdatest/smartui-migration-tool\n`;
markdown += `- **Issues:** https://github.com/lambdatest/smartui-migration-tool/issues\n`;
markdown += `- **SmartUI Docs:** https://www.lambdatest.com/smart-ui\n\n`;
return markdown;
}
createHTMLReport(report, options) {
const { summary, fileChanges, dependencyChanges, scriptChanges, validationResults, recommendations, metadata } = report;
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SmartUI Migration Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
background: #f8f9fa;
}
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px;
border-radius: 10px;
margin-bottom: 30px;
text-align: center;
}
.header h1 { font-size: 2.5em; margin-bottom: 10px; }
.header p { font-size: 1.1em; opacity: 0.9; }
.summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 30px 0;
}
.summary-item {
background: white;
padding: 25px;
border-radius: 10px;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.2s;
}
.summary-item:hover { transform: translateY(-2px); }
.summary-item h3 {
margin: 0 0 10px 0;
color: #667eea;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 1px;
}
.summary-item p {
margin: 0;
font-size: 2em;
font-weight: bold;
color: #2c3e50;
}
.section {
background: white;
margin: 30px 0;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.section-header {
background: #f8f9fa;
padding: 20px;
border-bottom: 1px solid #e9ecef;
font-size: 1.3em;
font-weight: 600;
}
.section-content { padding: 20px; }
.file-change {
background: #f8f9fa;
padding: 20px;
margin: 15px 0;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.file-change h3 { margin: 0 0 10px 0; color: #2c3e50; }
.file-change .meta {
display: flex;
gap: 20px;
margin: 10px 0;
font-size: 0.9em;
color: #6c757d;
}
.validation-success { color: #28a745; }
.validation-warning { color: #ffc107; }
.validation-error { color: #dc3545; }
.recommendation {
background: #fff3cd;
padding: 20px;
margin: 15px 0;
border-radius: 8px;
border-left: 4px solid #ffc107;
}
.recommendation h3 { margin: 0 0 10px 0; color: #856404; }
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #dee2e6;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
}
.risk-low { color: #28a745; }
.risk-medium { color: #ffc107; }
.risk-high { color: #dc3545; }
.next-steps {
background: #e7f3ff;
padding: 25px;
border-radius: 10px;
margin: 30px 0;
}
.next-steps h2 { color: #0056b3; margin-bottom: 15px; }
.next-steps ol { padding-left: 20px; }
.next-steps li { margin: 8px 0; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>đ SmartUI Migration Report</h1>
<p>Migration completed on ${metadata.migrationDate.toLocaleString()}</p>
<p>Tool Version: ${metadata.toolVersion} | Platform: ${metadata.platform} | Framework: ${metadata.framework}</p>
</div>
<div class="summary">
<div class="summary-item">
<h3>Total Files</h3>
<p>${summary.totalFiles}</p>
</div>
<div class="summary-item">
<h3>Files Modified</h3>
<p>${summary.filesModified}</p>
</div>
<div class="summary-item">
<h3>Snapshots Migrated</h3>
<p>${summary.snapshotsMigrated}</p>
</div>
<div class="summary-item">
<h3>Success Rate</h3>
<p>${summary.successRate}%</p>
</div>
</div>
${fileChanges.length > 0 ? `
<div class="section">
<div class="section-header">đ File Changes</div>
<div class="section-content">
${fileChanges.map(fc => `
<div class="file-change">
<h3>${fc.filePath}</h3>
<div class="meta">
<span><strong>Type:</strong> ${fc.changeType}</span>
<span><strong>Risk:</strong> <span class="risk-${fc.riskLevel.toLowerCase()}">${fc.riskLevel}</span></span>
<span><strong>Changes:</strong> ${fc.changes.length}</span>
${fc.snapshotCount ? `<span><strong>Snapshots:</strong> ${fc.snapshotCount}</span>` : ''}
</div>
<p>${fc.summary}</p>
</div>
`).join('')}
</div>
</div>
` : ''}
${dependencyChanges.length > 0 ? `
<div class="section">
<div class="section-header">đĻ Dependency Changes</div>
<div class="section-content">
<table>
<thead>
<tr>
<th>Type</th>
<th>Package</th>
<th>Old Version</th>
<th>New Version</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
${dependencyChanges.map(dc => `
<tr>
<td>${dc.type}</td>
<td>${dc.package}</td>
<td>${dc.oldVersion || 'N/A'}</td>
<td>${dc.newVersion || 'N/A'}</td>
<td>${dc.reason}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
` : ''}
${validationResults.length > 0 ? `
<div class="section">
<div class="section-header">â
Validation Results</div>
<div class="section-content">
${validationResults.map(vr => `
<p class="validation-${vr.type.toLowerCase()}">
<strong>${vr.type}:</strong> ${vr.message}
${vr.suggestion ? `<br><em>Suggestion:</em> ${vr.suggestion}` : ''}
</p>
`).join('')}
</div>
</div>
` : ''}
${recommendations.length > 0 ? `
<div class="section">
<div class="section-header">đĄ Recommendations</div>
<div class="section-content">
${recommendations.map(rec => `
<div class="recommendation">
<h3>${rec.title} (${rec.priority})</h3>
<p>${rec.description}</p>
${rec.action ? `<p><strong>Action:</strong> ${rec.action}</p>` : ''}
</div>
`).join('')}
</div>
</div>
` : ''}
<div class="next-steps">
<h2>đ Next Steps</h2>
<ol>
<li><strong>Install SmartUI Dependencies:</strong> Run <code>npm install @lambdatest/smartui-cli</code> or equivalent for your package manager</li>
<li><strong>Configure SmartUI:</strong> Set up your SmartUI credentials and project configuration</li>
<li><strong>Update Environment Variables:</strong> Configure your CI/CD environment with SmartUI tokens</li>
<li><strong>Test Migration:</strong> Run your migrated tests to verify functionality</li>
<li><strong>Check SmartUI Dashboard:</strong> Verify test results in the SmartUI dashboard</li>
</ol>
</div>
</div>
</body>
</html>`;
}
createExecutiveSummary(report) {
return {
project: report.metadata.projectPath,
platform: report.metadata.platform,
framework: report.metadata.framework,
language: report.metadata.language,
migrationDate: report.metadata.migrationDate,
successRate: report.summary.successRate,
totalFiles: report.summary.totalFiles,
filesModified: report.summary.filesModified,
snapshotsMigrated: report.summary.snapshotsMigrated,
migrationTime: report.summary.migrationTime,
validationErrors: report.summary.validationErrors,
validationWarnings: report.summary.validationWarnings
};
}
formatExecutiveSummary(summary) {
return `# SmartUI Migration Executive Summary
## Project Overview
- **Project:** ${summary.project}
- **Platform:** ${summary.platform}
- **Framework:** ${summary.framework}
- **Language:** ${summary.language}
- **Migration Date:** ${summary.migrationDate.toLocaleString()}
## Migration Results
- **Success Rate:** ${summary.successRate}%
- **Total Files:** ${summary.totalFiles}
- **Files Modified:** ${summary.filesModified}
- **Snapshots Migrated:** ${summary.snapshotsMigrated}
- **Migration Time:** ${Math.round(summary.migrationTime / 1000)}s
## Validation Status
- **Errors:** ${summary.validationErrors}
- **Warnings:** ${summary.validationWarnings}
## Recommendation
${summary.successRate >= 95 ? 'â
Migration completed successfully with high confidence.' :
summary.successRate >= 80 ? 'â ī¸ Migration completed with some issues that need attention.' :
'â Migration completed with significant issues that require immediate attention.'}
`;
}
createDetailedChangeLog(report) {
return {
metadata: report.metadata,
changes: report.fileChanges.flatMap(fc => fc.changes),
dependencies: report.dependencyChanges,
scripts: report.scriptChanges
};
}
formatChangeLog(changeLog) {
let markdown = `# Detailed Change Log\n\n`;
markdown += `**Project:** ${changeLog.metadata.projectPath}\n`;
markdown += `**Date:** ${changeLog.metadata.migrationDate.toLocaleString()}\n\n`;
markdown += `## All Changes\n\n`;
changeLog.changes.forEach((change, index) => {
markdown += `### Change ${index + 1}: ${change.filePath}\n`;
markdown += `- **Type:** ${change.changeType}\n`;
markdown += `- **Line:** ${change.lineNumber || 'N/A'}\n`;
markdown += `- **Description:** ${change.description}\n`;
markdown += `- **Risk:** ${change.riskLevel}\n`;
markdown += `- **Timestamp:** ${change.timestamp.toLocaleString()}\n\n`;
});
return markdown;
}
getRiskEmoji(riskLevel) {
switch (riskLevel) {
case 'LOW': return 'đĸ';
case 'MEDIUM': return 'đĄ';
case 'HIGH': return 'đ´';
default: return 'âĒ';
}
}
getValidationEmoji(type) {
switch (type) {
case 'SUCCESS': return 'â
';
case 'WARNING': return 'â ī¸';
case 'ERROR': return 'â';
default: return 'âšī¸';
}
}
getRecommendationEmoji(type) {
switch (type) {
case 'ACTION': return 'đ¯';
case 'OPTIMIZATION': return 'âĄ';
case 'SECURITY': return 'đ';
case 'PERFORMANCE': return 'đ';
default: return 'đĄ';
}
}
}
exports.ReportGenerator = ReportGenerator;
//# sourceMappingURL=ReportGenerator.js.map