diff --git a/docs/docs.json b/docs/docs.json index 1b8b03806c..6fe1cd5968 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1053,6 +1053,7 @@ "v1.15.0/en/enterprise/guides/update-crew", "v1.15.0/en/enterprise/guides/enable-crew-studio", "v1.15.0/en/enterprise/guides/capture_telemetry_logs", + "v1.15.0/en/enterprise/guides/datadog", "v1.15.0/en/enterprise/guides/azure-openai-setup", "v1.15.0/en/enterprise/guides/vertex-ai-workload-identity-setup", "v1.15.0/en/enterprise/guides/tool-repository", @@ -9705,6 +9706,7 @@ "v1.15.0/pt-BR/enterprise/guides/update-crew", "v1.15.0/pt-BR/enterprise/guides/enable-crew-studio", "v1.15.0/pt-BR/enterprise/guides/capture_telemetry_logs", + "v1.15.0/pt-BR/enterprise/guides/datadog", "v1.15.0/pt-BR/enterprise/guides/azure-openai-setup", "v1.15.0/pt-BR/enterprise/guides/tool-repository", "v1.15.0/pt-BR/enterprise/guides/custom-mcp-server", @@ -18100,6 +18102,7 @@ "v1.15.0/ko/enterprise/guides/update-crew", "v1.15.0/ko/enterprise/guides/enable-crew-studio", "v1.15.0/ko/enterprise/guides/capture_telemetry_logs", + "v1.15.0/ko/enterprise/guides/datadog", "v1.15.0/ko/enterprise/guides/azure-openai-setup", "v1.15.0/ko/enterprise/guides/tool-repository", "v1.15.0/ko/enterprise/guides/custom-mcp-server", @@ -26687,6 +26690,7 @@ "v1.15.0/ar/enterprise/guides/update-crew", "v1.15.0/ar/enterprise/guides/enable-crew-studio", "v1.15.0/ar/enterprise/guides/capture_telemetry_logs", + "v1.15.0/ar/enterprise/guides/datadog", "v1.15.0/ar/enterprise/guides/azure-openai-setup", "v1.15.0/ar/enterprise/guides/tool-repository", "v1.15.0/ar/enterprise/guides/custom-mcp-server", diff --git a/lib/devtools/src/crewai_devtools/docs_versioning.py b/lib/devtools/src/crewai_devtools/docs_versioning.py index a816a80e4a..7142d04470 100644 --- a/lib/devtools/src/crewai_devtools/docs_versioning.py +++ b/lib/devtools/src/crewai_devtools/docs_versioning.py @@ -201,49 +201,46 @@ def _walk_pages(node: Any, transform: Callable[[str], str]) -> Any: return node -def _is_version_slug(value: str) -> bool: - return bool(VERSION_SLUG_RE.match(value)) - - -def _previous_default(versions: list[dict[str, Any]]) -> dict[str, Any] | None: - """Return the entry currently marked default (or the first versioned).""" +def _edge_entry(versions: list[dict[str, Any]]) -> dict[str, Any] | None: + """Return the Edge version entry, the rolling channel matching main HEAD.""" for v in versions: - if v.get("default") and _is_version_slug(v.get("version", "")): - return v - for v in versions: - if _is_version_slug(v.get("version", "")): + if v.get("version") == EDGE_VERSION: return v return None def _build_new_entry( - previous: dict[str, Any], version_slug: str, locale: str, docs_root: Path + edge: dict[str, Any], version_slug: str, docs_root: Path ) -> dict[str, Any] | None: - """Clone the previous default's nav into a new entry for ``version_slug``. + """Clone the Edge nav into a new entry for ``version_slug``. + + Freezing a release means promoting *Edge* (which tracks main HEAD) to the + new Latest, so the new version's navigation is cloned from the Edge entry + rather than from the previous frozen version. Cloning from the previous + version would silently drop every page that landed in Edge since the last + release (the file gets copied into the snapshot by ``_copy_snapshot`` but + never appears in the version selector) and would ignore any Edge nav + restructuring. - Page paths are rewritten from ``v//...`` to + Page paths are rewritten from ``edge//...`` to ``v//...``. Paths that don't resolve to a file in the - snapshot are pruned and the now-empty groups/tabs cascade away. Returns - ``None`` if the locale has no resolvable content under the snapshot (e.g. - a locale that wasn't present in Edge yet). + freshly-copied snapshot are pruned and the now-empty groups/tabs cascade + away. Returns ``None`` if Edge has no resolvable content under the + snapshot. """ - new_entry = copy.deepcopy(previous) + new_entry = copy.deepcopy(edge) new_entry["version"] = version_slug new_entry["default"] = True new_entry["tag"] = LATEST_TAG - old_prefix = re.compile(rf"^{re.escape(previous['version'])}/") - locale_prefix = f"{locale}/" + edge_prefix = f"{EDGE_PREFIX}/" new_prefix = f"{version_slug}/" def transform(page: str) -> str: if page.startswith(new_prefix): return page - rewritten = old_prefix.sub(new_prefix, page) - if rewritten != page: - return rewritten - if page.startswith(locale_prefix): - return f"{new_prefix}{page}" + if page.startswith(edge_prefix): + return f"{new_prefix}{page[len(edge_prefix) :]}" return page rewritten = _walk_pages(new_entry, transform) @@ -389,18 +386,18 @@ def _migrate_docs_json(docs_json: Path, version_slug: str) -> tuple[int, int, in inserted = 0 skipped = 0 for block in data["navigation"]["languages"]: - locale = block["language"] versions: list[dict[str, Any]] = block.get("versions", []) if any(v.get("version") == version_slug for v in versions): skipped += 1 continue - previous = _previous_default(versions) - if previous is None: + edge = _edge_entry(versions) + if edge is None: + # No Edge channel for this locale; nothing to freeze. skipped += 1 continue - new_entry = _build_new_entry(previous, version_slug, locale, docs_root) + new_entry = _build_new_entry(edge, version_slug, docs_root) if new_entry is None: # Locale has no resolvable content under the snapshot yet (e.g. a # locale that didn't exist in Edge). Leave the block untouched. diff --git a/lib/devtools/tests/test_docs_versioning.py b/lib/devtools/tests/test_docs_versioning.py index acf1cecbba..7bdbec477e 100644 --- a/lib/devtools/tests/test_docs_versioning.py +++ b/lib/devtools/tests/test_docs_versioning.py @@ -31,6 +31,10 @@ def _build_docs_root(tmp_path: Path) -> Path: (edge_en / "api.mdx").write_text( '---\nopenapi: "/enterprise-api.en.yaml GET /foo"\n---\n' ) + # A page added to Edge after the previous release. It exists as a file and + # is wired into the Edge nav, but is intentionally absent from the v1.14.7 + # nav below — the freeze must still surface it in the new version. + (edge_en / "datadog.mdx").write_text("# Datadog (Edge)\n") (docs / "edge" / "enterprise-api.en.yaml").write_text("openapi: 3.0.0\n") # A pre-existing frozen snapshot to clone the nav structure from. @@ -58,6 +62,7 @@ def _build_docs_root(tmp_path: Path) -> Path: "edge/en/introduction", "edge/en/changelog", "edge/en/api", + "edge/en/datadog", ], } ], @@ -146,6 +151,25 @@ def test_inserts_version_after_edge_and_demotes_previous_default( assert "default" not in previous assert previous.get("tag") != "Latest" + def test_new_version_nav_is_cloned_from_edge_not_previous( + self, tmp_path: Path + ) -> None: + # Regression: the new version's nav must come from Edge so pages added + # to Edge since the last release ship in the freeze. Cloning the + # previous version's nav silently dropped them (the file was copied + # into the snapshot but never linked in the version selector). + docs = _build_docs_root(tmp_path) + + freeze("1.15.0", docs) + + data = json.loads((docs / "docs.json").read_text()) + versions = data["navigation"]["languages"][0]["versions"] + new_entry = next(v for v in versions if v["version"] == "v1.15.0") + pages = [p for tab in new_entry["tabs"] for p in tab["pages"]] + assert "v1.15.0/en/datadog" in pages + # And the file is present in the snapshot it points at. + assert (docs / "v1.15.0" / "en" / "datadog.mdx").is_file() + def test_updates_canonical_url_redirect_to_new_default( self, tmp_path: Path ) -> None: