#!/usr/bin/env python3 """ Universal version updater supporting multiple file formats. Supports: JSON, TOML, YAML, plain text, Unity asset files, etc. """ import json import re import sys from pathlib import Path from typing import List class VersionUpdater: def __init__(self): self.updated_files: List[str] = [] def update(self, file_path: str, new_version: str) -> bool: """Update version in file based on its format.""" path = Path(file_path) if not path.exists(): print(f"Error: File not found: {file_path}", file=sys.stderr) return False # Detect file type and update accordingly if path.suffix == '.json': return self._update_json(path, new_version) elif path.suffix == '.toml': return self._update_toml(path, new_version) elif path.suffix in ['.yaml', '.yml']: return self._update_yaml(path, new_version) elif path.name == 'ProjectSettings.asset': return self._update_unity_asset(path, new_version) elif path.suffix == '.uproject': return self._update_unreal_uproject(path, new_version) elif path.name in ['VERSION', 'version.txt']: return self._update_plain_text(path, new_version) else: print(f"Warning: Unknown file type: {file_path}", file=sys.stderr) return False def _update_json(self, path: Path, new_version: str) -> bool: """Update version in JSON file.""" try: data = json.loads(path.read_text(encoding='utf-8')) # Update version field if 'version' in data: old_version = data['version'] data['version'] = new_version print(f"Updated {path.name}: {old_version} → {new_version}") else: data['version'] = new_version print(f"Added version to {path.name}: {new_version}") # Write back with proper formatting path.write_text( json.dumps( data, indent=2, ensure_ascii=False) + '\n', encoding='utf-8') self.updated_files.append(str(path)) return True except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def _update_toml(self, path: Path, new_version: str) -> bool: """Update version in TOML file.""" try: content = path.read_text(encoding='utf-8') # Try to find and replace version patterns = [ (r'(version\s*=\s*["\'])([^"\']+)(["\'])', r'\g<1>' + new_version + r'\g<3>'), (r'(\[project\].*?version\s*=\s*["\'])([^"\']+)(["\'])', r'\g<1>' + new_version + r'\g<3>'), ] updated = False for pattern, replacement in patterns: if re.search(pattern, content, re.DOTALL): content = re.sub( pattern, replacement, content, flags=re.DOTALL) updated = True break if updated: path.write_text(content, encoding='utf-8') self.updated_files.append(str(path)) print(f"Updated {path.name} → {new_version}") return True else: print( f"Warning: Could not find version in { path.name}", file=sys.stderr) return False except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def _update_yaml(self, path: Path, new_version: str) -> bool: """Update version in YAML file.""" try: content = path.read_text(encoding='utf-8') # Replace version field pattern = r'(version:\s*["\']?)([^"\'\n]+)(["\']?)' replacement = r'\g<1>' + new_version + r'\g<3>' if re.search(pattern, content): content = re.sub(pattern, replacement, content) path.write_text(content, encoding='utf-8') self.updated_files.append(str(path)) print(f"Updated {path.name} → {new_version}") return True else: print( f"Warning: Could not find version in { path.name}", file=sys.stderr) return False except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def _update_unity_asset(self, path: Path, new_version: str) -> bool: """Update bundleVersion in Unity ProjectSettings.asset.""" try: content = path.read_text(encoding='utf-8') # Replace bundleVersion pattern = r'(bundleVersion:\s*)(.+)' replacement = r'\g<1>' + new_version if re.search(pattern, content): old_content = content content = re.sub(pattern, replacement, content) if content != old_content: path.write_text(content, encoding='utf-8') self.updated_files.append(str(path)) print( f"Updated Unity ProjectSettings.asset → {new_version}") return True print( f"Warning: Could not find bundleVersion in { path.name}", file=sys.stderr) return False except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def _update_unreal_uproject(self, path: Path, new_version: str) -> bool: """Update version in Unreal .uproject file.""" try: data = json.loads(path.read_text(encoding='utf-8')) # Unreal uses EngineAssociation or custom Version field if 'Version' in data: old_version = data['Version'] data['Version'] = new_version print(f"Updated {path.name}: {old_version} → {new_version}") else: data['Version'] = new_version print(f"Added Version to {path.name}: {new_version}") path.write_text( json.dumps( data, indent=4, ensure_ascii=False) + '\n', encoding='utf-8') self.updated_files.append(str(path)) return True except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def _update_plain_text(self, path: Path, new_version: str) -> bool: """Update plain text version file.""" try: path.write_text(new_version + '\n', encoding='utf-8') self.updated_files.append(str(path)) print(f"Updated {path.name} → {new_version}") return True except Exception as e: print(f"Error updating {path}: {e}", file=sys.stderr) return False def main(): if len(sys.argv) < 3: print( "Usage: python update_version.py ", file=sys.stderr) print( " python update_version.py " " ... ", file=sys.stderr) sys.exit(1) files = sys.argv[1:-1] new_version = sys.argv[-1] updater = VersionUpdater() success_count = 0 for file_path in files: if updater.update(file_path, new_version): success_count += 1 print(f"\nUpdated {success_count}/{len(files)} files successfully") if success_count < len(files): sys.exit(1) if __name__ == "__main__": main()