Skip to content

Release to PyPI

Release to PyPI #2

Workflow file for this run

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