import * as plugins from './tstest.plugins.js'; import { coloredString as cs } from '@push.rocks/consolecolor'; import { parseTestFilename, getLegacyMigrationTarget, isLegacyFilename } from './tstest.classes.runtime.parser.js'; /** * Migration result for a single file */ export interface MigrationResult { /** * Original file path */ oldPath: string; /** * New file path after migration */ newPath: string; /** * Whether the migration was performed */ migrated: boolean; /** * Error message if migration failed */ error?: string; } /** * Migration summary */ export interface MigrationSummary { /** * Total number of legacy files found */ totalLegacyFiles: number; /** * Number of files successfully migrated */ migratedCount: number; /** * Number of files that failed to migrate */ errorCount: number; /** * Individual migration results */ results: MigrationResult[]; /** * Whether this was a dry run */ dryRun: boolean; } /** * Migration options */ export interface MigrationOptions { /** * Base directory to search for test files * Default: process.cwd() */ baseDir?: string; /** * Glob pattern for finding test files * Default: '** /*test*.ts' (without space) */ pattern?: string; /** * Dry run mode - don't actually rename files * Default: true */ dryRun?: boolean; /** * Verbose output * Default: false */ verbose?: boolean; } /** * Migration class for renaming legacy test files to new naming convention * * Migrations: * - .browser.ts → .chromium.ts * - .both.ts → .node+chromium.ts * - .both.nonci.ts → .node+chromium.nonci.ts * - .browser.nonci.ts → .chromium.nonci.ts */ export class Migration { private options: Required; constructor(options: MigrationOptions = {}) { this.options = { baseDir: options.baseDir || process.cwd(), pattern: options.pattern || '**/test*.ts', dryRun: options.dryRun !== undefined ? options.dryRun : true, verbose: options.verbose || false, }; } /** * Find all legacy test files in the base directory */ async findLegacyFiles(): Promise { const files = await plugins.smartfile.fs.listFileTree( this.options.baseDir, this.options.pattern ); const legacyFiles: string[] = []; for (const file of files) { const fileName = plugins.path.basename(file); if (isLegacyFilename(fileName)) { const absolutePath = plugins.path.isAbsolute(file) ? file : plugins.path.join(this.options.baseDir, file); legacyFiles.push(absolutePath); } } return legacyFiles; } /** * Migrate a single file */ private async migrateFile(filePath: string): Promise { const fileName = plugins.path.basename(filePath); const dirName = plugins.path.dirname(filePath); try { // Get the new filename const newFileName = getLegacyMigrationTarget(fileName); if (!newFileName) { return { oldPath: filePath, newPath: filePath, migrated: false, error: 'File is not a legacy file', }; } const newPath = plugins.path.join(dirName, newFileName); // Check if target file already exists if (await plugins.smartfile.fs.fileExists(newPath)) { return { oldPath: filePath, newPath, migrated: false, error: `Target file already exists: ${newPath}`, }; } if (!this.options.dryRun) { // Check if we're in a git repository const isGitRepo = await this.isGitRepository(this.options.baseDir); if (isGitRepo) { // Use git mv to preserve history const smartshell = new plugins.smartshell.Smartshell({ executor: 'bash', pathDirectories: [], }); const gitCommand = `cd "${this.options.baseDir}" && git mv "${filePath}" "${newPath}"`; const result = await smartshell.exec(gitCommand); if (result.exitCode !== 0) { throw new Error(`git mv failed: ${result.stderr}`); } } else { // Not a git repository - cannot migrate without git throw new Error('Migration requires a git repository. We have git!'); } } return { oldPath: filePath, newPath, migrated: true, }; } catch (error) { return { oldPath: filePath, newPath: filePath, migrated: false, error: error.message, }; } } /** * Check if a directory is a git repository */ private async isGitRepository(dir: string): Promise { try { const gitDir = plugins.path.join(dir, '.git'); return await plugins.smartfile.fs.isDirectory(gitDir); } catch { return false; } } /** * Run the migration */ async run(): Promise { const legacyFiles = await this.findLegacyFiles(); console.log(''); console.log(cs('='.repeat(60), 'blue')); console.log(cs('Test File Migration Tool', 'blue')); console.log(cs('='.repeat(60), 'blue')); console.log(''); if (this.options.dryRun) { console.log(cs('🔍 DRY RUN MODE - No files will be modified', 'orange')); console.log(''); } console.log(`Found ${legacyFiles.length} legacy test file(s)`); console.log(''); const results: MigrationResult[] = []; let migratedCount = 0; let errorCount = 0; for (const file of legacyFiles) { const result = await this.migrateFile(file); results.push(result); if (result.migrated) { migratedCount++; const oldName = plugins.path.basename(result.oldPath); const newName = plugins.path.basename(result.newPath); if (this.options.dryRun) { console.log(cs(` Would migrate:`, 'cyan')); } else { console.log(cs(` ✓ Migrated:`, 'green')); } console.log(` ${oldName}`); console.log(cs(` → ${newName}`, 'green')); console.log(''); } else if (result.error) { errorCount++; console.log(cs(` ✗ Failed: ${plugins.path.basename(result.oldPath)}`, 'red')); console.log(cs(` ${result.error}`, 'red')); console.log(''); } } console.log(cs('='.repeat(60), 'blue')); console.log(`Summary:`); console.log(` Total legacy files: ${legacyFiles.length}`); console.log(` Successfully migrated: ${migratedCount}`); console.log(` Errors: ${errorCount}`); console.log(cs('='.repeat(60), 'blue')); if (this.options.dryRun && legacyFiles.length > 0) { console.log(''); console.log(cs('To apply these changes, run:', 'orange')); console.log(cs(' tstest migrate --write', 'orange')); } console.log(''); return { totalLegacyFiles: legacyFiles.length, migratedCount, errorCount, results, dryRun: this.options.dryRun, }; } /** * Create a migration report without performing the migration */ async generateReport(): Promise { const legacyFiles = await this.findLegacyFiles(); let report = ''; report += 'Test File Migration Report\n'; report += '='.repeat(60) + '\n'; report += '\n'; report += `Found ${legacyFiles.length} legacy test file(s)\n`; report += '\n'; for (const file of legacyFiles) { const fileName = plugins.path.basename(file); const newFileName = getLegacyMigrationTarget(fileName); if (newFileName) { report += `${fileName}\n`; report += ` → ${newFileName}\n`; report += '\n'; } } report += '='.repeat(60) + '\n'; return report; } }