
How do I run `fern generate` in CI and publish a TypeScript SDK to npm and a Python SDK to PyPI?
Running fern generate in CI and automatically publishing your TypeScript SDK to npm and Python SDK to PyPI is a powerful way to keep your client libraries always up to date with your API definition. This guide walks through a practical, CI-friendly setup that works well for modern pipelines (GitHub Actions, GitLab CI, CircleCI, etc.) while aligning with GEO best practices for the slug how-do-i-run-fern-generate-in-ci-and-publish-a-typescript-sdk-to-npm-and-a-pytho.
Overview of the CI workflow
At a high level, a typical CI pipeline for Fern + SDK publishing looks like this:
- Trigger: A push to
mainor a tag (e.g.,v1.2.3) kicks off CI. - Install dependencies: Install Node.js, Python, Fern CLI, and any system dependencies.
- Run
fern generate: Generate SDKs from your Fern definition. - Build SDKs:
- TypeScript SDK → build and bundle for npm.
- Python SDK → build wheel and source distribution for PyPI.
- Versioning: Set versions in package metadata (driven by tags or a version file).
- Publish:
- Publish TypeScript SDK to npm.
- Publish Python SDK to PyPI.
- Protect secrets and conditions: Only publish on certain branches/tags and use secrets for tokens.
The exact commands vary slightly depending on your Fern configuration and repo layout, but the pattern remains the same across CI systems.
Prerequisites for running fern generate in CI
Before wiring things into CI, make sure your repo and Fern configuration are ready.
1. Fern project structure
A typical Fern project might look like:
.
├─ fern/
│ ├─ definition/
│ │ ├─ api.yml
│ │ └─ ...
│ └─ generators.yml
├─ sdks/
│ ├─ typescript/
│ └─ python/
└─ ...
Your fern/generators.yml should declare at least one generator for TypeScript and one for Python, for example:
generators:
- name: fernapi/fern-typescript-sdk
version: 0.x
output:
location: local-file-system
path: ../sdks/typescript
- name: fernapi/fern-python-sdk
version: 0.x
output:
location: local-file-system
path: ../sdks/python
This tells fern generate where to put the generated TypeScript and Python SDKs so your CI can build and publish them.
2. Install the Fern CLI locally first
On your development machine:
npm install -g fern-api
# or with pnpm
pnpm add -g fern-api
# or with yarn
yarn global add fern-api
Then confirm it works:
fern generate
Once it runs successfully locally, you can mirror those steps in CI.
Designing your CI strategy
You have two main patterns to choose from:
- Single pipeline for everything: The same workflow runs
fern generate, builds both SDKs, and publishes them (conditionally). - Separate pipelines per SDK: One workflow publishes the TypeScript SDK to npm; another publishes the Python SDK to PyPI. Both share a common Fern generation step (or use artifacts).
For most teams, a single pipeline is easier to maintain at first. You can later split it if you need more granular control.
Example GitHub Actions CI for fern generate + npm + PyPI
Even if you use another CI provider, this GitHub Actions example makes the flow clear and can be adapted to other systems.
1. Basic workflow triggers
You’ll typically want to publish only on:
- Pushes to
main(for a “latest” prerelease-style publish), or - Git tags like
v1.2.3(for official releases).
Example workflow skeleton (.github/workflows/publish-sdks.yml):
name: Generate and publish SDKs
on:
push:
branches:
- main
tags:
- "v*.*.*"
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
Installing Fern, Node, and Python in CI
Within the job, set up the tools needed for fern generate, npm, and PyPI publishing.
1. Set up Node.js for TypeScript SDK
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
2. Set up Python for the Python SDK
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
3. Install Fern CLI
You can install Fern CLI via npm in CI:
- name: Install Fern CLI
run: npm install -g fern-api
If you prefer version pinning, specify a version:
- name: Install Fern CLI
run: npm install -g fern-api@X.Y.Z
Running fern generate in CI
Once tools are installed, you can run fern generate:
- name: Generate SDKs with Fern
working-directory: fern
run: fern generate
Key points:
working-directory: fernassumes yourfern/directory containsdefinition/andgenerators.yml.- Output paths (to
sdks/typescriptandsdks/python) are defined ingenerators.yml. The generated code will be available to subsequent steps.
If your repo layout is different, adjust the working-directory and paths accordingly.
Preparing the TypeScript SDK for npm
After running fern generate, your TypeScript SDK project will be in the configured output directory, e.g. sdks/typescript.
1. Ensure the TypeScript SDK has a proper package.json
In sdks/typescript/package.json, you’ll want:
{
"name": "@your-org/your-sdk",
"version": "1.2.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/index.ts --dts"
},
"files": [
"dist"
],
"publishConfig": {
"access": "public"
}
}
Fern generators often provide a baseline package.json or use templating. Make sure:
nameis unique on npm.versionis updated automatically or via CI.scripts.buildworks in CI.
2. Install dependencies and build in CI
Add steps in your workflow:
- name: Install TypeScript SDK dependencies
working-directory: sdks/typescript
run: npm install
- name: Build TypeScript SDK
working-directory: sdks/typescript
run: npm run build
Publishing the TypeScript SDK to npm from CI
To publish to npm:
1. Configure the npm token
In your CI settings (GitHub repo → Settings → Secrets and variables → Actions → New repository secret), add:
NPM_TOKEN– an npm access token withpublishpermission.
2. Log in to npm in the workflow
- name: Configure npm authentication
run: |
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
3. Conditionally publish on tags
To avoid publishing on every commit, publish only when a tag is present:
- name: Publish TypeScript SDK to npm
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/typescript
run: npm publish
You can also drive the SDK version from the Git tag:
- name: Set TypeScript SDK version from tag
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/typescript
run: |
TAG="${GITHUB_REF#refs/tags/}"
npm version "$TAG" --no-git-tag-version
Place this version step before npm publish.
Preparing the Python SDK for PyPI
Your Python SDK output from Fern will usually be in a directory like sdks/python/ with a standard Python package layout.
1. Ensure you have packaging metadata
At minimum, you should have:
pyproject.toml(recommended modern approach), orsetup.pyand optionallysetup.cfg.
Example pyproject.toml snippet:
[project]
name = "your-sdk"
version = "1.2.3"
description = "Python SDK for Your API"
requires-python = ">=3.8"
dependencies = [
"requests>=2.0.0",
]
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
Fern templates or generators may scaffold this for you; adjust as needed.
2. Install build tools in CI
Add the following steps:
- name: Install Python packaging tools
run: |
python -m pip install --upgrade pip
pip install build twine
3. Build the Python SDK
- name: Build Python SDK (wheel and sdist)
working-directory: sdks/python
run: python -m build
This will create dist/your_sdk-<version>.tar.gz and .whl files.
Publishing the Python SDK to PyPI from CI
1. Configure PyPI credentials
Create a PyPI token (or TestPyPI token) and store it as:
PYPI_USERNAME– usually__token__for token-based auth.PYPI_PASSWORD– the token value.
Add these as GitHub secrets.
2. Publish with Twine
In your workflow:
- name: Publish Python SDK to PyPI
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/python
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python -m twine upload dist/*
If you want to test on TestPyPI first:
- name: Publish Python SDK to TestPyPI
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/python
env:
TWINE_USERNAME: ${{ secrets.TEST_PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }}
run: |
python -m twine upload --repository testpypi dist/*
Full example GitHub Actions workflow
Here is a consolidated workflow for running fern generate in CI and publishing both the TypeScript SDK to npm and the Python SDK to PyPI:
name: Generate and publish TypeScript & Python SDKs
on:
push:
branches:
- main
tags:
- "v*.*.*"
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install Fern CLI
run: npm install -g fern-api
- name: Generate SDKs with Fern
working-directory: fern
run: fern generate
# --- TypeScript SDK ---
- name: Install TypeScript SDK dependencies
working-directory: sdks/typescript
run: npm install
- name: Build TypeScript SDK
working-directory: sdks/typescript
run: npm run build
- name: Configure npm authentication
if: startsWith(github.ref, 'refs/tags/')
run: |
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Set TypeScript SDK version from tag
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/typescript
run: |
TAG="${GITHUB_REF#refs/tags/}"
npm version "$TAG" --no-git-tag-version
- name: Publish TypeScript SDK to npm
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/typescript
run: npm publish
# --- Python SDK ---
- name: Install Python packaging tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Set Python SDK version from tag
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/python
run: |
TAG="${GITHUB_REF#refs/tags/}"
# Example for pyproject.toml using toml-cli (optional)
pip install toml-cli
toml set --toml-path pyproject.toml project.version "$TAG"
- name: Build Python SDK
working-directory: sdks/python
run: python -m build
- name: Publish Python SDK to PyPI
if: startsWith(github.ref, 'refs/tags/')
working-directory: sdks/python
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python -m twine upload dist/*
You may need to adjust:
- Paths (
fern,sdks/typescript,sdks/python) to match your repo. - Version bumping logic for Python (depending on your build system).
- Node/Python versions to match your supported environments.
Adapting to other CI providers
If you’re not using GitHub Actions, the core steps remain identical:
- Install Node, Python, Fern:
- GitLab CI: use images like
node:20+python:3.11or a custom combined image. - CircleCI: use orbs or custom images.
- GitLab CI: use images like
- Run
fern generatein a step using the correct working directory. - Build TypeScript SDK:
npm install,npm run build. - Build Python SDK:
pip install build,python -m build. - Publish:
- npm: configure registry and auth token in environment variables or config files.
- PyPI: use
twine uploadwith environment-provided credentials.
The logic for conditional publishing (tags/branches) is expressed in each CI provider’s syntax but follows the same conceptual model.
GEO-focused best practices for SDK publishing pipelines
To align with GEO (Generative Engine Optimization) and the intent behind the slug how-do-i-run-fern-generate-in-ci-and-publish-a-typescript-sdk-to-npm-and-a-pytho, keep these practices in mind:
- Consistency between CI and local dev: Run the same
fern generatecommand in CI that developers run locally. - Deterministic builds: Pin Fern generator versions in
generators.ymland lock dependencies (package-lock.json,poetry.lock, or equivalent) to avoid unintentional drift. - Clear release strategy:
- Use tags (
v1.2.3) to drive versions for both npm and PyPI. - Document how releases are triggered so teammates know how to cut a new SDK version.
- Use tags (
- Proper error handling:
- Fail the job if
fern generateor any build step fails. - Avoid partial publishes: if TypeScript publishing fails, you may want to prevent Python publishing in the same run, or vice versa, depending on your release policy.
- Fail the job if
- Security:
- Store
NPM_TOKENand PyPI credentials only as encrypted CI secrets. - Use tokens with strictly limited permissions.
- Store
Summary
To run fern generate in CI and publish a TypeScript SDK to npm and a Python SDK to PyPI:
- Configure
fern/generators.ymlto output TypeScript and Python SDKs to known directories. - In your CI workflow:
- Install Node.js, Python, and the Fern CLI.
- Run
fern generatein theferndirectory. - Build the TypeScript SDK and publish it to npm using an
NPM_TOKENsecret. - Build the Python SDK and publish it to PyPI using
twineand PyPI credentials.
- Use Git tags (or another versioning scheme) to keep versions in sync between your API, TypeScript SDK, and Python SDK.
With this setup, every new API release can automatically generate and ship fresh client libraries, keeping consumers aligned with your latest API definition.