Spaces:
Sleeping
Sleeping
| /** | |
| * TGN Migration Tool | |
| * | |
| * Converts old TGN files with lowercase "pass" and "resign" to uppercase "Pass" and "Resign" | |
| * to match the updated TGN specification. | |
| * | |
| * Usage: | |
| * npm run migrate:tgn -- <directory> | |
| * npm run migrate:tgn -- <directory> --dry-run | |
| * npm run migrate:tgn -- <directory> --backup | |
| */ | |
| import * as fs from "fs"; | |
| import * as path from "path"; | |
| import yargs from "yargs"; | |
| import { hideBin } from "yargs/helpers"; | |
| interface MigrationOptions { | |
| directory: string; | |
| dryRun: boolean; | |
| backup: boolean; | |
| recursive: boolean; | |
| } | |
| interface MigrationStats { | |
| filesScanned: number; | |
| filesModified: number; | |
| passReplacements: number; | |
| resignReplacements: number; | |
| errors: string[]; | |
| } | |
| /** | |
| * Check if a file is a TGN file | |
| */ | |
| function isTGNFile(filePath: string): boolean { | |
| return path.extname(filePath).toLowerCase() === ".tgn"; | |
| } | |
| /** | |
| * Find all TGN files in a directory | |
| */ | |
| function findTGNFiles(dir: string, recursive: boolean): string[] { | |
| const files: string[] = []; | |
| try { | |
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| const fullPath = path.join(dir, entry.name); | |
| if (entry.isDirectory() && recursive) { | |
| files.push(...findTGNFiles(fullPath, recursive)); | |
| } else if (entry.isFile() && isTGNFile(fullPath)) { | |
| files.push(fullPath); | |
| } | |
| } | |
| } catch (error) { | |
| console.error(`Error reading directory ${dir}:`, error); | |
| } | |
| return files; | |
| } | |
| /** | |
| * Migrate TGN content from lowercase to uppercase | |
| */ | |
| function migrateTGNContent(content: string): { newContent: string; passCount: number; resignCount: number } { | |
| let passCount = 0; | |
| let resignCount = 0; | |
| // Replace "pass" with "Pass" (as a standalone word in move notation) | |
| // Use word boundary to avoid replacing "pass" in other contexts | |
| const newContent = content | |
| .replace(/\bpass\b/g, () => { | |
| passCount++; | |
| return "Pass"; | |
| }) | |
| .replace(/\bresign\b/g, () => { | |
| resignCount++; | |
| return "Resign"; | |
| }); | |
| return { newContent, passCount, resignCount }; | |
| } | |
| /** | |
| * Create backup of a file | |
| */ | |
| function backupFile(filePath: string): string { | |
| const backupPath = filePath + ".backup"; | |
| fs.copyFileSync(filePath, backupPath); | |
| return backupPath; | |
| } | |
| /** | |
| * Migrate a single TGN file | |
| */ | |
| function migrateFile( | |
| filePath: string, | |
| options: MigrationOptions, | |
| stats: MigrationStats | |
| ): void { | |
| stats.filesScanned++; | |
| try { | |
| // Read file content | |
| const content = fs.readFileSync(filePath, "utf-8"); | |
| // Migrate content | |
| const { newContent, passCount, resignCount } = migrateTGNContent(content); | |
| // Check if file needs migration | |
| if (passCount === 0 && resignCount === 0) { | |
| return; // File already up to date | |
| } | |
| // Update statistics | |
| stats.passReplacements += passCount; | |
| stats.resignReplacements += resignCount; | |
| stats.filesModified++; | |
| // Log changes | |
| console.log(`\n📄 ${path.basename(filePath)}`); | |
| if (passCount > 0) { | |
| console.log(` ✓ Replaced ${passCount} "pass" → "Pass"`); | |
| } | |
| if (resignCount > 0) { | |
| console.log(` ✓ Replaced ${resignCount} "resign" → "Resign"`); | |
| } | |
| // Skip write in dry-run mode | |
| if (options.dryRun) { | |
| console.log(" (dry-run: no changes written)"); | |
| return; | |
| } | |
| // Create backup if requested | |
| if (options.backup) { | |
| const backupPath = backupFile(filePath); | |
| console.log(` 📦 Backup created: ${path.basename(backupPath)}`); | |
| } | |
| // Write migrated content | |
| fs.writeFileSync(filePath, newContent, "utf-8"); | |
| console.log(` ✅ File updated`); | |
| } catch (error) { | |
| const errorMsg = `Error processing ${filePath}: ${error}`; | |
| stats.errors.push(errorMsg); | |
| console.error(` ❌ ${errorMsg}`); | |
| } | |
| } | |
| /** | |
| * Main migration function | |
| */ | |
| function migrateTGNFiles(options: MigrationOptions): MigrationStats { | |
| const stats: MigrationStats = { | |
| filesScanned: 0, | |
| filesModified: 0, | |
| passReplacements: 0, | |
| resignReplacements: 0, | |
| errors: [] | |
| }; | |
| console.log("🔄 TGN Migration Tool"); | |
| console.log("=" .repeat(50)); | |
| console.log(`Directory: ${options.directory}`); | |
| console.log(`Recursive: ${options.recursive}`); | |
| console.log(`Dry run: ${options.dryRun}`); | |
| console.log(`Backup: ${options.backup}`); | |
| console.log("=" .repeat(50)); | |
| // Check if directory exists | |
| if (!fs.existsSync(options.directory)) { | |
| console.error(`❌ Directory not found: ${options.directory}`); | |
| process.exit(1); | |
| } | |
| // Find all TGN files | |
| const files = findTGNFiles(options.directory, options.recursive); | |
| if (files.length === 0) { | |
| console.log("\n⚠️ No TGN files found."); | |
| return stats; | |
| } | |
| console.log(`\n📁 Found ${files.length} TGN file(s)\n`); | |
| // Migrate each file | |
| for (const file of files) { | |
| migrateFile(file, options, stats); | |
| } | |
| // Print summary | |
| console.log("\n" + "=".repeat(50)); | |
| console.log("📊 Migration Summary"); | |
| console.log("=".repeat(50)); | |
| console.log(`Files scanned: ${stats.filesScanned}`); | |
| console.log(`Files modified: ${stats.filesModified}`); | |
| console.log(`Total "pass" → "Pass": ${stats.passReplacements}`); | |
| console.log(`Total "resign" → "Resign": ${stats.resignReplacements}`); | |
| if (stats.errors.length > 0) { | |
| console.log(`\n❌ Errors: ${stats.errors.length}`); | |
| stats.errors.forEach((error) => console.error(` - ${error}`)); | |
| } | |
| if (options.dryRun) { | |
| console.log("\n⚠️ Dry run mode - no files were modified"); | |
| } else if (stats.filesModified > 0) { | |
| console.log("\n✅ Migration completed successfully!"); | |
| } else { | |
| console.log("\n✨ All files are already up to date!"); | |
| } | |
| return stats; | |
| } | |
| /** | |
| * CLI Entry Point | |
| */ | |
| async function main() { | |
| const argv = await yargs(hideBin(process.argv)) | |
| .usage("Usage: $0 <directory> [options]") | |
| .command("$0 <directory>", "Migrate TGN files to uppercase Pass/Resign format", (yargs) => { | |
| return yargs.positional("directory", { | |
| describe: "Directory containing TGN files", | |
| type: "string", | |
| demandOption: true | |
| }); | |
| }) | |
| .option("dry-run", { | |
| alias: "n", | |
| type: "boolean", | |
| default: false, | |
| description: "Preview changes without modifying files" | |
| }) | |
| .option("backup", { | |
| alias: "b", | |
| type: "boolean", | |
| default: false, | |
| description: "Create .backup files before modifying" | |
| }) | |
| .option("recursive", { | |
| alias: "r", | |
| type: "boolean", | |
| default: false, | |
| description: "Process subdirectories recursively" | |
| }) | |
| .example([ | |
| ["$0 output", "Migrate all TGN files in output directory"], | |
| ["$0 output --dry-run", "Preview changes without modifying files"], | |
| ["$0 output --backup", "Create backups before modifying"], | |
| ["$0 output --recursive", "Process all subdirectories"] | |
| ]) | |
| .help("h") | |
| .alias("h", "help") | |
| .strict() | |
| .parse(); | |
| const options: MigrationOptions = { | |
| directory: argv.directory as string, | |
| dryRun: argv["dry-run"] as boolean, | |
| backup: argv.backup as boolean, | |
| recursive: argv.recursive as boolean | |
| }; | |
| migrateTGNFiles(options); | |
| } | |
| // Run the CLI when executed directly | |
| if (process.argv[1] === new URL(import.meta.url).pathname) { | |
| main().catch((error) => { | |
| console.error("Fatal error:", error); | |
| process.exit(1); | |
| }); | |
| } | |
| export { migrateTGNFiles, migrateTGNContent, MigrationOptions, MigrationStats }; | |