Release to PyPI #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Release to PyPI | |
on: | |
release: | |
types: [published] | |
workflow_dispatch: | |
inputs: | |
version: | |
description: 'Version to release (e.g., 0.3.0)' | |
required: true | |
type: string | |
environment: | |
description: 'Target environment' | |
required: true | |
default: 'pypi' | |
type: choice | |
options: | |
- pypi | |
- testpypi | |
dry_run: | |
description: 'Dry run (build only, do not publish)' | |
required: false | |
default: false | |
type: boolean | |
permissions: | |
contents: read | |
jobs: | |
validate-release: | |
runs-on: ubuntu-latest | |
outputs: | |
version: ${{ steps.get-version.outputs.version }} | |
tag-version: ${{ steps.get-version.outputs.tag-version }} | |
steps: | |
- name: ⚙️ Harden Runner | |
uses: step-security/harden-runner@v2 | |
with: | |
egress-policy: audit | |
- name: ⚙️ Checkout the project | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # Full history for UV build | |
- name: ⚙️ Install uv | |
uses: astral-sh/setup-uv@v6 | |
with: | |
version: "latest" | |
- name: ⚙️ Set up Python | |
run: uv python install 3.12 | |
security-scan: | |
runs-on: ubuntu-latest | |
needs: validate-release | |
steps: | |
- name: ⚙️ Harden Runner | |
uses: step-security/harden-runner@v2 | |
with: | |
egress-policy: audit | |
- name: ⚙️ Checkout the project | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: ⚙️ Install uv | |
uses: astral-sh/setup-uv@v6 | |
with: | |
version: "latest" | |
- name: ⚙️ Set Python up and add dependencies | |
run: | | |
uv python install 3.12 | |
uv sync --all-extras --dev | |
uv add --dev bandit | |
- name: ⚙️ Run security scan with bandit | |
run: | | |
uv run bandit -r src/ | |
test: | |
runs-on: ubuntu-latest | |
needs: validate-release | |
strategy: | |
matrix: | |
python-version: ["3.10", "3.11", "3.12", "3.13"] | |
services: | |
redis: | |
image: redis:latest | |
ports: | |
- 6379:6379 | |
options: >- | |
--health-cmd "redis-cli ping" | |
--health-interval 10s | |
--health-timeout 5s | |
--health-retries 5 | |
steps: | |
- name: ⚙️ Harden Runner | |
uses: step-security/harden-runner@v2 | |
with: | |
egress-policy: audit | |
- name: ⚙️ Checkout the project | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- name: ⚙️ Install uv | |
uses: astral-sh/setup-uv@v6 | |
with: | |
version: "latest" | |
- name: ⚙️ Set up Python ${{ matrix.python-version }} | |
run: | | |
uv python install ${{ matrix.python-version }} | |
uv sync --all-extras --dev | |
- name: ⚙️ Run tests | |
run: uv run pytest tests/ -v --tb=short | |
env: | |
REDIS_HOST: localhost | |
REDIS_PORT: 6379 | |
- name: ⚙️ Test MCP server startup | |
run: | | |
timeout 10s uv run python src/main.py || test $? = 124 | |
env: | |
REDIS_HOST: localhost | |
REDIS_PORT: 6379 | |
build-and-publish: | |
runs-on: ubuntu-latest | |
needs: [validate-release, security-scan, test] | |
environment: | |
name: ${{ github.event.inputs.environment || 'pypi' }} | |
url: ${{ github.event.inputs.environment == 'testpypi' && 'https://test.pypi.org/p/redis-mcp-server' || 'https://pypi.org/p/redis-mcp-server' }} | |
permissions: | |
id-token: write # IMPORTANT: mandatory for trusted publishing | |
contents: read | |
attestations: write # For build attestations | |
steps: | |
- name: ⚙️ Harden Runner | |
uses: step-security/harden-runner@v2 | |
with: | |
egress-policy: audit | |
- name: ⚙️ Checkout the project | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # Full history for UV build | |
- name: ⚙️ Install uv | |
uses: astral-sh/setup-uv@v6 | |
with: | |
version: "latest" | |
- name: ⚙️ Set up Python | |
run: uv python install 3.12 | |
- name: ⚙️ Build package | |
run: | | |
uv build --sdist --wheel | |
- name: ⚙️ Check package | |
run: | | |
uv add --dev twine | |
uv run twine check dist/* | |
- name: ⚙️ Generate build attestation | |
uses: actions/attest-build-provenance@v2 | |
with: | |
subject-path: 'dist/*' | |
- name: ⚙️ Publish to PyPI | |
if: ${{ !inputs.dry_run }} | |
uses: pypa/gh-action-pypi-publish@release/v1 | |
with: | |
repository-url: ${{ github.event.inputs.environment == 'testpypi' && 'https://test.pypi.org/legacy/' || '' }} | |
print-hash: true | |
attestations: true | |
- name: ⚙️ Dry run - Package ready for publishing | |
if: ${{ inputs.dry_run }} | |
run: | | |
echo "🔍 DRY RUN MODE - Package built successfully but not published" | |
echo "📦 Built packages:" | |
ls -la dist/ | |
echo "" | |
echo "✅ Package is ready for publishing to ${{ github.event.inputs.environment || 'pypi' }}" | |
- name: ⚙️ Upload build artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: dist-${{ needs.validate-release.outputs.version }} | |
path: dist/ | |
retention-days: 90 | |
notify-success: | |
runs-on: ubuntu-latest | |
needs: [validate-release, build-and-publish] | |
if: success() | |
steps: | |
- name: ⚙️ Success notification | |
run: | | |
if [[ "${{ inputs.dry_run }}" == "true" ]]; then | |
echo "🔍 DRY RUN COMPLETED - Redis MCP Server v${{ github.event.inputs.version || needs.validate-release.outputs.version }} ready for release!" | |
echo "📦 Package built successfully but not published" | |
echo "🎯 Target environment: ${{ github.event.inputs.environment || 'pypi' }}" | |
else | |
echo "🎉 Successfully released Redis MCP Server v${{ github.event.inputs.version || needs.validate-release.outputs.version }} to ${{ github.event.inputs.environment || 'PyPI' }}!" | |
if [[ "${{ github.event.inputs.environment }}" == "testpypi" ]]; then | |
echo "📦 Package: https://test.pypi.org/project/redis-mcp-server/${{ github.event.inputs.version || needs.validate-release.outputs.version }}/" | |
else | |
echo "📦 Package: https://pypi.org/project/redis-mcp-server/${{ github.event.inputs.version || needs.validate-release.outputs.version }}/" | |
fi | |
if [[ "${{ github.event_name }}" == "release" ]]; then | |
echo "🏷️ Release: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}" | |
else | |
echo "🚀 Manual release triggered by: ${{ github.actor }}" | |
fi | |
fi |