Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 29 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,8 @@ DOCS_PYTHON ?= .venv/bin/python
DOCS_JUPYTEXT ?= .venv/bin/jupytext
FERN_VERSION ?= $(shell jq -r .version fern/fern.config.json)
FERN ?= npx -y fern-api@$(FERN_VERSION)
FERN_ORG ?= $(shell jq -r .organization fern/fern.config.json)
FERN_GLOBAL_THEME ?= $(shell awk '/^global-theme:/ { print $$2; exit }' fern/docs.yml)

# Route urllib/requests/httpx through certifi's CA bundle. Necessary when uv
# resolves $(DOCS_PYTHON_VERSION) to a python.org installer build, which ships without
Expand Down Expand Up @@ -506,10 +508,34 @@ check-fern-docs-locally:
@$(MAKE) check-fern-docs
@echo "βœ… Fern docs check complete"

check-fern-theme-access:
@if [ -n "$(FERN_GLOBAL_THEME)" ]; then \
cd fern && $(FERN) docs theme list --org $(FERN_ORG) --json >/dev/null || { \
echo ""; \
echo "Unable to access Fern global theme '$(FERN_GLOBAL_THEME)' for org '$(FERN_ORG)'."; \
echo "Fix: sign in to https://dashboard.buildwithfern.com, then run:"; \
echo " cd fern && $(FERN) login"; \
echo "If it still fails, export a privileged DOCS_FERN_TOKEN as FERN_TOKEN."; \
exit 1; \
}; \
fi

serve-fern-docs-locally:
@$(MAKE) install-docs-deps
@$(MAKE) prepare-fern-docs
cd fern && PNPM_CONFIG_DANGEROUSLY_ALLOW_ALL_BUILDS=true $(FERN) docs dev
@$(MAKE) serve-fern-docs-dev

serve-fern-docs-dev:
@if $(MAKE) check-fern-theme-access >/dev/null 2>&1; then \
cd fern && PNPM_CONFIG_DANGEROUSLY_ALLOW_ALL_BUILDS=true $(FERN) docs dev; \
else \
echo "Unable to access Fern global theme '$(FERN_GLOBAL_THEME)' for org '$(FERN_ORG)'."; \
echo "Serving with the local NVIDIA-style fallback. Run 'make check-fern-theme-access' for auth details."; \
$(MAKE) serve-fern-docs-local-theme; \
fi

serve-fern-docs-local-theme:
@PNPM_CONFIG_DANGEROUSLY_ALLOW_ALL_BUILDS=true $(DOCS_PYTHON) fern/scripts/serve-local-docs-preview.py --root fern -- $(FERN) docs dev

