|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +# Release automation script for mcp-debugpy |
| 5 | +# Usage: ./scripts/release.sh [patch|minor|major] |
| 6 | + |
| 7 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 8 | +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" |
| 9 | +VENV_DIR="$ROOT_DIR/.venv" |
| 10 | + |
| 11 | +# Colors for output |
| 12 | +RED='\033[0;31m' |
| 13 | +GREEN='\033[0;32m' |
| 14 | +YELLOW='\033[1;33m' |
| 15 | +NC='\033[0m' # No Color |
| 16 | + |
| 17 | +# Print colored message |
| 18 | +print_msg() { |
| 19 | + local color=$1 |
| 20 | + shift |
| 21 | + echo -e "${color}$*${NC}" |
| 22 | +} |
| 23 | + |
| 24 | +print_error() { print_msg "$RED" "ERROR: $*"; } |
| 25 | +print_success() { print_msg "$GREEN" "✓ $*"; } |
| 26 | +print_info() { print_msg "$YELLOW" "→ $*"; } |
| 27 | + |
| 28 | +# Check if we're in the right directory |
| 29 | +if [[ ! -f "$ROOT_DIR/pyproject.toml" ]]; then |
| 30 | + print_error "Must be run from mcp-debugpy repository root or scripts directory" |
| 31 | + exit 1 |
| 32 | +fi |
| 33 | + |
| 34 | +cd "$ROOT_DIR" |
| 35 | + |
| 36 | +# Detect Python and ensure venv exists |
| 37 | +if [[ ! -d "$VENV_DIR" ]]; then |
| 38 | + print_error "Virtual environment not found at $VENV_DIR" |
| 39 | + print_info "Run: python -m venv .venv && .venv/bin/pip install -e '.[dev]'" |
| 40 | + exit 1 |
| 41 | +fi |
| 42 | + |
| 43 | +PYTHON="$VENV_DIR/bin/python" |
| 44 | +PIP="$VENV_DIR/bin/pip" |
| 45 | + |
| 46 | +if [[ ! -x "$PYTHON" ]]; then |
| 47 | + print_error "Python executable not found: $PYTHON" |
| 48 | + exit 1 |
| 49 | +fi |
| 50 | + |
| 51 | +# Ensure required packages are installed |
| 52 | +print_info "Checking required packages..." |
| 53 | +for pkg in black pytest; do |
| 54 | + if ! "$PYTHON" -c "import $pkg" 2>/dev/null; then |
| 55 | + print_info "Installing $pkg..." |
| 56 | + "$PIP" install -q "$pkg" |
| 57 | + fi |
| 58 | +done |
| 59 | +print_success "Required packages available" |
| 60 | + |
| 61 | +# Get version bump type (default: patch) |
| 62 | +BUMP_TYPE="${1:-patch}" |
| 63 | +if [[ ! "$BUMP_TYPE" =~ ^(patch|minor|major)$ ]]; then |
| 64 | + print_error "Invalid version bump type: $BUMP_TYPE" |
| 65 | + print_info "Usage: $0 [patch|minor|major]" |
| 66 | + exit 1 |
| 67 | +fi |
| 68 | + |
| 69 | +# Get current version from pyproject.toml |
| 70 | +CURRENT_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2) |
| 71 | +print_info "Current version: $CURRENT_VERSION" |
| 72 | + |
| 73 | +# Calculate new version |
| 74 | +IFS='.' read -r major minor patch <<< "$CURRENT_VERSION" |
| 75 | +case "$BUMP_TYPE" in |
| 76 | + major) NEW_VERSION="$((major + 1)).0.0" ;; |
| 77 | + minor) NEW_VERSION="${major}.$((minor + 1)).0" ;; |
| 78 | + patch) NEW_VERSION="${major}.${minor}.$((patch + 1))" ;; |
| 79 | +esac |
| 80 | + |
| 81 | +print_info "New version will be: $NEW_VERSION" |
| 82 | +read -p "Continue? [y/N] " -n 1 -r |
| 83 | +echo |
| 84 | +if [[ ! $REPLY =~ ^[Yy]$ ]]; then |
| 85 | + print_info "Cancelled" |
| 86 | + exit 0 |
| 87 | +fi |
| 88 | + |
| 89 | +# Check git status |
| 90 | +if [[ -n $(git status --porcelain) ]]; then |
| 91 | + print_error "Working directory is not clean. Commit or stash changes first." |
| 92 | + git status --short |
| 93 | + exit 1 |
| 94 | +fi |
| 95 | +print_success "Working directory is clean" |
| 96 | + |
| 97 | +# Ensure we're on main branch |
| 98 | +CURRENT_BRANCH=$(git branch --show-current) |
| 99 | +if [[ "$CURRENT_BRANCH" != "main" ]]; then |
| 100 | + print_error "Must be on main branch (currently on: $CURRENT_BRANCH)" |
| 101 | + exit 1 |
| 102 | +fi |
| 103 | +print_success "On main branch" |
| 104 | + |
| 105 | +# Pull latest changes |
| 106 | +print_info "Pulling latest changes..." |
| 107 | +git pull origin main |
| 108 | +print_success "Up to date with origin" |
| 109 | + |
| 110 | +# Run tests |
| 111 | +print_info "Running tests..." |
| 112 | +export PYTHONPATH="$ROOT_DIR:$ROOT_DIR/src:${PYTHONPATH:-}" |
| 113 | +if ! "$PYTHON" -m pytest tests/ -v --tb=short; then |
| 114 | + print_error "Tests failed!" |
| 115 | + exit 1 |
| 116 | +fi |
| 117 | +print_success "All tests passed" |
| 118 | + |
| 119 | +# Format code with Black |
| 120 | +print_info "Formatting code with Black..." |
| 121 | +"$PYTHON" -m black src/ tests/ scripts/ || true |
| 122 | +print_success "Code formatted" |
| 123 | + |
| 124 | +# Update version in pyproject.toml |
| 125 | +print_info "Updating version to $NEW_VERSION..." |
| 126 | +sed -i.bak "s/^version = \".*\"/version = \"$NEW_VERSION\"/" pyproject.toml |
| 127 | +rm -f pyproject.toml.bak |
| 128 | +print_success "Version updated in pyproject.toml" |
| 129 | + |
| 130 | +# Commit version bump |
| 131 | +git add pyproject.toml |
| 132 | +if [[ -n $(git status --porcelain) ]]; then |
| 133 | + git add -A |
| 134 | + git commit -m "chore: Bump version to $NEW_VERSION |
| 135 | +
|
| 136 | +🤖 Generated with [Claude Code](https://claude.com/claude-code) |
| 137 | +
|
| 138 | +Co-Authored-By: Claude <noreply@anthropic.com>" |
| 139 | + print_success "Version bump committed" |
| 140 | +else |
| 141 | + print_info "No changes to commit" |
| 142 | +fi |
| 143 | + |
| 144 | +# Create and push tag |
| 145 | +print_info "Creating tag v$NEW_VERSION..." |
| 146 | +git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION" |
| 147 | +print_success "Tag created" |
| 148 | + |
| 149 | +# Push changes and tag |
| 150 | +print_info "Pushing to origin..." |
| 151 | +git push origin main |
| 152 | +git push origin "v$NEW_VERSION" |
| 153 | +print_success "Pushed to origin" |
| 154 | + |
| 155 | +# Create GitHub release (if gh is available) |
| 156 | +if command -v gh &> /dev/null; then |
| 157 | + print_info "Creating GitHub release..." |
| 158 | + |
| 159 | + # Generate release notes |
| 160 | + PREV_TAG=$(git describe --tags --abbrev=0 "v$NEW_VERSION^" 2>/dev/null || echo "") |
| 161 | + if [[ -n "$PREV_TAG" ]]; then |
| 162 | + CHANGELOG_URL="https://github.com/markomanninen/mcp-debugpy/compare/${PREV_TAG}...v${NEW_VERSION}" |
| 163 | + else |
| 164 | + CHANGELOG_URL="https://github.com/markomanninen/mcp-debugpy/commits/v${NEW_VERSION}" |
| 165 | + fi |
| 166 | + |
| 167 | + RELEASE_NOTES="## v$NEW_VERSION |
| 168 | +
|
| 169 | +$(git log "${PREV_TAG}..HEAD" --pretty=format:"- %s" 2>/dev/null | grep -v "^- chore: Bump version" || echo "- Release v$NEW_VERSION") |
| 170 | +
|
| 171 | +**Full Changelog**: $CHANGELOG_URL" |
| 172 | + |
| 173 | + gh release create "v$NEW_VERSION" \ |
| 174 | + --title "v$NEW_VERSION" \ |
| 175 | + --notes "$RELEASE_NOTES" |
| 176 | + |
| 177 | + print_success "GitHub release created: https://github.com/markomanninen/mcp-debugpy/releases/tag/v$NEW_VERSION" |
| 178 | +else |
| 179 | + print_info "gh CLI not found - skipping GitHub release creation" |
| 180 | + print_info "Create release manually at: https://github.com/markomanninen/mcp-debugpy/releases/new?tag=v$NEW_VERSION" |
| 181 | +fi |
| 182 | + |
| 183 | +print_success "Release v$NEW_VERSION completed!" |
| 184 | +print_info "Next steps:" |
| 185 | +print_info " 1. Verify release at: https://github.com/markomanninen/mcp-debugpy/releases" |
| 186 | +print_info " 2. Monitor CI: https://github.com/markomanninen/mcp-debugpy/actions" |
0 commit comments