name: Publish to PyPI on: workflow_dispatch: inputs: version: description: "What to publish" type: choice required: true options: - "patch (1.5.1 → 1.5.2)" - "minor (1.5.1 → 1.6.0)" - "major (1.5.1 → 2.0.0)" - "patch-rc (1.5.1 → 1.5.2.rc1)" - "minor-rc (1.5.1 → 1.6.0.rc1)" - "major-rc (1.5.1 → 2.0.0.rc1)" - "next-rc (.rc1 → .rc2)" - "promote (1.6.0.rc2 → 1.6.0)" - "republish (retry failed publish, no version bump)" branch: description: "Branch to publish from (default: main)" type: string required: false default: "main" pull_request: types: [closed] permissions: contents: write pull-requests: write id-token: write actions: write jobs: # ── Create a version bump PR ──────────────────────────────── bump: name: Create release PR if: | github.event_name == 'workflow_dispatch' && !startsWith(inputs.version, 'republish') runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: ref: ${{ inputs.branch }} - name: Guard non-main branches env: VERSION: ${{ inputs.version }} BRANCH: ${{ inputs.branch }} run: | key=$(echo "$VERSION" | awk '{print $1}') if [ "$BRANCH" != "main" ]; then case "$key" in *-rc|next-rc) ;; # allowed *) echo "::error::Only RC releases are allowed from non-main branches (got '$key' on '$BRANCH')"; exit 1 ;; esac fi - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: "3.10" - name: Install dependencies run: pip install click packaging pyyaml colorama - name: Bump versions env: VERSION: ${{ inputs.version }} run: | key=$(echo "$VERSION" | awk '{print $1}') case "$key" in patch|minor|major) python .github/update_versions.py --bump-type "$key" ;; patch-rc|minor-rc|major-rc) bump="${key%-rc}" python .github/update_versions.py --bump-type "$bump" python .github/update_versions.py --pre rc ;; next-rc) python .github/update_versions.py --pre rc ;; promote) python .github/update_versions.py --bump-type release ;; esac - name: Read new version id: version run: | version=$(grep -m1 '__version__' livekit-agents/livekit/agents/version.py | sed 's/.*"\(.*\)".*/\1/') echo "version=$version" >> "$GITHUB_OUTPUT" echo "New version: $version" - name: Close existing release PRs env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh pr list --state open --json number,headRefName \ --jq '.[] | select(.headRefName | startswith("release/v")) | .number' | while read -r pr; do echo "Superseding release PR #$pr" gh pr comment "$pr" --body "Superseded by a new release." gh pr close "$pr" --delete-branch || true done - name: Create release PR env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.version.outputs.version }} BASE_BRANCH: ${{ inputs.branch }} run: | branch="release/v${VERSION}" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git checkout -b "$branch" git add -A git commit -m "v${VERSION}" git push --force origin "$branch" gh pr create \ --base "$BASE_BRANCH" \ --head "$branch" \ --title "livekit-agents@${VERSION}" \ --body "Merging this PR will publish all packages as **${VERSION}** to PyPI." # ── Discover, build, publish ──────────────────────────────── # Triggered by: merging a release PR OR republish dispatch discover: name: Discover packages if: | (github.event_name == 'pull_request' && github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v')) || (github.event_name == 'workflow_dispatch' && startsWith(inputs.version, 'republish')) runs-on: ubuntu-latest outputs: packages: ${{ steps.list.outputs.packages }} steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: ref: ${{ inputs.branch || github.event.pull_request.merge_commit_sha }} - name: List publishable packages id: list run: | packages=() packages+=("livekit-agents") for dir in livekit-plugins/livekit-plugins-*; do if [ ! -d "$dir" ]; then continue; fi name="" if [ -f "$dir/pyproject.toml" ]; then name=$(grep -m1 '^name\s*=\s*"' "$dir/pyproject.toml" | sed 's/.*"\(.*\)".*/\1/') fi if [ -z "$name" ] && [ -f "$dir/setup.py" ]; then name=$(grep -m1 'name\s*=\s*"' "$dir/setup.py" | sed 's/.*"\(.*\)".*/\1/') fi if [ -z "$name" ]; then name=$(basename "$dir") fi packages+=("$name") done json=$(printf '%s\n' "${packages[@]}" | jq -R . | jq -sc .) echo "packages=$json" >> "$GITHUB_OUTPUT" echo "Will publish: $json" tag: name: Tag release needs: discover if: | github.event_name == 'pull_request' && github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 with: ref: ${{ github.event.pull_request.merge_commit_sha }} - name: Create git tag env: HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | # HEAD_REF is release/v1.5.2 → extract 1.5.2 version="${HEAD_REF#release/v}" tag="livekit-agents@${version}" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git tag "$tag" git push origin "$tag" build: name: Build ${{ matrix.package }} needs: discover strategy: fail-fast: false matrix: package: ${{ fromJson(needs.discover.outputs.packages) }} uses: ./.github/workflows/build.yml with: package: ${{ matrix.package }} artifact_name: dist-${{ matrix.package }} publish: name: Publish ${{ matrix.package }} needs: [discover, build] if: always() && needs.discover.result == 'success' runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 10 matrix: package: ${{ fromJson(needs.discover.outputs.packages) }} environment: pypi permissions: id-token: write steps: - name: Download build artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: dist-${{ matrix.package }} path: dist/ - name: List distributions run: ls -la dist/ - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1 docs: name: Publish docs needs: [publish] if: always() && needs.publish.result == 'success' uses: ./.github/workflows/publish-docs.yml secrets: inherit deploy-examples: name: Deploy examples needs: [publish] if: always() && needs.publish.result == 'success' uses: ./.github/workflows/deploy-examples.yml with: ref: ${{ inputs.branch || github.event.pull_request.merge_commit_sha }} secrets: inherit dispatch-downstream-bumps: name: Bump livekit-agents in downstream repos needs: [publish] # Only fire for stable releases merged via a release/vX.Y.Z PR. # RC head refs look like release/v1.5.13rc1 and won't match the regex below. # Republish retries should use internal-actions' workflow_dispatch fallback. if: | always() && needs.publish.result == 'success' && github.event_name == 'pull_request' && github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - name: Resolve stable version from head ref id: version env: HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | version="${HEAD_REF#release/v}" if ! echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then echo "::notice::Head ref '$HEAD_REF' is not a stable release — skipping downstream bump." exit 0 fi echo "version=$version" >> "$GITHUB_OUTPUT" - name: Dispatch to internal-actions if: steps.version.outputs.version != '' env: GH_TOKEN: ${{ secrets.SYNC_DISPATCH_TOKEN }} TARGET_REPO: ${{ vars.TARGET_REPO }} VERSION: ${{ steps.version.outputs.version }} run: | gh api -X POST "repos/${TARGET_REPO}/dispatches" \ -f event_type="livekit-agents-published" \ -f client_payload[version]="$VERSION" \ -f client_payload[source_repo]="${{ github.repository }}" \ -f client_payload[source_sha]="${{ github.event.pull_request.merge_commit_sha }}"