feat(cli-docs): auto-generate CLI reference from schema JSON #3221
Draft
Mpdreamz wants to merge 18 commits intofeature/arghfrom
Draft
feat(cli-docs): auto-generate CLI reference from schema JSON #3221Mpdreamz wants to merge 18 commits intofeature/arghfrom
Mpdreamz wants to merge 18 commits intofeature/arghfrom
Conversation
Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds a new `cli:` toc entry type in docset.yml that renders an entire CLI reference section — namespaces, commands, parameters, usage — from an argh __schema JSON file with no hand-maintained markdown. Key pieces: - `CliReferenceRef` toc record + YAML parser (cli: + folder: + children:) - `ArghSchema` deserialization models with AOT-safe STJ source gen - `CliRootFile`, `CliNamespaceFile`, `CliCommandFile` — MarkdownFile subrecords that synthesise page content from schema data - `CliReferenceDocsBuilderExtension` — auto-enabled when a cli: entry is present; pre-creates synthetic files, handles supplemental docs - Navigation builder wires synthetic files into the nav tree; explicit children: prepend regular docs before generated pages - Supplemental docs: changelog/index.md and ns-/cmd- prefix conventions inject intro prose into generated pages; validation errors on mismatches - Multiline bash wrapping for usage lines >80 chars - [ns]/[cmd] nav pills (inline style, right-aligned) distinguish generated pages in the sidebar; breadcrumb/prev-next strip the prefix - Fix: serve no longer crashes in git worktrees (ReloadGeneratorService falls back to SourceDirectory when DocumentationCheckoutDirectory is null) - Fix: synthetic files recognised by link validator (existsInSet check) - CI step added to ci.yml to keep docs/cli-schema.json current - docs/cli/ replaced: hand-written pages removed, replaced with generated reference + installation, shell-autocompletion, changelog supplemental docs, and an "Automated Reference" how-to guide Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Schema v2 changes (from argh PR#39):
- entryAssembly renamed to name
- kind replaced by type using JSON Schema primitives
("string", "integer", "number", "boolean", "array", "enum")
- New param fields: defaultValue, enumValues, elementType, repeatable,
separator, aliases, hidden
- New command fields: aliases, hidden
- schemaVersion bumped from 1 to 2
ArghSchema.cs updated to v2 models while keeping v1 fallback support
in FormatKindV1 for generators not yet on the new format.
CliMarkdownGenerator updates:
- IsBoolFlag and FormatTypeHint now use JSON Schema type strings
- EnumValues rendered from schema field instead of parsing summary text
- DefaultValue rendered from schema field (skips literal "default")
- Hidden params/commands filtered from generated pages
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ards
New general-purpose {page-card} directive renders a bordered full-row
navigation card with title, description, and right chevron — similar
to the prev/next navigation at the bottom of pages.
Usage:
:::{page-card} [Title](./path/to/page.md)
Optional description text.
:::
Constraints:
- Argument must be a markdown link [Title](url)
- URL must be a local .md path or crosslink, not an absolute URL
- URL is resolved using the same logic as DiagnosticLinkInlineParser
(relative to the source file's directory), so links always point to
the correct page regardless of nesting depth
CliMarkdownGenerator updated to emit {page-card} blocks for Commands
and Sub-namespaces/Namespaces sections on namespace and root pages,
replacing the plain definition lists.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…tion attrs - PageCardBlock.FinalizeAndValidate now resolves the relative URL using the same logic as DiagnosticLinkInlineParser (relative to source file directory, not the docroot), so ./cmd-init.md from cli/changelog/index.md correctly resolves to /cli/changelog/cmd-init - PageCardView adds hx-select-oob and preload attributes so htmx partial page updates work the same as all other internal links Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…hor indicators Clean URLs for CLI command pages (no cmd- prefix): - /cli/assembler/deploy/apply instead of /cli/assembler/deploy/cmd-apply - Exception: commands named "index" keep cmd- prefix to avoid collision with namespace index.md pages (e.g. assembler/index.md) - Works for both serve (URL routing) and static HTML build (output path) - Physical supplemental cmd-*.md files share the same CliFile instance as the synthetic clean path via a _createdFiles cache, ensuring NavigationDocumentationFileLookup finds the file from either key Fragment anchors on parameter definitions: - DefinitionListAnchorRenderer subclasses HtmlDefinitionListRenderer to inject id attributes on <dt> elements containing inline code - id extracted from the longest --flag-name; positional terms get <name> - Only triggers for code-quoted terms (backtick syntax) - Registered via UseDefinitionTermAnchors() pipeline extension Hover anchor indicators: - Heading .headerlink shows # via ::after pseudo-element on hover - dt[id] shows # via ::before pseudo-element on hover - Uses CSS opacity transition, # hidden by default Navigation title virtual property made virtual on MarkdownFile. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace CSS ::before pseudo-element with an actual <a class="paramlink"> inside each <dt id> element. Clicking # now updates the URL fragment (e.g. /cli/assembler/deploy/apply#environment) and can be shared. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…able Wrap the entire <dt> content in <a class="paramlink"> (same pattern as headings), so clicking anywhere on the term navigates to the fragment. # appears to the left via CSS ::before on hover, mirroring the heading anchor behaviour. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…rve fix - CLI heading badges now use colored spans (.cli-badge-ns / .cli-badge-cmd) instead of backtick code — purple for namespace, amber for command, matching the sidebar nav pill colors. Badges are right-aligned via flex. - Badge labels read "cli namespace" / "cli command". - Binary name (schema.Name) is now threaded into CliNamespaceFile and CliCommandFile so the --help codeblock and generated usage lines use the correct binary name instead of a hardcoded value. - GenerateUsage: when cmd.Usage is null, auto-generate a usage line with required named flags shown explicitly (--flag <flag>) and optional flags collapsed to [options]. - FileSystemFactory.InMemoryForPath: new overload that scopes the mock write filesystem to the git root of a given path, fixing the scoped filesystem access-denied error when `serve --path` points outside the docs-builder repo. - ServeCommand and InMemoryBuildState updated to use InMemoryForPath. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ows "{name} CLI"
- HierarchyCandidates for root now yields "index.md" before "ns-root.md",
so a cli/index.md in the supplemental folder is picked up as the root
page body (consistent with how namespace index.md supplementals work).
- ValidateSupplementalFiles no longer errors on root-level index.md;
it is now validated the same way as any other supplemental index.md
(error only if unmatched).
- CliRootFile.NavigationTitle overrides to "{schema.Name} CLI" so the
root entry in the sidebar reads e.g. "elastic CLI" / "docs-builder CLI".
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Keep @functions block (nav pills) from this branch and the isTopLevel index link block added upstream. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…atting - Regenerate docs/cli-schema.json (upstream added new commands) - Fix import ordering in DirectiveBlockParser.cs and DirectiveHtmlRenderer.cs - Fix Prettier formatting in styles.css Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…self-redirects CLI reference pages are now synthetic (generated from schema) rather than physical files. The authoring test framework's copyTargetsFromRealDocsIntoMock was guarded by `File.Exists` which skips synthetic targets, causing redirect validation to fail for all tests that load the real _redirects.yml. - Remove the File.Exists guard so stubs are added for all local redirect targets; synthetic page validity is checked by the production build - Remove 9 no-op self-redirects (cli/changelog/*.md → same) and the cli/assembler/index.md self-redirect Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
CLI reference pages are volatile by nature (generated from schema) and were never shipped to production, so their redirects have no value. - Remove all cli/* entries from _redirects.yml - Revert Setup.fs to File.Exists guard (unconditional stubs broke other tests) - Update LinkReferenceFile.fs snapshot to remove stale CLI entries - Update PhysicalDocsetTests to check for CliReferenceRef instead of FolderRef for the cli TOC entry - Skip synthetic files in Move.ProcessMarkdownFile — they have no physical content and ReadAllTextAsync would throw FileNotFoundException Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this?
This PR adds automatic CLI reference documentation generation to docs-builder. Point it at a JSON schema file describing a CLI's command tree and it generates a full, navigable reference — one page per command, one page per namespace — wired directly into the docs site's table of contents.
docs-builder's own CLI reference (
/cli/) is now generated this way.cursorful-video-1777577780994.mp4
Why PR #3202 is a prerequisite
This PR consumes a schema JSON file that describes a CLI's full command tree — every namespace, command, and parameter — with names, types, required/optional, descriptions, default values, and enum choices. The schema format is straightforward and any CLI from any ecosystem can emit it.
docs-builder inherits schema generation from Nullean.Argh, which is what PR #3202 migrates to. Nullean.Argh 0.13.0 introduced a built-in
--emit-schema/__schemacommand that outputs exactly this JSON. That's the convenient on-ramp — but a Go CLI, a Python CLI, or anything else that can write the same JSON structure can use this system just as well.Configuring it
One new key in
docset.yml:That's it. docs-builder discovers the full command tree from the schema and generates the rest. The
childrenlist is for narrative pages (installation guides, config walkthroughs) that sit alongside the reference.To serve a CLI repo's docs standalone:
(This PR also fixes a scoped-filesystem bug that prevented
--pathfrom pointing outside the docs-builder repo.)What gets generated
Namespace index pages
Every namespace (
elastic stack,elastic cloud hosted, …) gets an index page with:cli namespacebadge right-aligned in the<h1>binary namespace --helpusage codeblockThe
{page-card}directiveThe page cards on namespace indexes are powered by a new general-purpose
{page-card}directive, available in any hand-written Markdown page::::{page-card} [Installation](./installation.md) Get the CLI installed and linked in under a minute. ::: :::{page-card} [Elasticsearch overview](elasticsearch://docs/get-started.md) Cross-link to another docs set using the standard crosslink scheme. :::The argument must be a Markdown link —
[Title](url). The URL is restricted to local relative paths (.mdfiles) or crosslink scheme URIs (e.g.elasticsearch://…). Absolutehttp:///https://URLs are rejected at build time with a clear error, keeping page cards as in-docs navigation rather than external links. The body (description line) is optional.Command pages
Every command gets a page with:
cli commandbadge right-aligned in the<h1>(amber, matching the sidebar pill)Usage lines that are actually useful
When the schema provides an explicit usage string it's used as-is (with automatic wrapping at 80 characters into multiline bash-continuation style). When there's no explicit usage — which is common — docs-builder generates one from the schema itself:
Required named flags appear individually with their placeholder (
--flag <flag>). Positional arguments are listed with angle brackets. Optional flags collapse to[options]. The result is a line you can copy, fill in the blanks, and run — not a generic[options]mystery.Parameter anchors
Every flag and option has a fragment anchor (
#flag-name). Hover any parameter term to reveal a#link; clicking it updates the URL so you can share a direct link to a specific flag.Full-path page titles and sidebar pills
Pages carry their full command path as the title (
assembler bloom-filter, not justbloom-filter) so they're unambiguous in search and browser history. The sidebar shows colour-coded pills — purple for namespaces, amber for commands — matching the badge colours in the heading.Injecting custom content (supplemental docs)
The generated content is great for reference, but sometimes you need context, examples, or warnings that don't belong in the schema. The supplemental docs system handles this without touching the generator.
Folder layout (using
folder: clias the virtual root):Alternative flat naming also works:
ns-assembler.md,cmd-assembler-bloom-filter.md.Supplemental content replaces only the description section. The schema-driven heading, usage codeblock, parameter tables, and navigation cards still render — supplemental adds human context on top of machine precision.
Keeping docs in sync with the CLI
Generated docs are only valuable if they stay accurate. A CI step enforces this — it re-runs
docs-builder __schema, diffs the output against the committeddocs/cli-schema.json, and fails the build if they differ:Add a command, rename a flag, change a description — CI tells you to regenerate the schema before merging. The docs can never silently lag behind the CLI.
Test plan
docs-builder serve --path docs→/cli/renders docs-builder's own CLI referencedocs-builder serve --path <external-cli-repo>/docs→ CLI reference renders with correct binary name in usage linesusagefield show a generated usage line with required flags explicit{page-card}directive works in a hand-authored.mdpageelastic CLI/docs-builder CLIroot titles with correct colour pills🤖 Generated with Claude Code