Skip to main content
Glama

MockLoop MCP Server

Official
by MockLoop
bump_version.pyโ€ข8.61 kB
#!/usr/bin/env python3 """ Version bumping script for mockloop-mcp. This script automates the process of bumping version numbers across the project, updating the changelog, and creating git commits and tags. Usage: python scripts/bump_version.py patch python scripts/bump_version.py minor python scripts/bump_version.py major python scripts/bump_version.py --version 1.2.3 python scripts/bump_version.py --pre-release alpha """ import argparse from datetime import datetime from pathlib import Path import re import subprocess import sys class VersionBumper: """Handles version bumping operations for the project.""" def __init__(self, project_root: Path): self.project_root = project_root self.pyproject_path = project_root / "pyproject.toml" self.init_path = project_root / "src" / "mockloop_mcp" / "__init__.py" self.changelog_path = project_root / "CHANGELOG.md" def get_current_version(self) -> str: """Get the current version from pyproject.toml.""" if not self.pyproject_path.exists(): raise FileNotFoundError( f"pyproject.toml not found at {self.pyproject_path}" ) content = self.pyproject_path.read_text() match = re.search(r'version\s*=\s*"([^"]+)"', content) if not match: raise ValueError("Version not found in pyproject.toml") return match.group(1) def validate_version_consistency(self) -> bool: """Validate that versions are consistent across files.""" pyproject_version = self.get_current_version() # Check __init__.py if self.init_path.exists(): init_content = self.init_path.read_text() init_match = re.search(r'__version__\s*=\s*"([^"]+)"', init_content) if init_match and init_match.group(1) != pyproject_version: return False return True def parse_version(self, version: str) -> tuple[int, int, int, str | None]: """Parse a semantic version string.""" # Handle pre-release versions like 1.0.0-alpha.1 if "-" in version: base_version, pre_release = version.split("-", 1) else: base_version, pre_release = version, None parts = base_version.split(".") if len(parts) != 3: raise ValueError(f"Invalid version format: {version}") try: major, minor, patch = map(int, parts) except ValueError as err: raise ValueError(f"Invalid version format: {version}") from err return major, minor, patch, pre_release def bump_version(self, bump_type: str, pre_release: str | None = None) -> str: """Bump version based on type (major, minor, patch).""" current = self.get_current_version() major, minor, patch, current_pre = self.parse_version(current) if bump_type == "major": major += 1 minor = 0 patch = 0 elif bump_type == "minor": minor += 1 patch = 0 elif bump_type == "patch": patch += 1 else: raise ValueError(f"Invalid bump type: {bump_type}") new_version = f"{major}.{minor}.{patch}" if pre_release: new_version += f"-{pre_release}" return new_version def set_version(self, new_version: str) -> None: """Set version in all relevant files.""" # Validate version format self.parse_version(new_version) # Update pyproject.toml content = self.pyproject_path.read_text() content = re.sub( r'version\s*=\s*"[^"]+"', f'version = "{new_version}"', content ) self.pyproject_path.write_text(content) # Update __init__.py if self.init_path.exists(): content = self.init_path.read_text() content = re.sub( r'__version__\s*=\s*"[^"]+"', f'__version__ = "{new_version}"', content ) self.init_path.write_text(content) def update_changelog(self, new_version: str) -> None: """Update CHANGELOG.md with new version and date.""" if not self.changelog_path.exists(): return content = self.changelog_path.read_text() today = datetime.now().strftime("%Y-%m-%d") # Replace [Unreleased] with the new version content = re.sub( r"\[Unreleased\]", f"[{new_version}] - {today}", content, count=1 ) # Add new [Unreleased] section at the top unreleased_section = """## [Unreleased] ### Added ### Changed ### Deprecated ### Removed ### Fixed ### Security """ # Find the first version section and insert before it version_pattern = r"(## \[\d+\.\d+\.\d+.*?\])" match = re.search(version_pattern, content) if match: content = content.replace( match.group(1), unreleased_section + match.group(1) ) else: # If no version sections found, add after the header header_end = content.find("\n\n") + 2 content = content[:header_end] + unreleased_section + content[header_end:] # Update comparison links at the bottom if f"[{new_version}]:" not in content: # Add new version link unreleased_link = ( "[Unreleased]: https://github.com/mockloop/mockloop-mcp/compare/" ) if unreleased_link in content: content = content.replace( unreleased_link, f"{unreleased_link}v{new_version}...HEAD\n[{new_version}]: https://github.com/mockloop/mockloop-mcp/releases/tag/v{new_version}\n", ) self.changelog_path.write_text(content) def create_git_commit_and_tag(self, version: str) -> None: """Create git commit and tag for the release.""" try: # Check if we're in a git repository subprocess.run(["git", "status"], check=True, capture_output=True) # Add changed files subprocess.run(["git", "add", "pyproject.toml"], check=True) if self.init_path.exists(): subprocess.run(["git", "add", str(self.init_path)], check=True) if self.changelog_path.exists(): subprocess.run(["git", "add", str(self.changelog_path)], check=True) # Create commit commit_message = f"chore: bump version to {version}" subprocess.run(["git", "commit", "-m", commit_message], check=True) # Create tag tag_name = f"v{version}" tag_message = f"Release version {version}" subprocess.run( ["git", "tag", "-a", tag_name, "-m", tag_message], check=True ) except subprocess.CalledProcessError: pass except FileNotFoundError: pass def main(): """Main entry point for the version bumping script.""" parser = argparse.ArgumentParser(description="Bump version for mockloop-mcp") group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "bump_type", nargs="?", choices=["major", "minor", "patch"], help="Type of version bump", ) group.add_argument("--version", help="Set specific version") parser.add_argument( "--pre-release", help="Pre-release identifier (alpha, beta, rc)" ) parser.add_argument("--no-git", action="store_true", help="Skip git operations") parser.add_argument( "--dry-run", action="store_true", help="Show what would be done without making changes", ) args = parser.parse_args() # Find project root script_path = Path(__file__).resolve() project_root = script_path.parent.parent bumper = VersionBumper(project_root) try: # Validate current state if not bumper.validate_version_consistency(): sys.exit(1) bumper.get_current_version() # Determine new version if args.version: new_version = args.version else: new_version = bumper.bump_version(args.bump_type, args.pre_release) if args.dry_run: return # Make changes bumper.set_version(new_version) bumper.update_changelog(new_version) if not args.no_git: bumper.create_git_commit_and_tag(new_version) except Exception: sys.exit(1) if __name__ == "__main__": main()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MockLoop/mockloop-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server