diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 291ce8d..2735622 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,21 +75,8 @@ jobs: run: | set -euo pipefail - echo "Updating MCPForUnity/package.json to $NEW_VERSION" - jq ".version = \"${NEW_VERSION}\"" MCPForUnity/package.json > MCPForUnity/package.json.tmp - mv MCPForUnity/package.json.tmp MCPForUnity/package.json - - echo "Updating Server/pyproject.toml to $NEW_VERSION" - sed -i '0,/^version = ".*"/s//version = "'"$NEW_VERSION"'"/' "Server/pyproject.toml" - - echo "Updating Server/README.md version references to v$NEW_VERSION" - sed -i 's|git+https://github.com/CoplayDev/unity-mcp@v[0-9]\+\.[0-9]\+\.[0-9]\+#subdirectory=Server|git+https://github.com/CoplayDev/unity-mcp@v'"$NEW_VERSION"'#subdirectory=Server|g' Server/README.md - - echo "Updating root README.md fixed version examples to v$NEW_VERSION" - sed -i 's|https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v[0-9]\+\.[0-9]\+\.[0-9]\+|https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v'"$NEW_VERSION"'|g' README.md - - echo "Updating docs/i18n/README-zh.md fixed version examples to v$NEW_VERSION" - sed -i 's|https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v[0-9]\+\.[0-9]\+\.[0-9]\+|https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v'"$NEW_VERSION"'|g' docs/i18n/README-zh.md + echo "Updating all version references to $NEW_VERSION" + python3 tools/update_versions.py --version "$NEW_VERSION" - name: Commit and push changes env: @@ -99,7 +86,7 @@ jobs: set -euo pipefail git config user.name "GitHub Actions" git config user.email "actions@github.com" - git add MCPForUnity/package.json "Server/pyproject.toml" Server/README.md README.md docs/i18n/README-zh.md + git add MCPForUnity/package.json manifest.json "Server/pyproject.toml" Server/README.md README.md docs/i18n/README-zh.md if git diff --cached --quiet; then echo "No version changes to commit." else diff --git a/tools/update_versions.py b/tools/update_versions.py new file mode 100755 index 0000000..18675e4 --- /dev/null +++ b/tools/update_versions.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +"""Update version across all project files. + +This script updates the version in all files that need it: +- MCPForUnity/package.json (Unity package version) +- manifest.json (MCP bundle manifest) +- Server/pyproject.toml (Python package version) +- Server/README.md (version references) +- README.md (fixed version examples) +- docs/i18n/README-zh.md (fixed version examples) + +Usage: + python3 tools/update_versions.py [--dry-run] [--version VERSION] + +Options: + --dry-run: Show what would be updated without making changes + --version: Specify version to use (auto-detected from package.json if not provided) + +Examples: + # Update all files to match package.json version + python3 tools/update_versions.py + + # Update all files to a specific version + python3 tools/update_versions.py --version 9.2.0 + + # Dry run to see what would be updated + python3 tools/update_versions.py --dry-run +""" + +import argparse +import json +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +PACKAGE_JSON = REPO_ROOT / "MCPForUnity" / "package.json" +MANIFEST_JSON = REPO_ROOT / "manifest.json" +PYPROJECT_TOML = REPO_ROOT / "Server" / "pyproject.toml" +SERVER_README = REPO_ROOT / "Server" / "README.md" +ROOT_README = REPO_ROOT / "README.md" +ZH_README = REPO_ROOT / "docs" / "i18n" / "README-zh.md" + + +def load_package_version() -> str: + """Load version from package.json.""" + if not PACKAGE_JSON.exists(): + raise FileNotFoundError(f"Package file not found: {PACKAGE_JSON}") + + package_data = json.loads(PACKAGE_JSON.read_text(encoding="utf-8")) + version = package_data.get("version") + + if not version: + raise ValueError("No version found in package.json") + + return version + + +def update_package_json(new_version: str, dry_run: bool = False) -> bool: + """Update version in MCPForUnity/package.json.""" + if not PACKAGE_JSON.exists(): + print(f"Warning: {PACKAGE_JSON.relative_to(REPO_ROOT)} not found") + return False + + package_data = json.loads(PACKAGE_JSON.read_text(encoding="utf-8")) + current_version = package_data.get("version", "unknown") + + if current_version == new_version: + print(f"✓ {PACKAGE_JSON.relative_to(REPO_ROOT)} already at v{new_version}") + return False + + print( + f"Updating {PACKAGE_JSON.relative_to(REPO_ROOT)}: {current_version} → {new_version}") + + if not dry_run: + package_data["version"] = new_version + PACKAGE_JSON.write_text( + json.dumps(package_data, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + return True + + +def update_manifest_json(new_version: str, dry_run: bool = False) -> bool: + """Update version in manifest.json.""" + if not MANIFEST_JSON.exists(): + print(f"Warning: {MANIFEST_JSON.relative_to(REPO_ROOT)} not found") + return False + + manifest = json.loads(MANIFEST_JSON.read_text(encoding="utf-8")) + current_version = manifest.get("version", "unknown") + + if current_version == new_version: + print(f"✓ {MANIFEST_JSON.relative_to(REPO_ROOT)} already at v{new_version}") + return False + + print( + f"Updating {MANIFEST_JSON.relative_to(REPO_ROOT)}: {current_version} → {new_version}") + + if not dry_run: + manifest["version"] = new_version + MANIFEST_JSON.write_text( + json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + return True + + +def update_pyproject_toml(new_version: str, dry_run: bool = False) -> bool: + """Update version in Server/pyproject.toml.""" + if not PYPROJECT_TOML.exists(): + print(f"Warning: {PYPROJECT_TOML.relative_to(REPO_ROOT)} not found") + return False + + content = PYPROJECT_TOML.read_text(encoding="utf-8") + + # Find current version + version_match = re.search(r'^version = "([^"]+)"', content, re.MULTILINE) + if not version_match: + print( + f"Warning: Could not find version in {PYPROJECT_TOML.relative_to(REPO_ROOT)}") + return False + + current_version = version_match.group(1) + + if current_version == new_version: + print(f"✓ {PYPROJECT_TOML.relative_to(REPO_ROOT)} already at v{new_version}") + return False + + print( + f"Updating {PYPROJECT_TOML.relative_to(REPO_ROOT)}: {current_version} → {new_version}") + + if not dry_run: + # Replace only the first occurrence (the version field) + content = re.sub( + r'^version = ".*"', f'version = "{new_version}"', content, count=1, flags=re.MULTILINE) + PYPROJECT_TOML.write_text(content, encoding="utf-8") + + return True + + +def update_server_readme(new_version: str, dry_run: bool = False) -> bool: + """Update version references in Server/README.md.""" + if not SERVER_README.exists(): + print(f"Warning: {SERVER_README.relative_to(REPO_ROOT)} not found") + return False + + content = SERVER_README.read_text(encoding="utf-8") + + # Pattern to match git+https URLs with version tags + pattern = r'git\+https://github\.com/CoplayDev/unity-mcp@v[0-9]+\.[0-9]+\.[0-9]+#subdirectory=Server' + replacement = f'git+https://github.com/CoplayDev/unity-mcp@v{new_version}#subdirectory=Server' + + if not re.search(pattern, content): + print( + f"✓ {SERVER_README.relative_to(REPO_ROOT)} has no version references to update") + return False + + print( + f"Updating version references in {SERVER_README.relative_to(REPO_ROOT)}") + + if not dry_run: + content = re.sub(pattern, replacement, content) + SERVER_README.write_text(content, encoding="utf-8") + + return True + + +def update_root_readme(new_version: str, dry_run: bool = False) -> bool: + """Update fixed version examples in README.md.""" + if not ROOT_README.exists(): + print(f"Warning: {ROOT_README.relative_to(REPO_ROOT)} not found") + return False + + content = ROOT_README.read_text(encoding="utf-8") + + # Pattern to match git URLs with fixed version tags + pattern = r'https://github\.com/CoplayDev/unity-mcp\.git\?path=/MCPForUnity#v[0-9]+\.[0-9]+\.[0-9]+' + replacement = f'https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v{new_version}' + + if not re.search(pattern, content): + print( + f"✓ {ROOT_README.relative_to(REPO_ROOT)} has no version references to update") + return False + + print( + f"Updating version references in {ROOT_README.relative_to(REPO_ROOT)}") + + if not dry_run: + content = re.sub(pattern, replacement, content) + ROOT_README.write_text(content, encoding="utf-8") + + return True + + +def update_zh_readme(new_version: str, dry_run: bool = False) -> bool: + """Update fixed version examples in docs/i18n/README-zh.md.""" + if not ZH_README.exists(): + print(f"Warning: {ZH_README.relative_to(REPO_ROOT)} not found") + return False + + content = ZH_README.read_text(encoding="utf-8") + + # Pattern to match git URLs with fixed version tags + pattern = r'https://github\.com/CoplayDev/unity-mcp\.git\?path=/MCPForUnity#v[0-9]+\.[0-9]+\.[0-9]+' + replacement = f'https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v{new_version}' + + if not re.search(pattern, content): + print( + f"✓ {ZH_README.relative_to(REPO_ROOT)} has no version references to update") + return False + + print(f"Updating version references in {ZH_README.relative_to(REPO_ROOT)}") + + if not dry_run: + content = re.sub(pattern, replacement, content) + ZH_README.write_text(content, encoding="utf-8") + + return True + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Update version across all project files", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be updated without making changes", + ) + parser.add_argument( + "--version", + help="Version to set (auto-detected from package.json if not provided)", + ) + parser.add_argument( + "--update-package", + action="store_true", + help="Also update MCPForUnity/package.json with the specified version", + ) + + args = parser.parse_args() + + try: + # Determine version + if args.version: + version = args.version + print(f"Using specified version: {version}") + else: + version = load_package_version() + print(f"Auto-detected version from package.json: {version}") + + # Update all files + updates_made = [] + + # Always update package.json if a version is specified + if args.version: + if update_package_json(version, args.dry_run): + updates_made.append("MCPForUnity/package.json") + + if update_manifest_json(version, args.dry_run): + updates_made.append("manifest.json") + + if update_pyproject_toml(version, args.dry_run): + updates_made.append("Server/pyproject.toml") + + if update_server_readme(version, args.dry_run): + updates_made.append("Server/README.md") + + if update_root_readme(version, args.dry_run): + updates_made.append("README.md") + + if update_zh_readme(version, args.dry_run): + updates_made.append("docs/i18n/README-zh.md") + + # Summary + if args.dry_run: + print("\nDry run complete. No files were modified.") + else: + if updates_made: + print( + f"\nUpdated {len(updates_made)} files: {', '.join(updates_made)}") + else: + print("\nAll files already at the correct version.") + + return 0 + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main())