Skip to content

docs: add Fern MDX transform scripts and CI integration - #7058

Closed
dagil-nvidia wants to merge 3 commits into
mainfrom
dagil-nvidia/fernify-scripts
Closed

docs: add Fern MDX transform scripts and CI integration#7058
dagil-nvidia wants to merge 3 commits into
mainfrom
dagil-nvidia/fernify-scripts

Conversation

@dagil-nvidia

@dagil-nvidia dagil-nvidia commented Mar 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add fernify transform scripts that convert generated API reference Markdown into Fern MDX components
  • _fern_helpers.py: shared transforms (HTML <details><Accordion>, Markdown tables → <CardGroup>/<Card>, slugify, frontmatter injection)
  • fernify_python_api.py: transforms Python API reference output
  • fernify_rust_api.py: transforms Rust API reference output
  • fernify_k8s_api.py: transforms K8s CRD reference (wraps API groups in <Tabs>, resource lists in <CardGroup>, type defs in <Accordion>)
  • CI integration: adds Python setup, runs all 3 fernify scripts before syncing to docs-website, and pins Rust docs.rs links to release version on tag pushes

Design

The fernify scripts run at CI time (not at commit time). The raw Markdown checked into docs/ is GitHub-compatible, and CI transforms it into Fern MDX before syncing to the docs-website branch. This means:

  • Docs render correctly on both GitHub and the Fern docs site
  • No Fern-specific syntax leaks into the source repo

Known Limitations

The fernify transforms rely on regex-based Markdown parsing, which is inherently fragile. We chose this approach because:

  • The input Markdown is generated by our own scripts, so the structure is predictable
  • Full AST-based Markdown parsing (e.g., markdown-it) would add significant dependencies for relatively simple transforms
  • The transforms are idempotent and run at CI time, so failures are caught before publish

We acknowledge this is a v1 approach and plan to iterate as edge cases are discovered. Unit tests for the transform helpers (_fern_helpers.py) will follow in a fast-follow PR.

Expected CI Failure

The Preview or publish docs check fails on this PR because the fernify scripts expect docs/api/python/README.md and docs/api/rust/README.md to exist — these files are created by PR #7057. Once #7057 is merged first, this check will pass.

