-
Notifications
You must be signed in to change notification settings - Fork 7
docs(sdks): document SDK auto-versioning for self-hosted setups #5473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
50cfc84
62ea9bf
2bc6bb0
3928167
6841990
8790f20
c6cf941
36ef307
d30f602
2058fc5
944d37c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,3 +108,5 @@ exceptions: | |
| - SPDX | ||
| - BCP | ||
| - ISO | ||
| - AUTO | ||
| - SHA | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -197,3 +197,198 @@ sequenceDiagram | |||||
| CLI->>CLI: Run container, generate SDK | ||||||
| CLI->>CLI: Push to output (local/GitHub) | ||||||
| ``` | ||||||
|
|
||||||
| ## Determining SDK versions | ||||||
|
|
||||||
| When self-hosting, your pipeline picks the version number for each SDK release. Cloud generation handles this automatically via [Fern Autorelease](/learn/sdks/overview/autorelease), but self-hosted setups need to compute the next version themselves. | ||||||
|
|
||||||
| The CLI exposes two workflows for this. Both can be wired into the same generation pipeline: | ||||||
|
|
||||||
| - **`--version AUTO`** is the recommended approach. Fern classifies the change and picks the next version for you. This is the path that continues to receive improvements. | ||||||
| - **`fern ir` + `fern diff`** is the deterministic alternative. Use it when you need explicit control over how the version number is derived, or when you can't depend on an external LLM provider. | ||||||
|
|
||||||
| ### `--version AUTO` (recommended) | ||||||
|
|
||||||
| Pass `--version AUTO` to `fern generate` and Fern analyzes the diff between the previous and current SDK output, classifies the change as `MAJOR`, `MINOR`, or `PATCH`, and applies the next [semantic version](https://semver.org/) to the generated package. The same analysis also produces: | ||||||
|
|
||||||
| - A changelog entry describing what changed | ||||||
| - A PR description summarizing the release | ||||||
| - A [conventional commit](https://www.conventionalcommits.org/) message | ||||||
|
|
||||||
| ```bash | ||||||
| FERN_TOKEN=<token> fern generate --version AUTO | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Missing The example command on this self-hosted generation page omits the
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||
| ``` | ||||||
|
|
||||||
| <Info> | ||||||
| `--version AUTO` requires: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 [vale] reported by reviewdog 🐶 |
||||||
| - A `FERN_TOKEN` for organization verification (same as all local generation). | ||||||
| - An `ai:` block in `generators.yml` selecting the LLM provider and model (see below). | ||||||
| - The corresponding provider credentials in your environment (see below). | ||||||
| - A self-hosted GitHub output configured in `generators.yml` (with `github.uri` and `github.token`, [as shown above](#setup)), because the auto-versioning pipeline pushes the version bump and changelog back to the SDK repo. | ||||||
| </Info> | ||||||
|
|
||||||
| Configure the provider and model in `generators.yml`: | ||||||
|
|
||||||
| ```yaml title="generators.yml" | ||||||
| ai: | ||||||
| provider: openai # openai | anthropic | bedrock | ||||||
| model: gpt-4o # any model name the provider accepts | ||||||
| ``` | ||||||
|
|
||||||
| Set the matching API key in your environment so the Fern CLI can call the provider: | ||||||
|
|
||||||
| - `OPENAI_API_KEY` for OpenAI | ||||||
| - `ANTHROPIC_API_KEY` for Anthropic | ||||||
| - Standard AWS credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`) for AWS Bedrock | ||||||
|
|
||||||
| The diff is sent to the provider's API using your credentials — Fern's infrastructure is not involved in the analysis. | ||||||
|
|
||||||
| ### Deterministic versioning with `fern ir` + `fern diff` | ||||||
|
|
||||||
| If you'd rather derive the version number yourself, use the `fern ir` and `fern diff` commands to compute the bump from your API definition. This flow has no LLM dependency: `fern diff` walks the intermediate representation (IR) of both specs and returns a deterministic result. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should explain why someone would pick AUTO over deterministic versioning. At face value deterministic looks a lot more logica.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed — rewrote the comparison bullets to make the AUTO advantage concrete instead of just asserting "recommended" (d30f602):
So the answer to "why pick AUTO" is: AUTO does the release-prep work for you, deterministic only hands back the version number. Happy to iterate if you'd like more concrete examples (e.g., side-by-side of what each command actually outputs) — let me know. |
||||||
|
|
||||||
| The workflow has three steps: | ||||||
|
|
||||||
| <Steps> | ||||||
| <Step title="Generate the previous IR"> | ||||||
|
|
||||||
| Check out the spec as it was at the last SDK generation and write its IR to disk: | ||||||
|
|
||||||
| ```bash | ||||||
| fern ir old-ir.json | ||||||
| ``` | ||||||
|
|
||||||
| Pass `--api <name>` if your [project defines multiple APIs](/learn/sdks/overview/project-structure). | ||||||
|
|
||||||
| </Step> | ||||||
| <Step title="Generate the current IR"> | ||||||
|
|
||||||
| Check out the spec at `HEAD` and write its IR to disk: | ||||||
|
|
||||||
| ```bash | ||||||
| fern ir new-ir.json | ||||||
| ``` | ||||||
|
|
||||||
| </Step> | ||||||
| <Step title="Diff the two IRs"> | ||||||
|
|
||||||
| Run `fern diff` with `--from-version` set to the SDK's current version. When `--from-version` is provided, the command prints a JSON object containing the computed bump and the next version: | ||||||
|
|
||||||
| ```bash | ||||||
| fern diff \ | ||||||
| --from old-ir.json \ | ||||||
| --to new-ir.json \ | ||||||
| --from-version 1.4.2 | ||||||
| ``` | ||||||
|
|
||||||
| ```json | ||||||
| { | ||||||
| "bump": "minor", | ||||||
| "nextVersion": "1.5.0", | ||||||
| "errors": [] | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| `bump` is one of `major`, `minor`, `patch`, or `no_change`. Pass `nextVersion` to `fern generate` to release that exact version: | ||||||
|
|
||||||
| <Tabs> | ||||||
| <Tab title="CLI v1" language="cli-v1"> | ||||||
| ```bash | ||||||
| fern generate --version 1.5.0 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Missing The
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||
| ``` | ||||||
| </Tab> | ||||||
| <Tab title="CLI v2" language="cli-v2"> | ||||||
| ```bash | ||||||
| fern generate --output-version 1.5.0 | ||||||
| ``` | ||||||
| </Tab> | ||||||
| </Tabs> | ||||||
|
|
||||||
| </Step> | ||||||
| </Steps> | ||||||
|
|
||||||
| <Info> | ||||||
| `fern diff` exits with a non-zero status when the computed bump is `major`. This makes it easy to gate breaking changes on a manual review step in CI. | ||||||
| </Info> | ||||||
|
|
||||||
| #### Wiring `fern ir` + `fern diff` into CI | ||||||
|
|
||||||
| The non-obvious part is reproducing the IR for the *previous* spec. Each generated SDK records the config repo commit it was generated from in `.fern/metadata.json`: | ||||||
|
|
||||||
| ```json title=".fern/metadata.json" | ||||||
| { | ||||||
| "cliVersion": "0.74.0", | ||||||
| "generatorName": "fernapi/fern-python-sdk", | ||||||
| "generatorVersion": "4.0.0", | ||||||
| "originGitCommit": "a1b2c3d4e5f6...", | ||||||
| "requestedVersion": "1.4.2", | ||||||
| "sdkVersion": "1.4.2" | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| `originGitCommit` is the SHA in the **config repo** that produced the SDK at its current version. Check that commit out, run `fern ir`, then switch back to `HEAD` and run `fern ir` again to produce both inputs to `fern diff`. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 [vale] reported by reviewdog 🐶 |
||||||
|
|
||||||
| The following GitHub Actions snippet runs in the config repo on every push to `main` and computes the next version for a Python SDK whose `originGitCommit` and `sdkVersion` are read from the SDK repo: | ||||||
|
|
||||||
| ```yaml title=".github/workflows/release-sdk.yml" | ||||||
| name: Release SDK | ||||||
|
|
||||||
| on: | ||||||
| push: | ||||||
| branches: [main] | ||||||
|
|
||||||
| jobs: | ||||||
| release: | ||||||
| runs-on: ubuntu-latest | ||||||
| env: | ||||||
| FERN_TOKEN: ${{ secrets.FERN_TOKEN }} | ||||||
| SDK_REPO: your-org/python-sdk | ||||||
| steps: | ||||||
| - name: Check out config repo | ||||||
| uses: actions/checkout@v4 | ||||||
| with: | ||||||
| fetch-depth: 0 | ||||||
|
|
||||||
| - name: Set up Fern CLI | ||||||
| uses: fern-api/setup-fern-cli@v1 | ||||||
|
|
||||||
| - name: Read previous SDK metadata | ||||||
| id: meta | ||||||
| run: | | ||||||
| curl -fsSL \ | ||||||
| -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | ||||||
| -H "Accept: application/vnd.github.raw" \ | ||||||
| "https://api.github.com/repos/${SDK_REPO}/contents/.fern/metadata.json" \ | ||||||
| > metadata.json | ||||||
| echo "old_sha=$(jq -r .originGitCommit metadata.json)" >> "$GITHUB_OUTPUT" | ||||||
| echo "from_version=$(jq -r .sdkVersion metadata.json)" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
| - name: Generate previous IR | ||||||
| run: | | ||||||
| git checkout ${{ steps.meta.outputs.old_sha }} | ||||||
| fern ir old-ir.json --api <your-api-name> | ||||||
|
|
||||||
| - name: Generate current IR | ||||||
| run: | | ||||||
| git checkout ${{ github.sha }} | ||||||
| fern ir new-ir.json --api <your-api-name> | ||||||
|
|
||||||
| - name: Compute next version | ||||||
| id: diff | ||||||
| run: | | ||||||
| result=$(fern diff \ | ||||||
| --from old-ir.json \ | ||||||
| --to new-ir.json \ | ||||||
| --from-version ${{ steps.meta.outputs.from_version }}) || true | ||||||
| bump=$(echo "$result" | jq -r .bump) | ||||||
| if [ "$bump" = "major" ]; then | ||||||
| echo "Computed a major version bump — manual review required before releasing." | ||||||
| exit 1 | ||||||
| fi | ||||||
| echo "next_version=$(echo "$result" | jq -r .nextVersion)" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
| - name: Generate SDK | ||||||
| run: fern generate --group python-sdk --version ${{ steps.diff.outputs.next_version }} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Missing The complete CI workflow example omits the
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||
| ``` | ||||||
|
|
||||||
| `fetch-depth: 0` on the checkout step is required so the runner has the history needed to check out `originGitCommit`. Replace the metadata-fetch step with whatever mechanism gives your pipeline access to the SDK repo's `.fern/metadata.json` (a checkout of the SDK repo, a release artifact, an internal API, and so on). | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't link to autorelease, it's a hidden page for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch — dropped the link, kept the prose reference. d30f602.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aside - @Swimburger I'm guessing you think we should remove the reference to auto-release entirely. Is that right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dropped the Autorelease reference entirely in 944d37c. Intro now reads: "When self-hosting, your pipeline picks the version number for each SDK release. Unlike cloud generation, self-hosted setups need to compute the next version themselves."