Skip to content

Commit 46dc8b2

Browse files
docs: prepare Fern docs workflow (#622)
* docs: prepare fern generated artifacts * docs: update fern migration artifacts * docs: leave colab notebooks unchanged * docs: add VLM recipe cards to Fern * docs: trim Dev Notes sidebar * docs: collapse older Dev Notes in sidebar * docs: add Fern publishing workflows * docs: gate Fern publishing on check * docs: restrict hosted previews for fork PRs * docs: clean Fern preview URL * docs: cancel stale preview runs * docs: clarify devnotes notebook reuse * docs: clean older versions route * docs: document Fern versioning conventions * docs: add Fern release version guard * docs: harden Fern release tag handling * ci: let docs preview continue after fern failure * ci: split docs preview deploy * docs: clarify fern make commands * ci: harden fern deploy workflows * docs: render preview notebooks without outputs * ci: keep docs preview deploy inline * docs: align notebook code highlighting * docs: show notebook snippet scrollbars * docs: isolate fern preview check failures * ci: align fern release docs behavior
1 parent da4875d commit 46dc8b2

57 files changed

Lines changed: 7608 additions & 9699 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/datadesigner-docs/SKILL.md

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,31 @@ description: >
1212

1313
Unified skill for adding, updating, moving, and removing pages on the NeMo Data Designer Fern docs site.
1414

15-
Production URL: **`docs.nvidia.com/nemo/datadesigner`** (see `instances` in [`fern/docs.yml`](../../../fern/docs.yml)). Source of truth for everything user-facing is `fern/`.
15+
Current URL: **`datadesigner.docs.buildwithfern.com/nemo/datadesigner`** (see `instances` in [`fern/docs.yml`](../../../fern/docs.yml)). Source of truth for everything user-facing is `fern/`.
1616

1717
## Scope Rule
1818

1919
**ALL doc edits happen under `fern/`.** The legacy `docs/` directory is the original MkDocs source — `docs/notebook_source/*.py` (jupytext) and `docs/devnotes/*.md` are still canonical for notebooks and dev notes (they feed Fern), but **do not add new top-level prose pages under `docs/`**. Concept pages, recipes, plugins, code reference — all of these live under `fern/versions/v0.5.8/pages/`.
2020

2121
## Versioning Model: Floating Latest
2222

23-
DataDesigner uses a **floating-latest pointer** (matches NeMo Curator). One canonical MDX tree backs both the "Latest" tab and the current frozen train.
23+
DataDesigner currently has Fern-native version entries backed by one shared migrated MDX tree. `latest` is a real rolling nav file; `v0.5.9` and `v0.5.8` are release nav files. All currently reuse `v0.5.8/pages/`.
2424

2525
```
2626
fern/versions/
27-
├── latest.yml ← Unix symlink → v0.5.8.yml
28-
├── v0.5.8.yml ← real nav file (paths point at ./v0.5.8/pages/...)
29-
└── v0.5.8/pages/ ← single canonical MDX tree
27+
├── latest.yml ← rolling nav file (reuses ./v0.5.8/pages/...)
28+
├── v0.5.9.yml ← release nav file (reuses ./v0.5.8/pages/...)
29+
├── v0.5.8.yml ← release nav file (reuses ./v0.5.8/pages/...)
30+
└── v0.5.8/pages/ ← shared migrated MDX tree
3031
```
3132

32-
`docs.yml` registers both `slug: latest` and `slug: v0.5.8`. When you edit a page, **you only edit the v0.5.8 copy** — the symlink means `latest` automatically tracks. No mirror step.
33+
`docs.yml` registers `slug: latest`, `slug: v0.5.9`, and `slug: v0.5.8`. When you edit shared docs, edit `v0.5.8/pages/`. Add version-specific page copies only when content diverges.
3334

34-
When a new release ships (e.g. v0.5.9):
35+
Dev Notes are rolling release: `latest.yml` can include posts from `main` that are not in the release nav yet. Frozen release navs (`v0.5.9.yml`, `v0.5.8.yml`) should include only posts available at that release point.
3536

36-
1. `cp -R fern/versions/v0.5.8 fern/versions/v0.5.9`
37-
2. `cp fern/versions/v0.5.8.yml fern/versions/v0.5.9.yml`
38-
3. `sed -i '' 's|./v0.5.8/pages/|./v0.5.9/pages/|g' fern/versions/v0.5.9.yml`
39-
4. `ln -sf v0.5.9.yml fern/versions/latest.yml` (re-target the symlink)
40-
5. Add a `v0.5.9` entry to `docs.yml`'s `versions:` list and update the `latest` entry's `display-name`.
37+
Released versions older than `v0.5.8` remain on the legacy MkDocs archive at `https://nvidia-nemo.github.io/DataDesigner/<version>/`. `docs.yml` redirects `/nemo/datadesigner/v<version>/...` to those archives for versions without a real Fern tree.
4138

42-
Old `v0.5.8` then renders unchanged from its frozen tree at `/v0.5.8/...`. The published site at `/latest/...` follows the symlink to `v0.5.9`.
39+
For future Fern-native releases, add a version YAML that reuses shared pages by default. Copy only pages that need version-specific content.
4340

4441
## Layout at a Glance
4542

@@ -59,20 +56,21 @@ fern/
5956
│ ├── TrajectoryViewer.tsx ← multi-turn tool-call traces (research dev notes)
6057
│ ├── BadgeLinks.tsx ← header shields (license, github, etc.)
6158
│ ├── Tag.tsx, CustomCard.tsx, CustomFooter.tsx
62-
│ ├── notebooks/ ← per-tutorial *.json + *.ts (MDX import target)
59+
│ ├── notebooks/ ← gitignored per-tutorial *.json + *.ts output
6360
│ └── devnotes/ ← .authors.yml, authors-data.ts, per-post trajectory data
6461
├── scripts/
6562
│ └── ipynb-to-fern-json.py ← .ipynb → fern/components/notebooks/*.{json,ts}
6663
├── code-reference/ ← gitignored; populated by `fern docs md generate`
6764
└── versions/
68-
├── latest.yml -> v0.5.8.yml
65+
├── latest.yml ← rolling navigation tree
66+
├── v0.5.9.yml ← release navigation tree, reuses v0.5.8/pages/
6967
├── v0.5.8.yml ← navigation tree
70-
└── v0.5.8/pages/ ← MDX content
68+
└── v0.5.8/pages/ ← shared MDX content
7169
```
7270

7371
## URL Routing Rules
7472

75-
Fern's URL is computed from the **section/page titles in `v0.5.8.yml`**, not the file path:
73+
Fern's URL is computed from the **section/page titles in the active version YAML**, not the file path:
7674

7775
```
7876
File system Published URL
@@ -88,7 +86,7 @@ Rules:
8886
- **Page title → kebab-case slug**: `page: Text-to-SQL for Nemotron Super``text-to-sql-for-nemotron-super` (the filename `text-to-sql.mdx` is irrelevant for routing).
8987
- **Subdirectories in the file path are dropped**`devnotes/posts/foo.mdx` becomes `/dev-notes/<page-title>` (no `/posts/`).
9088

91-
When in doubt, recompute by walking the page's position in `v0.5.8.yml` and slugifying each title.
89+
When in doubt, recompute by walking the page's position in the active version YAML and slugifying each title.
9290

9391
## Operations
9492

@@ -116,7 +114,7 @@ When in doubt, recompute by walking the page's position in `v0.5.8.yml` and slug
116114
```
117115
118116
4. The URL becomes `/<section-slug>/<page-title-slug>`. Update any cross-references in other MDX accordingly.
119-
5. **Do not edit `latest.yml`** — it's the symlink and auto-tracks.
117+
5. If the page is a rolling Dev Note that should appear before the next release, add it to `latest.yml` only.
120118

121119
### Update a Page
122120

@@ -261,12 +259,14 @@ layout: overview # optional — only on landing pages
261259
---
262260
```
263261

264-
Do **not** add `position:` (we use explicit nav order in `v0.5.8.yml`), `date:`, or `authors:` to frontmatter — Fern's runtime treats `authors:` as a JSX scope variable and explodes when a component tries to reference it. For dev notes, see "Dev Notes" below.
262+
Do **not** add `position:` (we use explicit nav order in the version YAML), `date:`, or `authors:` to frontmatter — Fern's runtime treats `authors:` as a JSX scope variable and explodes when a component tries to reference it. For dev notes, see "Dev Notes" below.
265263

266264
## Dev Notes (Blog Posts)
267265

268266
Dev notes live under `fern/versions/v0.5.8/pages/devnotes/posts/`. They use the dev-notes kit components: **`<Authors>`, `<MetricsTable>`, `<TrajectoryViewer>`, `<ExpandableCode>`, `<CustomCard>`** (sources in `fern/components/`, CSS in `fern/styles/`).
269267

268+
For rolling posts on `main`, add the page and card to `latest.yml` first. Add the same nav entry to a release YAML only when that post is part of that release.
269+
270270
### Authors Byline
271271

272272
Author registry: `fern/components/devnotes/.authors.yml` (source of truth) + `fern/components/devnotes/authors-data.ts` (typed copy that `Authors.tsx` imports). Edit both together.
@@ -364,26 +364,27 @@ docs/notebook_source/*.py (jupytext format — canonical source, edit
364364
│ make convert-execute-notebooks # jupytext --execute (needs NVIDIA_API_KEY)
365365
366366
docs/notebooks/*.ipynb (executed; outputs captured)
367-
│ make generate-colab-notebooks # injects "Open in Colab" badge
368-
369-
docs/colab_notebooks/*.ipynb (committed; for "Open in Colab" links)
370-
│ make generate-fern-notebooks # auto-prefers docs/notebooks/ when present
367+
368+
│ make generate-fern-notebooks # per-file prefers executed docs/notebooks/
369+
│ # otherwise converts notebook_source/*.py directly
371370
372-
fern/components/notebooks/*.{json,ts}
371+
fern/components/notebooks/*.{json,ts} (gitignored; generated before preview/publish)
373372
```
374373

375-
The `.ts` is what the wrapper MDX imports — Fern's bundler doesn't follow `.json` imports cleanly.
374+
`docs/colab_notebooks/*.ipynb` is a separate committed output for "Open in Colab" links. It is generated by `make generate-colab-notebooks`, but it is not a Fern docs build input.
375+
376+
The `.ts` is what the wrapper MDX imports. Fern's bundler doesn't follow `.json` imports cleanly.
376377

377378
### Make Targets
378379

379380
| Command | When |
380381
|---------|------|
381-
| `make generate-fern-notebooks` | Notebook prose changed, no need to re-execute. Auto-detects `docs/notebooks/` (executed) vs `docs/colab_notebooks/` (un-executed snapshots). |
382+
| `make generate-fern-notebooks` | Notebook prose changed, no need to re-execute. Per file, prefers `docs/notebooks/` (executed) and falls back to converting `docs/notebook_source/*.py` directly. |
382383
| `make generate-fern-notebooks-with-outputs` | Notebook code changed, want fresh outputs. Needs `NVIDIA_API_KEY` (and `OPENROUTER_API_KEY` for image notebooks 5–6). |
383384

384-
Both targets pin to `DOCS_PYTHON ?= 3.13` because `pyarrow` lacks Python 3.14 wheels. Override via `DOCS_PYTHON=3.12 make ...`.
385+
Install notebook docs dependencies first with `make install-dev-notebooks`. Docs setup pins to `DOCS_PYTHON_VERSION ?= 3.13` because `pyarrow` lacks Python 3.14 wheels. Override via `DOCS_PYTHON_VERSION=3.12 make ...`.
385386

386-
The `convert-execute-notebooks` step loops per-file with `|| failed=...`, so one notebook missing an API key won't kill the others — failures are reported at the end and the chain continues with whatever succeeded.
387+
The `convert-execute-notebooks` step loops per file so one notebook missing an API key does not prevent later notebooks from running. Any failure is reported after the loop and the make target exits non-zero.
387388

388389
### Wrapper Page
389390

@@ -408,13 +409,13 @@ The converter (`fern/scripts/ipynb-to-fern-json.py`) **auto-strips the leading C
408409

409410
## Python API Reference (`libraries:`)
410411

411-
`docs.yml` declares a `libraries:` block pointing at `packages/data-designer-config/src/data_designer/config`. Generated output lands at `fern/code-reference/data-designer/` **gitignored**. To populate locally:
412+
`docs.yml` declares a `libraries:` block pointing at `packages/data-designer-config/src/data_designer/config`. Local generation uses `py2fern` against that same source. Generated output lands at `fern/code-reference/data-designer/` - **gitignored**. To populate locally:
412413

413414
```bash
414-
cd fern && fern docs md generate
415+
make generate-fern-api-reference
415416
```
416417

417-
No FERN_TOKEN required. Re-run when the upstream Python source changes.
418+
This does not require Fern auth. Re-run when the upstream Python source changes. If you need to compare with Fern's native generator, use `make generate-fern-api-reference-native` with Fern auth.
418419

419420
The generated tree is wired into the nav via `versions/v0.5.8.yml`'s "Code Reference > Python API" folder entry (`folder: ../code-reference/data-designer`). The nav also includes prose pages under "Topic Overviews" — those are conceptual landings that link to the auto-generated reference.
420421

@@ -466,7 +467,7 @@ fern docs dev # localhost:3000 hot-reload preview
466467
To generate the API reference for local preview:
467468

468469
```bash
469-
cd fern && fern docs md generate # populates fern/code-reference/ (gitignored)
470+
make generate-fern-api-reference # py2fern; populates fern/code-reference/ (gitignored)
470471
```
471472

472473
If the "Python API" sidebar folder is empty, you forgot this step.
@@ -484,32 +485,7 @@ When the team adds a Fern preview workflow (modeled after Gym's `fern-docs-previ
484485

485486
## Cutting a New Version Train
486487

487-
When a release ships (e.g. `v0.5.9`):
488-
489-
1. Copy `fern/versions/v0.5.8/` → `fern/versions/v0.5.9/` (frozen snapshot).
490-
2. Copy `fern/versions/v0.5.8.yml` → `fern/versions/v0.5.9.yml` and rewrite `./v0.5.8/pages/` → `./v0.5.9/pages/` paths.
491-
3. Re-target the symlink: `ln -sf v0.5.9.yml fern/versions/latest.yml`.
492-
4. Add to `fern/docs.yml`:
493-
494-
```yaml
495-
versions:
496-
- display-name: "Latest · v0.5.9"
497-
path: versions/latest.yml
498-
slug: latest
499-
availability: stable
500-
- display-name: "v0.5.9"
501-
path: versions/v0.5.9.yml
502-
slug: v0.5.9
503-
availability: stable
504-
- display-name: "v0.5.8"
505-
path: versions/v0.5.8.yml
506-
slug: v0.5.8
507-
availability: stable
508-
```
509-
510-
5. Add v0.5.9-specific redirect entries (`/nemo/datadesigner/v0.5.9/:path*/index.html` → `/nemo/datadesigner/v0.5.9/:path*` etc.).
511-
512-
The `v0.5.8` tree continues to render at frozen URLs.
488+
Do not copy page trees by hand. Add a new version YAML that reuses shared pages by default; copy only pages that need version-specific content. If that becomes tedious, add a build-time materialization script before `fern generate --docs`.
513489

514490
## Debugging
515491

@@ -523,8 +499,8 @@ The `v0.5.8` tree continues to render at frozen URLs.
523499
| "Something went wrong!" runtime error | A custom component is throwing — check `<Authors ids={authors} />` (use literal array) or `<ExpandableCode>` (currently broken in SSR) |
524500
| Notebook page renders raw `<a href=colab...>` HTML | `.ts` was generated before the colab-strip improvement; re-run `make generate-fern-notebooks` |
525501
| Notebook page has no cell outputs | Ran without `NVIDIA_API_KEY` or `convert-execute-notebooks` failed; run `make generate-fern-notebooks-with-outputs` |
526-
| `URLError: [SSL: CERTIFICATE_VERIFY_FAILED]` during notebook execution | `DOCS_CERTS` not propagated; ensure you're invoking via the make target, not raw `uv run` |
527-
| `Failed to build pyarrow==X` from source | `DOCS_PYTHON` resolved to 3.14+; override with `DOCS_PYTHON=3.13 make ...` (or just rely on the default) |
502+
| `URLError: [SSL: CERTIFICATE_VERIFY_FAILED]` during notebook execution | `DOCS_CERTS` not propagated; ensure you're invoking via the make target, not raw Python |
503+
| `Failed to build pyarrow==X` from source | `DOCS_PYTHON_VERSION` resolved to 3.14+; override with `DOCS_PYTHON_VERSION=3.13 make ...` (or just rely on the default) |
528504
| Cards on landing all link to the same wrong URL | `href` not matching Fern's slugified-title rule — recompute as `/<section-slug>/<page-title-slug>` |
529505
| Image broken in preview, file exists at `fern/assets/...` | Reference uses relative `../assets/...` — change to absolute `/assets/...` (relative paths break across version slugs) |
530506

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
name: Build Fern docs
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
use_cache:
7+
description: "Use cached notebooks for unchanged sources"
8+
type: boolean
9+
default: true
10+
release_tag:
11+
description: "Release tag to validate and publish. Defaults to the latest published release."
12+
type: string
13+
required: false
14+
release:
15+
types:
16+
- published
17+
18+
permissions: {}
19+
20+
jobs:
21+
check-release-version:
22+
if: github.event_name != 'release' || github.event.release.prerelease != true
23+
runs-on: ubuntu-latest
24+
permissions:
25+
contents: read
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
29+
30+
- name: Find release tag
31+
id: release-tag
32+
env:
33+
GH_TOKEN: ${{ github.token }}
34+
EVENT_NAME: ${{ github.event_name }}
35+
EVENT_RELEASE_TAG: ${{ github.event.release.tag_name }}
36+
INPUT_RELEASE_TAG: ${{ inputs.release_tag }}
37+
run: |
38+
if [ "$EVENT_NAME" = "release" ]; then
39+
RELEASE_TAG="$EVENT_RELEASE_TAG"
40+
elif [ -n "$INPUT_RELEASE_TAG" ]; then
41+
RELEASE_TAG="$INPUT_RELEASE_TAG"
42+
else
43+
RELEASE_TAG=$(gh release view --json tagName -q .tagName)
44+
if [ -z "$RELEASE_TAG" ]; then
45+
echo "::error::Could not find the latest published release tag."
46+
exit 1
47+
fi
48+
fi
49+
50+
echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
51+
52+
- name: Check Fern release version
53+
env:
54+
RELEASE_TAG: ${{ steps.release-tag.outputs.release_tag }}
55+
run: python3 fern/scripts/fern-release-version.py check --version "$RELEASE_TAG"
56+
57+
build-notebooks:
58+
needs: check-release-version
59+
uses: ./.github/workflows/build-notebooks.yml
60+
permissions:
61+
actions: read
62+
contents: write
63+
with:
64+
use_cache: ${{ github.event_name == 'workflow_dispatch' && inputs.use_cache || false }}
65+
secrets: inherit
66+
67+
publish:
68+
needs:
69+
- check-release-version
70+
- build-notebooks
71+
runs-on: ubuntu-latest
72+
permissions:
73+
contents: read
74+
env:
75+
FERN_TOKEN: ${{ secrets.DOCS_FERN_TOKEN }}
76+
steps:
77+
- name: Checkout repository
78+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
79+
80+
- name: Install uv
81+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
82+
with:
83+
version: "0.9.5"
84+
85+
- name: Set up Python
86+
run: uv python install 3.13
87+
88+
- name: Install docs dependencies
89+
run: uv sync --python 3.13 --all-packages --group docs --group notebooks
90+
91+
- name: Download executed notebooks
92+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
93+
with:
94+
name: notebooks
95+
path: docs/notebooks
96+
97+
- name: Check Fern docs
98+
run: make check-fern-docs
99+
100+
- name: Publish Fern docs
101+
run: |
102+
if [ -z "$FERN_TOKEN" ]; then
103+
echo "::error::DOCS_FERN_TOKEN secret is required to publish Fern docs."
104+
exit 1
105+
fi
106+
107+
cd fern
108+
fern_version=$(jq -r .version fern.config.json)
109+
if [[ ! "$fern_version" =~ ^[0-9]+[.][0-9]+[.][0-9]+([-.][0-9A-Za-z]+)*$ ]]; then
110+
echo "::error::Invalid Fern version in fern.config.json: $fern_version"
111+
exit 1
112+
fi
113+
114+
npx -y "fern-api@$fern_version" generate --docs --no-prompt

.github/workflows/build-notebooks.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
version: "0.9.5"
3535
- name: Set up Python
3636
run: uv python install 3.11
37+
- name: Install notebook dependencies
38+
run: uv sync --all-packages --group notebooks --group docs
3739
- name: Restore notebook cache
3840
if: inputs.use_cache
3941
id: cache

0 commit comments

Comments
 (0)