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
4 changes: 3 additions & 1 deletion fern/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ OpenShell uses [Fern](https://buildwithfern.com/) to validate, preview, and publ
| `fern/assets/` | Logos and other shared assets. |
| `fern/main.css` | Site-wide styles. |

In a normal source checkout, `fern/docs.yml` points the `latest` version at `docs/index.yml`. Release automation builds the multi-version configuration on the generated `docs-website` branch.
In a normal source checkout, `fern/docs.yml` points the `dev` version at `docs/index.yml`. Release automation builds the multi-version configuration on the generated `docs-website` branch and maps the source documentation to the channel being published.

## Local development

Expand Down Expand Up @@ -54,6 +54,8 @@ The sync and publish workflows share the `docs-website` concurrency group. This

The `dev` snapshot also owns the shared Fern configuration, components, assets, and CSS on `docs-website`. The `latest` snapshot copies its documentation and navigation but does not replace those shared files. This keeps the site configuration aligned with `main` while preserving the released content.

A `dev` sync copies the top-level `announcement` from the source `fern/docs.yml`. This announcement is the global fallback, and removing it from the source removes it from `docs-website`. Each snapshot sync copies the source version announcement only to the channel being updated. A version announcement overrides the global announcement for that version, so Release Dev cannot change the `latest` announcement and Release Tag cannot change the `dev` announcement.

## Manual maintenance and publishing

Maintainers can run `.github/workflows/sync-docs.yml` manually to add, refresh, or remove a historical version snapshot. The workflow preserves snapshots that were not selected. Production publishing is disabled by default for a manual sync.
Expand Down
11 changes: 5 additions & 6 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-License-Identifier: Apache-2.0
# Fern site config — merged with skills/convert-to-fern/assets/theme/nvidia/docs-theme.yml pattern
# Production: https://docs.nvidia.com/openshell/ — preview + custom-domain share path prefix `openshell`.
# Internal MDX links use /latest/...; basepath-aware resolves them under /openshell/ on production.
# Source checkouts and pull request previews expose the current documentation at /dev.
instances:
- url: openshell.docs.buildwithfern.com/openshell
custom-domain: docs.nvidia.com/openshell
Expand Down Expand Up @@ -38,7 +38,7 @@ logo:
dark: ./assets/NVIDIA_dark.svg
light: ./assets/NVIDIA_light.svg
height: 20
href: /openshell/latest
href: /openshell
right-text: OpenShell

favicon: ./assets/NVIDIA_symbol.svg
Expand All @@ -56,11 +56,10 @@ experimental:
- ./components

versions:
- display-name: Latest
- display-name: Dev
path: ../docs/index.yml
slug: latest
announcement:
message: '<span style="display: block; padding: 0.375rem 0; text-align: left;"><strong>OpenShell 0.1.0 is coming soon.</strong> <a href="https://github.com/NVIDIA/OpenShell/milestone/10" target="_blank" rel="noreferrer">Track progress in the 0.1.0 milestone</a>, <a href="https://docs.nvidia.com/openshell/dev/index.html" target="_blank" rel="noreferrer">read the prerelease documentation</a>, or <a href="https://docs.nvidia.com/openshell/dev/about/installation#install-a-prerelease" target="_blank" rel="noreferrer">install a prerelease</a>.</span>'
slug: dev
availability: beta

redirects:
- source: "/openshell/latest/sandboxes/providers-v2"
Expand Down
43 changes: 41 additions & 2 deletions tasks/scripts/sync_docs_website.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class VersionEntry:
display_name: str
path: str
availability: str | None = None
announcement: YamlMapping | None = None


def parse_args() -> argparse.Namespace:
Expand Down Expand Up @@ -312,6 +313,7 @@ def parse_versions(raw_versions: object) -> list[VersionEntry]:
display_name = entry.get("display-name")
path = entry.get("path")
availability = entry.get("availability")
announcement = entry.get("announcement")
if (
isinstance(slug, str)
and isinstance(display_name, str)
Expand All @@ -325,6 +327,9 @@ def parse_versions(raw_versions: object) -> list[VersionEntry]:
availability=availability
if isinstance(availability, str)
else None,
announcement=cast("YamlMapping", announcement)
if isinstance(announcement, dict)
else None,
)
)
return entries
Expand All @@ -349,8 +354,8 @@ def ordered_entries(
return [by_slug[slug] for slug in order]


def render_versions(entries: list[VersionEntry]) -> list[dict[str, str]]:
rendered: list[dict[str, str]] = []
def render_versions(entries: list[VersionEntry]) -> list[YamlMapping]:
rendered: list[YamlMapping] = []
for entry in entries:
item = {
"display-name": entry.display_name,
Expand All @@ -359,10 +364,36 @@ def render_versions(entries: list[VersionEntry]) -> list[dict[str, str]]:
}
if entry.availability is not None:
item["availability"] = entry.availability
if entry.announcement is not None:
item["announcement"] = entry.announcement
rendered.append(item)
return rendered


def sync_global_announcement(source_docs_yml: Path, target_docs_yml: Path) -> None:
source_data = read_yaml(source_docs_yml)
source_announcement = source_data.get("announcement")
if source_announcement is not None and not isinstance(source_announcement, dict):
raise ValueError("docs.yml announcement must be a mapping")

target_data = read_yaml(target_docs_yml)
if source_announcement is None:
target_data.pop("announcement", None)
else:
target_data["announcement"] = source_announcement
write_yaml(target_docs_yml, target_data)


def source_version_announcement(docs_yml: Path, slug: str) -> YamlMapping | None:
entries = parse_versions(read_yaml(docs_yml).get("versions"))
for entry in entries:
if entry.slug == slug:
return entry.announcement
if len(entries) == 1:
return entries[0].announcement
return None


def component_dirs(fern_dir: Path) -> list[str]:
dirs: list[str] = []
preferred = ["pages-latest", "pages-dev"]
Expand Down Expand Up @@ -408,6 +439,7 @@ def write_snapshot(
copy_if_exists(
source_fern / "fern.config.json", target_fern / "fern.config.json"
)
sync_global_announcement(source_fern / "docs.yml", target_fern / "docs.yml")

versions_dir = target_fern / "versions"
versions_dir.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -483,6 +515,9 @@ def sync_docs(args: argparse.Namespace) -> None:
display_name=slug,
path=f"./versions/{slug}.yml",
availability=stable_availability,
announcement=source_version_announcement(
source_fern / "docs.yml", slug
),
),
refresh_shared=False,
)
Expand All @@ -509,6 +544,9 @@ def sync_docs(args: argparse.Namespace) -> None:
display_name=display_override or f"Latest ({slug})",
path="./versions/latest.yml",
availability=stable_availability,
announcement=source_version_announcement(
source_fern / "docs.yml", "latest"
),
),
refresh_shared=False,
)
Expand Down Expand Up @@ -548,6 +586,7 @@ def sync_docs(args: argparse.Namespace) -> None:
display_name=display_name,
path=f"./versions/{slug}.yml",
availability=availability,
announcement=source_version_announcement(source_fern / "docs.yml", slug),
),
refresh_shared=channel == "dev",
)
Expand Down
Loading
Loading