UNPKG

smartui-migration-tool

Version:

Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI

465 lines 18.6 kB
"use strict"; 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