Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/production-spec-pagination-secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@runpod/typescript-api-sdk': minor
---

Resync the vendored production spec and regenerate the types. Adds cursor
pagination on the list endpoints (`cursor` and `limit` query parameters, and a
`pagination` block carrying `nextCursor` and `hasNextPage` on the response) and
the account-secrets paths. Both are additive upstream, so existing calls keep
their current types.
28 changes: 26 additions & 2 deletions .github/workflows/spec-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ jobs:
- run: pnpm test
- run: pnpm test:package
- run: pnpm test:examples
# The update PR carries its own version bump and changelog, so merging it
# releases. Main's pending changesets are consumed into the same version.
- name: Version the spec update

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the one I'd fix before relying on the new flow.

version.yml only runs the eligibility and publish jobs when has_changesets == 'false'. That holds if main had nothing pending when this branch was last regenerated. But the branch is only refreshed once a day, so there's a window of up to 24 hours where a changeset merges to main after the 09:23 UTC run and before someone merges the spec PR.

In that case the merge commit has package.json at 0.2.0, a ## 0.2.0 changelog entry, and one unconsumed changeset. changesets/action reports hasChangesets: true, eligibility is skipped, nothing publishes, and a new Version SDK PR opens at 0.3.0. So 0.2.0 exists in the changelog but never reaches npm, and the PR body's "merging it publishes" is wrong for that merge with no signal beyond a skipped job. (The existing changeset-release/main flow doesn't have this problem in practice because the action re-pushes that branch on every push to main.)

Suggested fix: drop the has_changesets == 'false' condition on the eligibility job in version.yml. release.mjs plan already requires a merged PR from a release branch whose merge commit is exactly this SHA, so evaluating it on every push to main is safe, and in the race case it publishes 0.2.0 while the version job opens the 0.3.0 PR in parallel. Happy for that to be a follow-up if you'd rather keep this PR to what it is.

env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if git diff --quiet -- spec/openapi.yaml src/generated/schema.ts; then
echo 'No spec change; nothing to version.'
exit 0
fi
cat > .changeset/production-spec-update.md <<EOF
---
'@runpod/typescript-api-sdk': minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, design note. This replaces "classify the bump explicitly" with a fixed minor. On 0.x that's the right maximal choice, since caret ranges don't cross a 0.x minor anyway. After 1.0 a removed operation would still ship as a minor.

The escape hatch already exists: land a major changeset on main and re-dispatch this workflow, and Changesets takes the highest bump. Worth one sentence in docs/development.md so a reviewer of a bot PR knows to do that instead of editing the bot branch, which gets overwritten.

---

Refresh the vendored production spec as of $(date -u +%Y-%m-%d) and
regenerate the types. Validation run: $RUN_URL
EOF
pnpm changeset:version
# Run even with no diff so the action can close a superseded update PR.
- name: Create or update the spec review PR
id: update-pr
Expand All @@ -53,6 +72,10 @@ jobs:
add-paths: |
spec/openapi.yaml
src/generated/schema.ts
.changeset
package.json
CHANGELOG.md
pnpm-lock.yaml
commit-message: 'chore(spec): refresh production API contract'
title: 'chore(spec): review production API contract update'
body: |
Expand All @@ -67,11 +90,12 @@ jobs:
Check those results before merging.

Review removed or changed operations, request/response types, and compatibility.
Classify the eventual version bump explicitly; a spec diff is not necessarily a patch.
This PR bumps the version as a minor release and writes the changelog.
**Merging it publishes that version to npm** and creates the tag and GitHub release.
This branch is regenerated from the default branch: put durable fixes there,
rather than editing the bot branch.

No automatic merge, version bump, tag, GitHub release, or npm publication.
No automatic merge.
- name: Run the full runtime matrix on the generated branch
if: steps.update-pr.outputs.pull-request-number != '' && steps.update-pr.outputs.pull-request-operation != 'closed'
env:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,11 @@ jobs:
branch=$(gh pr view "$VERSION_PR" --json headRefName --jq .headRefName)
gh workflow run ci.yml --ref "$branch"

# Evaluated on every push: release.mjs only approves the merge commit of a
# release-branch PR, and a spec PR merge can publish while changesets from
# other PRs are still pending.
eligibility:
needs: version
if: needs.version.outputs.has_changesets == 'false'
runs-on: ubuntu-latest
outputs:
eligible: ${{ steps.plan.outputs.eligible }}
Expand Down
48 changes: 44 additions & 4 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,15 @@ fail the run before updating the PR.

The workflow explicitly dispatches `SDK checks` on the update branch for Node
20/22/24, rather than relying on bot-generated PR events. Review that matrix
before merging. Neither workflow merges, bumps versions, tags, or publishes.
Review the compatibility impact of spec changes before choosing a version bump.
Bot-branch edits can be overwritten on the next refresh; land durable fixes on
the default branch instead.
before merging. When the spec changed, the update PR also carries a minor
version bump and changelog entry, consuming any changesets pending on the
default branch. Merging it publishes that version (see
[releases](releases.md)). Nothing merges automatically, so review the
compatibility impact before merging. If the change needs a bigger bump than
minor, land a changeset with that bump on the default branch and dispatch the
workflow again; Changesets takes the highest pending bump. Bot-branch edits can
be overwritten on the next refresh; land durable fixes on the default branch
instead.

