From 50cfc84886a336eb08116857de12c903d52c63ca Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 18:27:48 +0000 Subject: [PATCH 01/11] docs(sdks): document SDK auto-versioning for self-hosted setups Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index f4f4d9a97..52aa09198 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -197,3 +197,176 @@ 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 Fern's hosted AI service. + +### `--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= fern generate --version AUTO +``` + + +`--version AUTO` requires: +- A `FERN_TOKEN` (the diff analysis runs as a hosted Fern service that authenticates your organization the same way [cloud generation](/learn/sdks/overview/how-it-works) does). +- 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. + + +This approach is designed for the [`fern-api/fern-generate`](https://github.com/fern-api/fern-generate) GitHub Action that powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. + +### 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 dependency on hosted AI: `fern diff` walks the intermediate representation (IR) of both specs and returns a deterministic result. It's the legacy path — `--version AUTO` will keep getting improvements first — but it remains fully supported for setups that need explicit control. + +The workflow has three steps: + + + + +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 ` if your [project defines multiple APIs](/learn/sdks/overview/project-structure). + + + + +Check out the spec at `HEAD` and write its IR to disk: + +```bash +fern ir new-ir.json +``` + + + + +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" +} +``` + +`bump` is one of `major`, `minor`, `patch`, or `no_change`. Pass `nextVersion` to `fern generate` to release that exact version: + + + +```bash +fern generate --version 1.5.0 +``` + + +```bash +fern generate --output-version 1.5.0 +``` + + + + + + + +`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. + + +#### 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`. + +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 plants + + - name: Generate current IR + run: | + git checkout ${{ github.sha }} + fern ir new-ir.json --api plants + + - 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 }}) + 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 }} +``` + +`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). From 62ea9bf723a28fd961e39887830942fde9d44261 Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 18:31:43 +0000 Subject: [PATCH 02/11] chore(vale): allow AUTO and SHA as valid acronyms Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .vale/styles/FernStyles/Acronyms.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vale/styles/FernStyles/Acronyms.yml b/.vale/styles/FernStyles/Acronyms.yml index 7af037cdc..e444fef52 100644 --- a/.vale/styles/FernStyles/Acronyms.yml +++ b/.vale/styles/FernStyles/Acronyms.yml @@ -108,3 +108,5 @@ exceptions: - SPDX - BCP - ISO + - AUTO + - SHA From 2bc6bb0dfd8512690337c15ff8cf8ee567439160 Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 20:47:23 +0000 Subject: [PATCH 03/11] docs(sdks): address review feedback on auto-versioning section - Fix --version AUTO requirements: LLM call is made locally from the user's machine, not via a hosted Fern service. List FERN_TOKEN plus the supported LLM provider keys (OpenAI, Anthropic, AWS Bedrock). - Drop redundant editorial sentence from the fern ir + fern diff intro; the bullet summary already covers the framing. - Add the errors field to the fern diff JSON output example so it matches the actual Result shape. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 52aa09198..3e1dc5aec 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -221,7 +221,11 @@ FERN_TOKEN= fern generate --version AUTO `--version AUTO` requires: -- A `FERN_TOKEN` (the diff analysis runs as a hosted Fern service that authenticates your organization the same way [cloud generation](/learn/sdks/overview/how-it-works) does). +- A `FERN_TOKEN` (for organization verification, same as all local generation). +- An LLM provider API key for the diff analysis. Set one of: + - `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. - 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. @@ -229,7 +233,7 @@ This approach is designed for the [`fern-api/fern-generate`](https://github.com/ ### 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 dependency on hosted AI: `fern diff` walks the intermediate representation (IR) of both specs and returns a deterministic result. It's the legacy path — `--version AUTO` will keep getting improvements first — but it remains fully supported for setups that need explicit control. +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. The workflow has three steps: @@ -268,7 +272,8 @@ fern diff \ ```json { "bump": "minor", - "nextVersion": "1.5.0" + "nextVersion": "1.5.0", + "errors": [] } ``` From 3928167b9b22365f909ea82af8a8dc02a4b7a08f Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 20:58:59 +0000 Subject: [PATCH 04/11] docs(sdks): clarify --version AUTO LLM data flow The Fern CLI calls the configured LLM provider's API directly using the user's credentials; Fern's infrastructure is not involved in the analysis. Also drop the matching 'Fern's hosted AI service' phrasing from the intro bullet. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 3e1dc5aec..62627ba0f 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -205,7 +205,7 @@ When self-hosting, your pipeline picks the version number for each SDK release. 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 Fern's hosted AI service. +- **`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) @@ -222,11 +222,13 @@ FERN_TOKEN= fern generate --version AUTO `--version AUTO` requires: - A `FERN_TOKEN` (for organization verification, same as all local generation). -- An LLM provider API key for the diff analysis. Set one of: +- Credentials for the LLM provider that performs the diff analysis. Set one of: - `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. - 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. + +The diff is sent to whichever LLM provider you configure. The Fern CLI calls that provider's API directly using your credentials — Fern's infrastructure is not involved in the analysis. This approach is designed for the [`fern-api/fern-generate`](https://github.com/fern-api/fern-generate) GitHub Action that powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. From 684199021c862d2e8e46c20aa9c5f06508635a0d Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 21:01:33 +0000 Subject: [PATCH 05/11] docs(sdks): add ai: generators.yml block to --version AUTO requirements --version AUTO needs both an ai: block in generators.yml (picking the provider and model) and the corresponding API key in the environment. List both, plus the FERN_TOKEN and self-hosted GitHub output. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 62627ba0f..3307702de 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -221,14 +221,20 @@ FERN_TOKEN= fern generate --version AUTO `--version AUTO` requires: -- A `FERN_TOKEN` (for organization verification, same as all local generation). -- Credentials for the LLM provider that performs the diff analysis. Set one of: +- A `FERN_TOKEN` for organization verification (same as all local generation). +- An `ai:` block in `generators.yml` that picks the LLM provider and model: + ```yaml title="generators.yml" + ai: + provider: openai # openai | anthropic | bedrock + model: gpt-4o # any model name the provider accepts + ``` +- The corresponding API key in your environment so the provider's SDK can authenticate: - `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. - 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. -The diff is sent to whichever LLM provider you configure. The Fern CLI calls that provider's API directly using your credentials — Fern's infrastructure is not involved in the analysis. +The diff is sent to the provider's API using your credentials — Fern's infrastructure is not involved in the analysis. This approach is designed for the [`fern-api/fern-generate`](https://github.com/fern-api/fern-generate) GitHub Action that powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. From 8790f20ef32ea47ecd612fd1dcb7ac025e289618 Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 21:06:00 +0000 Subject: [PATCH 06/11] docs(sdks): pull ai: yaml block and provider key list out of Info callout Nested code blocks inside list items inside were not parsing cleanly: the title="generators.yml" was bleeding into the bullet text and the [as shown above](#setup) link was being stripped. Keep the requirements as plain bullets in the callout, then show the config snippet and env-var list as regular prose below. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 3307702de..398c77f75 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -222,20 +222,26 @@ FERN_TOKEN= fern generate --version AUTO `--version AUTO` requires: - A `FERN_TOKEN` for organization verification (same as all local generation). -- An `ai:` block in `generators.yml` that picks the LLM provider and model: - ```yaml title="generators.yml" - ai: - provider: openai # openai | anthropic | bedrock - model: gpt-4o # any model name the provider accepts - ``` -- The corresponding API key in your environment so the provider's SDK can authenticate: - - `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. +- 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. + + +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. - This approach is designed for the [`fern-api/fern-generate`](https://github.com/fern-api/fern-generate) GitHub Action that powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. From c6cf9418d89e2e8e3618414648bf0f2e11920476 Mon Sep 17 00:00:00 2001 From: judah Date: Tue, 12 May 2026 21:21:57 +0000 Subject: [PATCH 07/11] docs(sdks): drop fern-api/fern-generate link, gate major bump in CI snippet - Remove the dead link to the private fern-api/fern-generate repo and reword the sentence to use 'fern automations generate'. - Replace the hardcoded '--api plants' placeholder with '--api ' so readers don't copy the example name. - Fix the Compute next version step: 'fern diff' exits 1 on a major bump, which under set -e killed the step before jq ran. Capture with '|| true', read .bump, exit 1 with an explicit message on major, and only write next_version for minor/patch/no_change. This matches what the Info callout above the snippet describes. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 398c77f75..77fb28f46 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -243,7 +243,7 @@ Set the matching API key in your environment so the Fern CLI can call the provid The diff is sent to the provider's API using your credentials — Fern's infrastructure is not involved in the analysis. -This approach is designed for the [`fern-api/fern-generate`](https://github.com/fern-api/fern-generate) GitHub Action that powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. +This approach is designed for use with `fern automations generate`, which powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. ### Deterministic versioning with `fern ir` + `fern diff` @@ -368,12 +368,12 @@ jobs: - name: Generate previous IR run: | git checkout ${{ steps.meta.outputs.old_sha }} - fern ir old-ir.json --api plants + fern ir old-ir.json --api - name: Generate current IR run: | git checkout ${{ github.sha }} - fern ir new-ir.json --api plants + fern ir new-ir.json --api - name: Compute next version id: diff @@ -381,7 +381,12 @@ jobs: result=$(fern diff \ --from old-ir.json \ --to new-ir.json \ - --from-version ${{ steps.meta.outputs.from_version }}) + --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 From 36ef307ccacde05094fe1a25d11ec9f56fa9853f Mon Sep 17 00:00:00 2001 From: jsklan <100491078+jsklan@users.noreply.github.com> Date: Tue, 12 May 2026 20:43:12 -0400 Subject: [PATCH 08/11] Remove extraneous line about automations --- fern/products/sdks/deep-dives/self-hosted.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 77fb28f46..5b11ea381 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -243,8 +243,6 @@ Set the matching API key in your environment so the Fern CLI can call the provid The diff is sent to the provider's API using your credentials — Fern's infrastructure is not involved in the analysis. -This approach is designed for use with `fern automations generate`, which powers Fern's automated release pipeline, but it works standalone anywhere that meets the requirements above. - ### 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. From d30f602220458e2e58253a898e50a6455977cf86 Mon Sep 17 00:00:00 2001 From: judah Date: Wed, 13 May 2026 01:17:24 +0000 Subject: [PATCH 09/11] docs(sdks): unlink hidden Autorelease ref; sharpen AUTO vs deterministic framing - Drop link to /learn/sdks/overview/autorelease since that page is hidden today; keep the prose reference to Fern Autorelease. - Rewrite the AUTO vs deterministic intro bullets to make the AUTO advantage concrete: AUTO also writes the changelog entry, PR description, and conventional-commit message, while fern ir + fern diff only returns the bump and next version. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 5b11ea381..04da3d351 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -200,12 +200,12 @@ sequenceDiagram ## 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. +When self-hosting, your pipeline picks the version number for each SDK release. Cloud generation handles this automatically via Fern 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`** is the recommended approach. Fern classifies the change, picks the next version, and writes the changelog entry, PR description, and conventional-commit message for you. This is also the path that continues to receive improvements. +- **`fern ir` + `fern diff`** is the deterministic alternative. It returns only the computed bump and next version — the rest of the release artifacts are yours to write. 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) From 2058fc57ecfa1907269ff2a6629adabf1b0a4aa2 Mon Sep 17 00:00:00 2001 From: Devin Logan Date: Wed, 13 May 2026 08:33:39 -0400 Subject: [PATCH 10/11] restructure, break into new page --- .../deep-dives/self-hosted-versioning.mdx | 213 ++++++++++++++++++ fern/products/sdks/deep-dives/self-hosted.mdx | 203 +---------------- fern/products/sdks/sdks.yml | 7 +- 3 files changed, 225 insertions(+), 198 deletions(-) create mode 100644 fern/products/sdks/deep-dives/self-hosted-versioning.mdx diff --git a/fern/products/sdks/deep-dives/self-hosted-versioning.mdx b/fern/products/sdks/deep-dives/self-hosted-versioning.mdx new file mode 100644 index 000000000..134771a22 --- /dev/null +++ b/fern/products/sdks/deep-dives/self-hosted-versioning.mdx @@ -0,0 +1,213 @@ +--- +title: Self-hosted SDK versioning +description: Compute the next SDK version number from your own CI when self-hosting Fern SDK generation. +--- + + + +When self-hosting, your pipeline picks the version number for each SDK release. Cloud generation handles this automatically via Fern Autorelease, but [self-hosted setups](/learn/sdks/deep-dives/self-hosted) 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, picks the next version, and writes the changelog entry, PR description, and conventional-commit message for you. This is also the path that continues to receive improvements. +- **`fern ir` + `fern diff`** is the deterministic alternative. It returns only the computed bump and next version — the rest of the release artifacts are yours to write. 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 + + +`--version AUTO` requires: +- A `FERN_TOKEN` for organization verification (same as all self-hosted generation). +- A [GitHub output](/learn/sdks/deep-dives/self-hosted#setup) configured in `generators.yml`, since the pipeline pushes the version bump and changelog back to the SDK repo. + + + + + +Set 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 +``` + + + + +Export the matching API key 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. + + + + +```bash +FERN_TOKEN= fern generate --version AUTO +``` + + + + +## 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. + +The workflow has three steps: + + + + +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 ` if your [project defines multiple APIs](/learn/sdks/overview/project-structure). + + + + +Check out the spec at `HEAD` and write its IR to disk: + +```bash +fern ir new-ir.json +``` + + + + +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: + + + +```bash +fern generate --version 1.5.0 +``` + + +```bash +fern generate --output-version 1.5.0 +``` + + + + + + + +`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. + + +### 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`. + + + +The following 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 + + - name: Generate current IR + run: | + git checkout ${{ github.sha }} + fern ir new-ir.json --api + + - 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 }} +``` + +`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). + + diff --git a/fern/products/sdks/deep-dives/self-hosted.mdx b/fern/products/sdks/deep-dives/self-hosted.mdx index 04da3d351..4567ee772 100644 --- a/fern/products/sdks/deep-dives/self-hosted.mdx +++ b/fern/products/sdks/deep-dives/self-hosted.mdx @@ -13,7 +13,7 @@ Fern SDK generation [runs on Fern's infrastructure by default](/sdks/overview/ho - Has strict compliance or security requirements - Needs full control over your SDK generation process -When you self-host, you're responsible for infrastructure management and SDK distribution. Self-hosted SDK generation includes [all Fern SDK features](/learn/sdks/overview/capabilities). +When you self-host, you're responsible for infrastructure management, SDK distribution, and [computing SDK version numbers](/learn/sdks/deep-dives/self-hosted-versioning). Self-hosted SDK generation includes [all Fern SDK features](/learn/sdks/overview/capabilities). Unless you have specific requirements that prevent using Fern's default hosting, we recommend **using our managed cloud generation solution** for easier setup and maintenance. @@ -120,6 +120,12 @@ Set up GitHub secrets for authentication. In your repository's **Settings** > ** + + +Cloud generation picks SDK version numbers automatically. With self-hosting, your pipeline computes the next version itself — see [Self-hosted SDK versioning](/learn/sdks/deep-dives/self-hosted-versioning) for the two supported workflows. + + + Use the `--local` flag to generate SDKs locally instead of using Fern's cloud infrastructure. You can combine `--local` with `--group` to generate specific SDKs locally. @@ -197,198 +203,3 @@ 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, 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, picks the next version, and writes the changelog entry, PR description, and conventional-commit message for you. This is also the path that continues to receive improvements. -- **`fern ir` + `fern diff`** is the deterministic alternative. It returns only the computed bump and next version — the rest of the release artifacts are yours to write. 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= fern generate --version AUTO -``` - - -`--version AUTO` requires: -- 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. - - -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. - -The workflow has three steps: - - - - -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 ` if your [project defines multiple APIs](/learn/sdks/overview/project-structure). - - - - -Check out the spec at `HEAD` and write its IR to disk: - -```bash -fern ir new-ir.json -``` - - - - -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: - - - -```bash -fern generate --version 1.5.0 -``` - - -```bash -fern generate --output-version 1.5.0 -``` - - - - - - - -`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. - - -#### 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`. - -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 - - - name: Generate current IR - run: | - git checkout ${{ github.sha }} - fern ir new-ir.json --api - - - 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 }} -``` - -`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). diff --git a/fern/products/sdks/sdks.yml b/fern/products/sdks/sdks.yml index 2e9f95fc9..1fc3b9540 100644 --- a/fern/products/sdks/sdks.yml +++ b/fern/products/sdks/sdks.yml @@ -256,13 +256,16 @@ navigation: - page: Testing path: ./deep-dives/testing.mdx slug: testing - - section: Hosting + - section: Self-hosting slug: deep-dives collapsed: true contents: - - page: Self-hosted SDKs + - page: Setup path: ./deep-dives/self-hosted.mdx slug: self-hosted + - page: Versioning + path: ./deep-dives/self-hosted-versioning.mdx + slug: self-hosted-versioning - section: Reference contents: - page: generators.yml From 944d37c4ffb0a2be6f4d8f2b1a15f62fd538e3fb Mon Sep 17 00:00:00 2001 From: judah Date: Thu, 14 May 2026 22:00:05 +0000 Subject: [PATCH 11/11] drop Autorelease reference from versioning page intro Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- fern/products/sdks/deep-dives/self-hosted-versioning.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/products/sdks/deep-dives/self-hosted-versioning.mdx b/fern/products/sdks/deep-dives/self-hosted-versioning.mdx index 134771a22..1d41c4064 100644 --- a/fern/products/sdks/deep-dives/self-hosted-versioning.mdx +++ b/fern/products/sdks/deep-dives/self-hosted-versioning.mdx @@ -5,7 +5,7 @@ description: Compute the next SDK version number from your own CI when self-host -When self-hosting, your pipeline picks the version number for each SDK release. Cloud generation handles this automatically via Fern Autorelease, but [self-hosted setups](/learn/sdks/deep-dives/self-hosted) need to compute the next version themselves. +When self-hosting, your pipeline picks the version number for each SDK release. Unlike cloud generation, [self-hosted setups](/learn/sdks/deep-dives/self-hosted) need to compute the next version themselves. The CLI exposes two workflows for this. Both can be wired into the same generation pipeline: