# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# ReleaseKit: Automated Release Pipeline
#
# This workflow implements a release-please-style pipeline for the
# genkit Python SDK. It uses releasekit to automate:
#
# 1. PREPARE: On push to main, compute version bumps, generate
# changelogs, and open/update a Release PR.
# 2. RELEASE: When a Release PR is merged, tag the merge commit,
# create a GitHub Release, and trigger publishing.
# 3. PUBLISH: Build and publish packages to PyPI in topological order.
#
# Flow:
#
# push to main ──► releasekit prepare ──► Release PR
# │
# merge PR
# │
# ▼
# releasekit release ──► tags + GitHub Release
# │
# ▼
# releasekit publish ──► PyPI
#
# The workflow is idempotent: re-running any step is safe because
# releasekit skips already-created tags and already-published versions.
name: "ReleaseKit: Python (uv)"
on:
push:
branches: [main]
paths:
- "py/packages/**"
- "py/plugins/**"
pull_request:
types: [closed]
branches: [main]
# Only one release pipeline runs at a time.
concurrency:
group: releasekit-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write # Create tags, releases, and push to release branch
pull-requests: write # Open/update Release PRs, manage labels
env:
RELEASEKIT_DIR: py/tools/releasekit
WORKSPACE_DIR: py
jobs:
# ═══════════════════════════════════════════════════════════════════════
# PREPARE: Compute bumps and open/update Release PR
#
# Runs on every push to main that touches package or plugin code.
# Creates a Release PR with version bumps, changelogs, and an
# embedded JSON manifest.
# ═══════════════════════════════════════════════════════════════════════
prepare:
name: Prepare Release PR
if: github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
has_bumps: ${{ steps.prepare.outputs.has_bumps }}
pr_url: ${{ steps.prepare.outputs.pr_url }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # Full history needed for conventional commit parsing
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install uv and setup Python
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: "3.12"
- name: Install releasekit
run: |
cd ${{ env.RELEASEKIT_DIR }}
uv sync
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Run releasekit prepare
id: prepare
run: |
cd ${{ env.WORKSPACE_DIR }}
OUTPUT=$(uv run --directory ../tools/releasekit releasekit prepare 2>&1) || {
echo "::warning::releasekit prepare exited with non-zero status"
echo "has_bumps=false" >> "$GITHUB_OUTPUT"
echo "$OUTPUT"
exit 0
}
echo "$OUTPUT"
# Parse output for PR URL
PR_URL=$(echo "$OUTPUT" | grep -oP 'Release PR: \K.*' || echo "")
if [ -n "$PR_URL" ]; then
echo "has_bumps=true" >> "$GITHUB_OUTPUT"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
else
echo "has_bumps=false" >> "$GITHUB_OUTPUT"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ═══════════════════════════════════════════════════════════════════════
# RELEASE: Tag merge commit and create GitHub Release
#
# Runs when a Release PR (labeled "autorelease: pending") is merged.
# Extracts the manifest from the PR body and creates tags + Release.
# ═══════════════════════════════════════════════════════════════════════
release:
name: Tag and Release
if: |
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'autorelease: pending')
runs-on: ubuntu-latest
outputs:
release_url: ${{ steps.release.outputs.release_url }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install uv and setup Python
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: "3.12"
- name: Install releasekit
run: |
cd ${{ env.RELEASEKIT_DIR }}
uv sync
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Run releasekit release
id: release
run: |
cd ${{ env.WORKSPACE_DIR }}
OUTPUT=$(uv run --directory ../tools/releasekit releasekit release 2>&1)
echo "$OUTPUT"
# Parse release URL
RELEASE_URL=$(echo "$OUTPUT" | grep -oP 'release_url=\K.*' || echo "")
echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ═══════════════════════════════════════════════════════════════════════
# PUBLISH: Build and publish packages to PyPI
#
# Runs after the release job completes. Publishes packages in
# topological order with retry and ephemeral version pinning.
# ═══════════════════════════════════════════════════════════════════════
publish:
name: Publish to PyPI
needs: release
runs-on: ubuntu-latest
environment: pypi # Requires manual approval if configured
permissions:
id-token: write # Trusted publishing (OIDC)
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends build-essential libffi-dev
- name: Install uv and setup Python
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: "3.12"
- name: Install workspace + releasekit
run: |
cd ${{ env.WORKSPACE_DIR }}
uv sync
cd tools/releasekit
uv sync
- name: Run releasekit publish
run: |
cd ${{ env.WORKSPACE_DIR }}
uv run --directory tools/releasekit releasekit publish \
--force \
--check-url
env:
# For trusted publishing (OIDC), no token needed.
# For API token auth, set PYPI_TOKEN in repo secrets.
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
- name: Upload manifest artifact
uses: actions/upload-artifact@v4
with:
name: release-manifest
path: ${{ env.WORKSPACE_DIR }}/release-manifest.json
retention-days: 90
# ═══════════════════════════════════════════════════════════════════════
# NOTIFY: Post-release notifications
#
# Fires a repository_dispatch event so downstream repos (e.g.
# genkit-community-plugins) can update their dependencies.
# ═══════════════════════════════════════════════════════════════════════
notify:
name: Notify Downstream
needs: publish
runs-on: ubuntu-latest
steps:
- name: Dispatch release event
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
event-type: genkit-python-release
client-payload: '{"release_url": "${{ needs.release.outputs.release_url }}"}'