Merge pull request #217 from svnlto/main
feat: streamline GitHub Actions workflows and improve contributor experience
This commit is contained in:
@@ -50,7 +50,6 @@ pytest.ini
|
||||
.env
|
||||
.env.local
|
||||
examples/
|
||||
scripts/bump_version.py
|
||||
code_quality_checks.sh
|
||||
run_integration_tests.sh
|
||||
|
||||
|
||||
43
.github/pull_request_template.md
vendored
43
.github/pull_request_template.md
vendored
@@ -1,28 +1,35 @@
|
||||
## PR Title Format
|
||||
|
||||
**Please ensure your PR title follows one of these formats:**
|
||||
**Please ensure your PR title follows [Conventional Commits](https://www.conventionalcommits.org/) format:**
|
||||
|
||||
### Version Bumping Prefixes (trigger Docker build + version bump):
|
||||
- `feat: <description>` - New features (triggers MINOR version bump)
|
||||
- `fix: <description>` - Bug fixes (triggers PATCH version bump)
|
||||
- `breaking: <description>` or `BREAKING CHANGE: <description>` - Breaking changes (triggers MAJOR version bump)
|
||||
- `perf: <description>` - Performance improvements (triggers PATCH version bump)
|
||||
- `refactor: <description>` - Code refactoring (triggers PATCH version bump)
|
||||
### Version Bumping Types (trigger semantic release):
|
||||
- `feat: <description>` - New features → **MINOR** version bump (1.1.0 → 1.2.0)
|
||||
- `fix: <description>` - Bug fixes → **PATCH** version bump (1.1.0 → 1.1.1)
|
||||
- `perf: <description>` - Performance improvements → **PATCH** version bump (1.1.0 → 1.1.1)
|
||||
|
||||
### Non-Version Prefixes (no version bump):
|
||||
- `docs: <description>` - Documentation only
|
||||
### Breaking Changes (trigger MAJOR version bump):
|
||||
For breaking changes, use any commit type above with `BREAKING CHANGE:` in the commit body or `!` after the type:
|
||||
- `feat!: <description>` → **MAJOR** version bump (1.1.0 → 2.0.0)
|
||||
- `fix!: <description>` → **MAJOR** version bump (1.1.0 → 2.0.0)
|
||||
|
||||
### Non-Versioning Types (no release):
|
||||
- `build: <description>` - Build system changes
|
||||
- `chore: <description>` - Maintenance tasks
|
||||
- `test: <description>` - Test additions/changes
|
||||
- `ci: <description>` - CI/CD changes
|
||||
- `style: <description>` - Code style changes
|
||||
- `docs: <description>` - Documentation only
|
||||
- `refactor: <description>` - Code refactoring (no functional changes)
|
||||
- `style: <description>` - Code style/formatting changes
|
||||
- `test: <description>` - Test additions/changes
|
||||
|
||||
### Docker Build Options:
|
||||
- `docker: <description>` - Force Docker build without version bump
|
||||
- `docs+docker: <description>` - Documentation + Docker build
|
||||
- `chore+docker: <description>` - Maintenance + Docker build
|
||||
- `test+docker: <description>` - Tests + Docker build
|
||||
- `ci+docker: <description>` - CI changes + Docker build
|
||||
- `style+docker: <description>` - Style changes + Docker build
|
||||
### Docker Build Triggering:
|
||||
|
||||
Docker builds are **independent** of versioning and trigger based on:
|
||||
|
||||
**Automatic**: When PRs modify relevant files:
|
||||
- Python files (`*.py`), `requirements*.txt`, `pyproject.toml`
|
||||
- Docker files (`Dockerfile`, `docker-compose.yml`, `.dockerignore`)
|
||||
|
||||
**Manual**: Add the `docker-build` label to force builds for any PR.
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
248
.github/workflows/auto-version.yml
vendored
248
.github/workflows/auto-version.yml
vendored
@@ -1,248 +0,0 @@
|
||||
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.PAT }}
|
||||
|
||||
- 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
|
||||
echo "should_build_docker=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
|
||||
echo "should_build_docker=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
|
||||
echo "should_build_docker=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "$PR_TITLE_LOWER" =~ ^docker: ]]; then
|
||||
echo "Detected docker build request - no version bump but build Docker"
|
||||
echo "bump_type=none" >> $GITHUB_OUTPUT
|
||||
echo "should_bump=false" >> $GITHUB_OUTPUT
|
||||
echo "should_build_docker=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "$PR_TITLE_LOWER" =~ ^(docs|chore|test|ci|style)\+docker: ]]; then
|
||||
echo "Detected non-versioned change with Docker build request"
|
||||
echo "bump_type=none" >> $GITHUB_OUTPUT
|
||||
echo "should_bump=false" >> $GITHUB_OUTPUT
|
||||
echo "should_build_docker=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
|
||||
echo "should_build_docker=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No recognized prefix - no version bump"
|
||||
echo "bump_type=none" >> $GITHUB_OUTPUT
|
||||
echo "should_bump=false" >> $GITHUB_OUTPUT
|
||||
echo "should_build_docker=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: Trigger Docker build
|
||||
if: steps.bump_type.outputs.should_build_docker == 'true'
|
||||
run: |
|
||||
echo "🐳 Triggering Docker build and publish workflow"
|
||||
# The Docker workflow will be triggered by the tag creation (if version bumped)
|
||||
# or by repository_dispatch (if docker: prefix without version bump)
|
||||
if [ "${{ steps.bump_type.outputs.should_bump }}" == "false" ]; then
|
||||
# For docker: prefix without version bump, trigger via repository_dispatch
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/dispatches" \
|
||||
-d '{"event_type":"docker-build","client_payload":{"pr_number":"${{ github.event.pull_request.number }}","pr_title":"${{ github.event.pull_request.title }}","commit_sha":"${{ github.sha }}"}}'
|
||||
|
||||
# Add comment to PR about Docker build
|
||||
COMMENT_BODY="🐳 **Docker Image Build Triggered**
|
||||
|
||||
This PR triggered a Docker image build because of the \`+docker\` suffix in the title.
|
||||
|
||||
**Expected Image Tags:**
|
||||
- \`ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}\`
|
||||
- \`ghcr.io/${{ github.repository_owner }}/zen-mcp-server:main-${{ github.sha }}\`
|
||||
|
||||
**To test the image after build completes:**
|
||||
\`\`\`bash
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}
|
||||
\`\`\`
|
||||
|
||||
**Claude Desktop config for testing:**
|
||||
\`\`\`json
|
||||
{
|
||||
\"mcpServers\": {
|
||||
\"gemini\": {
|
||||
\"command\": \"docker\",
|
||||
\"args\": [
|
||||
\"run\", \"--rm\", \"-i\",
|
||||
\"-e\", \"GEMINI_API_KEY\",
|
||||
\"ghcr.io/${{ github.repository_owner }}/zen-mcp-server:pr-${{ github.event.pull_request.number }}\"
|
||||
],
|
||||
\"env\": {
|
||||
\"GEMINI_API_KEY\": \"your-api-key-here\"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
View the build progress in the [Actions tab](https://github.com/${{ github.repository }}/actions)."
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
||||
-d "{\"body\":\"$COMMENT_BODY\"}"
|
||||
fi
|
||||
|
||||
- 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
|
||||
echo "- **Docker**: Will build and publish with new tag" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.bump_type.outputs.should_build_docker }}" == "true" ]; then
|
||||
echo "### 🐳 Docker Build Requested" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No version bump but Docker image will be built and published." >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **PR**: #${{ github.event.pull_request.number }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Title**: ${{ github.event.pull_request.title }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Docker tag**: Based on commit SHA" >> $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
|
||||
|
||||
129
.github/workflows/docker-pr.yml
vendored
Normal file
129
.github/workflows/docker-pr.yml
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
name: PR Docker Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
paths:
|
||||
- '**.py'
|
||||
- 'requirements*.txt'
|
||||
- 'pyproject.toml'
|
||||
- 'Dockerfile'
|
||||
- 'docker-compose.yml'
|
||||
- '.dockerignore'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: Build Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.action == 'opened' ||
|
||||
github.event.action == 'synchronize' ||
|
||||
github.event.action == 'reopened' ||
|
||||
contains(github.event.pull_request.labels.*.name, 'docker-build')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
# PR-specific tag for testing
|
||||
type=raw,value=pr-${{ github.event.number }}-${{ github.sha }}
|
||||
type=raw,value=pr-${{ github.event.number }}
|
||||
|
||||
- name: Build and push Docker image (internal PRs)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build Docker image (fork PRs)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: false
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Add Docker build comment (internal PRs)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
|
||||
with:
|
||||
header: docker-build
|
||||
message: |
|
||||
## 🐳 Docker Build Complete
|
||||
|
||||
**PR**: #${{ github.event.number }} | **Commit**: `${{ github.sha }}`
|
||||
|
||||
```
|
||||
${{ steps.meta.outputs.tags }}
|
||||
```
|
||||
|
||||
**Test:** `docker pull ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}`
|
||||
|
||||
**Claude config:**
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"zen": {
|
||||
"command": "docker",
|
||||
"args": ["run", "--rm", "-i", "-e", "GEMINI_API_KEY", "ghcr.io/${{ github.repository }}:pr-${{ github.event.number }}"],
|
||||
"env": { "GEMINI_API_KEY": "your-key" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
💡 Add `docker-build` label to manually trigger builds
|
||||
|
||||
|
||||
- name: Update job summary (internal PRs)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
{
|
||||
echo "## 🐳 Docker Build Complete"
|
||||
echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}"
|
||||
echo '```'
|
||||
echo "${{ steps.meta.outputs.tags }}"
|
||||
echo '```'
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Update job summary (fork PRs)
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
run: |
|
||||
{
|
||||
echo "## 🐳 Docker Build Complete (Build Only)"
|
||||
echo "**PR**: #${{ github.event.number }} | **Commit**: ${{ github.sha }}"
|
||||
echo "✅ Multi-platform Docker build successful"
|
||||
echo "Note: Fork PRs only build (no push) for security"
|
||||
} >> $GITHUB_STEP_SUMMARY
|
||||
116
.github/workflows/docker-release.yml
vendored
Normal file
116
.github/workflows/docker-release.yml
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
name: Docker Release Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to build (leave empty for latest release)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
name: Build and Push Docker Image
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# If triggered by workflow_dispatch with a tag, checkout that tag
|
||||
ref: ${{ inputs.tag || github.event.release.tag_name }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
# Tag with the release version
|
||||
type=semver,pattern={{version}},value=${{ inputs.tag || github.event.release.tag_name }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ inputs.tag || github.event.release.tag_name }}
|
||||
type=semver,pattern={{major}},value=${{ inputs.tag || github.event.release.tag_name }}
|
||||
# Also tag as latest for the most recent release
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Update release with Docker info
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
RELEASE_TAG="${{ github.event.release.tag_name }}"
|
||||
DOCKER_TAGS=$(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' ')
|
||||
|
||||
# Add Docker information to the release
|
||||
gh release edit "$RELEASE_TAG" --notes-file - << EOF
|
||||
${{ github.event.release.body }}
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker Images
|
||||
|
||||
This release is available as Docker images:
|
||||
|
||||
$(echo "$DOCKER_TAGS" | sed 's/ghcr.io/- `ghcr.io/g' | sed 's/ /`\n/g')
|
||||
|
||||
**Quick start with Docker:**
|
||||
\`\`\`bash
|
||||
docker pull ghcr.io/${{ github.repository }}:$RELEASE_TAG
|
||||
\`\`\`
|
||||
|
||||
**Claude Desktop configuration:**
|
||||
\`\`\`json
|
||||
{
|
||||
"mcpServers": {
|
||||
"zen-mcp-server": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run", "--rm", "-i",
|
||||
"-e", "GEMINI_API_KEY",
|
||||
"ghcr.io/${{ github.repository }}:$RELEASE_TAG"
|
||||
],
|
||||
"env": {
|
||||
"GEMINI_API_KEY": "your-api-key-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
EOF
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create deployment summary
|
||||
run: |
|
||||
echo "## 🐳 Docker Release Build Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Release**: ${{ inputs.tag || github.event.release.tag_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Images built:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
47
.github/workflows/semantic-pr.yml
vendored
Normal file
47
.github/workflows/semantic-pr.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Semantic PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, synchronize]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
semantic-pr:
|
||||
name: Validate PR
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check PR Title
|
||||
id: lint-pr-title
|
||||
uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Add PR error comment
|
||||
uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
|
||||
if: always() && (steps.lint-pr-title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
|
||||
|
||||
Details:
|
||||
|
||||
```
|
||||
${{ steps.lint-pr-title.outputs.error_message }}
|
||||
```
|
||||
|
||||
- name: Delete PR error comment
|
||||
uses: marocchino/sticky-pull-request-comment@d2ad0de260ae8b0235ce059e63f2949ba9e05943 # v2.9.3
|
||||
if: ${{ steps.lint-pr-title.outputs.error_message == null }}
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
61
.github/workflows/semantic-release.yml
vendored
Normal file
61
.github/workflows/semantic-release.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Semantic Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency: release
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
persist-credentials: true
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install python-semantic-release
|
||||
|
||||
- name: Verify tests pass
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration"
|
||||
|
||||
- name: Run semantic release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
semantic-release version
|
||||
semantic-release publish
|
||||
|
||||
- name: Upload build artifacts to release
|
||||
if: hashFiles('dist/*') != ''
|
||||
run: |
|
||||
# Get the latest release tag
|
||||
LATEST_TAG=$(gh release list --limit 1 --json tagName --jq '.[0].tagName')
|
||||
if [ ! -z "$LATEST_TAG" ]; then
|
||||
echo "Uploading artifacts to release $LATEST_TAG"
|
||||
gh release upload "$LATEST_TAG" dist/* --clobber
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
81
.github/workflows/test.yml
vendored
81
.github/workflows/test.yml
vendored
@@ -1,10 +1,8 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -14,47 +12,46 @@ jobs:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Run only unit tests (exclude simulation tests and integration tests)
|
||||
# Integration tests require local-llama which isn't available in CI
|
||||
python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration"
|
||||
env:
|
||||
# Ensure no API key is accidentally used in CI
|
||||
GEMINI_API_KEY: ""
|
||||
OPENAI_API_KEY: ""
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
# Run only unit tests (exclude simulation tests and integration tests)
|
||||
# Integration tests require local-llama which isn't available in CI
|
||||
python -m pytest tests/ -v --ignore=simulator_tests/ -m "not integration"
|
||||
env:
|
||||
# Ensure no API key is accidentally used in CI
|
||||
GEMINI_API_KEY: ""
|
||||
OPENAI_API_KEY: ""
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run black formatter check
|
||||
run: black --check . --exclude="test_simulation_files/"
|
||||
|
||||
- name: Run ruff linter
|
||||
run: ruff check . --exclude test_simulation_files
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
- name: Run black formatter check
|
||||
run: black --check . --exclude="test_simulation_files/"
|
||||
|
||||
- name: Run ruff linter
|
||||
run: ruff check . --exclude test_simulation_files
|
||||
|
||||
38
.pre-commit-config.yaml
Normal file
38
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
default_stages: [pre-commit, pre-push]
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 6.0.1
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.8
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
|
||||
# Configuration for specific tools
|
||||
default_language_version:
|
||||
python: python3
|
||||
|
||||
# Exclude patterns
|
||||
exclude: |
|
||||
(?x)^(
|
||||
\.git/|
|
||||
\.venv/|
|
||||
venv/|
|
||||
\.zen_venv/|
|
||||
__pycache__/|
|
||||
\.pytest_cache/|
|
||||
logs/|
|
||||
dist/|
|
||||
build/|
|
||||
test_simulation_files/
|
||||
)
|
||||
@@ -23,8 +23,16 @@ We maintain high code quality standards. **All contributions must pass our autom
|
||||
|
||||
#### Required Code Quality Checks
|
||||
|
||||
Before submitting any PR, run our automated quality check script:
|
||||
**Option 1 - Automated (Recommended):**
|
||||
```bash
|
||||
# Install pre-commit hooks (one-time setup)
|
||||
pre-commit install
|
||||
|
||||
# Now linting runs automatically on every commit
|
||||
# Includes: ruff (with auto-fix), black, isort
|
||||
```
|
||||
|
||||
**Option 2 - Manual:**
|
||||
```bash
|
||||
# Run the comprehensive quality checks script
|
||||
./code_quality_checks.sh
|
||||
@@ -32,7 +40,7 @@ Before submitting any PR, run our automated quality check script:
|
||||
|
||||
This script automatically runs:
|
||||
- Ruff linting with auto-fix
|
||||
- Black code formatting
|
||||
- Black code formatting
|
||||
- Import sorting with isort
|
||||
- Complete unit test suite (361 tests)
|
||||
- Verification that all checks pass 100%
|
||||
@@ -56,7 +64,7 @@ python -m pytest -xvs
|
||||
python communication_simulator_test.py
|
||||
```
|
||||
|
||||
**Important**:
|
||||
**Important**:
|
||||
- **Every single test must pass** - we have zero tolerance for failing tests in CI
|
||||
- All linting must pass cleanly (ruff, black, isort)
|
||||
- Import sorting must be correct
|
||||
@@ -69,12 +77,12 @@ python communication_simulator_test.py
|
||||
1. **New features MUST include tests**:
|
||||
- Add unit tests in `tests/` for new functions or classes
|
||||
- Test both success and error cases
|
||||
|
||||
|
||||
2. **Tool changes require simulator tests**:
|
||||
- Add simulator tests in `simulator_tests/` for new or modified tools
|
||||
- Use realistic prompts that demonstrate the feature
|
||||
- Validate output through server logs
|
||||
|
||||
|
||||
3. **Bug fixes require regression tests**:
|
||||
- Add a test that would have caught the bug
|
||||
- Ensure the bug cannot reoccur
|
||||
@@ -136,14 +144,14 @@ def process_model_response(
|
||||
max_tokens: Optional[int] = None
|
||||
) -> ProcessedResult:
|
||||
"""Process and validate model response.
|
||||
|
||||
|
||||
Args:
|
||||
response: Raw response from the model provider
|
||||
max_tokens: Optional token limit for truncation
|
||||
|
||||
|
||||
Returns:
|
||||
ProcessedResult with validated and formatted content
|
||||
|
||||
|
||||
Raises:
|
||||
ValueError: If response is invalid or exceeds limits
|
||||
"""
|
||||
@@ -237,4 +245,4 @@ Contributors are recognized in:
|
||||
- Release notes for significant contributions
|
||||
- Special mentions for exceptional work
|
||||
|
||||
Thank you for contributing to Zen MCP Server! Your efforts help make this tool better for everyone.
|
||||
Thank you for contributing to Zen MCP Server! Your efforts help make this tool better for everyone.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "zen-mcp-server"
|
||||
version = "0.1.0"
|
||||
version = "1.1.0"
|
||||
description = "AI-powered MCP server with multiple model providers"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
@@ -84,6 +84,33 @@ ignore = [
|
||||
"tests/*" = ["B011"]
|
||||
"tests/conftest.py" = ["E402"] # Module level imports not at top of file - needed for test setup
|
||||
|
||||
[tool.semantic_release]
|
||||
version_toml = ["pyproject.toml:project.version"]
|
||||
branch = "main"
|
||||
build_command = "python -m pip install --upgrade build && python -m build"
|
||||
dist_path = "dist/"
|
||||
upload_to_vcs_release = true
|
||||
upload_to_repository = false
|
||||
remove_dist = false
|
||||
commit_version_number = true
|
||||
commit_message = "chore(release): {version}\n\nAutomatically generated by python-semantic-release"
|
||||
tag_format = "v{version}"
|
||||
|
||||
[tool.semantic_release.branches.main]
|
||||
match = "main"
|
||||
prerelease = false
|
||||
|
||||
[tool.semantic_release.changelog]
|
||||
exclude_commit_patterns = []
|
||||
|
||||
[tool.semantic_release.commit_parser_options]
|
||||
allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"]
|
||||
minor_tags = ["feat"]
|
||||
patch_tags = ["fix", "perf"]
|
||||
|
||||
[tool.semantic_release.remote.token]
|
||||
env = "GH_TOKEN"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
@@ -3,4 +3,6 @@ pytest-asyncio>=0.21.0
|
||||
pytest-mock>=3.11.0
|
||||
black>=23.0.0
|
||||
ruff>=0.1.0
|
||||
isort>=5.12.0
|
||||
isort>=5.12.0
|
||||
python-semantic-release>=10.3.0
|
||||
build>=1.0.0
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/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
|
||||
|
||||
|
||||
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 <major|minor|patch>")
|
||||
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}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user