mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-03 16:11:17 +08:00
* docs(fern): sync announcement configuration Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * fix(docs): scope announcements to synced channel Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * docs(fern): use dev as source version Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> * docs(fern): use channel-neutral logo link Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com> --------- Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
984 lines
32 KiB
Python
984 lines
32 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Tests for tasks/scripts/sync_docs_website.py.
|
|
|
|
Run via `mise run test:docs-website`, which provides pytest + PyYAML through
|
|
`uv run --with ...`. pytest puts this file's directory on sys.path, so the
|
|
sibling script imports directly as `sync_docs_website`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from argparse import Namespace
|
|
from pathlib import Path
|
|
from typing import cast
|
|
|
|
import pytest
|
|
import sync_docs_website as sdw
|
|
import yaml
|
|
|
|
|
|
def read_yaml(path: Path) -> dict:
|
|
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def read_workflow(name: str) -> dict:
|
|
path = Path(__file__).resolve().parents[2] / ".github" / "workflows" / name
|
|
return yaml.load(path.read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
|
|
|
|
|
|
def test_release_workflows_sync_and_publish_docs_once() -> None:
|
|
dev = read_workflow("release-dev.yml")
|
|
tag = read_workflow("release-tag.yml")
|
|
dev_job = dev["jobs"]["publish-fern-docs"]
|
|
tag_job = tag["jobs"]["publish-fern-docs"]
|
|
|
|
assert dev_job["needs"] == [
|
|
"compute-versions",
|
|
"release-dev",
|
|
"release-helm",
|
|
"trigger-wheel-publish",
|
|
]
|
|
assert dev_job["uses"] == "./.github/workflows/sync-docs.yml"
|
|
assert dev_job["with"]["channel"] == "dev"
|
|
assert (
|
|
dev_job["with"]["release_version"]
|
|
== "${{ needs.compute-versions.outputs.docs_version }}"
|
|
)
|
|
assert dev_job["with"]["publish"] == "true"
|
|
assert dev_job["with"]["display_name"] == "Dev"
|
|
assert dev_job["with"]["availability"] == "beta"
|
|
|
|
assert tag_job["needs"] == [
|
|
"compute-versions",
|
|
"release",
|
|
"publish-sdk-typescript",
|
|
"release-helm",
|
|
"trigger-wheel-publish",
|
|
]
|
|
assert tag_job["uses"] == "./.github/workflows/sync-docs.yml"
|
|
assert tag_job["with"]["channel"] == "latest"
|
|
assert (
|
|
tag_job["with"]["release_version"]
|
|
== "${{ needs.compute-versions.outputs.semver }}"
|
|
)
|
|
assert "version_slug" not in tag_job["with"]
|
|
assert (
|
|
tag_job["with"]["display_name"]
|
|
== "Latest (v${{ needs.compute-versions.outputs.semver }})"
|
|
)
|
|
assert tag_job["with"]["publish"] == "true"
|
|
assert "is_prerelease != 'true'" in tag_job["if"]
|
|
|
|
for workflow_name in ("release-dev.yml", "release-tag.yml"):
|
|
workflow_path = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ ".github"
|
|
/ "workflows"
|
|
/ workflow_name
|
|
)
|
|
workflow_text = workflow_path.read_text(encoding="utf-8")
|
|
assert workflow_text.count("uses: ./.github/workflows/sync-docs.yml") == 1
|
|
assert "fern generate --docs" not in workflow_text
|
|
|
|
|
|
def test_sync_workflow_serializes_sync_and_publish() -> None:
|
|
workflow = read_workflow("sync-docs.yml")
|
|
triggers = workflow["on"]
|
|
publish_input = triggers["workflow_call"]["inputs"]["publish"]
|
|
assert publish_input["type"] == "boolean"
|
|
assert publish_input["default"] == "false"
|
|
assert workflow["concurrency"]["group"] == "docs-website"
|
|
assert workflow["concurrency"]["queue"] == "max"
|
|
publish_workflow = read_workflow("publish-docs-website.yml")
|
|
assert publish_workflow["concurrency"]["queue"] == "max"
|
|
|
|
steps = workflow["jobs"]["sync"]["steps"]
|
|
step_names = [step["name"] for step in steps]
|
|
assert step_names.index("Commit docs website changes") < step_names.index(
|
|
"Publish Fern docs"
|
|
)
|
|
publish_step = next(step for step in steps if step["name"] == "Publish Fern docs")
|
|
assert publish_step["if"] == "${{ inputs.publish }}"
|
|
assert publish_step["working-directory"] == "docs-website/fern"
|
|
update_step = next(step for step in steps if step["name"] == "Update docs snapshot")
|
|
assert "git -C source rev-parse HEAD" in update_step["run"]
|
|
assert '--source-sha "$SOURCE_SHA"' in update_step["run"]
|
|
|
|
|
|
def test_resolve_slug_channels() -> None:
|
|
assert sdw.resolve_slug("dev", "") == "dev"
|
|
assert sdw.resolve_slug("latest", "") == "latest"
|
|
assert sdw.resolve_slug("stable", "v0.1.0") == "v0.1.0"
|
|
assert sdw.resolve_slug("version", "v0.0.36") == "v0.0.36"
|
|
|
|
|
|
def test_resolve_slug_version_requires_slug() -> None:
|
|
with pytest.raises(ValueError):
|
|
sdw.resolve_slug("version", "")
|
|
|
|
|
|
def test_resolve_slug_rejects_unsafe_characters() -> None:
|
|
# Guards the slug that becomes a directory name (pages-<slug>).
|
|
with pytest.raises(ValueError):
|
|
sdw.resolve_slug("version", "../escape")
|
|
with pytest.raises(ValueError):
|
|
sdw.resolve_slug("version", "v1 0")
|
|
|
|
|
|
def test_resolve_display_name() -> None:
|
|
assert sdw.resolve_display_name("dev", "dev", "main", "") == "dev"
|
|
assert (
|
|
sdw.resolve_display_name("latest", "latest", "v0.0.57", "")
|
|
== "Latest (v0.0.57)"
|
|
)
|
|
assert sdw.resolve_display_name("latest", "latest", "abc123", "") == "Latest"
|
|
assert sdw.resolve_display_name("version", "v0.0.36", "v0.0.36", "") == "v0.0.36"
|
|
assert sdw.resolve_display_name("dev", "dev", "main", "Custom") == "Custom"
|
|
|
|
|
|
def test_resolve_availability() -> None:
|
|
assert sdw.resolve_availability("dev", "") == "beta"
|
|
assert sdw.resolve_availability("latest", "") is None
|
|
assert sdw.resolve_availability("version", "") is None
|
|
assert sdw.resolve_availability("version", "deprecated") == "deprecated"
|
|
with pytest.raises(ValueError):
|
|
sdw.resolve_availability("dev", "alpha")
|
|
|
|
|
|
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."},
|
|
}
|
|
]
|
|
|
|
entries = sdw.parse_versions(raw_versions)
|
|
|
|
assert entries == [
|
|
sdw.VersionEntry(
|
|
"v0.0.36",
|
|
"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_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": [],
|
|
}
|
|
),
|
|
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_global_announcement(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",
|
|
},
|
|
{
|
|
"display-name": "Dev",
|
|
"path": "./versions/dev.yml",
|
|
"slug": "dev",
|
|
"announcement": {"message": "Development docs."},
|
|
},
|
|
]
|
|
|
|
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": [
|
|
{
|
|
"display-name": "Dev",
|
|
"path": "../docs/index.yml",
|
|
"slug": "dev",
|
|
"announcement": {"message": "Snapshot announcement."},
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
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:
|
|
existing = [
|
|
sdw.VersionEntry("v0.0.36", "v0.0.36", "./versions/v0.0.36.yml"),
|
|
sdw.VersionEntry("dev", "dev", "./versions/dev.yml"),
|
|
]
|
|
updated = sdw.VersionEntry("latest", "Latest", "./versions/latest.yml")
|
|
ordered = [entry.slug for entry in sdw.ordered_entries(existing, updated)]
|
|
assert ordered == ["latest", "dev", "v0.0.36"]
|
|
|
|
|
|
def test_prefix_navigation_paths() -> None:
|
|
nav: dict[str, object] = {
|
|
"navigation": [
|
|
{"page": "Intro", "path": "intro.mdx"},
|
|
{
|
|
"section": "Guide",
|
|
"folder": "guide",
|
|
"contents": [{"path": "guide/a.mdx"}],
|
|
},
|
|
{"page": "External", "path": "https://example.com"},
|
|
]
|
|
}
|
|
sdw.prefix_navigation_paths(nav, "pages-dev")
|
|
navigation = cast("list[dict[str, object]]", nav["navigation"])
|
|
guide = navigation[1]
|
|
contents = cast("list[dict[str, object]]", guide["contents"])
|
|
assert navigation[0]["path"] == "../pages-dev/intro.mdx"
|
|
assert guide["folder"] == "../pages-dev/guide"
|
|
assert contents[0]["path"] == "../pages-dev/guide/a.mdx"
|
|
# Absolute URLs are left untouched.
|
|
assert navigation[2]["path"] == "https://example.com"
|
|
|
|
|
|
def _make_source_tree(root: Path) -> None:
|
|
docs = root / "docs"
|
|
docs.mkdir(parents=True)
|
|
(docs / "intro.mdx").write_text("# Intro\n", encoding="utf-8")
|
|
(docs / "index.yml").write_text(
|
|
yaml.safe_dump({"navigation": [{"page": "Intro", "path": "intro.mdx"}]}),
|
|
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("<svg/>", encoding="utf-8")
|
|
(fern / "components").mkdir(parents=True)
|
|
(fern / "components" / "Card.tsx").write_text(
|
|
"export const Card = 1;\n", encoding="utf-8"
|
|
)
|
|
(fern / "main.css").write_text("body{}\n", encoding="utf-8")
|
|
(fern / "fern.config.json").write_text('{"version": "0.0.0"}\n', encoding="utf-8")
|
|
|
|
|
|
def _make_docs_website_tree(root: Path) -> None:
|
|
fern = root / "fern"
|
|
fern.mkdir(parents=True)
|
|
(fern / "docs.yml").write_text(yaml.safe_dump({"versions": []}), encoding="utf-8")
|
|
|
|
|
|
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)
|
|
_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",
|
|
)
|
|
(website / "fern" / "docs.yml").write_text(
|
|
yaml.safe_dump(
|
|
{
|
|
"announcement": {"message": "Stale global announcement."},
|
|
"versions": [],
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
operation="sync",
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="latest",
|
|
source_ref="release-sha",
|
|
source_sha="release-sha",
|
|
release_version="0.0.116",
|
|
version_slug="",
|
|
display_name="Latest (v0.0.116)",
|
|
availability="",
|
|
)
|
|
)
|
|
(source / "fern" / "docs.yml").write_text(
|
|
yaml.safe_dump(
|
|
{
|
|
"versions": [
|
|
{
|
|
"display-name": "Dev",
|
|
"path": "../docs/index.yml",
|
|
"slug": "dev",
|
|
"announcement": {"message": "OpenShell 0.1.0 is coming soon."},
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
operation="sync",
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="dev",
|
|
source_ref="main",
|
|
source_sha="dev-sha",
|
|
release_version="0.0.117.dev56",
|
|
version_slug="",
|
|
display_name="Dev",
|
|
availability="beta",
|
|
)
|
|
)
|
|
|
|
fern = website / "fern"
|
|
assert (fern / "pages-dev" / "intro.mdx").is_file()
|
|
assert (fern / "assets" / "logo.svg").is_file()
|
|
|
|
version_nav = read_yaml(fern / "versions" / "dev.yml")
|
|
assert version_nav["navigation"][0]["path"] == "../pages-dev/intro.mdx"
|
|
|
|
docs_yml = read_yaml(fern / "docs.yml")
|
|
assert docs_yml["versions"] == [
|
|
{
|
|
"display-name": "Latest (v0.0.116)",
|
|
"path": "./versions/latest.yml",
|
|
"slug": "latest",
|
|
"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": "Dev",
|
|
"path": "../docs/index.yml",
|
|
"slug": "dev",
|
|
"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"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
docs_yml_path = website / "fern" / "docs.yml"
|
|
docs_yml_path.write_text(
|
|
yaml.safe_dump(
|
|
{
|
|
"versions": [
|
|
{
|
|
"display-name": "v0.0.36",
|
|
"path": "./versions/v0.0.36.yml",
|
|
"slug": "v0.0.36",
|
|
"availability": "deprecated",
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
operation="sync",
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="dev",
|
|
source_ref="main",
|
|
source_sha="dev-sha",
|
|
release_version="0.0.117.dev56",
|
|
version_slug="",
|
|
display_name="Dev (v0.0.117.dev56)",
|
|
availability="beta",
|
|
)
|
|
)
|
|
|
|
versions = read_yaml(docs_yml_path)["versions"]
|
|
assert versions == [
|
|
{
|
|
"display-name": "Dev (v0.0.117.dev56)",
|
|
"path": "./versions/dev.yml",
|
|
"slug": "dev",
|
|
"availability": "beta",
|
|
},
|
|
{
|
|
"display-name": "v0.0.36",
|
|
"path": "./versions/v0.0.36.yml",
|
|
"slug": "v0.0.36",
|
|
"availability": "deprecated",
|
|
},
|
|
]
|
|
|
|
|
|
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:
|
|
source = tmp_path / "source"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref="v0.2.0",
|
|
source_sha="new-sha",
|
|
release_version="0.2.0",
|
|
version_slug="v0.2.0",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
fern = website / "fern"
|
|
assert (fern / "pages-v0.2.0" / "intro.mdx").is_file()
|
|
assert (fern / "pages-latest" / "intro.mdx").is_file()
|
|
versions = read_yaml(fern / "docs.yml")["versions"]
|
|
assert [entry["slug"] for entry in versions] == ["latest", "v0.2.0"]
|
|
assert versions[0]["display-name"] == "Latest (v0.2.0)"
|
|
assert versions[0]["availability"] == "stable"
|
|
assert versions[1]["availability"] == "stable"
|
|
snapshots = read_yaml(fern / sdw.SNAPSHOT_METADATA_FILE)["snapshots"]
|
|
assert snapshots["latest"] == {
|
|
"source-ref": "v0.2.0",
|
|
"source-sha": "new-sha",
|
|
"version": "0.2.0",
|
|
}
|
|
assert snapshots["v0.2.0"] == {
|
|
"source-ref": "v0.2.0",
|
|
"source-sha": "new-sha",
|
|
"version": "0.2.0",
|
|
}
|
|
|
|
|
|
def test_n_minus_one_sync_does_not_move_latest_backwards(tmp_path: Path) -> None:
|
|
current = tmp_path / "current"
|
|
maintenance = tmp_path / "maintenance"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(current)
|
|
_make_source_tree(maintenance)
|
|
(current / "docs" / "intro.mdx").write_text("# Current\n", encoding="utf-8")
|
|
(maintenance / "docs" / "intro.mdx").write_text("# Maintenance\n", encoding="utf-8")
|
|
_make_docs_website_tree(website)
|
|
|
|
for source, source_sha, version in (
|
|
(current, "current-sha", "0.3.1"),
|
|
(maintenance, "maintenance-sha", "0.2.7"),
|
|
):
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref=f"v{version}",
|
|
source_sha=source_sha,
|
|
release_version=version,
|
|
version_slug=f"v{version}",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
fern = website / "fern"
|
|
assert (fern / "pages-latest" / "intro.mdx").read_text(
|
|
encoding="utf-8"
|
|
) == "# Current\n"
|
|
assert (fern / "pages-v0.2.7" / "intro.mdx").read_text(
|
|
encoding="utf-8"
|
|
) == "# Maintenance\n"
|
|
snapshots = read_yaml(fern / sdw.SNAPSHOT_METADATA_FILE)["snapshots"]
|
|
assert snapshots["latest"] == {
|
|
"source-ref": "v0.3.1",
|
|
"source-sha": "current-sha",
|
|
"version": "0.3.1",
|
|
}
|
|
|
|
|
|
def test_stable_sync_preserves_newer_legacy_latest_without_metadata(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
source = tmp_path / "source"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
fern = website / "fern"
|
|
(fern / "pages-latest").mkdir()
|
|
(fern / "pages-latest" / "intro.mdx").write_text(
|
|
"# Existing latest\n", encoding="utf-8"
|
|
)
|
|
(fern / "docs.yml").write_text(
|
|
yaml.safe_dump(
|
|
{
|
|
"versions": [
|
|
{
|
|
"display-name": "Latest (v0.3.1)",
|
|
"path": "./versions/latest.yml",
|
|
"slug": "latest",
|
|
}
|
|
]
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref="v0.2.7",
|
|
source_sha="maintenance-sha",
|
|
release_version="0.2.7",
|
|
version_slug="v0.2.7",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
assert (fern / "pages-latest" / "intro.mdx").read_text(
|
|
encoding="utf-8"
|
|
) == "# Existing latest\n"
|
|
snapshots = read_yaml(fern / sdw.SNAPSHOT_METADATA_FILE)["snapshots"]
|
|
assert snapshots["latest"] == {
|
|
"source-ref": "",
|
|
"source-sha": "",
|
|
"version": "0.3.1",
|
|
}
|
|
|
|
|
|
def test_dev_sync_rejects_stale_or_conflicting_updates(tmp_path: Path) -> None:
|
|
current = tmp_path / "current"
|
|
stale = tmp_path / "stale"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(current)
|
|
_make_source_tree(stale)
|
|
(current / "docs" / "intro.mdx").write_text("# Current\n", encoding="utf-8")
|
|
(stale / "docs" / "intro.mdx").write_text("# Stale\n", encoding="utf-8")
|
|
_make_docs_website_tree(website)
|
|
|
|
def sync(source: Path, source_sha: str, version: str) -> None:
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="dev",
|
|
source_ref="main",
|
|
source_sha=source_sha,
|
|
release_version=version,
|
|
version_slug="",
|
|
display_name=f"Dev (v{version})",
|
|
availability="beta",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
sync(current, "current-sha", "0.3.2.dev10")
|
|
sync(stale, "stale-sha", "0.3.2.dev9")
|
|
intro = website / "fern" / "pages-dev" / "intro.mdx"
|
|
assert intro.read_text(encoding="utf-8") == "# Current\n"
|
|
|
|
with pytest.raises(ValueError, match="already points to current-sha"):
|
|
sync(stale, "other-sha", "0.3.2.dev10")
|
|
|
|
|
|
def test_dev_sync_allows_explicit_rollback(tmp_path: Path) -> None:
|
|
current = tmp_path / "current"
|
|
stale = tmp_path / "stale"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(current)
|
|
_make_source_tree(stale)
|
|
(current / "docs" / "intro.mdx").write_text("# Current\n", encoding="utf-8")
|
|
(stale / "docs" / "intro.mdx").write_text("# Rolled back\n", encoding="utf-8")
|
|
_make_docs_website_tree(website)
|
|
|
|
def sync(source: Path, source_sha: str, version: str, allow_rollback: bool) -> None:
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="dev",
|
|
source_ref="main",
|
|
source_sha=source_sha,
|
|
release_version=version,
|
|
version_slug="",
|
|
display_name=f"Dev (v{version})",
|
|
availability="beta",
|
|
allow_rollback=allow_rollback,
|
|
)
|
|
)
|
|
|
|
sync(current, "current-sha", "0.3.2.dev10", False)
|
|
sync(stale, "rollback-sha", "0.3.2.dev9", True)
|
|
|
|
fern = website / "fern"
|
|
assert (fern / "pages-dev" / "intro.mdx").read_text(
|
|
encoding="utf-8"
|
|
) == "# Rolled back\n"
|
|
snapshots = read_yaml(fern / sdw.SNAPSHOT_METADATA_FILE)["snapshots"]
|
|
assert snapshots["dev"] == {
|
|
"source-ref": "main",
|
|
"source-sha": "rollback-sha",
|
|
"version": "0.3.2.dev9",
|
|
}
|
|
|
|
|
|
def test_immutable_snapshot_cannot_change_source(tmp_path: Path) -> None:
|
|
source = tmp_path / "source"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
args = Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref="main",
|
|
source_sha="release-sha",
|
|
release_version="0.2.0",
|
|
version_slug="v0.2.0",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
sdw.sync_docs(args)
|
|
|
|
args.source_sha = "different-sha"
|
|
with pytest.raises(ValueError, match=r"immutable snapshot v0\.2\.0"):
|
|
sdw.sync_docs(args)
|
|
|
|
|
|
def test_only_dev_refreshes_shared_fern_files(tmp_path: Path) -> None:
|
|
dev = tmp_path / "dev"
|
|
release = tmp_path / "release"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(dev)
|
|
_make_source_tree(release)
|
|
(dev / "fern" / "components" / "Card.tsx").write_text(
|
|
"export const Card = 'dev';\n", encoding="utf-8"
|
|
)
|
|
(release / "fern" / "components" / "Card.tsx").write_text(
|
|
"export const Card = 'release';\n", encoding="utf-8"
|
|
)
|
|
_make_docs_website_tree(website)
|
|
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=dev,
|
|
docs_website_root=website,
|
|
channel="dev",
|
|
source_ref="main",
|
|
source_sha="dev-sha",
|
|
release_version="0.2.1.dev1",
|
|
version_slug="",
|
|
display_name="Dev (v0.2.1.dev1)",
|
|
availability="beta",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=release,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref="v0.2.0",
|
|
source_sha="release-sha",
|
|
release_version="0.2.0",
|
|
version_slug="v0.2.0",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
card = website / "fern" / "components" / "Card.tsx"
|
|
assert card.read_text(encoding="utf-8") == "export const Card = 'dev';\n"
|
|
|
|
|
|
def test_remove_docs_drops_snapshot(tmp_path: Path) -> None:
|
|
source = tmp_path / "source"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
|
|
base = Namespace(
|
|
operation="sync",
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="version",
|
|
source_ref="v0.0.36",
|
|
source_sha="release-sha",
|
|
version_slug="v0.0.36",
|
|
display_name="",
|
|
availability="deprecated",
|
|
)
|
|
sdw.sync_docs(base)
|
|
|
|
fern = website / "fern"
|
|
assert (fern / "pages-v0.0.36").is_dir()
|
|
assert (fern / "versions" / "v0.0.36.yml").is_file()
|
|
|
|
sdw.remove_docs(
|
|
Namespace(
|
|
operation="remove",
|
|
source_root=None,
|
|
docs_website_root=website,
|
|
channel="version",
|
|
source_ref="",
|
|
version_slug="v0.0.36",
|
|
display_name="",
|
|
availability="",
|
|
)
|
|
)
|
|
|
|
assert not (fern / "pages-v0.0.36").exists()
|
|
assert not (fern / "versions" / "v0.0.36.yml").exists()
|
|
docs_yml = read_yaml(fern / "docs.yml")
|
|
assert [entry["slug"] for entry in docs_yml["versions"]] == []
|
|
|
|
|
|
@pytest.mark.parametrize("channel", ["stable", "version"])
|
|
def test_immutable_snapshot_uses_resolved_commit_identity(
|
|
tmp_path: Path, channel: str
|
|
) -> None:
|
|
source = tmp_path / "source"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(source)
|
|
_make_docs_website_tree(website)
|
|
args = Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel=channel,
|
|
source_ref="main",
|
|
source_sha="first-sha",
|
|
release_version="0.2.0" if channel == "stable" else "",
|
|
version_slug="v0.2.0",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
sdw.sync_docs(args)
|
|
|
|
(source / "docs" / "intro.mdx").write_text("# Changed\n", encoding="utf-8")
|
|
args.source_sha = "second-sha"
|
|
|
|
with pytest.raises(ValueError, match=r"immutable snapshot v0\.2\.0"):
|
|
sdw.sync_docs(args)
|
|
assert (website / "fern" / "pages-v0.2.0" / "intro.mdx").read_text(
|
|
encoding="utf-8"
|
|
) == "# Intro\n"
|
|
|
|
|
|
def test_stable_promotion_replaces_latest_page_components(tmp_path: Path) -> None:
|
|
old = tmp_path / "old"
|
|
new = tmp_path / "new"
|
|
website = tmp_path / "docs-website"
|
|
_make_source_tree(old)
|
|
_make_source_tree(new)
|
|
(old / "docs" / "_components").mkdir()
|
|
(old / "docs" / "_components" / "Widget.tsx").write_text(
|
|
"export const Widget = 'old';\n", encoding="utf-8"
|
|
)
|
|
(new / "docs" / "_components").mkdir()
|
|
(new / "docs" / "_components" / "Widget.tsx").write_text(
|
|
"export const Widget = 'new';\n", encoding="utf-8"
|
|
)
|
|
_make_docs_website_tree(website)
|
|
|
|
for source, source_sha, version in (
|
|
(old, "old-sha", "1.0.0"),
|
|
(new, "new-sha", "1.1.0"),
|
|
):
|
|
sdw.sync_docs(
|
|
Namespace(
|
|
source_root=source,
|
|
docs_website_root=website,
|
|
channel="stable",
|
|
source_ref=f"v{version}",
|
|
source_sha=source_sha,
|
|
release_version=version,
|
|
version_slug=f"v{version}",
|
|
display_name="",
|
|
availability="",
|
|
allow_rollback=False,
|
|
)
|
|
)
|
|
|
|
widget = website / "fern" / "pages-latest" / "_components" / "Widget.tsx"
|
|
assert widget.read_text(encoding="utf-8") == "export const Widget = 'new';\n"
|