Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
173 changes: 173 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,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.
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 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=<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` (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.
</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 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:

<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"
}
```

`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