From 92109fd44a484d68bbb2f72af58e4c96fd7cfd93 Mon Sep 17 00:00:00 2001 From: Luke Parke <5702154+LukasParke@users.noreply.github.com> Date: Thu, 22 Oct 2026 01:13:34 +0000 Subject: [PATCH 1/2] ci: publish PyPI releases directly instead of via Speakeasy Replace speakeasy-api/sdk-generation-action's sdk-publish.yaml reusable workflow with an in-repo job that reads releaseVersion from gen.lock, publishes (skipping versions already released), and creates the v GitHub release. Generation still uses Speakeasy. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/sdk_publish.yaml | 107 ++++++++++++++++++++++++++--- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/.github/workflows/sdk_publish.yaml b/.github/workflows/sdk_publish.yaml index 5a2a0339..c03d27bd 100644 --- a/.github/workflows/sdk_publish.yaml +++ b/.github/workflows/sdk_publish.yaml @@ -1,10 +1,18 @@ name: Publish + +# Publishes the openrouter package to PyPI and creates the matching GitHub +# release when a Speakeasy regen lands on main (gen.lock changes). Generation +# still runs through speakeasy-api/sdk-generation-action; publishing is done +# here directly rather than through its sdk-publish.yaml reusable workflow. +# +# Both steps are idempotent so a failed run can simply be re-run: a PyPI +# version that already exists is not re-uploaded, and an existing release is +# left alone. + permissions: - checks: write contents: write - pull-requests: write - statuses: write - id-token: write + id-token: write # allows switching to PyPI trusted publishing without workflow changes + "on": push: branches: @@ -12,12 +20,89 @@ permissions: paths: - .speakeasy/gen.lock workflow_dispatch: {} + +concurrency: + group: publish + cancel-in-progress: false + jobs: publish: - uses: speakeasy-api/sdk-generation-action/.github/workflows/sdk-publish.yaml@68f3bfefb79354d39358bf4699863dabd2cea0f6 # v15.60.11 - with: - target: open-router - secrets: - github_access_token: ${{ secrets.GITHUB_TOKEN }} - pypi_token: ${{ secrets.PYPI_TOKEN }} - speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }} + name: Publish to PyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Resolve release version + id: version + run: | + VERSION=$(yq '.management.releaseVersion' .speakeasy/gen.lock) + PKG_VERSION=$(yq -p toml -oy '.project.version' pyproject.toml) + PKG_NAME=$(yq -p toml -oy '.project.name' pyproject.toml) + if [ "$VERSION" != "$PKG_VERSION" ]; then + echo "::error::gen.lock releaseVersion ($VERSION) does not match pyproject.toml version ($PKG_VERSION)" + exit 1 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "name=$PKG_NAME" >> "$GITHUB_OUTPUT" + echo "Resolved $PKG_NAME==$VERSION" + + - name: Check PyPI for existing version + id: pypi + env: + PKG_NAME: ${{ steps.version.outputs.name }} + VERSION: ${{ steps.version.outputs.version }} + run: | + STATUS=$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/$PKG_NAME/$VERSION/json") + if [ "$STATUS" = "200" ]; then + echo "$PKG_NAME==$VERSION is already on PyPI — skipping publish" + echo "published=true" >> "$GITHUB_OUTPUT" + elif [ "$STATUS" = "404" ]; then + echo "published=false" >> "$GITHUB_OUTPUT" + else + echo "::error::Unexpected HTTP $STATUS from PyPI while checking $PKG_NAME==$VERSION" + exit 1 + fi + + - name: Install uv + if: steps.pypi.outputs.published == 'false' + uses: astral-sh/setup-uv@v5 + + - name: Build + if: steps.pypi.outputs.published == 'false' + run: | + uv run python scripts/prepare_readme.py + uv build + + - name: Publish + if: steps.pypi.outputs.published == 'false' + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: uv publish + + - name: Create GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.version }} + PKG_NAME: ${{ steps.version.outputs.name }} + run: | + TAG="v$VERSION" + if gh release view "$TAG" >/dev/null 2>&1; then + echo "Release $TAG already exists — nothing to do" + exit 0 + fi + + { + echo "[$PKG_NAME $VERSION](https://pypi.org/project/$PKG_NAME/$VERSION)" + yq '.releaseNotes // ""' .speakeasy/gen.lock + echo "Generated with [Speakeasy CLI $(yq '.management.speakeasyVersion' .speakeasy/gen.lock)](https://github.com/speakeasy-api/speakeasy/releases)" + } > "$RUNNER_TEMP/notes.md" + + ARGS=() + if [[ "$VERSION" == *"-"* ]]; then + ARGS+=(--prerelease --latest=false) + fi + gh release create "$TAG" \ + --target "$GITHUB_SHA" \ + --title "python - $TAG - $(date -u '+%Y-%m-%d %H:%M:%S')" \ + --notes-file "$RUNNER_TEMP/notes.md" \ + "${ARGS[@]}" From 38488529f7f2366fb8074a15390eb9cadcc8068a Mon Sep 17 00:00:00 2001 From: Luke Parke <5702154+LukasParke@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:50:52 +0000 Subject: [PATCH 2/2] ci: pin publish workflow actions to commit SHAs Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/sdk_publish.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sdk_publish.yaml b/.github/workflows/sdk_publish.yaml index c03d27bd..7184ee78 100644 --- a/.github/workflows/sdk_publish.yaml +++ b/.github/workflows/sdk_publish.yaml @@ -30,7 +30,7 @@ jobs: name: Publish to PyPI runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 - name: Resolve release version id: version @@ -65,7 +65,7 @@ jobs: - name: Install uv if: steps.pypi.outputs.published == 'false' - uses: astral-sh/setup-uv@v5 + uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - name: Build if: steps.pypi.outputs.published == 'false'