To enable automated update PRs, allow GitHub Actions to create pull requests.
The update job needs `contents: write`, `pull-requests: write`, and `actions: write`.
Expand All @@ -69,6 +74,41 @@ encodings that need a different runtime serializer.
CI verifies generated freshness, Python and TypeScript tests, and installed
ESM/CJS consumers on Node 20, 22, and 24. The separate SDK release workflow validates the same matrix before publishing.

## The other generator: runpod-mcp

Two repositories generate from the same `https://api.runpod.io/v2/openapi.json`,
and they produce different things for different consumers. Knowing which is
which saves an afternoon when an API change looks half-applied.

This repository generates **types**. `spec/openapi.yaml` becomes
`src/generated/schema.ts`, a file of TypeScript declarations with no runtime
code. It is what lets `sdk.GET("/v2/pods")` know that the path exists, which
query parameters it accepts, and what shape comes back. Everything around it
(retries, deadlines, rate-limit metadata, the SSE iterator) is written by hand.

The [MCP server](https://github.com/runpod/runpod-mcp) generates **tool
definitions**. Its own vendored copy of the spec becomes
`src/specgen/generated/tools.gen.ts`, an array of plain objects: tool name,
description written for a language model, JSON Schema for the arguments, and
the method and path to call. That array is data read at runtime to answer
`tools/list` and to route a call, not types erased at compile time.

The two are stacked rather than parallel: the MCP server's generated tools
describe what to call, and the call itself goes through this SDK, which it
pins as a devDependency and bundles into its build. So an upstream API change
usually has to be taken up **twice, in order**:

1. Here: pull the spec, regenerate, bump the version, and publish. The daily
automation's spec PR carries the version bump, so merging it publishes.
2. In runpod-mcp: pull the spec and regenerate its tools, and bump the pinned
SDK version when its hand-written tools need the new types.

Step 2's generated half picks up new paths and parameters on its own. Its
hand-written tools do not, and they are the ones that call this SDK, so they
stay on the old contract until a published version carries the new types. A
resync that stops after step 1, or after step 2's regeneration alone, leaves
part of the surface stale while looking complete.

## Release preparation

Use [Changesets](releases.md) to record release notes and compatibility changes.
Expand Down
6 changes: 5 additions & 1 deletion docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ PR, then explicitly dispatches SDK CI on that PR. Review the Node 20/22/24 check
and manually squash-merge the release PR when ready to release.

Publication requires the workflow commit to be the merge commit of a release PR
from `changeset-release/main` in the same repository. Ordinary commits, the
from `changeset-release/main` or `automation/production-spec` in the same
repository. The daily spec update PR carries its own minor bump and changelog,
so merging it releases without a separate version PR, even if changesets from
other PRs landed on `main` after the bot last refreshed the branch. Those go
into the next version PR. Ordinary commits, the
initial repository import, and open release PRs do not publish. The workflow
revalidates Node 20/22/24, checks the packed package, publishes to npm, and creates
a `v<version>` tag and GitHub release. A failure stops the remaining steps.
Expand Down
6 changes: 5 additions & 1 deletion scripts/release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { execFileSync } from 'node:child_process';
import { appendFileSync, readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';

// Branches whose PRs carry a version bump: the Changesets release PR and the
// daily spec update PR.
const releaseBranches = ['changeset-release/main', 'automation/production-spec'];

export function approvedRelease(pr, repository, sha) {
return Boolean(pr.merged_at && pr.merge_commit_sha === sha &&
pr.base?.ref === 'main' && pr.base?.repo?.full_name === repository &&
pr.head?.ref === 'changeset-release/main' && pr.head?.repo?.full_name === repository);
releaseBranches.includes(pr.head?.ref) && pr.head?.repo?.full_name === repository);
}

export function releaseVersion(pkg, changelog) {
Expand Down
2 changes: 2 additions & 0 deletions scripts/test_release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ const pr = { merged_at: '2026-01-01', merge_commit_sha: sha,

test('only the merged release PR commit is approved, including manual retries', () => {
assert.equal(approvedRelease(pr, repository, sha), true);
assert.equal(approvedRelease({ ...pr, head: { ...pr.head, ref: 'automation/production-spec' } }, repository, sha), true);
for (const rejected of [
{ ...pr, merged_at: null }, { ...pr, merge_commit_sha: 'b'.repeat(40) },
{ ...pr, head: { ...pr.head, ref: 'feature' } },
{ ...pr, head: { ...pr.head, repo: { full_name: 'fork/sdk' } } },
{ ...pr, head: { ref: 'automation/production-spec', repo: { full_name: 'fork/sdk' } } },
{ ...pr, base: { ...pr.base, ref: 'development' } },
]) assert.equal(approvedRelease(rejected, repository, sha), false);
});
Expand Down
Loading
Loading