Skip to content
Open
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
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ generate-cli-commands: ## Run generation of the CLI commands

.PHONY: generate-cli-reference-docs
generate-cli-reference-docs: ## Generate the CLI reference documentation
$(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py reference > docs/cli/reference.mdx
$(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py summary > docs/fern/snippets/_snippets/cli-summary.mdx
NMP_CONFIG_FILE_PATH="$(NMP_CONFIG_FILE_PATH)" $(UV) run --frozen packages/nemo_platform_ext/scripts/docs_generator.py all

.PHONY: generate-config-reference-docs
generate-config-reference-docs: ## Generate the platform config reference documentation
Expand Down
35 changes: 28 additions & 7 deletions packages/nemo_platform_ext/scripts/docs_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from datetime import datetime
from functools import cache
from importlib import import_module
from pathlib import Path
from types import ModuleType
from typing import Any

Expand Down Expand Up @@ -724,38 +725,58 @@ def _escape_mdx_line(line: str) -> str:
}


_REPO_ROOT = Path(__file__).resolve().parents[3]
_REFERENCE_DOCS_PATH = _REPO_ROOT / "docs/cli/reference.mdx"
_SUMMARY_DOCS_PATH = _REPO_ROOT / "docs/fern/snippets/_snippets/cli-summary.mdx"


def _enable_plugin_cli_docs() -> None:
"""Include supported plugin commands in generated CLI documentation."""
import os

os.environ.update(_PLUGIN_DOCS_DISCOVERY_ENV)


def _with_trailing_newline(output: str) -> str:
"""Return output with exactly the trailing newline expected in generated files."""
return output if output.endswith("\n") else output + "\n"


def write_docs_files(app: typer.Typer, reference_path: Path, summary_path: Path, name: str = "nemo") -> None:
"""Write generated CLI reference and summary docs from one imported CLI app."""
reference_path.parent.mkdir(parents=True, exist_ok=True)
summary_path.parent.mkdir(parents=True, exist_ok=True)
reference_path.write_text(_with_trailing_newline(generate_docs(app, name=name)), encoding="utf-8")
summary_path.write_text(_with_trailing_newline(generate_index_snippet(app, name=name)), encoding="utf-8")


def main() -> None:
"""Generate CLI documentation and print to stdout.

Usage:
docs_generator.py reference # Full CLI reference
docs_generator.py summary # Index page summary snippet
docs_generator.py all # Write both generated docs files
"""
import sys

if len(sys.argv) != 2 or sys.argv[1] not in ("reference", "summary", "all"):
print("Usage: docs_generator.py {reference|summary|all}", file=sys.stderr)
sys.exit(1)

_enable_plugin_cli_docs()

from nemo_platform_ext.cli.app import app

if len(sys.argv) != 2 or sys.argv[1] not in ("reference", "summary"):
print("Usage: docs_generator.py {reference|summary}", file=sys.stderr)
sys.exit(1)

mode = sys.argv[1]
if mode == "all":
write_docs_files(app, _REFERENCE_DOCS_PATH, _SUMMARY_DOCS_PATH, name="nemo")
return
if mode == "summary":
output = generate_index_snippet(app, name="nemo")
else:
output = generate_docs(app, name="nemo")
sys.stdout.write(output)
if not output.endswith("\n"):
sys.stdout.write("\n")
sys.stdout.write(_with_trailing_newline(output))


if __name__ == "__main__":
Expand Down
24 changes: 24 additions & 0 deletions packages/nemo_platform_ext/tests/cli/test_docs_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def _load_docs_generator():
_docs_generator = _load_docs_generator()
generate_docs = _docs_generator.generate_docs
generate_index_snippet = _docs_generator.generate_index_snippet
write_docs_files = _docs_generator.write_docs_files
with_trailing_newline = _docs_generator._with_trailing_newline
enable_plugin_cli_docs = _docs_generator._enable_plugin_cli_docs
documented_plugin_clis = _docs_generator._DOCUMENTED_PLUGIN_CLIS
plugin_docs_discovery_env = _docs_generator._PLUGIN_DOCS_DISCOVERY_ENV
Expand Down Expand Up @@ -143,3 +145,25 @@ def visible() -> None:
assert "hidden-command" not in snippet
assert "Hidden command." not in snippet
assert "* `--help, -h`: Show this message and exit." in reference


def test_write_docs_files_matches_individual_generators(tmp_path):
docs_app = typer.Typer()

@docs_app.callback()
def main() -> None:
"""Test CLI."""

@docs_app.command(rich_help_panel="Setup")
def visible() -> None:
"""Visible command."""

reference_path = tmp_path / "docs/cli/reference.mdx"
summary_path = tmp_path / "docs/fern/snippets/_snippets/cli-summary.mdx"

write_docs_files(docs_app, reference_path, summary_path, name="nemo")

assert reference_path.read_text(encoding="utf-8") == generate_docs(docs_app, name="nemo")
assert summary_path.read_text(encoding="utf-8") == with_trailing_newline(
generate_index_snippet(docs_app, name="nemo")
)
2 changes: 1 addition & 1 deletion script/generate-openapi-spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

uv run --frozen python -m script.generate_openapi_spec -v "$@"
uv run --frozen python -m script.generate_openapi_spec "$@"
Loading
Loading