#!/usr/bin/env python3
"""
Release automation script for Cyber Sentinel MCP
"""
import os
import sys
import subprocess
import argparse
from pathlib import Path
import re
def run_command(cmd, check=True):
"""Run a shell command and return the result"""
print(f"Running: {cmd}")
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if check and result.returncode != 0:
print(f"Error running command: {cmd}")
print(f"stdout: {result.stdout}")
print(f"stderr: {result.stderr}")
sys.exit(1)
return result
def get_current_version():
"""Get current version from pyproject.toml"""
pyproject_path = Path("pyproject.toml")
if not pyproject_path.exists():
print("pyproject.toml not found!")
sys.exit(1)
content = pyproject_path.read_text()
match = re.search(r'version = "([^"]+)"', content)
if not match:
print("Version not found in pyproject.toml")
sys.exit(1)
return match.group(1)
def update_version(new_version):
"""Update version in pyproject.toml"""
pyproject_path = Path("pyproject.toml")
content = pyproject_path.read_text()
# Update version
content = re.sub(r'version = "[^"]+"', f'version = "{new_version}"', content)
pyproject_path.write_text(content)
print(f"Updated version to {new_version}")
def run_tests():
"""Run the test suite"""
print("Running tests...")
run_command("python -m pytest tests/ -v")
print("✅ All tests passed!")
def run_linting():
"""Run code quality checks"""
print("Running code quality checks...")
run_command("black --check src/ tests/")
run_command("isort --check-only src/ tests/")
# Run mypy but don't fail on external library issues
print("Running mypy type checking...")
result = run_command("mypy src/", check=False)
if result.returncode != 0:
if "Pattern matching is only supported" in result.stderr or "Pattern matching is only supported" in result.stdout:
print("⚠️ mypy found external library compatibility issues (acceptable)")
else:
print(f"❌ mypy failed with errors: {result.stderr}")
sys.exit(1)
else:
print("✅ mypy type checking passed!")
print("✅ Code quality checks passed!")
def build_package():
"""Build the package"""
print("Building package...")
run_command("python -m build")
print("✅ Package built successfully!")
def create_git_tag(version):
"""Create and push git tag"""
tag = f"v{version}"
print(f"Creating git tag: {tag}")
run_command(f"git add .")
run_command(f'git commit -m "Release {tag}"')
run_command(f"git tag {tag}")
run_command("git push origin main")
run_command(f"git push origin {tag}")
print(f"✅ Git tag {tag} created and pushed!")
def publish_to_pypi(test=False):
"""Publish package to PyPI"""
if test:
print("Publishing to Test PyPI...")
run_command("twine upload --repository testpypi dist/*")
print("✅ Published to Test PyPI!")
else:
print("Publishing to PyPI...")
run_command("twine upload dist/*")
print("✅ Published to PyPI!")
def main():
parser = argparse.ArgumentParser(description="Release Cyber Sentinel MCP")
parser.add_argument("version", help="New version number (e.g., 0.1.1)")
parser.add_argument("--test", action="store_true", help="Publish to Test PyPI instead")
parser.add_argument("--skip-tests", action="store_true", help="Skip running tests")
parser.add_argument("--skip-lint", action="store_true", help="Skip linting checks")
parser.add_argument("--skip-git", action="store_true", help="Skip git operations")
args = parser.parse_args()
# Validate version format
if not re.match(r'^\d+\.\d+\.\d+$', args.version):
print("Version must be in format X.Y.Z (e.g., 0.1.1)")
sys.exit(1)
current_version = get_current_version()
print(f"Current version: {current_version}")
print(f"New version: {args.version}")
# Confirm release
response = input(f"Release version {args.version}? (y/N): ")
if response.lower() != 'y':
print("Release cancelled")
sys.exit(0)
try:
# Run checks
if not args.skip_tests:
run_tests()
if not args.skip_lint:
run_linting()
# Update version
update_version(args.version)
# Build package
build_package()
# Git operations
if not args.skip_git:
create_git_tag(args.version)
# Publish
publish_to_pypi(test=args.test)
print(f"🎉 Successfully released version {args.version}!")
if not args.test:
print("\nNext steps:")
print("1. Check PyPI: https://pypi.org/project/cyber-sentinel-mcp/")
print("2. Update GitHub release notes")
print("3. Announce the release")
except KeyboardInterrupt:
print("\nRelease cancelled by user")
sys.exit(1)
except Exception as e:
print(f"Release failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()