From c0ea0e501b3432012d04c118d28aa48c1287591d Mon Sep 17 00:00:00 2001 From: Patryk Ciechanski Date: Thu, 12 Jun 2025 08:55:17 +0200 Subject: [PATCH] feat: Add automatic semantic versioning workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create GitHub Actions workflow for automatic version bumping based on PR title prefixes - Add version bumping script (scripts/bump_version.py) for programmatic updates - Update PR template with semantic versioning guidelines - Document versioning workflow in contributing guide - Integrate with existing Docker build workflow via git tags This enables automatic version management: - feat: triggers MINOR version bump - fix: triggers PATCH version bump - breaking: triggers MAJOR version bump - docs/chore/test: no version bump ๐Ÿค– Generated with Claude Code Co-Authored-By: Claude --- .github/pull_request_template.md | 137 ++++++----------------- .github/workflows/auto-version.yml | 168 +++++++++++++++++++++++++++++ docs/contributing/workflows.md | 43 +++++++- scripts/README.md | 44 ++++++++ scripts/bump_version.py | 116 ++++++++++++++++++++ 5 files changed, 401 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/auto-version.yml create mode 100644 scripts/README.md create mode 100755 scripts/bump_version.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a0a51e2..db4aaac 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,116 +1,49 @@ - +## PR Title Format -## Related Issue +**Please ensure your PR title follows one of these formats:** - - - - -Closes # - -## Type of Change - - - -- [ ] ๐Ÿž Bug fix (non-breaking change which fixes an issue) -- [ ] โœจ New feature (non-breaking change which adds functionality) -- [ ] ๐Ÿ› ๏ธ New Gemini tool (adds a new tool like `chat`, `codereview`, etc.) -- [ ] ๐Ÿ’ฅ Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] ๐Ÿ“– Documentation update -- [ ] ๐Ÿงน Refactor or chore (no user-facing changes) -- [ ] ๐Ÿ—๏ธ Infrastructure/CI changes +- `feat: ` - New features (triggers MINOR version bump) +- `fix: ` - Bug fixes (triggers PATCH version bump) +- `breaking: ` or `BREAKING CHANGE: ` - Breaking changes (triggers MAJOR version bump) +- `perf: ` - Performance improvements (triggers PATCH version bump) +- `refactor: ` - Code refactoring (triggers PATCH version bump) +- `docs: ` - Documentation only (no version bump) +- `chore: ` - Maintenance tasks (no version bump) +- `test: ` - Test additions/changes (no version bump) +- `ci: ` - CI/CD changes (no version bump) +- `style: ` - Code style changes (no version bump) ## Description - +Please provide a clear and concise description of what this PR does. + +## Changes Made + +- [ ] List the specific changes made +- [ ] Include any breaking changes +- [ ] Note any dependencies added/removed ## Testing - +- [ ] Unit tests pass +- [ ] Integration tests pass (if applicable) +- [ ] Manual testing completed +- [ ] Documentation updated (if needed) -### Unit Tests (Required) -- [ ] I have added new unit tests to cover my changes -- [ ] I have run `python -m pytest tests/ --ignore=tests/test_live_integration.py -v` and all tests pass -- [ ] New tests use proper mocking and don't require API keys +## Related Issues -### Live Integration Tests (Recommended) -- [ ] I have tested this with a real Gemini API key using `python tests/test_live_integration.py` -- [ ] The changes work as expected with actual API calls -- [ ] I have tested this on [macOS/Linux/Windows (WSL2)] +Fixes #(issue number) -### Docker Testing (If Applicable) -- [ ] I have tested the Docker build: `docker build -t test-image .` -- [ ] I have tested the Docker functionality: `./setup-docker.sh` -- [ ] Docker integration works with the changes +## Checklist -## Code Quality +- [ ] PR title follows the format guidelines above +- [ ] Code follows the project's style guidelines +- [ ] Self-review completed +- [ ] Tests added/updated as needed +- [ ] Documentation updated as needed +- [ ] All tests passing +- [ ] Ready for review - +## Additional Notes -- [ ] My code follows the project's style guidelines (`black .` and `ruff check .`) -- [ ] I have run the linting tools and fixed any issues -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] My changes generate no new warnings -- [ ] I have updated type hints where applicable - -## Documentation - - - -- [ ] I have made corresponding changes to the documentation -- [ ] I have updated the README.md if my changes affect usage -- [ ] I have updated CONTRIBUTING.md if my changes affect the development workflow -- [ ] For new tools: I have added usage examples and parameter documentation - -## Breaking Changes - - - -- [ ] This change is backwards compatible -- [ ] OR: I have documented the breaking changes and migration path below - - - -## Additional Context - - - -## Checklist for Maintainers - - - -- [ ] Code review completed -- [ ] All CI checks passing -- [ ] Breaking changes properly documented -- [ ] Version bump needed (if applicable) -- [ ] Documentation updated and accurate \ No newline at end of file +Any additional information that reviewers should know. \ No newline at end of file diff --git a/.github/workflows/auto-version.yml b/.github/workflows/auto-version.yml new file mode 100644 index 0000000..a9c526f --- /dev/null +++ b/.github/workflows/auto-version.yml @@ -0,0 +1,168 @@ +name: Auto Version + +on: + pull_request: + types: [closed] + branches: [main] + +jobs: + version: + # Only run if PR was merged (not just closed) + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Determine version bump type + id: bump_type + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + echo "PR Title: $PR_TITLE" + + # Convert to lowercase for case-insensitive matching + PR_TITLE_LOWER=$(echo "$PR_TITLE" | tr '[:upper:]' '[:lower:]') + + # Determine bump type based on PR title prefix + if [[ "$PR_TITLE_LOWER" =~ ^(breaking|breaking[[:space:]]change): ]]; then + echo "Detected BREAKING CHANGE - major version bump" + echo "bump_type=major" >> $GITHUB_OUTPUT + echo "should_bump=true" >> $GITHUB_OUTPUT + elif [[ "$PR_TITLE_LOWER" =~ ^feat: ]]; then + echo "Detected new feature - minor version bump" + echo "bump_type=minor" >> $GITHUB_OUTPUT + echo "should_bump=true" >> $GITHUB_OUTPUT + elif [[ "$PR_TITLE_LOWER" =~ ^(fix|perf|refactor): ]]; then + echo "Detected fix/perf/refactor - patch version bump" + echo "bump_type=patch" >> $GITHUB_OUTPUT + echo "should_bump=true" >> $GITHUB_OUTPUT + elif [[ "$PR_TITLE_LOWER" =~ ^(docs|chore|test|ci|style): ]]; then + echo "Detected non-versioned change - no version bump" + echo "bump_type=none" >> $GITHUB_OUTPUT + echo "should_bump=false" >> $GITHUB_OUTPUT + else: + echo "No recognized prefix - no version bump" + echo "bump_type=none" >> $GITHUB_OUTPUT + echo "should_bump=false" >> $GITHUB_OUTPUT + fi + + - name: Get current version + if: steps.bump_type.outputs.should_bump == 'true' + id: current_version + run: | + CURRENT_VERSION=$(python -c "from config import __version__; print(__version__)") + echo "Current version: $CURRENT_VERSION" + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + - name: Bump version + if: steps.bump_type.outputs.should_bump == 'true' + id: new_version + run: | + python scripts/bump_version.py ${{ steps.bump_type.outputs.bump_type }} + NEW_VERSION=$(python -c "from config import __version__; print(__version__)") + echo "New version: $NEW_VERSION" + echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT + + - name: Commit version change + if: steps.bump_type.outputs.should_bump == 'true' + run: | + git add config.py + git commit -m "chore: bump version to ${{ steps.new_version.outputs.version }} + + Automated version bump from PR #${{ github.event.pull_request.number }} + ${{ github.event.pull_request.title }} + + Co-authored-by: ${{ github.event.pull_request.user.login }} <${{ github.event.pull_request.user.id }}+${{ github.event.pull_request.user.login }}@users.noreply.github.com>" + git push + + - name: Create git tag + if: steps.bump_type.outputs.should_bump == 'true' + run: | + git tag -a "v${{ steps.new_version.outputs.version }}" -m "Release v${{ steps.new_version.outputs.version }} + + Changes in this release: + - ${{ github.event.pull_request.title }} + + PR: #${{ github.event.pull_request.number }} + Author: @${{ github.event.pull_request.user.login }}" + git push origin "v${{ steps.new_version.outputs.version }}" + + - name: Generate release notes + if: steps.bump_type.outputs.should_bump == 'true' + id: release_notes + run: | + # Extract PR body for release notes + PR_BODY=$(cat << 'EOF' + ${{ github.event.pull_request.body }} + EOF + ) + + # Create release notes + RELEASE_NOTES=$(cat << EOF + ## What's Changed + + ${{ github.event.pull_request.title }} by @${{ github.event.pull_request.user.login }} in #${{ github.event.pull_request.number }} + + ### Details + + $PR_BODY + + ### Version Info + - Previous version: ${{ steps.current_version.outputs.version }} + - New version: ${{ steps.new_version.outputs.version }} + - Bump type: ${{ steps.bump_type.outputs.bump_type }} + + **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.current_version.outputs.version }}...v${{ steps.new_version.outputs.version }} + EOF + ) + + # Save to file for GitHub release + echo "$RELEASE_NOTES" > release_notes.md + + - name: Create GitHub release + if: steps.bump_type.outputs.should_bump == 'true' + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.new_version.outputs.version }} + name: Release v${{ steps.new_version.outputs.version }} + body_path: release_notes.md + draft: false + prerelease: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Summary + run: | + if [ "${{ steps.bump_type.outputs.should_bump }}" == "true" ]; then + echo "### โœ… Version Bumped Successfully" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Previous version**: ${{ steps.current_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **New version**: ${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Bump type**: ${{ steps.bump_type.outputs.bump_type }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag**: v${{ steps.new_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY + else + echo "### โ„น๏ธ No Version Bump Required" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "PR title prefix did not require a version bump." >> $GITHUB_STEP_SUMMARY + echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY + echo "- **Title**: ${{ github.event.pull_request.title }}" >> $GITHUB_STEP_SUMMARY + fi \ No newline at end of file diff --git a/docs/contributing/workflows.md b/docs/contributing/workflows.md index d333df5..3222b76 100644 --- a/docs/contributing/workflows.md +++ b/docs/contributing/workflows.md @@ -414,6 +414,39 @@ jobs: ## Release Workflow +### Automatic Versioning System + +**Semantic versioning is automatically managed based on PR title prefixes**: + +#### PR Title Conventions +- `feat:` - New features โ†’ **MINOR** version bump (0.X.0) +- `fix:` - Bug fixes โ†’ **PATCH** version bump (0.0.X) +- `breaking:` or `BREAKING CHANGE:` - Breaking changes โ†’ **MAJOR** version bump (X.0.0) +- `perf:` - Performance improvements โ†’ **PATCH** version bump +- `refactor:` - Code refactoring โ†’ **PATCH** version bump +- `docs:`, `chore:`, `test:`, `ci:`, `style:` - No version bump + +#### Automatic Version Workflow +1. **Create PR with appropriate prefix**: `feat: Add new debugging capability` +2. **PR gets reviewed and merged to main** +3. **GitHub Action automatically**: + - Detects version bump type from PR title + - Updates version in `config.py` + - Updates `__updated__` timestamp + - Commits version change + - Creates git tag (e.g., `v3.3.0`) + - Generates GitHub release with PR description + - Triggers Docker build workflow + +#### Manual Version Bumping (if needed) +```bash +# Run the version bump script manually +python scripts/bump_version.py + +# Example: bump minor version +python scripts/bump_version.py minor +``` + ### Pre-Release Validation **Comprehensive validation before release**: @@ -444,11 +477,11 @@ claude-code-cli --tool precommit --path /workspace/ ### Release Documentation -**Update release documentation**: -1. **CHANGELOG.md**: Document all changes, breaking changes, migration notes -2. **README.md**: Update installation and usage instructions -3. **docs/**: Ensure all documentation reflects current version -4. **Version Tags**: Create semantic version tags +**Automatic release notes are generated from PR descriptions**: +1. **GitHub Release**: Created automatically with PR details +2. **CHANGELOG.md**: Update manually for major releases +3. **README.md**: Update installation instructions if needed +4. **docs/**: Ensure documentation reflects new features ### Deployment Process diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..7bcc45a --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,44 @@ +# Scripts Directory + +This directory contains utility scripts for the Gemini MCP Server project. + +## bump_version.py + +A utility script for semantic version bumping that integrates with the automatic versioning workflow. + +### Usage + +```bash +python scripts/bump_version.py +``` + +### Examples + +```bash +# Bump patch version (e.g., 3.2.0 โ†’ 3.2.1) +python scripts/bump_version.py patch + +# Bump minor version (e.g., 3.2.0 โ†’ 3.3.0) +python scripts/bump_version.py minor + +# Bump major version (e.g., 3.2.0 โ†’ 4.0.0) +python scripts/bump_version.py major +``` + +### Features + +- Reads current version from `config.py` +- Applies semantic versioning rules +- Updates both `__version__` and `__updated__` fields +- Preserves file formatting and structure +- Outputs new version for GitHub Actions integration + +### Integration + +This script is primarily used by the GitHub Actions workflow (`.github/workflows/auto-version.yml`) for automatic version bumping based on PR title prefixes. Manual usage is available for special cases. + +### Version Bump Rules + +- **Major**: Increments first digit, resets others (3.2.1 โ†’ 4.0.0) +- **Minor**: Increments second digit, resets patch (3.2.1 โ†’ 3.3.0) +- **Patch**: Increments third digit (3.2.1 โ†’ 3.2.2) \ No newline at end of file diff --git a/scripts/bump_version.py b/scripts/bump_version.py new file mode 100755 index 0000000..e89b4bc --- /dev/null +++ b/scripts/bump_version.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Version bumping utility for Gemini MCP Server + +This script handles semantic version bumping for the project by: +- Reading current version from config.py +- Applying the appropriate version bump (major, minor, patch) +- Updating config.py with new version and timestamp +- Preserving file structure and formatting +""" + +import re +import sys +from datetime import datetime +from pathlib import Path +from typing import Tuple + + +def parse_version(version_string: str) -> Tuple[int, int, int]: + """Parse semantic version string into tuple of integers.""" + match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version_string) + if not match: + raise ValueError(f"Invalid version format: {version_string}") + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + +def bump_version(version: Tuple[int, int, int], bump_type: str) -> Tuple[int, int, int]: + """Apply version bump according to semantic versioning rules.""" + major, minor, patch = version + + if bump_type == "major": + return (major + 1, 0, 0) + elif bump_type == "minor": + return (major, minor + 1, 0) + elif bump_type == "patch": + return (major, minor, patch + 1) + else: + raise ValueError(f"Invalid bump type: {bump_type}") + + +def update_config_file(new_version: str) -> None: + """Update version and timestamp in config.py while preserving structure.""" + config_path = Path(__file__).parent.parent / "config.py" + + if not config_path.exists(): + raise FileNotFoundError(f"config.py not found at {config_path}") + + # Read the current content + content = config_path.read_text() + + # Update version using regex to preserve formatting + version_pattern = r'(__version__\s*=\s*["\'])[\d\.]+(["\'])' + content = re.sub(version_pattern, rf'\g<1>{new_version}\g<2>', content) + + # Update the __updated__ field with current date + current_date = datetime.now().strftime("%Y-%m-%d") + updated_pattern = r'(__updated__\s*=\s*["\'])[\d\-]+(["\'])' + content = re.sub(updated_pattern, rf'\g<1>{current_date}\g<2>', content) + + # Write back the updated content + config_path.write_text(content) + print(f"Updated config.py: version={new_version}, updated={current_date}") + + +def get_current_version() -> str: + """Extract current version from config.py.""" + config_path = Path(__file__).parent.parent / "config.py" + + if not config_path.exists(): + raise FileNotFoundError(f"config.py not found at {config_path}") + + content = config_path.read_text() + match = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content) + + if not match: + raise ValueError("Could not find __version__ in config.py") + + return match.group(1) + + +def main(): + """Main entry point for version bumping.""" + if len(sys.argv) != 2: + print("Usage: python bump_version.py ") + sys.exit(1) + + bump_type = sys.argv[1].lower() + if bump_type not in ["major", "minor", "patch"]: + print(f"Invalid bump type: {bump_type}") + print("Valid types: major, minor, patch") + sys.exit(1) + + try: + # Get current version + current = get_current_version() + print(f"Current version: {current}") + + # Parse and bump version + version_tuple = parse_version(current) + new_version_tuple = bump_version(version_tuple, bump_type) + new_version = f"{new_version_tuple[0]}.{new_version_tuple[1]}.{new_version_tuple[2]}" + + # Update config file + update_config_file(new_version) + + # Output new version for GitHub Actions + print(f"New version: {new_version}") + print(f"::set-output name=version::{new_version}") + + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file