convert-execute-notebooks:
ifeq ($(USE_CACHE),1)
Expand Down Expand Up @@ -720,7 +746,7 @@ clean-test-coverage:
.PHONY: bench-cli-startup bench-cli-startup-verbose \
build build-config build-engine build-interface \
check-all check-all-fix check-config check-engine check-interface \
check-fern-docs check-fern-docs-locally check-fern-release-version check-license-headers \
check-fern-docs check-fern-docs-locally check-fern-release-version check-fern-theme-access check-license-headers \
clean clean-dist clean-notebooks clean-pycache clean-test-coverage \
convert-execute-notebooks \
coverage coverage-config coverage-engine coverage-interface \
Expand All @@ -729,7 +755,7 @@ clean-test-coverage:
generate-colab-notebooks generate-fern-notebooks generate-fern-notebooks-with-outputs help \
install install-dev install-dev-notebooks install-dev-recipes install-docs-deps \
lint lint-config lint-engine lint-fix lint-fix-config lint-fix-engine lint-fix-interface lint-interface \
perf-import perf-import-runtime prepare-fern-docs prepare-fern-release publish serve-fern-docs-locally show-versions \
perf-import perf-import-runtime prepare-fern-docs prepare-fern-release publish serve-fern-docs-dev serve-fern-docs-local-theme serve-fern-docs-locally show-versions \
health-checks \
test test-config test-config-isolated test-e2e test-engine test-engine-isolated \
test-interface test-interface-isolated test-isolated \
Expand Down
11 changes: 11 additions & 0 deletions fern/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ make serve-fern-docs-locally
```

`serve-fern-docs-locally` generates notebook artifacts before starting `fern docs dev`. It does not publish.
It uses the real NVIDIA global theme when Fern auth can access it. Without that access, it serves from a temporary local preview config with a close NVIDIA-style fallback.

## CI and publishing

Expand Down Expand Up @@ -146,6 +147,10 @@ render-time guard (`document.getElementById`, a module flag) would risk an SSR
hydration mismatch. If per-instance duplication ever matters, revisit once Fern
supports React's `<style precedence>` hoisting.

The only checked-in standalone CSS is `styles/local-preview.css`, used by
`make serve-fern-docs-locally` only when the Fern global theme is inaccessible.
It is not referenced from `docs.yml`.

## Common commands

Primary local commands:
Expand Down Expand Up @@ -174,3 +179,9 @@ Raw Fern CLI commands, normally wrapped by Make:
| `fern docs dev` | Local preview at `http://localhost:3000` |
| `fern check` | Validate `docs.yml` and MDX |
| `fern generate --docs --preview` | Hosted preview on `*.docs.buildwithfern.com` (needs Fern token) |

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| Local preview uses the local fallback theme | Sign in to https://dashboard.buildwithfern.com, run `cd fern && npx -y fern-api@$(jq -r .version fern.config.json) login`, then retry. If it still falls back, export a privileged `DOCS_FERN_TOKEN` as `FERN_TOKEN`. |
112 changes: 112 additions & 0 deletions fern/scripts/serve-local-docs-preview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Serve Fern docs with a local approximation of the NVIDIA global theme."""

from __future__ import annotations

import argparse
import signal
import subprocess
import sys
import tempfile
from pathlib import Path

import yaml

LOCAL_THEME_CONFIG = {
"layout": {
"searchbar-placement": "header",
"page-width": "1376px",
"sidebar-width": "248px",
"content-width": "812px",
"tabs-placement": "header",
"hide-feedback": True,
},
"colors": {
"accent-primary": {
"dark": "#76B900",
"light": "#004B31",
},
"background": {
"dark": "#000000",
"light": "#FFFFFF",
},
},
"theme": {
"page-actions": "toolbar",
"footer-nav": "minimal",
},
}


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--root", default="fern", help="Path to the Fern docs root")
parser.add_argument("command", nargs=argparse.REMAINDER)
args = parser.parse_args()
if args.command[:1] == ["--"]:
args.command = args.command[1:]
if not args.command:
parser.error("missing command to run")
return args


def write_local_docs_config(root: Path, preview_root: Path) -> None:
config = yaml.safe_load((root / "docs.yml").read_text(encoding="utf-8"))
config.pop("global-theme", None)

for key, value in LOCAL_THEME_CONFIG.items():
config.setdefault(key, value)

logo = dict(config.get("logo") or {})
logo.setdefault("height", 20)
config["logo"] = logo

css = config.get("css")
local_css = "./styles/local-preview.css"
if css is None:
config["css"] = [local_css]
elif isinstance(css, list) and local_css not in css:
config["css"] = [*css, local_css]
elif isinstance(css, str) and css != local_css:
config["css"] = [css, local_css]

(preview_root / "docs.yml").write_text(
yaml.safe_dump(config, sort_keys=False),
encoding="utf-8",
)


def build_preview_root(root: Path, preview_root: Path) -> None:
for child in root.iterdir():
if child.name == "docs.yml":
continue
target = preview_root / child.name
target.symlink_to(child, target_is_directory=child.is_dir())
write_local_docs_config(root, preview_root)


def run_command(command: list[str], cwd: Path) -> int:
process = subprocess.Popen(command, cwd=cwd)
try:
return process.wait()
except KeyboardInterrupt:
process.send_signal(signal.SIGINT)
return process.wait()


def main() -> int:
args = parse_args()
root = Path(args.root).resolve()
with tempfile.TemporaryDirectory(prefix="fern-local-preview-") as temp_dir:
preview_root = Path(temp_dir) / "fern"
preview_root.mkdir()
build_preview_root(root, preview_root)
print(f"Using local Fern preview config at {preview_root / 'docs.yml'}", file=sys.stderr)
return run_command(args.command, preview_root)


if __name__ == "__main__":
raise SystemExit(main())
48 changes: 48 additions & 0 deletions fern/styles/local-preview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
:root {
--nv-green: #76b900;
--nv-green-dark: #004b31;
--nv-black: #000000;
--nv-white: #ffffff;
}

a,
.fern-mdx-link {
text-decoration-color: var(--nv-green) !important;
}

.fern-mdx-link:hover {
color: var(--nv-green-dark) !important;
}

html[data-theme="dark"] .fern-mdx-link:hover {
color: var(--nv-green) !important;
}

.fern-header,
header {
border-bottom: 1px solid var(--border, rgba(118, 185, 0, 0.24)) !important;
}

#fern-sidebar {
border-right: 1px solid var(--border, rgba(118, 185, 0, 0.2)) !important;
}

.fern-sidebar-link[aria-current="page"],
.fern-sidebar-link[data-state="active"] {
border-left: 3px solid var(--nv-green) !important;
}

.fern-sidebar-link:hover,
.fern-card:hover {
box-shadow: inset 0 0 0 1px rgba(118, 185, 0, 0.38) !important;
}

button,
[role="button"] {
border-radius: 4px;
}

code,
pre {
border-color: rgba(118, 185, 0, 0.18) !important;
}
Loading