PR Series (split from #6989)

Merge in order:

  1. Docstringsdocs: add docstrings to Python components and bindings #7056
  2. API reference generators + navigationdocs: add API reference generators and navigation #7057 (provides the input files these scripts transform)
  3. Fernify transform scripts + CIdocs: add Fern MDX transform scripts and CI integration #7058 (this PR)

Test Plan

  • Pre-commit hooks pass
  • python3 docs/scripts/fernify_python_api.py transforms docs/api/python/README.md correctly
  • python3 docs/scripts/fernify_rust_api.py transforms docs/api/rust/README.md correctly
  • python3 docs/scripts/fernify_k8s_api.py transforms docs/kubernetes/api-reference.md correctly
  • CI workflow runs fernify step successfully
  • Fern preview renders all API reference pages

Follow-Up

  • Add unit tests for _fern_helpers.py transform functions (fast-follow PR)

Add fernify transform scripts that convert generated API reference
Markdown into Fern MDX components for the documentation site:

- _fern_helpers.py: shared transforms (details->Accordion, tables->Cards,
  slugify, frontmatter injection)
- fernify_python_api.py: transforms Python API reference
- fernify_rust_api.py: transforms Rust API reference
- fernify_k8s_api.py: transforms K8s CRD reference (wraps API groups
  in Tabs, resource lists in CardGroups, type defs in Accordions)

CI integration (.github/workflows/fern-docs.yml):
- Add Python setup + pip install for fernify dependencies
- Run all 3 fernify scripts before syncing to docs-website
- Pin Rust docs.rs links to release version on tag pushes

Part 3 of 3 (split from #6989):
  1. Docstrings (#7056)
  2. API reference generators + navigation (#7057)
  3. Fernify transform scripts + CI (this PR)

Signed-off-by: Dan Gil <dagil@nvidia.com>
Made-with: Cursor
@dagil-nvidia
dagil-nvidia requested a review from a team March 7, 2026 05:14
@github-actions github-actions Bot added docs documentation Improvements or additions to documentation actions labels Mar 7, 2026
@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request introduces an API documentation generation pipeline for Fern, adding workflow steps to generate and transform API docs for Python, Rust, and Kubernetes, a shared helper module with utilities for MDX/JSX manipulation and content transformation, and language-specific fernification scripts that convert API reference markdown into Fern-compatible MDX.

Changes

Cohort / File(s) Summary
Workflow & CI/CD
.github/workflows/fern-docs.yml
Added Python setup, API doc dependencies installation, and Fernify generation steps for Python, Rust, and Kubernetes sources in docs job; added Rust docs.rs link version pinning in release workflow.
Documentation Helpers
docs/scripts/_fern_helpers.py
New module with 11 utility functions for JSX/MDX escaping, text slugification, rendering cards/tables/accordions, and transforming markdown content to Fern components (details-to-accordion, table-to-cards, headings-to-accordion).
API Transformation Scripts
docs/scripts/fernify_python_api.py, docs/scripts/fernify_rust_api.py, docs/scripts/fernify_k8s_api.py
Three new language-specific scripts transforming API reference markdown into Fern MDX via modular pipelines; Python and Rust use helper-based transforms; Kubernetes includes resource card building, API group tabulation, version backticking, and admonition conversion.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A curious bunny hops through docs with glee,
Fernifying APIs for all to see!
From Python prose to Kubernetes might,
Transforming tables into cards so bright,
With helpers and accordions, stacked up high,
This rabbit-approved pipeline touches the sky! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding Fern MDX transform scripts and their CI integration.
Docstring Coverage ✅ Passed Docstring coverage is 84.38% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description comprehensively covers the changes, design rationale, limitations, CI expectations, and test plan with proper context and organization.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
docs/scripts/fernify_k8s_api.py (1)

260-265: Scan the full document in the idempotency guard.

Checking only the first 50 lines makes reruns brittle. If the first <Tabs>, <CardGroup>, or <Accordion> moves below Line 50, this will fernify an already-processed file again.

Suggested fix
 def _is_already_fernified(text: str) -> bool:
     """Detect if the file has already been processed (contains Fern MDX)."""
-    for line in text.split("\n")[:50]:
-        if "<Tabs>" in line or "<CardGroup" in line or "<Accordion" in line:
-            return True
-    return False
+    return any(marker in text for marker in ("<Tabs>", "<CardGroup", "<Accordion"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/scripts/fernify_k8s_api.py` around lines 260 - 265, The idempotency
guard in _is_already_fernified only scans the first 50 lines which is brittle;
change it to scan the entire document (e.g., check the full text rather than
text.split("\n")[:50]) and return True if any of the target markers ("<Tabs>",
"<CardGroup", "<Accordion") appear anywhere; update _is_already_fernified to use
a full-text search (or any(...) over all lines) so reruns correctly detect
already-fernified files regardless of where the tags appear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/fern-docs.yml:
- Around line 165-166: The workflow step in .github/workflows/fern-docs.yml that
runs "pip install -r source-checkout/docs/scripts/requirements-apidocs.txt"
points to a missing file and will fail; either remove that pip install step
entirely or add the referenced requirements-apidocs.txt containing the needed
third‑party packages. If you choose to remove it, delete the "Install API doc
dependencies" job/step (the run line) since the new fernify scripts
(fernify_python_api.py, fernify_rust_api.py, fernify_k8s_api.py) only use stdlib
and local modules; if you choose to keep it, create
source-checkout/docs/scripts/requirements-apidocs.txt and list any real
third-party dependencies required by the doc pipeline.

In `@docs/scripts/_fern_helpers.py`:
- Around line 16-17: render_details is passing the raw captured summary
(m.group(1)) into render_accordion which causes a second HTML-escape; instead
unescape the group before calling render_accordion so the summary is only
escaped once (e.g. use html.unescape(m.group(1)) or otherwise pass the original
unescaped summary to render_accordion). Update the calls in render_details and
the other similar sites (the blocks around lines 109-120 and 145-153) to
unescape the captured summary before invoking render_accordion.
- Around line 37-55: Card descriptions are emitted raw into <Card> bodies
without escaping and fernify_details_to_accordion is double-escaping summaries;
to fix, ensure you call escape_mdx_prose when rendering card descriptions (where
the <Card> body is constructed) so MDX-sensitive chars are escaped exactly once,
and change fernify_details_to_accordion so it does not pass an already-escaped
capture to render_accordion — either stop re-escaping the regex capture there or
adjust render_accordion to accept an already_escaped flag; specifically, locate
usages that construct Card content and add a single call to
escape_mdx_prose(text) and modify fernify_details_to_accordion/render_accordion
to perform escaping in one place only to prevent double-escaping.

In `@docs/scripts/fernify_k8s_api.py`:
- Around line 141-149: build_resource_cards' meta parameter is being ignored
because _resolve_meta always reads RESOURCE_META; update the code so callers'
custom metadata is honored: change _resolve_meta signature to accept an optional
meta dict (e.g., def _resolve_meta(name: str, full_text: str, meta:
dict[str,str]|None = None) -> dict[str,str]) and have it return meta[name] if
meta and name in meta, then fallback to RESOURCE_META, then to extracting
description and _discover_go_source; also update every call site (notably
build_resource_cards) to pass the incoming meta argument into _resolve_meta so
custom metadata is used.

---

Nitpick comments:
In `@docs/scripts/fernify_k8s_api.py`:
- Around line 260-265: The idempotency guard in _is_already_fernified only scans
the first 50 lines which is brittle; change it to scan the entire document
(e.g., check the full text rather than text.split("\n")[:50]) and return True if
any of the target markers ("<Tabs>", "<CardGroup", "<Accordion") appear
anywhere; update _is_already_fernified to use a full-text search (or any(...)
over all lines) so reruns correctly detect already-fernified files regardless of
where the tags appear.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 97e13d56-b189-4de1-bf4c-43974feca249

📥 Commits

Reviewing files that changed from the base of the PR and between f1dbea4 and a992a89.

📒 Files selected for processing (5)
  • .github/workflows/fern-docs.yml
  • docs/scripts/_fern_helpers.py
  • docs/scripts/fernify_k8s_api.py
  • docs/scripts/fernify_python_api.py
  • docs/scripts/fernify_rust_api.py

Comment thread .github/workflows/fern-docs.yml Outdated
Comment on lines +16 to +17
import re
from pathlib import Path

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.

⚠️ Potential issue | 🟠 Major

Don't double-escape <summary> text during details→accordion conversion.

render_details() already escapes the summary on the way into HTML. Passing m.group(1) straight into render_accordion() escapes the entities a second time, so titles containing &, <, > or quotes will render as &amp;amp;, &amp;lt;, etc. in the Python API path that uses this helper.

Suggested fix
+import html
 import re
 from pathlib import Path
@@
 def fernify_details_to_accordion(text: str) -> str:
     """Convert all <details>/<summary> blocks to Fern <Accordion> components."""
 
     def _replace(m: re.Match) -> str:
-        title = m.group(1)
+        title = html.unescape(m.group(1))
         body = m.group(2).strip()
         return render_accordion(title, body)

Also applies to: 109-120, 145-153

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/scripts/_fern_helpers.py` around lines 16 - 17, render_details is
passing the raw captured summary (m.group(1)) into render_accordion which causes
a second HTML-escape; instead unescape the group before calling render_accordion
so the summary is only escaped once (e.g. use html.unescape(m.group(1)) or
otherwise pass the original unescaped summary to render_accordion). Update the
calls in render_details and the other similar sites (the blocks around lines
109-120 and 145-153) to unescape the captured summary before invoking
render_accordion.

Comment thread docs/scripts/_fern_helpers.py
Comment thread docs/scripts/fernify_k8s_api.py Outdated
Signed-off-by: Dan Gil <dagil@nvidia.com>
Made-with: Cursor
@dagil-nvidia
dagil-nvidia marked this pull request as draft March 12, 2026 21:14
@dagil-nvidia

Copy link
Copy Markdown
Collaborator Author

Closing as stale. This work has been untouched for ~2 months and is superseded or no longer prioritized. Reopen if we pick it back up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

actions docs documentation Improvements or additions to documentation size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant