317 lines
7.7 KiB
TypeScript
317 lines
7.7 KiB
TypeScript
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<MigrationOptions>;
|
|
|
|
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<string[]> {
|
|
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<MigrationResult> {
|
|
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<boolean> {
|
|
try {
|
|
const gitDir = plugins.path.join(dir, '.git');
|
|
return await plugins.smartfile.fs.isDirectory(gitDir);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the migration
|
|
*/
|
|
async run(): Promise<MigrationSummary> {
|
|
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<string> {
|
|
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;
|
|
}
|
|
}
|