Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions .vale/styles/FernStyles/Acronyms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,5 @@ exceptions:
- SPDX
- BCP
- ISO
- AUTO
- SHA
180 changes: 180 additions & 0 deletions fern/products/sdks/deep-dives/self-hosted.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,183 @@ 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.
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes

Copy link
Copy Markdown
Contributor

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."


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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Missing --local flag in --version AUTO example command

The example command on this self-hosted generation page omits the --local flag. The CLI changelog at fern/products/cli-api-reference/cli-changelog/2025-11-18.mdx:10 documents this feature as "Add automatic semantic versioning to local generation with --version AUTO --local flags", confirming both flags are required together. Without --local, the command would attempt cloud generation instead of self-hosted generation, which defeats the purpose of this page and could send API data over the network — exactly what organizations choosing self-hosted want to avoid.

Suggested change
FERN_TOKEN=<token> fern generate --version AUTO
FERN_TOKEN=<token> fern generate --local --version AUTO
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

```

<Info>
`--version AUTO` requires:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'AUTO' has no definition.

- A `FERN_TOKEN` (for organization verification, same as all local generation).
- 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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚫 [vale] reported by reviewdog 🐶
[Microsoft.Contractions] Use 'isn't' instead of 'is not'.

</Info>

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 LLM dependency: `fern diff` walks the intermediate representation (IR) of both specs and returns a deterministic result.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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):

  • --version AUTO: classifies the change, picks the next version, and writes the changelog entry, PR description, and conventional-commit message for you. Also the path that continues to receive improvements.
  • fern ir + fern diff: returns only the computed bump and next version — the rest of the release artifacts are yours to write. Use when you need explicit control over how the number is derived, or can't depend on an external LLM provider.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Missing --local flag in deterministic versioning example commands

The fern generate commands in both CLI v1 and CLI v2 tabs omit the --local flag. Since these examples appear on the self-hosted generation page and follow a self-hosted versioning workflow (fern ir + fern diff), users would expect --local to be present. Without it, the commands would trigger cloud generation instead of local generation.

Suggested change
fern generate --version 1.5.0
fern generate --local --version 1.5.0
Open in Devin Review

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`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 [vale] reported by reviewdog 🐶
[FernStyles.Acronyms] 'SHA' has no definition.


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 }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Missing --local flag in CI workflow's fern generate step

The complete CI workflow example omits the --local flag from the final fern generate command. Per the CLI changelog (fern/products/cli-api-reference/cli-changelog/2025-11-18.mdx:10), --local is required for self-hosted generation. Since this is a copy-paste-ready workflow that users are instructed to adapt, the missing flag would cause CI to run cloud generation instead of local generation, contradicting the entire self-hosted setup documented on this page.

Suggested change
run: fern generate --group python-sdk --version ${{ steps.diff.outputs.next_version }}
run: fern generate --local --group python-sdk --version ${{ steps.diff.outputs.next_version }}
Open in Devin Review

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).
Loading