From fde5a94b9fb5d3d2749b5541759525412d90ff1b Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Thu, 17 Sep 2026 17:01:56 -0700 Subject: [PATCH 1/4] docs(fern): sync announcement configuration Signed-off-by: Piotr Mlocek --- fern/README.md | 2 + fern/docs.yml | 2 + tasks/scripts/sync_docs_website.py | 65 ++++++++++++- tasks/scripts/sync_docs_website_test.py | 116 +++++++++++++++++++++++- 4 files changed, 182 insertions(+), 3 deletions(-) diff --git a/fern/README.md b/fern/README.md index b8e0ea3964..f8f512792e 100644 --- a/fern/README.md +++ b/fern/README.md @@ -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 announcement settings from the source `fern/docs.yml`. The top-level `announcement` is the global fallback and removing it from the source removes it from `docs-website`. An announcement under a `versions` entry applies only to the matching version and overrides the global announcement for that version. + ## 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. diff --git a/fern/docs.yml b/fern/docs.yml index 65639bd5d0..2d33707023 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -59,6 +59,8 @@ versions: - display-name: Latest path: ../docs/index.yml slug: latest + announcement: + message: 'OpenShell 0.1.0 is coming soon.
Track progress in the 0.1.0 milestone, read the prerelease documentation, or install a prerelease.
' redirects: - source: "/openshell/latest/sandboxes/providers-v2" diff --git a/tasks/scripts/sync_docs_website.py b/tasks/scripts/sync_docs_website.py index 62000394d3..3b98b933c0 100644 --- a/tasks/scripts/sync_docs_website.py +++ b/tasks/scripts/sync_docs_website.py @@ -36,6 +36,7 @@ class VersionEntry: display_name: str path: str availability: str | None = None + announcement: YamlMapping | None = None def parse_args() -> argparse.Namespace: @@ -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) @@ -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 @@ -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, @@ -359,10 +364,56 @@ 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_announcements(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") + + source_entries = { + entry.slug: entry for entry in parse_versions(source_data.get("versions")) + } + target_data = read_yaml(target_docs_yml) + if source_announcement is None: + target_data.pop("announcement", None) + else: + target_data["announcement"] = source_announcement + + target_entries = parse_versions(target_data.get("versions")) + for entry in target_entries: + source_entry = source_entries.get(entry.slug) + if source_entry is not None: + entry.announcement = source_entry.announcement + target_data["versions"] = render_versions(target_entries) + write_yaml(target_docs_yml, target_data) + + +def version_announcement(docs_yml: Path, slug: str) -> YamlMapping | None: + return next( + ( + entry.announcement + for entry in parse_versions(read_yaml(docs_yml).get("versions")) + if entry.slug == slug + ), + None, + ) + + +def resolve_version_announcement( + source_docs_yml: Path, target_docs_yml: Path, slug: str +) -> YamlMapping | None: + for entry in parse_versions(read_yaml(source_docs_yml).get("versions")): + if entry.slug == slug: + return entry.announcement + return version_announcement(target_docs_yml, slug) + + def component_dirs(fern_dir: Path) -> list[str]: dirs: list[str] = [] preferred = ["pages-latest", "pages-dev"] @@ -408,6 +459,7 @@ def write_snapshot( copy_if_exists( source_fern / "fern.config.json", target_fern / "fern.config.json" ) + sync_announcements(source_fern / "docs.yml", target_fern / "docs.yml") versions_dir = target_fern / "versions" versions_dir.mkdir(parents=True, exist_ok=True) @@ -483,6 +535,9 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=slug, path=f"./versions/{slug}.yml", availability=stable_availability, + announcement=resolve_version_announcement( + source_fern / "docs.yml", docs_yml, slug + ), ), refresh_shared=False, ) @@ -509,6 +564,9 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=display_override or f"Latest ({slug})", path="./versions/latest.yml", availability=stable_availability, + announcement=resolve_version_announcement( + source_fern / "docs.yml", docs_yml, "latest" + ), ), refresh_shared=False, ) @@ -548,6 +606,9 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=display_name, path=f"./versions/{slug}.yml", availability=availability, + announcement=resolve_version_announcement( + source_fern / "docs.yml", docs_yml, slug + ), ), refresh_shared=channel == "dev", ) diff --git a/tasks/scripts/sync_docs_website_test.py b/tasks/scripts/sync_docs_website_test.py index 076181ee39..1fdc381bb7 100644 --- a/tasks/scripts/sync_docs_website_test.py +++ b/tasks/scripts/sync_docs_website_test.py @@ -147,13 +147,14 @@ def test_resolve_availability() -> None: sdw.resolve_availability("dev", "alpha") -def test_parse_and_render_versions_preserves_availability() -> None: +def test_parse_and_render_versions_preserves_version_settings() -> None: raw_versions = [ { "display-name": "v0.0.36", "path": "./versions/v0.0.36.yml", "slug": "v0.0.36", "availability": "deprecated", + "announcement": {"message": "Upgrade to the latest version."}, } ] @@ -165,11 +166,96 @@ def test_parse_and_render_versions_preserves_availability() -> None: "v0.0.36", "./versions/v0.0.36.yml", "deprecated", + {"message": "Upgrade to the latest version."}, ) ] assert sdw.render_versions(entries) == raw_versions +def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: + source_docs_yml = tmp_path / "source.yml" + target_docs_yml = tmp_path / "target.yml" + source_docs_yml.write_text( + yaml.safe_dump( + { + "announcement": {"message": "Current global announcement."}, + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + } + ], + } + ), + encoding="utf-8", + ) + target_docs_yml.write_text( + yaml.safe_dump( + { + "announcement": {"message": "Stale global announcement."}, + "versions": [ + { + "display-name": "Latest (v0.0.116)", + "path": "./versions/latest.yml", + "slug": "latest", + }, + { + "display-name": "Dev", + "path": "./versions/dev.yml", + "slug": "dev", + "announcement": {"message": "Development docs."}, + }, + ], + } + ), + encoding="utf-8", + ) + + sdw.sync_announcements(source_docs_yml, target_docs_yml) + + target_data = read_yaml(target_docs_yml) + assert target_data["announcement"] == {"message": "Current global announcement."} + assert target_data["versions"] == [ + { + "display-name": "Latest (v0.0.116)", + "path": "./versions/latest.yml", + "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + }, + { + "display-name": "Dev", + "path": "./versions/dev.yml", + "slug": "dev", + "announcement": {"message": "Development docs."}, + }, + ] + + source_docs_yml.write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + } + ] + } + ), + encoding="utf-8", + ) + + sdw.sync_announcements(source_docs_yml, target_docs_yml) + + target_data = read_yaml(target_docs_yml) + assert "announcement" not in target_data + versions = target_data["versions"] + assert "announcement" not in versions[0] + assert versions[1]["announcement"] == {"message": "Development docs."} + + def test_ordered_entries_pins_latest_then_dev() -> None: existing = [ sdw.VersionEntry("v0.0.36", "v0.0.36", "./versions/v0.0.36.yml"), @@ -212,6 +298,8 @@ def _make_source_tree(root: Path) -> None: encoding="utf-8", ) fern = root / "fern" + fern.mkdir(parents=True) + (fern / "docs.yml").write_text(yaml.safe_dump({"versions": []}), encoding="utf-8") (fern / "assets").mkdir(parents=True) (fern / "assets" / "logo.svg").write_text("", encoding="utf-8") (fern / "components").mkdir(parents=True) @@ -233,6 +321,30 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No website = tmp_path / "docs-website" _make_source_tree(source) _make_docs_website_tree(website) + (source / "fern" / "docs.yml").write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + } + ] + } + ), + encoding="utf-8", + ) + (website / "fern" / "docs.yml").write_text( + yaml.safe_dump( + { + "announcement": {"message": "Stale global announcement."}, + "versions": [], + } + ), + encoding="utf-8", + ) sdw.sync_docs( Namespace( @@ -276,6 +388,7 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No "display-name": "Latest (v0.0.116)", "path": "./versions/latest.yml", "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, }, { "display-name": "Dev", @@ -284,6 +397,7 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No "availability": "beta", }, ] + assert "announcement" not in docs_yml assert "./components" in docs_yml["experimental"]["mdx-components"] From 4ca7da1758b05b4a4a73ab145b74821e79c00da6 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Thu, 17 Sep 2026 17:27:37 -0700 Subject: [PATCH 2/4] fix(docs): scope announcements to synced channel Signed-off-by: Piotr Mlocek --- fern/README.md | 2 +- tasks/scripts/sync_docs_website.py | 48 ++----- tasks/scripts/sync_docs_website_test.py | 169 +++++++++++++++++++++--- 3 files changed, 162 insertions(+), 57 deletions(-) diff --git a/fern/README.md b/fern/README.md index f8f512792e..25c9be1fa7 100644 --- a/fern/README.md +++ b/fern/README.md @@ -54,7 +54,7 @@ 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 announcement settings from the source `fern/docs.yml`. The top-level `announcement` is the global fallback and removing it from the source removes it from `docs-website`. An announcement under a `versions` entry applies only to the matching version and overrides the global announcement for that version. +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 diff --git a/tasks/scripts/sync_docs_website.py b/tasks/scripts/sync_docs_website.py index 3b98b933c0..396dd71268 100644 --- a/tasks/scripts/sync_docs_website.py +++ b/tasks/scripts/sync_docs_website.py @@ -370,48 +370,28 @@ def render_versions(entries: list[VersionEntry]) -> list[YamlMapping]: return rendered -def sync_announcements(source_docs_yml: Path, target_docs_yml: Path) -> None: +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") - source_entries = { - entry.slug: entry for entry in parse_versions(source_data.get("versions")) - } target_data = read_yaml(target_docs_yml) if source_announcement is None: target_data.pop("announcement", None) else: target_data["announcement"] = source_announcement - - target_entries = parse_versions(target_data.get("versions")) - for entry in target_entries: - source_entry = source_entries.get(entry.slug) - if source_entry is not None: - entry.announcement = source_entry.announcement - target_data["versions"] = render_versions(target_entries) write_yaml(target_docs_yml, target_data) -def version_announcement(docs_yml: Path, slug: str) -> YamlMapping | None: - return next( - ( - entry.announcement - for entry in parse_versions(read_yaml(docs_yml).get("versions")) - if entry.slug == slug - ), - None, - ) - - -def resolve_version_announcement( - source_docs_yml: Path, target_docs_yml: Path, slug: str -) -> YamlMapping | None: - for entry in parse_versions(read_yaml(source_docs_yml).get("versions")): +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 - return version_announcement(target_docs_yml, slug) + if len(entries) == 1: + return entries[0].announcement + return None def component_dirs(fern_dir: Path) -> list[str]: @@ -459,7 +439,7 @@ def write_snapshot( copy_if_exists( source_fern / "fern.config.json", target_fern / "fern.config.json" ) - sync_announcements(source_fern / "docs.yml", target_fern / "docs.yml") + sync_global_announcement(source_fern / "docs.yml", target_fern / "docs.yml") versions_dir = target_fern / "versions" versions_dir.mkdir(parents=True, exist_ok=True) @@ -535,8 +515,8 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=slug, path=f"./versions/{slug}.yml", availability=stable_availability, - announcement=resolve_version_announcement( - source_fern / "docs.yml", docs_yml, slug + announcement=source_version_announcement( + source_fern / "docs.yml", slug ), ), refresh_shared=False, @@ -564,8 +544,8 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=display_override or f"Latest ({slug})", path="./versions/latest.yml", availability=stable_availability, - announcement=resolve_version_announcement( - source_fern / "docs.yml", docs_yml, "latest" + announcement=source_version_announcement( + source_fern / "docs.yml", "latest" ), ), refresh_shared=False, @@ -606,9 +586,7 @@ def sync_docs(args: argparse.Namespace) -> None: display_name=display_name, path=f"./versions/{slug}.yml", availability=availability, - announcement=resolve_version_announcement( - source_fern / "docs.yml", docs_yml, slug - ), + announcement=source_version_announcement(source_fern / "docs.yml", slug), ), refresh_shared=channel == "dev", ) diff --git a/tasks/scripts/sync_docs_website_test.py b/tasks/scripts/sync_docs_website_test.py index 1fdc381bb7..3c94263502 100644 --- a/tasks/scripts/sync_docs_website_test.py +++ b/tasks/scripts/sync_docs_website_test.py @@ -172,21 +172,14 @@ def test_parse_and_render_versions_preserves_version_settings() -> None: assert sdw.render_versions(entries) == raw_versions -def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: +def test_sync_global_announcement_applies_source_config(tmp_path: Path) -> None: source_docs_yml = tmp_path / "source.yml" target_docs_yml = tmp_path / "target.yml" source_docs_yml.write_text( yaml.safe_dump( { "announcement": {"message": "Current global announcement."}, - "versions": [ - { - "display-name": "Latest", - "path": "../docs/index.yml", - "slug": "latest", - "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, - } - ], + "versions": [], } ), encoding="utf-8", @@ -213,7 +206,7 @@ def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: encoding="utf-8", ) - sdw.sync_announcements(source_docs_yml, target_docs_yml) + sdw.sync_global_announcement(source_docs_yml, target_docs_yml) target_data = read_yaml(target_docs_yml) assert target_data["announcement"] == {"message": "Current global announcement."} @@ -222,7 +215,6 @@ def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: "display-name": "Latest (v0.0.116)", "path": "./versions/latest.yml", "slug": "latest", - "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, }, { "display-name": "Dev", @@ -233,6 +225,24 @@ def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: ] source_docs_yml.write_text( + yaml.safe_dump({"versions": []}), + encoding="utf-8", + ) + + sdw.sync_global_announcement(source_docs_yml, target_docs_yml) + + target_data = read_yaml(target_docs_yml) + assert "announcement" not in target_data + versions = target_data["versions"] + assert "announcement" not in versions[0] + assert versions[1]["announcement"] == {"message": "Development docs."} + + +def test_source_version_announcement_maps_single_source_version_to_channel( + tmp_path: Path, +) -> None: + docs_yml = tmp_path / "docs.yml" + docs_yml.write_text( yaml.safe_dump( { "versions": [ @@ -240,6 +250,7 @@ def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: "display-name": "Latest", "path": "../docs/index.yml", "slug": "latest", + "announcement": {"message": "Snapshot announcement."}, } ] } @@ -247,13 +258,10 @@ def test_sync_announcements_applies_source_config(tmp_path: Path) -> None: encoding="utf-8", ) - sdw.sync_announcements(source_docs_yml, target_docs_yml) - - target_data = read_yaml(target_docs_yml) - assert "announcement" not in target_data - versions = target_data["versions"] - assert "announcement" not in versions[0] - assert versions[1]["announcement"] == {"message": "Development docs."} + expected = {"message": "Snapshot announcement."} + assert sdw.source_version_announcement(docs_yml, "latest") == expected + assert sdw.source_version_announcement(docs_yml, "dev") == expected + assert sdw.source_version_announcement(docs_yml, "v0.0.116") == expected def test_ordered_entries_pins_latest_then_dev() -> None: @@ -316,7 +324,9 @@ def _make_docs_website_tree(root: Path) -> None: (fern / "docs.yml").write_text(yaml.safe_dump({"versions": []}), encoding="utf-8") -def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> None: +def test_sync_docs_scopes_version_announcements_to_updated_channel( + tmp_path: Path, +) -> None: source = tmp_path / "source" website = tmp_path / "docs-website" _make_source_tree(source) @@ -329,7 +339,9 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No "display-name": "Latest", "path": "../docs/index.yml", "slug": "latest", - "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + "announcement": { + "message": "Version 0.0.116 is the final alpha release." + }, } ] } @@ -360,6 +372,21 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No availability="", ) ) + (source / "fern" / "docs.yml").write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + } + ] + } + ), + encoding="utf-8", + ) sdw.sync_docs( Namespace( operation="sync", @@ -388,18 +415,53 @@ def test_sync_docs_creates_planned_latest_and_dev_selector(tmp_path: Path) -> No "display-name": "Latest (v0.0.116)", "path": "./versions/latest.yml", "slug": "latest", - "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, + "announcement": {"message": "Version 0.0.116 is the final alpha release."}, }, { "display-name": "Dev", "path": "./versions/dev.yml", "slug": "dev", "availability": "beta", + "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, }, ] assert "announcement" not in docs_yml assert "./components" in docs_yml["experimental"]["mdx-components"] + (source / "fern" / "docs.yml").write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + "announcement": {"message": "OpenShell 0.1.0 is released."}, + } + ] + } + ), + encoding="utf-8", + ) + sdw.sync_docs( + Namespace( + operation="sync", + source_root=source, + docs_website_root=website, + channel="latest", + source_ref="release-0.1.0-sha", + source_sha="release-0.1.0-sha", + release_version="0.1.0", + version_slug="", + display_name="Latest (v0.1.0)", + availability="", + ) + ) + + versions = read_yaml(fern / "docs.yml")["versions"] + assert versions[0]["announcement"] == {"message": "OpenShell 0.1.0 is released."} + assert versions[1]["announcement"] == {"message": "OpenShell 0.1.0 is coming soon."} + def test_sync_docs_preserves_other_version_availability(tmp_path: Path) -> None: source = tmp_path / "source" @@ -455,6 +517,71 @@ def test_sync_docs_preserves_other_version_availability(tmp_path: Path) -> None: ] +def test_latest_sync_updates_legacy_snapshot_announcement(tmp_path: Path) -> None: + source = tmp_path / "source" + website = tmp_path / "docs-website" + _make_source_tree(source) + _make_docs_website_tree(website) + (source / "fern" / "docs.yml").write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest", + "path": "../docs/index.yml", + "slug": "latest", + "announcement": { + "message": "Version 0.0.116 is the final alpha release." + }, + } + ] + } + ), + encoding="utf-8", + ) + docs_yml_path = website / "fern" / "docs.yml" + docs_yml_path.write_text( + yaml.safe_dump( + { + "versions": [ + { + "display-name": "Latest (v0.0.116)", + "path": "./versions/latest.yml", + "slug": "latest", + } + ] + } + ), + encoding="utf-8", + ) + + sdw.sync_docs( + Namespace( + operation="sync", + source_root=source, + docs_website_root=website, + channel="latest", + source_ref="docs/v0.0.116-announcement", + source_sha="announcement-sha", + release_version="0.0.116", + version_slug="", + display_name="Latest (v0.0.116)", + availability="", + ) + ) + + latest = read_yaml(docs_yml_path)["versions"][0] + assert latest["announcement"] == { + "message": "Version 0.0.116 is the final alpha release." + } + snapshots = read_yaml(website / "fern" / sdw.SNAPSHOT_METADATA_FILE)["snapshots"] + assert snapshots["latest"] == { + "source-ref": "docs/v0.0.116-announcement", + "source-sha": "announcement-sha", + "version": "0.0.116", + } + + def test_stable_sync_creates_immutable_version_and_promotes_latest( tmp_path: Path, ) -> None: From a4648ebbd896e9067a758326ea939940a2b04ec3 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Thu, 17 Sep 2026 17:40:54 -0700 Subject: [PATCH 3/4] docs(fern): use dev as source version Signed-off-by: Piotr Mlocek --- fern/README.md | 2 +- fern/docs.yml | 11 +++++------ tasks/scripts/sync_docs_website_test.py | 12 ++++++------ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/fern/README.md b/fern/README.md index 25c9be1fa7..8e86d83041 100644 --- a/fern/README.md +++ b/fern/README.md @@ -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 diff --git a/fern/docs.yml b/fern/docs.yml index 2d33707023..a4503fbddd 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -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 @@ -38,7 +38,7 @@ logo: dark: ./assets/NVIDIA_dark.svg light: ./assets/NVIDIA_light.svg height: 20 - href: /openshell/latest + href: /openshell/dev right-text: OpenShell favicon: ./assets/NVIDIA_symbol.svg @@ -56,11 +56,10 @@ experimental: - ./components versions: - - display-name: Latest + - display-name: Dev path: ../docs/index.yml - slug: latest - announcement: - message: 'OpenShell 0.1.0 is coming soon.
Track progress in the 0.1.0 milestone, read the prerelease documentation, or install a prerelease.
' + slug: dev + availability: beta redirects: - source: "/openshell/latest/sandboxes/providers-v2" diff --git a/tasks/scripts/sync_docs_website_test.py b/tasks/scripts/sync_docs_website_test.py index 3c94263502..bef5ac995d 100644 --- a/tasks/scripts/sync_docs_website_test.py +++ b/tasks/scripts/sync_docs_website_test.py @@ -247,9 +247,9 @@ def test_source_version_announcement_maps_single_source_version_to_channel( { "versions": [ { - "display-name": "Latest", + "display-name": "Dev", "path": "../docs/index.yml", - "slug": "latest", + "slug": "dev", "announcement": {"message": "Snapshot announcement."}, } ] @@ -377,9 +377,9 @@ def test_sync_docs_scopes_version_announcements_to_updated_channel( { "versions": [ { - "display-name": "Latest", + "display-name": "Dev", "path": "../docs/index.yml", - "slug": "latest", + "slug": "dev", "announcement": {"message": "OpenShell 0.1.0 is coming soon."}, } ] @@ -433,9 +433,9 @@ def test_sync_docs_scopes_version_announcements_to_updated_channel( { "versions": [ { - "display-name": "Latest", + "display-name": "Dev", "path": "../docs/index.yml", - "slug": "latest", + "slug": "dev", "announcement": {"message": "OpenShell 0.1.0 is released."}, } ] From 26dd9c4fa689b4811fc52e3ada30919c25fa2f33 Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Thu, 17 Sep 2026 17:42:02 -0700 Subject: [PATCH 4/4] docs(fern): use channel-neutral logo link Signed-off-by: Piotr Mlocek --- fern/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/docs.yml b/fern/docs.yml index a4503fbddd..ad6ea31a06 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -38,7 +38,7 @@ logo: dark: ./assets/NVIDIA_dark.svg light: ./assets/NVIDIA_light.svg height: 20 - href: /openshell/dev + href: /openshell right-text: OpenShell favicon: ./assets/NVIDIA_symbol.svg