smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
465 lines • 18.6 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.RollbackManager = void 0;
const path = __importStar(require("path"));
const fs = __importStar(require("fs/promises"));
const Logger_1 = require("../utils/Logger");
class RollbackManager {
constructor(projectPath, verbose = false) {
this.checkpoints = new Map();
this.checkpointsDir = path.join(projectPath, '.smartui-checkpoints');
this.verbose = verbose;
}
/**
* Create a checkpoint before migration
*/
async createCheckpoint(projectPath, description, metadata = {}) {
if (this.verbose)
Logger_1.logger.debug(`Creating checkpoint: ${description}`);
const checkpoint = {
id: this.generateCheckpointId(),
timestamp: new Date(),
description,
projectPath,
files: await this.backupProjectFiles(projectPath),
packageJson: await this.backupPackageJson(projectPath),
pomXml: await this.backupPomXml(projectPath),
metadata: {
migrationVersion: '1.5.0',
platform: metadata.platform || 'unknown',
framework: metadata.framework || 'unknown',
language: metadata.language || 'unknown',
filesCount: 0,
totalSize: 0,
...metadata
}
};
// Update metadata with actual counts
checkpoint.metadata.filesCount = checkpoint.files.length;
checkpoint.metadata.totalSize = checkpoint.files.reduce((sum, file) => sum + file.content.length, 0);
this.checkpoints.set(checkpoint.id, checkpoint);
await this.saveCheckpoint(checkpoint);
if (this.verbose)
Logger_1.logger.debug(`Checkpoint created: ${checkpoint.id}`);
return checkpoint;
}
/**
* Rollback to a specific checkpoint
*/
async rollbackToCheckpoint(checkpointId) {
const startTime = Date.now();
const restoredFiles = [];
const errors = [];
if (this.verbose)
Logger_1.logger.debug(`Rolling back to checkpoint: ${checkpointId}`);
try {
const checkpoint = await this.loadCheckpoint(checkpointId);
if (!checkpoint) {
return {
success: false,
message: `Checkpoint ${checkpointId} not found`,
restoredFiles: [],
errors: [`Checkpoint ${checkpointId} not found`],
duration: Date.now() - startTime
};
}
// Restore project files
for (const file of checkpoint.files) {
try {
const filePath = path.join(checkpoint.projectPath, file.path);
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, file.content, 'utf-8');
restoredFiles.push(file.path);
}
catch (error) {
errors.push(`Failed to restore ${file.path}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Restore package.json
if (checkpoint.packageJson) {
try {
const packageJsonPath = path.join(checkpoint.projectPath, 'package.json');
await fs.writeFile(packageJsonPath, checkpoint.packageJson, 'utf-8');
restoredFiles.push('package.json');
}
catch (error) {
errors.push(`Failed to restore package.json: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Restore pom.xml
if (checkpoint.pomXml) {
try {
const pomPath = path.join(checkpoint.projectPath, 'pom.xml');
await fs.writeFile(pomPath, checkpoint.pomXml, 'utf-8');
restoredFiles.push('pom.xml');
}
catch (error) {
errors.push(`Failed to restore pom.xml: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Clean up SmartUI installations
await this.cleanupSmartUIInstallations(checkpoint.projectPath);
const success = errors.length === 0;
const message = success
? `Successfully rolled back to checkpoint ${checkpointId}`
: `Rollback completed with ${errors.length} errors`;
if (this.verbose)
Logger_1.logger.debug(`Rollback completed: ${message}`);
return {
success,
message,
restoredFiles,
errors,
duration: Date.now() - startTime
};
}
catch (error) {
return {
success: false,
message: `Rollback failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
restoredFiles,
errors: [...errors, error instanceof Error ? error.message : 'Unknown error'],
duration: Date.now() - startTime
};
}
}
/**
* Clean up after rollback
*/
async cleanupAfterRollback(checkpointId) {
const cleanedFiles = [];
const errors = [];
if (this.verbose)
Logger_1.logger.debug(`Cleaning up after rollback: ${checkpointId}`);
try {
// Remove SmartUI configuration files
const smartuiFiles = ['.smartui.json', '.env', 'setup-smartui.sh', 'test-smartui.sh', 'cleanup-smartui.sh', 'validate-smartui.sh'];
for (const file of smartuiFiles) {
try {
const filePath = path.join(this.checkpointsDir, '..', file);
await fs.unlink(filePath);
cleanedFiles.push(file);
}
catch {
// File doesn't exist, skip
}
}
// Remove CI/CD configuration files
const ciFiles = ['.github/workflows/smartui.yml', 'Jenkinsfile', '.gitlab-ci.yml', 'azure-pipelines.yml', '.circleci/config.yml', '.travis.yml'];
for (const file of ciFiles) {
try {
const filePath = path.join(this.checkpointsDir, '..', file);
await fs.unlink(filePath);
cleanedFiles.push(file);
}
catch {
// File doesn't exist, skip
}
}
// Remove checkpoint
await this.removeCheckpoint(checkpointId);
const success = errors.length === 0;
const message = success
? `Cleanup completed successfully`
: `Cleanup completed with ${errors.length} errors`;
if (this.verbose)
Logger_1.logger.debug(`Cleanup completed: ${message}`);
return {
success,
message,
cleanedFiles,
errors
};
}
catch (error) {
return {
success: false,
message: `Cleanup failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
cleanedFiles,
errors: [...errors, error instanceof Error ? error.message : 'Unknown error']
};
}
}
/**
* List available checkpoints
*/
async listCheckpoints() {
const checkpoints = [];
try {
await fs.mkdir(this.checkpointsDir, { recursive: true });
const files = await fs.readdir(this.checkpointsDir);
for (const file of files) {
if (file.endsWith('.json')) {
try {
const checkpoint = await this.loadCheckpoint(file.replace('.json', ''));
if (checkpoint) {
checkpoints.push(checkpoint);
}
}
catch {
// Skip invalid checkpoint files
}
}
}
}
catch {
// No checkpoints directory exists
}
return checkpoints.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}
/**
* Get checkpoint by ID
*/
async getCheckpoint(checkpointId) {
return await this.loadCheckpoint(checkpointId);
}
/**
* Delete a checkpoint
*/
async deleteCheckpoint(checkpointId) {
try {
await this.removeCheckpoint(checkpointId);
this.checkpoints.delete(checkpointId);
return true;
}
catch {
return false;
}
}
// Private methods
generateCheckpointId() {
return `checkpoint_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async backupProjectFiles(projectPath) {
const files = [];
const sourceFiles = await this.findSourceFiles(projectPath);
for (const file of sourceFiles) {
try {
const filePath = path.join(projectPath, file);
const content = await fs.readFile(filePath, 'utf-8');
const checksum = this.calculateChecksum(content);
files.push({
path: file,
content,
timestamp: new Date(),
checksum
});
}
catch (error) {
if (this.verbose)
Logger_1.logger.debug(`Skipped file ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
return files;
}
async backupPackageJson(projectPath) {
try {
const packageJsonPath = path.join(projectPath, 'package.json');
return await fs.readFile(packageJsonPath, 'utf-8');
}
catch {
return undefined;
}
}
async backupPomXml(projectPath) {
try {
const pomPath = path.join(projectPath, 'pom.xml');
return await fs.readFile(pomPath, 'utf-8');
}
catch {
return undefined;
}
}
async findSourceFiles(projectPath) {
const patterns = ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx', '**/*.java', '**/*.py', '**/*.json', '**/*.xml', '**/*.yml', '**/*.yaml'];
const files = [];
for (const pattern of patterns) {
try {
const patternFiles = await this.findFilesByPattern(projectPath, pattern);
files.push(...patternFiles);
}
catch {
// Skip patterns that don't match
}
}
return files;
}
async findFilesByPattern(projectPath, pattern) {
const files = [];
try {
const entries = await fs.readdir(projectPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && this.matchesPattern(entry.name, pattern)) {
files.push(entry.name);
}
else if (entry.isDirectory() && !this.shouldSkipDirectory(entry.name)) {
const subFiles = await this.findFilesInDirectory(path.join(projectPath, entry.name), pattern);
files.push(...subFiles.map(f => path.join(entry.name, f)));
}
}
}
catch {
// Skip directories that can't be read
}
return files;
}
async findFilesInDirectory(dirPath, pattern) {
const files = [];
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && this.matchesPattern(entry.name, pattern)) {
files.push(entry.name);
}
else if (entry.isDirectory() && !this.shouldSkipDirectory(entry.name)) {
const subFiles = await this.findFilesInDirectory(path.join(dirPath, entry.name), pattern);
files.push(...subFiles.map(f => path.join(entry.name, f)));
}
}
}
catch {
// Skip directories that can't be read
}
return files;
}
matchesPattern(filename, pattern) {
// Simplified pattern matching
if (pattern.includes('**/*.js'))
return filename.endsWith('.js');
if (pattern.includes('**/*.ts'))
return filename.endsWith('.ts');
if (pattern.includes('**/*.jsx'))
return filename.endsWith('.jsx');
if (pattern.includes('**/*.tsx'))
return filename.endsWith('.tsx');
if (pattern.includes('**/*.java'))
return filename.endsWith('.java');
if (pattern.includes('**/*.py'))
return filename.endsWith('.py');
if (pattern.includes('**/*.json'))
return filename.endsWith('.json');
if (pattern.includes('**/*.xml'))
return filename.endsWith('.xml');
if (pattern.includes('**/*.yml'))
return filename.endsWith('.yml');
if (pattern.includes('**/*.yaml'))
return filename.endsWith('.yaml');
return false;
}
shouldSkipDirectory(dirName) {
const skipDirs = ['.git', 'node_modules', '.next', 'dist', 'build', 'coverage', '.nyc_output', '.smartui-backup', '.smartui-checkpoints'];
return skipDirs.includes(dirName) || dirName.startsWith('.');
}
calculateChecksum(content) {
// Simple checksum calculation
let hash = 0;
for (let i = 0; i < content.length; i++) {
const char = content.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(36);
}
async saveCheckpoint(checkpoint) {
await fs.mkdir(this.checkpointsDir, { recursive: true });
const checkpointPath = path.join(this.checkpointsDir, `${checkpoint.id}.json`);
await fs.writeFile(checkpointPath, JSON.stringify(checkpoint, null, 2), 'utf-8');
}
async loadCheckpoint(checkpointId) {
try {
const checkpointPath = path.join(this.checkpointsDir, `${checkpointId}.json`);
const content = await fs.readFile(checkpointPath, 'utf-8');
const checkpoint = JSON.parse(content);
// Convert timestamp back to Date object
checkpoint.timestamp = new Date(checkpoint.timestamp);
checkpoint.files.forEach((file) => {
file.timestamp = new Date(file.timestamp);
});
return checkpoint;
}
catch {
return null;
}
}
async removeCheckpoint(checkpointId) {
try {
const checkpointPath = path.join(this.checkpointsDir, `${checkpointId}.json`);
await fs.unlink(checkpointPath);
}
catch {
// Checkpoint file doesn't exist
}
}
async cleanupSmartUIInstallations(projectPath) {
// Remove SmartUI packages from package.json
try {
const packageJsonPath = path.join(projectPath, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
if (packageJson.dependencies) {
Object.keys(packageJson.dependencies).forEach(dep => {
if (dep.includes('smartui') || dep.includes('lambdatest')) {
delete packageJson.dependencies[dep];
}
});
}
if (packageJson.devDependencies) {
Object.keys(packageJson.devDependencies).forEach(dep => {
if (dep.includes('smartui') || dep.includes('lambdatest')) {
delete packageJson.devDependencies[dep];
}
});
}
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
}
catch {
// Not a Node.js project or package.json doesn't exist
}
// Remove SmartUI dependencies from pom.xml
try {
const pomPath = path.join(projectPath, 'pom.xml');
let pomContent = await fs.readFile(pomPath, 'utf-8');
// Remove SmartUI dependencies
pomContent = pomContent.replace(/<dependency>[\s\S]*?<groupId>com\.lambdatest<\/groupId>[\s\S]*?<\/dependency>/g, '');
pomContent = pomContent.replace(/<dependency>[\s\S]*?<groupId>io\.github\.lambdatest<\/groupId>[\s\S]*?<\/dependency>/g, '');
await fs.writeFile(pomPath, pomContent, 'utf-8');
}
catch {
// Not a Maven project or pom.xml doesn't exist
}
}
}
exports.RollbackManager = RollbackManager;
//# sourceMappingURL=RollbackManager.js.map
;