diff --git a/fern/scripts/fern-published-branch.py b/fern/scripts/fern-published-branch.py index a28be833c..9cb93f36f 100644 --- a/fern/scripts/fern-published-branch.py +++ b/fern/scripts/fern-published-branch.py @@ -34,6 +34,10 @@ "site", } PUBLISH_METADATA_PATH = Path("fern/publish-metadata.json") +FERN_ROOT_CONFIG_PATHS = [ + "fern/docs.yml", + "fern/fern.config.json", +] FERN_DEVNOTE_SUPPORT_PATHS = [ "fern/assets", "fern/components/Authors.tsx", @@ -204,6 +208,12 @@ def copy_path(source: Path, target: Path) -> None: shutil.copy2(source, target) +def copy_required_path(source: Path, target: Path) -> None: + if not source.exists(): + raise PublishedBranchError(f"Missing required source path: {source}") + copy_path(source, target) + + def clear_published_tree(root: Path) -> None: root.mkdir(parents=True, exist_ok=True) for path in root.iterdir(): @@ -317,6 +327,14 @@ def materialize_version_nav_pages(published_root: Path) -> None: nav.write_text("".join(lines)) +def sync_fern_root_config(source_root: Path, published_root: Path) -> None: + preserved_versions_block = normalize_latest_display_name(versions_block(published_root / "fern" / "docs.yml")) + for rel_path in FERN_ROOT_CONFIG_PATHS: + copy_required_path(source_root / rel_path, published_root / rel_path) + restore_versions_block(published_root / "fern" / "docs.yml", preserved_versions_block) + validate_redirect_targets(published_root) + + def sync_source(args: argparse.Namespace) -> int: source_root = Path(args.source_root) published_root = Path(args.published_root) @@ -383,6 +401,8 @@ def patch_devnotes(args: argparse.Namespace) -> int: if not target_nav.exists(): raise PublishedBranchError(f"Missing {target_nav}; publish a Fern release snapshot first") + sync_fern_root_config(source_root, published_root) + for rel_path in FERN_DEVNOTE_SUPPORT_PATHS: copy_path(source_root / rel_path, published_root / rel_path) diff --git a/packages/data-designer/tests/docs/test_fern_published_branch.py b/packages/data-designer/tests/docs/test_fern_published_branch.py new file mode 100644 index 000000000..9fcd88a5b --- /dev/null +++ b/packages/data-designer/tests/docs/test_fern_published_branch.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import argparse +import importlib.util +from pathlib import Path +from types import ModuleType + +SCRIPT_PATH = Path(__file__).resolve().parents[4] / "fern" / "scripts" / "fern-published-branch.py" + + +def load_script_module() -> ModuleType: + spec = importlib.util.spec_from_file_location("fern_published_branch", SCRIPT_PATH) + assert spec is not None + loader = spec.loader + assert loader is not None + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + +def write_text(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + + +def patch_args(source_root: Path, published_root: Path) -> argparse.Namespace: + return argparse.Namespace( + source_root=source_root, + published_root=published_root, + metadata_source_repository=None, + metadata_source_ref=None, + metadata_source_sha=None, + metadata_release_tag=None, + metadata_published_branch=None, + ) + + +def test_patch_devnotes_syncs_root_config_and_preserves_published_versions(tmp_path: Path) -> None: + module = load_script_module() + source_root = tmp_path / "source" + published_root = tmp_path / "published" + + write_text( + source_root / "fern" / "docs.yml", + """instances: +- url: datadesigner.docs.buildwithfern.com/nemo/datadesigner +title: Source Fern Docs +global-theme: nvidia +navbar-links: +- type: github + value: https://github.com/NVIDIA-NeMo/DataDesigner +versions: +- display-name: "Latest" + path: versions/latest.yml + slug: latest +redirects: + - source: "/nemo/datadesigner/getting-started" + destination: "/nemo/datadesigner/getting-started/welcome" +""", + ) + write_text(source_root / "fern" / "fern.config.json", '{"organization": "nvidia", "version": "5.41.1"}\n') + write_text(source_root / "fern" / "assets" / "current-devnote-asset.png", "new asset") + write_text( + source_root / "fern" / "versions" / "latest.yml", + """navigation: + - section: Dev Notes + contents: + - page: New Note + path: ./latest/pages/devnotes/posts/new-note.mdx + - section: Concepts + contents: [] +""", + ) + write_text(source_root / "fern" / "versions" / "latest" / "pages" / "devnotes" / "posts" / "new-note.mdx", "# New") + + write_text( + published_root / "fern" / "docs.yml", + """instances: +- url: datadesigner.docs.buildwithfern.com/nemo/datadesigner +title: Published Fern Docs +footer: ./components/OldFooter.tsx +layout: + searchbar-placement: header +versions: +- display-name: latest + path: versions/latest.yml + slug: latest +- display-name: "v0.6.0" + path: versions/v0.6.0.yml + slug: v0.6.0 +redirects: + - source: "/nemo/datadesigner/getting-started" + destination: "/nemo/datadesigner/getting-started/welcome" +""", + ) + write_text(published_root / "fern" / "fern.config.json", '{"organization": "nvidia", "version": "4.106.0"}\n') + write_text(published_root / "fern" / "assets" / "published-only-asset.png", "old asset") + write_text( + published_root / "fern" / "versions" / "latest.yml", + """navigation: + - section: Dev Notes + contents: + - page: Old Note + path: ./latest/pages/devnotes/posts/old-note.mdx + - section: Concepts + contents: [] +""", + ) + + assert module.patch_devnotes(patch_args(source_root, published_root)) == 0 + + published_docs = (published_root / "fern" / "docs.yml").read_text() + assert "title: Source Fern Docs" in published_docs + assert "global-theme: nvidia" in published_docs + assert "navbar-links:" in published_docs + assert "title: Published Fern Docs" not in published_docs + assert "footer: ./components/OldFooter.tsx" not in published_docs + assert "searchbar-placement: header" not in published_docs + assert '- display-name: "Latest"' in published_docs + assert 'display-name: "v0.6.0"' in published_docs + assert (published_root / "fern" / "fern.config.json").read_text() == ( + '{"organization": "nvidia", "version": "5.41.1"}\n' + ) + assert (published_root / "fern" / "assets" / "current-devnote-asset.png").read_text() == "new asset" + assert not (published_root / "fern" / "assets" / "published-only-asset.png").exists() + assert (published_root / "fern" / "versions" / "latest" / "pages" / "devnotes" / "posts" / "new-note.mdx").exists()