From 3e5ce5cb3e412412c6f0812488ca16abfe4c1992 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 30 Apr 2026 11:21:40 +0200 Subject: [PATCH 01/18] feat(cli-docs): generate CLI reference pages from argh __schema JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .github/workflows/ci.yml | 7 + docs/_docset.yml | 51 +- docs/_redirects.yml | 51 +- docs/building-blocks/inbound-cross-links.md | 4 +- docs/cli-schema.json | 4345 +++++++++++++++++ docs/cli/assembler/assemble.md | 91 - .../assembler-bloom-filter-create.md | 24 - .../assembler-bloom-filter-lookup.md | 17 - docs/cli/assembler/assembler-build.md | 51 - docs/cli/assembler/assembler-clone.md | 29 - docs/cli/assembler/assembler-config-init.md | 31 - .../assembler-content-source-match.md | 33 - .../assembler-content-source-validate.md | 13 - docs/cli/assembler/assembler-deploy-apply.md | 24 - docs/cli/assembler/assembler-deploy-plan.md | 27 - .../assembler-deploy-update-redirects.md | 21 - docs/cli/assembler/assembler-index.md | 75 - ...bler-navigation-validate-link-reference.md | 20 - .../assembler-navigation-validate.md | 13 - docs/cli/assembler/assembler-serve.md | 21 - docs/cli/assembler/index.md | 64 - docs/cli/changelog/add.md | 240 - docs/cli/changelog/bundle-amend.md | 125 - docs/cli/changelog/bundle.md | 686 --- docs/cli/changelog/cmd-bundle.md | 84 + docs/cli/changelog/evaluate-pr.md | 99 - docs/cli/changelog/gh-release.md | 102 - docs/cli/changelog/index.md | 21 +- docs/cli/changelog/init.md | 71 - docs/cli/changelog/remove.md | 286 -- docs/cli/changelog/render.md | 143 - docs/cli/cli-reference-how-to.md | 134 + docs/cli/docset/build.md | 65 - docs/cli/docset/diff-validate.md | 18 - docs/cli/docset/format.md | 145 - docs/cli/docset/index-command.md | 71 - docs/cli/docset/index.md | 26 - docs/cli/docset/mv.md | 25 - docs/cli/docset/serve.md | 33 - docs/cli/index.md | 54 - docs/cli/installation.md | 39 + docs/cli/links/inbound-links-validate-all.md | 9 - .../inbound-links-validate-link-reference.md | 17 - docs/cli/links/inbound-links-validate.md | 17 - docs/cli/links/index.md | 15 - docs/cli/shell-autocompletion.md | 40 + docs/contribute/bundle-changelogs.md | 22 +- docs/contribute/changelog.md | 2 +- docs/contribute/configure-changelogs.md | 2 +- docs/contribute/create-changelogs.md | 12 +- docs/contribute/publish-changelogs.md | 2 +- docs/syntax/changelog.md | 16 +- .../Toc/CliReference/ArghSchema.cs | 63 + .../Toc/CliReference/ArghSchemaJsonContext.cs | 11 + .../Toc/CliReference/CliReferenceRef.cs | 21 + .../Toc/DocumentationSetFile.cs | 64 + .../Toc/TableOfContentsYamlConverters.cs | 9 + .../Node/DocumentationSetNavigation.cs | 163 + .../Navigation/_TocTreeNav.cshtml | 35 +- .../Extensions/CliReference/CliCommandFile.cs | 54 + .../CliReference/CliMarkdownGenerator.cs | 346 ++ .../CliReference/CliNamespaceFile.cs | 54 + .../CliReferenceDocsBuilderExtension.cs | 337 ++ .../Extensions/CliReference/CliRootFile.cs | 52 + src/Elastic.Markdown/IO/DocumentationSet.cs | 27 + src/Elastic.Markdown/IO/MarkdownFile.cs | 2 +- .../Layout/_Breadcrumbs.cshtml | 7 +- .../Layout/_PrevNextNav.cshtml | 9 +- .../DiagnosticLinkInlineParser.cs | 5 +- .../Http/ReloadGeneratorService.cs | 4 +- 70 files changed, 5987 insertions(+), 2909 deletions(-) create mode 100644 docs/cli-schema.json delete mode 100644 docs/cli/assembler/assemble.md delete mode 100644 docs/cli/assembler/assembler-bloom-filter-create.md delete mode 100644 docs/cli/assembler/assembler-bloom-filter-lookup.md delete mode 100644 docs/cli/assembler/assembler-build.md delete mode 100644 docs/cli/assembler/assembler-clone.md delete mode 100644 docs/cli/assembler/assembler-config-init.md delete mode 100644 docs/cli/assembler/assembler-content-source-match.md delete mode 100644 docs/cli/assembler/assembler-content-source-validate.md delete mode 100644 docs/cli/assembler/assembler-deploy-apply.md delete mode 100644 docs/cli/assembler/assembler-deploy-plan.md delete mode 100644 docs/cli/assembler/assembler-deploy-update-redirects.md delete mode 100644 docs/cli/assembler/assembler-index.md delete mode 100644 docs/cli/assembler/assembler-navigation-validate-link-reference.md delete mode 100644 docs/cli/assembler/assembler-navigation-validate.md delete mode 100644 docs/cli/assembler/assembler-serve.md delete mode 100644 docs/cli/assembler/index.md delete mode 100644 docs/cli/changelog/add.md delete mode 100644 docs/cli/changelog/bundle-amend.md delete mode 100644 docs/cli/changelog/bundle.md create mode 100644 docs/cli/changelog/cmd-bundle.md delete mode 100644 docs/cli/changelog/evaluate-pr.md delete mode 100644 docs/cli/changelog/gh-release.md delete mode 100644 docs/cli/changelog/init.md delete mode 100644 docs/cli/changelog/remove.md delete mode 100644 docs/cli/changelog/render.md create mode 100644 docs/cli/cli-reference-how-to.md delete mode 100644 docs/cli/docset/build.md delete mode 100644 docs/cli/docset/diff-validate.md delete mode 100644 docs/cli/docset/format.md delete mode 100644 docs/cli/docset/index-command.md delete mode 100644 docs/cli/docset/index.md delete mode 100644 docs/cli/docset/mv.md delete mode 100644 docs/cli/docset/serve.md delete mode 100644 docs/cli/index.md create mode 100644 docs/cli/installation.md delete mode 100644 docs/cli/links/inbound-links-validate-all.md delete mode 100644 docs/cli/links/inbound-links-validate-link-reference.md delete mode 100644 docs/cli/links/inbound-links-validate.md delete mode 100644 docs/cli/links/index.md create mode 100644 docs/cli/shell-autocompletion.md create mode 100644 src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs create mode 100644 src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs create mode 100644 src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs create mode 100644 src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs create mode 100644 src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs create mode 100644 src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs create mode 100644 src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs create mode 100644 src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 302edbc4f0..db20400825 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -158,6 +158,13 @@ jobs: - name: Compile run: dotnet run --project build -c release -- compile + - name: Check CLI schema is up to date + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + dotnet run --project src/tooling/docs-builder -c release --no-build -- __schema > docs/cli-schema.json.tmp + diff docs/cli-schema.json docs/cli-schema.json.tmp || (echo "docs/cli-schema.json is out of date — run: dotnet run --project src/tooling/docs-builder -- __schema > docs/cli-schema.json" && exit 1) + rm docs/cli-schema.json.tmp + - name: Test run: dotnet run --project build -c release -- unit-test diff --git a/docs/_docset.yml b/docs/_docset.yml index 8c56fc0d0a..7a7f0b8274 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -160,53 +160,12 @@ toc: - file: tabs.md - file: tagged_regions.md - file: titles.md - - folder: cli + - cli: cli-schema.json + folder: cli children: - - file: index.md - - folder: docset - children: - - file: index.md - - file: build.md - - file: diff-validate.md - - file: format.md - - file: index-command.md - - file: mv.md - - file: serve.md - - folder: assembler - children: - - file: index.md - - file: assemble.md - - file: assembler-bloom-filter-create.md - - file: assembler-bloom-filter-lookup.md - - file: assembler-build.md - - file: assembler-clone.md - - file: assembler-config-init.md - - file: assembler-content-source-match.md - - file: assembler-content-source-validate.md - - file: assembler-deploy-apply.md - - file: assembler-deploy-plan.md - - file: assembler-deploy-update-redirects.md - - file: assembler-index.md - - file: assembler-navigation-validate.md - - file: assembler-navigation-validate-link-reference.md - - file: assembler-serve.md - - folder: links - children: - - file: index.md - - file: inbound-links-validate.md - - file: inbound-links-validate-all.md - - file: inbound-links-validate-link-reference.md - - folder: changelog - children: - - file: index.md - - file: add.md - - file: bundle.md - - file: bundle-amend.md - - file: evaluate-pr.md - - file: gh-release.md - - file: init.md - - file: remove.md - - file: render.md + - file: installation.md + - file: shell-autocompletion.md + - file: cli-reference-how-to.md - folder: mcp children: - file: index.md diff --git a/docs/_redirects.yml b/docs/_redirects.yml index 350e9d57b1..9b16755856 100644 --- a/docs/_redirects.yml +++ b/docs/_redirects.yml @@ -2,17 +2,52 @@ redirects: 'migration/freeze/gh-action.md' : 'index.md' 'migration/freeze/index.md' : 'index.md' 'cli/mcp.md': 'index.md' - 'cli/release/changelog-add.md': 'cli/changelog/add.md' + # Legacy cli/release/* redirects — now point to generated changelog command pages + 'cli/release/changelog-add.md': 'cli/changelog/cmd-add.md' 'cli/release/changelog-evaluate-artifact.md': 'index.md' - 'cli/release/changelog-evaluate-pr.md': 'cli/changelog/evaluate-pr.md' - 'cli/release/changelog-bundle.md': 'cli/changelog/bundle.md' - 'cli/release/changelog-bundle-amend.md': 'cli/changelog/bundle-amend.md' - 'cli/release/changelog-gh-release.md': 'cli/changelog/gh-release.md' - 'cli/release/changelog-init.md': 'cli/changelog/init.md' + 'cli/release/changelog-evaluate-pr.md': 'cli/changelog/cmd-evaluate-pr.md' + 'cli/release/changelog-bundle.md': 'cli/changelog/cmd-bundle.md' + 'cli/release/changelog-bundle-amend.md': 'cli/changelog/cmd-bundle-amend.md' + 'cli/release/changelog-gh-release.md': 'cli/changelog/cmd-gh-release.md' + 'cli/release/changelog-init.md': 'cli/changelog/cmd-init.md' 'cli/release/changelog-prepare-artifact.md': 'index.md' - 'cli/release/changelog-remove.md': 'cli/changelog/remove.md' - 'cli/release/changelog-render.md': 'cli/changelog/render.md' + 'cli/release/changelog-remove.md': 'cli/changelog/cmd-remove.md' + 'cli/release/changelog-render.md': 'cli/changelog/cmd-render.md' 'cli/release/index.md': 'cli/changelog/index.md' + # Old hand-written CLI page redirects → generated pages + 'cli/changelog/add.md': 'cli/changelog/cmd-add.md' + 'cli/changelog/bundle.md': 'cli/changelog/cmd-bundle.md' + 'cli/changelog/bundle-amend.md': 'cli/changelog/cmd-bundle-amend.md' + 'cli/changelog/evaluate-pr.md': 'cli/changelog/cmd-evaluate-pr.md' + 'cli/changelog/gh-release.md': 'cli/changelog/cmd-gh-release.md' + 'cli/changelog/init.md': 'cli/changelog/cmd-init.md' + 'cli/changelog/remove.md': 'cli/changelog/cmd-remove.md' + 'cli/changelog/render.md': 'cli/changelog/cmd-render.md' + 'cli/docset/index.md': 'cli/index.md' + 'cli/docset/build.md': 'cli/cmd-build.md' + 'cli/docset/diff-validate.md': 'cli/cmd-diff.md' + 'cli/docset/format.md': 'cli/cmd-format.md' + 'cli/docset/index-command.md': 'cli/cmd-index.md' + 'cli/docset/mv.md': 'cli/cmd-mv.md' + 'cli/docset/serve.md': 'cli/cmd-serve.md' + 'cli/assembler/index.md': 'cli/assembler/index.md' + 'cli/assembler/assemble.md': 'cli/cmd-assemble.md' + 'cli/assembler/assembler-build.md': 'cli/assembler/cmd-build.md' + 'cli/assembler/assembler-clone.md': 'cli/assembler/cmd-clone.md' + 'cli/assembler/assembler-config-init.md': 'cli/assembler/config/cmd-init.md' + 'cli/assembler/assembler-content-source-match.md': 'cli/assembler/content-source/cmd-match.md' + 'cli/assembler/assembler-content-source-validate.md': 'cli/assembler/content-source/cmd-validate.md' + 'cli/assembler/assembler-deploy-apply.md': 'cli/assembler/deploy/cmd-apply.md' + 'cli/assembler/assembler-deploy-plan.md': 'cli/assembler/deploy/cmd-plan.md' + 'cli/assembler/assembler-deploy-update-redirects.md': 'cli/assembler/deploy/cmd-update-redirects.md' + 'cli/assembler/assembler-index.md': 'cli/assembler/cmd-index.md' + 'cli/assembler/assembler-navigation-validate.md': 'cli/assembler/navigation/cmd-validate.md' + 'cli/assembler/assembler-navigation-validate-link-reference.md': 'cli/assembler/navigation/cmd-validate-link-reference.md' + 'cli/assembler/assembler-serve.md': 'cli/assembler/cmd-serve.md' + 'cli/links/index.md': 'cli/inbound-links/index.md' + 'cli/links/inbound-links-validate.md': 'cli/inbound-links/cmd-validate.md' + 'cli/links/inbound-links-validate-all.md': 'cli/inbound-links/cmd-validate-all.md' + 'cli/links/inbound-links-validate-link-reference.md': 'cli/inbound-links/cmd-validate-link-reference.md' 'testing/redirects/4th-page.md': 'testing/redirects/5th-page.md' 'testing/redirects/9th-page.md': '!testing/redirects/5th-page.md' 'testing/redirects/6th-page.md': diff --git a/docs/building-blocks/inbound-cross-links.md b/docs/building-blocks/inbound-cross-links.md index 207aca023a..939f8a0970 100644 --- a/docs/building-blocks/inbound-cross-links.md +++ b/docs/building-blocks/inbound-cross-links.md @@ -16,9 +16,9 @@ Inbound cross-link validation allows you to: ## How it works -A regular [build](../cli/docset/build.md) of a documentation set won't validate inbound links automatically. +A regular [build](../cli/cmd-build.md) of a documentation set won't validate inbound links automatically. -You have to use the [inbound-links validate-link-reference](../cli/links/inbound-links-validate-link-reference.md) after a build to validate all inbound links. +You have to use the [inbound-links validate-link-reference](../cli/inbound-links/cmd-validate-link-reference.md) after a build to validate all inbound links. The reason for this is that validating all inbound links has to download all published [Link Index](link-index.md) files for the current [Content Source](../configure/content-sources.md). diff --git a/docs/cli-schema.json b/docs/cli-schema.json new file mode 100644 index 0000000000..75274e7116 --- /dev/null +++ b/docs/cli-schema.json @@ -0,0 +1,4345 @@ +{ + "schemaVersion": 1, + "entryAssembly": "docs-builder", + "version": "1.0.0.0", + "description": null, + "reservedMetaCommands": [ + "__complete", + "__completion", + "__schema" + ], + "globalOptions": [ + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "kind": "Enum:LogLevel", + "required": false, + "summary": "Minimum log level. Default: information", + "validations": null + }, + { + "role": "flag", + "name": "config-source", + "shortName": "c", + "kind": "Enum:ConfigurationSource", + "required": false, + "summary": "Override the configuration source: local, remote", + "validations": null + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip cloning private repositories", + "validations": null + } + ], + "rootDefault": null, + "commands": [ + { + "path": [], + "name": "assemble", + "summary": "Clone all repositories and build the unified documentation site in one step.", + "notes": "The assembler clones multiple documentation repositories and builds them into a single unified site\ncomposed by a shared navigation.yml. This command combines assembler config init,\nassembler clone, and assembler build into a single invocation.", + "usage": "docs-builder assemble --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target, e.g. dev, staging, production. Determines which configuration branch and index names are used.", + "validations": null + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set.", + "validations": null + }, + { + "role": "flag", + "name": "show-hints", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Print documentation hints emitted during the build.", + "validations": null + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "kind": "Collection\u003Cenum\u003E", + "required": false, + "summary": "Comma-separated list of exporters to run.\n Values: Html, Elasticsearch, Configuration, LinkMetadata, DocumentationState, LLMText, Redirects.\n Default: Html, Configuration, LinkMetadata, DocumentationState, Redirects.", + "validations": null + }, + { + "role": "flag", + "name": "assume-build", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Skip the build step when .artifacts/docs/index.html already exists. Intended for test scenarios only.", + "validations": null + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned link-registry ref.", + "validations": null + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk. Useful for iterating on the build.", + "validations": null + }, + { + "role": "flag", + "name": "serve", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Serve the site on port 4000 after a successful build.", + "validations": null + } + ] + }, + { + "path": [], + "name": "build", + "summary": "Build a single documentation set from source.", + "notes": "Locates the documentation root by searching for a docset.yml file starting at options .Path.\nThe output directory is wiped and rebuilt on each run unless incremental build detects no changes.", + "usage": "docs-builder build --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Root directory of the documentation source. Defaults to cwd/docs.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "output", + "shortName": "o", + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Destination for generated HTML. Defaults to .artifacts/html.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "path-prefix", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "URL path prefix prepended to every generated link.", + "validations": null + }, + { + "role": "flag", + "name": "force", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Delete and rebuild the output folder even if nothing changed.", + "validations": null + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "allow-indexing", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Emit meta robots tags that allow search engine indexing.", + "validations": null + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set.", + "validations": null + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "kind": "Collection\u003Cenum\u003E", + "required": false, + "summary": "Comma-separated list of exporters to run.\n Default: html, configuration, linkmetadata, documentationState, dedirects.", + "validations": null + }, + { + "role": "flag", + "name": "canonical-base-url", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "Base URL written into \u003Clink rel=canonical\u003E tags.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "skip-api", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip OpenAPI spec generation for faster builds.", + "validations": null + }, + { + "role": "flag", + "name": "skip-cross-links", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip fetching cross-doc-set link indexes.", + "validations": null + }, + { + "role": "flag", + "name": "in-memory", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": null, + "validations": null + } + ] + }, + { + "path": [], + "name": "diff", + "summary": "Verify every renamed or removed page in the current branch has a redirect entry.", + "notes": "Compares the git diff of the working branch against the redirect file. Exits 1 if any moved\nor deleted page is missing a redirect entry. Run before merging to catch broken links early.", + "usage": "docs-builder diff [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "Primitive:string", + "required": false, + "summary": "Root of the documentation source. Defaults to cwd/docs.", + "validations": null + } + ] + }, + { + "path": [], + "name": "format", + "summary": "Fix common formatting issues (irregular spacing, trailing whitespace) across documentation files.", + "notes": "Exactly one of --check or --write must be specified.", + "usage": "docs-builder format --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "Primitive:string", + "required": false, + "summary": "Documentation root. Defaults to cwd.", + "validations": null + }, + { + "role": "flag", + "name": "check", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Report files that need formatting without modifying them. Exits 1 when any file is out of format.", + "validations": null + }, + { + "role": "flag", + "name": "write", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Apply formatting changes in place.", + "validations": null + } + ] + }, + { + "path": [], + "name": "index", + "summary": "Index a single documentation set into Elasticsearch.", + "notes": "Builds the documentation set in metadata-only mode and streams the output to Elasticsearch.\nDoes not write HTML to disk. Requires a running cluster and valid credentials.", + "usage": "docs-builder index --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "-es,--endpoint, Elasticsearch endpoint URL. Falls back to env DOCUMENTATION_ELASTIC_URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "api-key", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY.", + "validations": null + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME.", + "validations": null + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD.", + "validations": null + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of search threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "index-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of index threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "eis", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "kind": "Primitive:TimeSpan?", + "required": false, + "summary": "How long to wait for the inference endpoint to become ready (e.g. 4m, 90s).", + "validations": [ + { + "kind": "timeSpanRange", + "min": "\u00221s\u0022", + "max": "\u002260m\u0022", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "force-reindex", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Force a full reindex, discarding any incremental state.", + "validations": null + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of documents per bulk request.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "10000", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "max-retries", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of retry attempts for failed bulk items.", + "validations": [ + { + "kind": "range", + "min": "0", + "max": "20", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "debug-mode", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "Route requests through this proxy URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "proxy-username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server username.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server password.", + "validations": null + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to a PEM or DER certificate file for SSL validation.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "pem", + "der", + "crt", + "cer" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "certificate-not-root", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root.", + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": null, + "validations": null + } + ] + }, + { + "path": [], + "name": "mv", + "summary": "Move a file or folder and rewrite all inbound links across the documentation set.", + "notes": null, + "usage": "docs-builder mv --_ \u003Cstring\u003E \u003Csource\u003E \u003Ctarget\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "positional", + "name": "source", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Source file or folder path.", + "validations": null + }, + { + "role": "positional", + "name": "target", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Destination file or folder path.", + "validations": null + }, + { + "role": "flag", + "name": "dry-run", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Print the changes that would be made without applying them.", + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "Primitive:string", + "required": false, + "summary": "Documentation root. Defaults to cwd.", + "validations": null + } + ] + }, + { + "path": [], + "name": "serve", + "summary": "Serve a documentation folder at http://localhost:3000 with live reload.", + "notes": "File-system changes are reflected without restarting the server.", + "usage": "docs-builder serve --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Documentation source directory. Defaults to the cwd/docs folder.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "port", + "shortName": null, + "kind": "Primitive:int", + "required": false, + "summary": "Port to serve the documentation. Default: 3000", + "validations": null + }, + { + "role": "flag", + "name": "watch", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Special flag for dotnet watch optimizations during development", + "validations": null + } + ] + } + ], + "namespaces": [ + { + "segment": "assembler", + "summary": "Build a unified documentation site by composing multiple documentation sets under a shared navigation.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler" + ], + "name": "build", + "summary": "Build the unified site from all previously cloned repositories.", + "notes": "Run after assembler clone. Reads every cloned repository, applies the shared navigation.yml,\nand writes the unified site to .artifacts/docs/.", + "usage": "docs-builder assembler build [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target, e.g. dev, staging, production. Determines which configuration branch and index names are used.", + "validations": null + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set.", + "validations": null + }, + { + "role": "flag", + "name": "show-hints", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Print documentation hints emitted during the build.", + "validations": null + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "kind": "Collection\u003Cenum\u003E", + "required": false, + "summary": "Comma-separated list of exporters to run.\n Values: Html, Elasticsearch, Configuration, LinkMetadata, DocumentationState, LLMText, Redirects.\n Default: Html, Configuration, LinkMetadata, DocumentationState, Redirects.", + "validations": null + }, + { + "role": "flag", + "name": "assume-build", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Skip the build step when .artifacts/docs/index.html already exists. Intended for test scenarios only.", + "validations": null + } + ] + }, + { + "path": [ + "assembler" + ], + "name": "clone", + "summary": "Clone all repositories listed in the assembler configuration.", + "notes": "Run assembler config init first to fetch the repository list. Clones into a local\nworking directory; subsequent assembler build reads from there.", + "usage": "docs-builder assembler clone [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target. Determines which repositories and branches are cloned.", + "validations": null + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned link-registry ref.", + "validations": null + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "validations": null + } + ] + }, + { + "path": [ + "assembler" + ], + "name": "index", + "summary": "Index the assembled documentation into Elasticsearch.", + "notes": "Runs an assembler build with only the Elasticsearch exporter enabled, then streams documents\nto the cluster. The index name is derived from the environment name.\n\n\nRun after assembler build or use instead of it when indexing is the only goal.", + "usage": "docs-builder assembler index --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "-es,--endpoint, Elasticsearch endpoint URL. Falls back to env DOCUMENTATION_ELASTIC_URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "api-key", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY.", + "validations": null + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME.", + "validations": null + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD.", + "validations": null + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of search threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "index-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of index threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "eis", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "kind": "Primitive:TimeSpan?", + "required": false, + "summary": "How long to wait for the inference endpoint to become ready (e.g. 4m, 90s).", + "validations": [ + { + "kind": "timeSpanRange", + "min": "\u00221s\u0022", + "max": "\u002260m\u0022", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "force-reindex", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Force a full reindex, discarding any incremental state.", + "validations": null + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of documents per bulk request.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "10000", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "max-retries", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of retry attempts for failed bulk items.", + "validations": [ + { + "kind": "range", + "min": "0", + "max": "20", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "debug-mode", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "Route requests through this proxy URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "proxy-username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server username.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server password.", + "validations": null + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to a PEM or DER certificate file for SSL validation.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "pem", + "der", + "crt", + "cer" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "certificate-not-root", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root.", + "validations": null + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target; becomes part of the Elasticsearch index name.", + "validations": null + } + ] + }, + { + "path": [ + "assembler" + ], + "name": "serve", + "summary": "Serve the output of a completed assembler build at http://localhost:4000.", + "notes": "Run after assembler build. Does not watch for file changes.", + "usage": "docs-builder assembler serve [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "port", + "shortName": null, + "kind": "Primitive:int", + "required": false, + "summary": "Port to listen on. Default: 4000.", + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Path to the built site. Defaults to .artifacts/docs/.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + }, + { + "path": [ + "assembler" + ], + "name": "sitemap", + "summary": "Generate sitemap.xml using accurate content_last_updated dates from Elasticsearch.", + "notes": "The sitemap generated by assembler build uses the current date as a placeholder.\nRun this command after assembler index to overwrite it with precise last-modified dates\nsourced from the search index.", + "usage": "docs-builder assembler sitemap --_ \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "-es,--endpoint, Elasticsearch endpoint URL. Falls back to env DOCUMENTATION_ELASTIC_URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "api-key", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY.", + "validations": null + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME.", + "validations": null + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD.", + "validations": null + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of search threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "index-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of index threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "eis", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "kind": "Primitive:TimeSpan?", + "required": false, + "summary": "How long to wait for the inference endpoint to become ready (e.g. 4m, 90s).", + "validations": [ + { + "kind": "timeSpanRange", + "min": "\u00221s\u0022", + "max": "\u002260m\u0022", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "force-reindex", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Force a full reindex, discarding any incremental state.", + "validations": null + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of documents per bulk request.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "10000", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "max-retries", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of retry attempts for failed bulk items.", + "validations": [ + { + "kind": "range", + "min": "0", + "max": "20", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "debug-mode", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "Route requests through this proxy URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "proxy-username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server username.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server password.", + "validations": null + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to a PEM or DER certificate file for SSL validation.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "pem", + "der", + "crt", + "cer" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "certificate-not-root", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root.", + "validations": null + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target; used to resolve the correct Elasticsearch index.", + "validations": null + } + ] + } + ], + "namespaces": [ + { + "segment": "bloom-filter", + "summary": "Build and query the bloom filter used for legacy-URL redirect coverage.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler", + "bloom-filter" + ], + "name": "create", + "summary": "Build a bloom filter binary from a local legacy-docs repository.", + "notes": "The bloom filter is a compact data structure that records which legacy URLs existed before migration.\nIt is used to verify redirect coverage: if a legacy URL is absent from the filter, any redirect\npointing to it cannot be validated. Run once after cloning the legacy-docs repository.", + "usage": "docs-builder assembler bloom-filter create --built-docs-dir \u003Cdir\u003E", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "built-docs-dir", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": true, + "summary": "Path to the local legacy-docs repository checkout.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + }, + { + "path": [ + "assembler", + "bloom-filter" + ], + "name": "lookup", + "summary": "Test whether a URL path is recorded in the bloom filter.", + "notes": null, + "usage": "docs-builder assembler bloom-filter lookup --path \u003Cstring\u003E", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "URL path to look up (e.g. /guide/en/elasticsearch/reference/current/index.html).", + "validations": null + } + ] + } + ], + "namespaces": [] + }, + { + "segment": "config", + "summary": "Fetch and manage the central assembler configuration repository.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler", + "config" + ], + "name": "init", + "summary": "Fetch the assembler configuration into local application data.", + "notes": "All assembler and codex commands read their repository list from a central configuration repository.\nRun this once before the first assembler clone or assemble invocation, and whenever\nthe configuration has changed upstream.", + "usage": "docs-builder assembler config init [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "git-ref", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Git ref to fetch. Defaults to main.", + "validations": null + }, + { + "role": "flag", + "name": "local", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Write the configuration into cwd so subsequent commands treat it as a local override.", + "validations": null + } + ] + } + ], + "namespaces": [] + }, + { + "segment": "content-source", + "summary": "Inspect and validate repository entries in the link registry.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler", + "content-source" + ], + "name": "match", + "summary": "Check whether a repository at a specific branch or tag should be included in the next build.", + "notes": "Exits 0 if the repository matches; 1 otherwise. Useful for conditional CI steps.", + "usage": "docs-builder assembler content-source match [\u003Crepository\u003E] [\u003Cbranch-or-tag\u003E]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "repository", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Repository slug to match (e.g. elastic/elasticsearch).", + "validations": null + }, + { + "role": "positional", + "name": "branch-or-tag", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Branch name or version tag to test against.", + "validations": null + } + ] + }, + { + "path": [ + "assembler", + "content-source" + ], + "name": "validate", + "summary": "Verify that every repository in the assembler configuration has an active published entry in the link registry.", + "notes": null, + "usage": "docs-builder assembler content-source validate", + "examples": [], + "parameters": [] + } + ], + "namespaces": [] + }, + { + "segment": "deploy", + "summary": "Deploy built documentation to S3 and update CloudFront redirect rules.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler", + "deploy" + ], + "name": "apply", + "summary": "Upload the changes described in a plan file to S3.", + "notes": "Run after assembler deploy plan. Applies the pre-computed diff to the S3 bucket.", + "usage": "docs-builder assembler deploy apply --environment \u003Cstring\u003E --s3-bucket-name \u003Cstring\u003E --plan-file \u003Cfile\u003E", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Named deployment target.", + "validations": null + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "S3 bucket to deploy to.", + "validations": null + }, + { + "role": "flag", + "name": "plan-file", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the plan file produced by assembler deploy plan.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "json" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + }, + { + "path": [ + "assembler", + "deploy" + ], + "name": "plan", + "summary": "Compute a diff of what would change when deploying to S3 and write it to a plan file.", + "notes": "Two-step deployment: plan computes the diff and writes a plan file; apply executes it.\nReview the plan before applying to avoid accidental mass deletions.", + "usage": "docs-builder assembler deploy plan --environment \u003Cstring\u003E --s3-bucket-name \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Named deployment target.", + "validations": null + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "S3 bucket to deploy to.", + "validations": null + }, + { + "role": "flag", + "name": "out", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to write the plan file. Defaults to stdout.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "delete-threshold", + "shortName": null, + "kind": "Primitive:float?", + "required": false, + "summary": "Abort if the plan would delete more than this percentage of objects (0\u2013100).", + "validations": null + } + ] + }, + { + "path": [ + "assembler", + "deploy" + ], + "name": "update-redirects", + "summary": "Push the redirects mapping to CloudFront\u0027s KeyValueStore.", + "notes": "Run after assembler build produces a redirects.json.", + "usage": "docs-builder assembler deploy update-redirects --environment \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Named deployment target.", + "validations": null + }, + { + "role": "flag", + "name": "redirects-file", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to redirects.json. Defaults to .artifacts/docs/redirects.json.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "json" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + } + ], + "namespaces": [] + }, + { + "segment": "navigation", + "summary": "Validate the global navigation structure and cross-doc-set link references.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "assembler", + "navigation" + ], + "name": "validate", + "summary": "Check navigation.yml for duplicate path prefixes and non-unique URLs.", + "notes": null, + "usage": "docs-builder assembler navigation validate", + "examples": [], + "parameters": [] + }, + { + "path": [ + "assembler", + "navigation" + ], + "name": "validate-link-reference", + "summary": "Check that no link in a local links.json conflicts with a path prefix defined in navigation.yml.", + "notes": null, + "usage": "docs-builder assembler navigation validate-link-reference [\u003Cfile\u003E]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "file", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to links.json. Defaults to .artifacts/docs/html/links.json.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "json" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + } + ], + "namespaces": [] + } + ] + }, + { + "segment": "changelog", + "summary": "Create, bundle, and publish changelog entries.", + "notes": null, + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "changelog" + ], + "name": "add", + "summary": "Create a new changelog entry YAML file.", + "notes": null, + "usage": "docs-builder changelog add [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "products", + "shortName": null, + "kind": "CustomParser:custom", + "required": false, + "summary": "Optional: Products affected in format \u0022product target lifecycle, ...\u0022 (e.g., \u0022elasticsearch 9.2.0 ga, cloud-serverless 2025-08-05\u0022). If not specified, will be inferred from repository or config defaults.", + "validations": null + }, + { + "role": "flag", + "name": "action", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: What users must do to mitigate", + "validations": null + }, + { + "role": "flag", + "name": "areas", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Optional: Area(s) affected (comma-separated or specify multiple times)", + "validations": null + }, + { + "role": "flag", + "name": "concise", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Omit schema reference comments from generated YAML files. Useful in CI to produce compact output.", + "validations": null + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Optional: Path to the changelog.yml configuration file. Defaults to \u0027docs/changelog.yml\u0027", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "description", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Additional information about the change (max 600 characters)", + "validations": null + }, + { + "role": "flag", + "name": "no-extract-release-notes", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Turn off extraction of release notes from PR descriptions. By default, release notes are extracted when using --prs. Matched release note text is used as the changelog description (only if --description is not explicitly provided). The changelog title comes from --title or the PR title, not from the release note section.", + "validations": null + }, + { + "role": "flag", + "name": "no-extract-issues", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Turn off extraction of linked references. When using --prs: turns off extraction of linked issues from the PR body (e.g., \u0022Fixes #123\u0022). When using --issues: turns off extraction of linked PRs from the issue body (e.g., \u0022Fixed by #123\u0022). By default, linked references are extracted in both cases.", + "validations": null + }, + { + "role": "flag", + "name": "feature-id", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Feature flag ID", + "validations": null + }, + { + "role": "flag", + "name": "highlight", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Optional: Include in release highlights", + "validations": null + }, + { + "role": "flag", + "name": "impact", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: How the user\u0027s environment is affected", + "validations": null + }, + { + "role": "flag", + "name": "issues", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Optional: Issue URL(s) or number(s) (comma-separated), or a path to a newline-delimited file containing issue URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated issues (e.g., \u0060--issues \u0022https://github.com/owner/repo/issues/123,456\u0022\u0060) or a file path (e.g., \u0060--issues /path/to/file.txt\u0060). If --owner and --repo are provided, issue numbers can be used instead of URLs. If specified, --title can be derived from the issue. Creates one changelog file per issue.", + "validations": null + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: GitHub repository owner (used when --prs or --issues contains just numbers, or when using --release-version). Falls back to bundle.owner in changelog.yml when not specified. If that value is also absent, \u0022elastic\u0022 is used.", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Output directory for the changelog. Falls back to bundle.directory in changelog.yml when not specified. Defaults to current directory.", + "validations": null + }, + { + "role": "flag", + "name": "prs", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Optional: Pull request URL(s) or PR number(s) (comma-separated), or a path to a newline-delimited file containing PR URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated PRs (e.g., \u0060--prs \u0022https://github.com/owner/repo/pull/123,6789\u0022\u0060) or a file path (e.g., \u0060--prs /path/to/file.txt\u0060). When specifying PRs directly, provide comma-separated values. When specifying a file path, provide a single value that points to a newline-delimited file. If --owner and --repo are provided, PR numbers can be used instead of URLs. If specified, --title can be derived from the PR. If mappings are configured, --areas and --type can also be derived from the PR. Creates one changelog file per PR.", + "validations": null + }, + { + "role": "flag", + "name": "release-version", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: GitHub release tag to fetch PRs from (e.g., \u0022v9.2.0\u0022 or \u0022latest\u0022). When specified, creates one changelog per PR in the release notes. Requires --repo (or bundle.repo in changelog.yml). Mutually exclusive with --prs and --issues. Does not create a bundle; use \u0027changelog gh-release\u0027 for that.", + "validations": null + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: GitHub repository name (used when --prs or --issues contains just numbers, or when using --release-version). Falls back to bundle.repo in changelog.yml when not specified.", + "validations": null + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: When used with --prs, remove square brackets and text within them from the beginning of PR titles, and also remove a colon if it follows the closing bracket (e.g., \u0022[Inference API] Title\u0022 becomes \u0022Title\u0022, \u0022[ES|QL]: Title\u0022 becomes \u0022Title\u0022, \u0022[Discover][ESQL] Title\u0022 becomes \u0022Title\u0022)", + "validations": null + }, + { + "role": "flag", + "name": "subtype", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Subtype for breaking changes (api, behavioral, configuration, etc.)", + "validations": null + }, + { + "role": "flag", + "name": "title", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: A short, user-facing title (max 80 characters). Required if neither --prs nor --issues is specified. If --prs and --title are specified, the latter value is used instead of what exists in the PR.", + "validations": null + }, + { + "role": "flag", + "name": "type", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Type of change (feature, enhancement, bug-fix, breaking-change, etc.). Required if neither --prs nor --issues is specified. If mappings are configured, type can be derived from the PR or issue.", + "validations": null + }, + { + "role": "flag", + "name": "use-pr-number", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Use PR numbers for filenames instead of timestamp-slug. With both --prs (which creates one changelog per specified PR) and --issues (which creates one changelog per specified issue), each changelog filename will be derived from its PR numbers. Requires --prs or --issues. Mutually exclusive with --use-issue-number.", + "validations": null + }, + { + "role": "flag", + "name": "use-issue-number", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Use issue numbers for filenames instead of timestamp-slug. With both --prs (which creates one changelog per specified PR) and --issues (which creates one changelog per specified issue), each changelog filename will be derived from its issues. Requires --prs or --issues. Mutually exclusive with --use-pr-number.", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "bundle", + "summary": "Aggregate changelog entries matching a filter into a single bundle YAML.", + "notes": "Accepts either a named profile from changelog.yml (e.g. bundle my-release 9.2.0) or\nan explicit filter flag. Exactly one filter must be specified: --all, --input-products,\n--prs, --issues, --release-version, or --report.", + "usage": "docs-builder changelog bundle [\u003Cprofile\u003E] [\u003Cprofile-arg\u003E] [\u003Cprofile-report\u003E] [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "profile", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Profile name from bundle.profiles in config (for example, \u0022elasticsearch-release\u0022). When specified, the second argument is the version or promotion report URL.", + "validations": null + }, + { + "role": "positional", + "name": "profile-arg", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Version number or promotion report URL/path when using a profile (for example, \u00229.2.0\u0022 or \u0022https://buildkite.../promotion-report.html\u0022)", + "validations": null + }, + { + "role": "positional", + "name": "profile-report", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Promotion report or URL list file when also providing a version. When provided, the second argument must be a version string and this is the PR/issue filter source (for example, \u0022bundle serverless-release 2026-02 ./report.html\u0022).", + "validations": null + }, + { + "role": "flag", + "name": "all", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Include all changelogs in the directory.", + "validations": null + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Optional: Path to the changelog.yml configuration file. Defaults to \u0027docs/changelog.yml\u0027", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "directory", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Optional: Directory containing changelog YAML files. Uses config bundle.directory or defaults to current directory", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "description", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Bundle description text with placeholder support. Supports VERSION, LIFECYCLE, OWNER, and REPO placeholders. Overrides bundle.description from config. In option-based mode, placeholders require --output-products to be explicitly specified.", + "validations": null + }, + { + "role": "flag", + "name": "hide-features", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Optional: Filter by feature IDs (comma-separated) or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. Entries with matching feature-id values will be commented out when the bundle is rendered (by CLI render or changelog directive).", + "validations": null + }, + { + "role": "flag", + "name": "no-release-date", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Skip auto-population of release date in the bundle. Mutually exclusive with --release-date. Not available in profile mode.", + "validations": null + }, + { + "role": "flag", + "name": "release-date", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Explicit release date for the bundle in YYYY-MM-DD format. Overrides auto-population behavior. Mutually exclusive with --no-release-date. Not available in profile mode.", + "validations": null + }, + { + "role": "flag", + "name": "input-products", + "shortName": null, + "kind": "CustomParser:custom", + "required": false, + "summary": "Filter by products in format \u0022product target lifecycle, ...\u0022 (for example, \u0022cloud-serverless 2025-12-02 ga, cloud-serverless 2025-12-06 beta\u0022). When specified, all three parts (product, target, lifecycle) are required but can be wildcards (*). Examples: \u0022elasticsearch * *\u0022 matches all elasticsearch changelogs, \u0022cloud-serverless 2025-12-02 *\u0022 matches cloud-serverless 2025-12-02 with any lifecycle, \u0022* 9.3.* *\u0022 matches any product with target starting with \u00229.3.\u0022, \u0022* * *\u0022 matches all changelogs (equivalent to --all).", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Output path for the bundled changelog. Can be either (1) a directory path, in which case \u0027changelog-bundle.yaml\u0027 is created in that directory, or (2) a file path ending in .yml or .yaml. Uses config bundle.output_directory or defaults to \u0027changelog-bundle.yaml\u0027 in the input directory", + "validations": null + }, + { + "role": "flag", + "name": "output-products", + "shortName": null, + "kind": "CustomParser:custom", + "required": false, + "summary": "Optional: Explicitly set the products array in the output file in format \u0022product target lifecycle, ...\u0022. Overrides any values from changelogs.", + "validations": null + }, + { + "role": "flag", + "name": "issues", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Filter by issue URLs (comma-separated), or a path to a newline-delimited file containing fully-qualified GitHub issue URLs. Can be specified multiple times.", + "validations": null + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub repository owner, which is used when PRs or issues are specified as numbers or when using --release-version. Falls back to bundle.owner in changelog.yml when not specified. If that value is also absent, \u0022elastic\u0022 is used.", + "validations": null + }, + { + "role": "flag", + "name": "plan", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Emit GitHub Actions step outputs (needs_network, needs_github_token, output_path) describing network requirements and the resolved output path, then exit without generating the bundle. Intended for CI actions.", + "validations": null + }, + { + "role": "flag", + "name": "prs", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Filter by pull request URLs (comma-separated), or a path to a newline-delimited file containing fully-qualified GitHub PR URLs. Can be specified multiple times.", + "validations": null + }, + { + "role": "flag", + "name": "release-version", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub release tag to use as a filter source (for example, \u0022v9.2.0\u0022 or \u0022latest\u0022). When specified, fetches the release, parses PR references from the release notes, and uses those PRs as the filter \u2014 equivalent to passing the PR list via --prs. When --output-products is not specified, it is inferred from the release tag and repository name.", + "validations": null + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub repository name, which is used when PRs or issues are specified as numbers or when using --release-version. Falls back to bundle.repo in changelog.yml when not specified. If that value is also absent, the product ID is used.", + "validations": null + }, + { + "role": "flag", + "name": "report", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "A URL or file path to a promotion report. Extracts PR URLs and uses them as the filter.", + "validations": null + }, + { + "role": "flag", + "name": "resolve", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Optional: Copy the contents of each changelog file into the entries array. Uses config bundle.resolve or defaults to false.", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "bundle-amend", + "summary": "Append additional changelog entries to a published bundle without modifying it.", + "notes": "Creates an immutable .amend-N.yaml sidecar file alongside the original bundle.", + "usage": "docs-builder changelog bundle-amend \u003Cbundle-path\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "bundle-path", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Required: Path to the original bundle file to amend", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "add", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Required: Path(s) to changelog YAML file(s) to add as comma-separated values (e.g., --add \u0022file1.yaml,file2.yaml\u0022). Supports tilde (~) expansion and relative paths.", + "validations": null + }, + { + "role": "flag", + "name": "resolve", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Optional: Copy the contents of each changelog file into the entries array. Use --no-resolve to explicitly turn off resolve (overrides inference from original bundle).", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "evaluate-pr", + "summary": "(CI) Evaluate a pull request for changelog generation eligibility and set GitHub Actions outputs.", + "notes": "Runs pre-flight checks (body-only edit, bot loop, manual edit), applies label rules from\nchangelog.yml, and resolves the entry type and title. Designed to be called from a\nGitHub Actions workflow step.", + "usage": "docs-builder changelog evaluate-pr --config \u003Cfile\u003E --owner \u003Cstring\u003E --repo \u003Cstring\u003E --pr-number \u003Cint\u003E --pr-title \u003Cstring\u003E --pr-labels \u003Cstring\u003E --head-ref \u003Cstring\u003E --head-sha \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the changelog.yml configuration file", + "validations": [ + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + } + ] + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "GitHub repository owner", + "validations": null + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "GitHub repository name", + "validations": null + }, + { + "role": "flag", + "name": "pr-number", + "shortName": null, + "kind": "Primitive:int", + "required": true, + "summary": "Pull request number", + "validations": null + }, + { + "role": "flag", + "name": "pr-title", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Pull request title", + "validations": null + }, + { + "role": "flag", + "name": "pr-labels", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Comma-separated PR labels", + "validations": null + }, + { + "role": "flag", + "name": "head-ref", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "PR head branch ref", + "validations": null + }, + { + "role": "flag", + "name": "head-sha", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "PR head commit SHA", + "validations": null + }, + { + "role": "flag", + "name": "event-action", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: GitHub event action (e.g., opened, synchronize, edited). When omitted, body-only-edit and bot-loop checks are skipped.", + "validations": null + }, + { + "role": "flag", + "name": "title-changed", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Whether the PR title changed (for edited events)", + "validations": null + }, + { + "role": "flag", + "name": "body-changed", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Whether the PR body changed (for edited events)", + "validations": null + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Remove square-bracket prefixes from the PR title", + "validations": null + }, + { + "role": "flag", + "name": "bot-name", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Bot login name for loop detection", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "gh-release", + "summary": "Create changelog entries from the PRs referenced in a GitHub release.", + "notes": null, + "usage": "docs-builder changelog gh-release \u003Crepo\u003E [\u003Cversion\u003E] [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Required: GitHub repository in owner/repo format (e.g., \u0022elastic/elasticsearch\u0022 or just \u0022elasticsearch\u0022 which defaults to elastic/elasticsearch)", + "validations": null + }, + { + "role": "positional", + "name": "version", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Version tag to fetch (e.g., \u0022v9.0.0\u0022, \u00229.0.0\u0022). Defaults to \u0022latest\u0022", + "validations": null + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Optional: Path to the changelog.yml configuration file. Defaults to \u0027docs/changelog.yml\u0027", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "description", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Bundle description text with placeholder support. Supports VERSION, LIFECYCLE, OWNER, and REPO placeholders. Overrides bundle.description from config.", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Output directory for changelog files. Falls back to bundle.directory in changelog.yml when not specified. Defaults to \u0027./changelogs\u0027", + "validations": null + }, + { + "role": "flag", + "name": "release-date", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Explicit release date for the bundle in YYYY-MM-DD format. Overrides GitHub release published date.", + "validations": null + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Remove square brackets and text within them from the beginning of PR titles (e.g., \u0022[Inference API] Title\u0022 becomes \u0022Title\u0022)", + "validations": null + }, + { + "role": "flag", + "name": "warn-on-type-mismatch", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Warn when the type inferred from release notes section headers doesn\u0027t match the type derived from PR labels. Defaults to true", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "init", + "summary": "Create changelog.yml and the changelog/releases directory structure.", + "notes": "Discovers the docs folder via docset.yml; falls back to creating PATH/docs.\nWhen changelog.yml already exists, updates only the paths specified via or .\nSeeds bundle.owner, bundle.repo, and bundle.link_allow_repos from the git remote origin when available.", + "usage": "docs-builder changelog init [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Repository root. Defaults to cwd.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "changelog-dir", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Changelog entry directory. Defaults to docs/changelog.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "bundles-dir", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Bundle output directory. Defaults to docs/releases.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub owner for seeding bundle defaults. Overrides the value inferred from git remote origin.", + "validations": null + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub repository name for seeding bundle defaults. Overrides the value inferred from git remote origin.", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "remove", + "summary": "Delete changelog entry files matching a filter.", + "notes": "Blocks when a file is referenced by an unresolved bundle to avoid breaking the {changelog}\ndirective in published documentation. Pass --force to override.", + "usage": "docs-builder changelog remove [\u003Cprofile\u003E] [\u003Cprofile-arg\u003E] [\u003Cprofile-report\u003E] [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "profile", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Profile name from bundle.profiles in config (for example, \u0022elasticsearch-release\u0022). When specified, the second argument is the version or promotion report URL.", + "validations": null + }, + { + "role": "positional", + "name": "profile-arg", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Version number or promotion report URL/path when using a profile (for example, \u00229.2.0\u0022 or \u0022https://buildkite.../promotion-report.html\u0022)", + "validations": null + }, + { + "role": "positional", + "name": "profile-report", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Promotion report or URL list file when also providing a version. When provided, the second argument must be a version string and this is the PR/issue filter source.", + "validations": null + }, + { + "role": "flag", + "name": "all", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Remove all changelogs in the directory. Exactly one filter option must be specified: --all, --products, --prs, --issues, or --report.", + "validations": null + }, + { + "role": "flag", + "name": "bundles-dir", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Optional: Override the directory that is scanned for bundles during the dependency check. Auto-discovered from config or fallback paths when not specified.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Optional: Path to the changelog.yml configuration file. Defaults to \u0027docs/changelog.yml\u0027", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "directory", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Optional: Directory containing changelog YAML files. Uses config bundle.directory or defaults to current directory", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "dry-run", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Print the files that would be removed without deleting them. Valid in both profile and raw mode.", + "validations": null + }, + { + "role": "flag", + "name": "force", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Proceed with removal even when files are referenced by unresolved bundles. Emits warnings instead of errors for each dependency. Valid in both profile and raw mode.", + "validations": null + }, + { + "role": "flag", + "name": "issues", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Filter by issue URLs (comma-separated) or a path to a newline-delimited file containing fully-qualified GitHub issue URLs. Can be specified multiple times.", + "validations": null + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: GitHub repository owner, which is used when PRs or issues are specified as numbers or when using --release-version. Falls back to bundle.owner in changelog.yml when not specified. If that value is also absent, \u0022elastic\u0022 is used.", + "validations": null + }, + { + "role": "flag", + "name": "products", + "shortName": null, + "kind": "CustomParser:custom", + "required": false, + "summary": "Filter by products in format \u0022product target lifecycle, ...\u0022 (for example, \u0022elasticsearch 9.3.0 ga\u0022). All three parts are required but can be wildcards (*).", + "validations": null + }, + { + "role": "flag", + "name": "prs", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Filter by pull request URLs (comma-separated) or a path to a newline-delimited file containing fully-qualified GitHub PR URLs. Can be specified multiple times.", + "validations": null + }, + { + "role": "flag", + "name": "release-version", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub release tag to use as a filter source (for example, \u0022v9.2.0\u0022 or \u0022latest\u0022). Fetches the release, parses PR references from the release notes, and removes changelogs whose PR URLs match \u2014 equivalent to passing the PR list using --prs.", + "validations": null + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "GitHub repository name, which is used when PRs or issues are specified as numbers or when --release-version is used. Falls back to bundle.repo in changelog.yml when not specified. If that value is also absent, the product ID is used.", + "validations": null + }, + { + "role": "flag", + "name": "report", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional (option-based mode only): URL or file path to a promotion report. Extracts PR URLs and uses them as the filter. Mutually exclusive with --all, --products, --prs, and --issues.", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "render", + "summary": "Render one or more changelog bundles to Markdown or AsciiDoc.", + "notes": null, + "usage": "docs-builder changelog render [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "input", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Required: Bundle input(s) in format \u0022bundle-file-path|changelog-file-path|repo|link-visibility\u0022 (use pipe as delimiter). To merge multiple bundles, separate them with commas. Only bundle-file-path is required. link-visibility can be \u0022hide-links\u0022 or \u0022keep-links\u0022 (default). Use \u0022hide-links\u0022 for private repositories; when set, all PR and issue links for each affected entry are hidden (entries may have multiple links via the prs and issues arrays). Paths support tilde (~) expansion and relative paths.", + "validations": null + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Optional: Path to the changelog.yml configuration file. Defaults to \u0027docs/changelog.yml\u0027", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "file-type", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Output file type. Valid values: \u0022markdown\u0022 or \u0022asciidoc\u0022. Defaults to \u0022markdown\u0022", + "validations": null + }, + { + "role": "flag", + "name": "hide-features", + "shortName": null, + "kind": "Collection\u003Cstring\u003E", + "required": false, + "summary": "Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. Entries with matching feature-id values will be commented out in the output.", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Output directory for rendered files. Defaults to current directory", + "validations": null + }, + { + "role": "flag", + "name": "subsections", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Optional: Group entries by area/component in subsections. For breaking changes with a subtype, groups by subtype instead of area. Defaults to false", + "validations": null + }, + { + "role": "flag", + "name": "title", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Optional: Title to use for section headers in output files. Defaults to version from first bundle", + "validations": null + } + ] + }, + { + "path": [ + "changelog" + ], + "name": "upload", + "summary": "Upload changelog entries or bundle artifacts to S3 or Elasticsearch.", + "notes": "Uses content-hash\u2013based incremental transfer \u2014 only changed files are uploaded.", + "usage": "docs-builder changelog upload --artifact-type \u003Cstring\u003E --target \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "artifact-type", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Artifact type to upload: \u0027changelog\u0027 (individual entries) or \u0027bundle\u0027 (consolidated bundles).", + "validations": null + }, + { + "role": "flag", + "name": "target", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": "Upload destination: \u0027s3\u0027 or \u0027elasticsearch\u0027.", + "validations": null + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "S3 bucket name (required when target is \u0027s3\u0027).", + "validations": null + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to changelog.yml configuration file. Defaults to docs/changelog.yml.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "directory", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Override changelog directory instead of reading it from config.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + } + ], + "namespaces": [] + }, + { + "segment": "codex", + "summary": "Build a documentation portal over multiple independent documentation sets, each with its own navigation.", + "notes": null, + "options": [], + "defaultCommand": { + "kind": "namespace", + "summary": "Clone all repositories and build the portal in one step.", + "notes": null, + "usage": "docs-builder codex __argh_root \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the codex.yml configuration file.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned ref.", + "validations": null + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Output directory for the built portal. Defaults to .artifacts/codex/.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "serve", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Serve the portal on port 4000 after a successful build.", + "validations": null + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "kind": "Enum:LogLevel", + "required": false, + "summary": "Minimum log level. Default: information", + "validations": null + }, + { + "role": "flag", + "name": "config-source", + "shortName": "c", + "kind": "Enum:ConfigurationSource", + "required": false, + "summary": "Override the configuration source: local, remote", + "validations": null + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip cloning private repositories", + "validations": null + } + ] + }, + "commands": [ + { + "path": [ + "codex" + ], + "name": "build", + "summary": "Build the portal from previously cloned repositories.", + "notes": "Run after codex clone.", + "usage": "docs-builder codex build \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the codex.yml configuration file.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Output directory. Defaults to .artifacts/codex/.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + }, + { + "path": [ + "codex" + ], + "name": "clone", + "summary": "Clone all repositories listed in the codex configuration.", + "notes": null, + "usage": "docs-builder codex clone \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the codex.yml configuration file.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Treat warnings as errors.", + "validations": null + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned ref.", + "validations": null + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "kind": "Primitive:bool", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "validations": null + } + ] + }, + { + "path": [ + "codex" + ], + "name": "index", + "summary": "Index the built portal documentation into Elasticsearch.", + "notes": "Run after codex build. Streams documents from all included documentation sets to the cluster.", + "usage": "docs-builder codex index --_ \u003Cstring\u003E \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "positional", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the codex.yml configuration file.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "-es,--endpoint, Elasticsearch endpoint URL. Falls back to env DOCUMENTATION_ELASTIC_URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "api-key", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY.", + "validations": null + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME.", + "validations": null + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD.", + "validations": null + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of search threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "index-num-threads", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of index threads for the inference endpoint.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "128", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "eis", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default).", + "validations": null + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "kind": "Primitive:TimeSpan?", + "required": false, + "summary": "How long to wait for the inference endpoint to become ready (e.g. 4m, 90s).", + "validations": [ + { + "kind": "timeSpanRange", + "min": "\u00221s\u0022", + "max": "\u002260m\u0022", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "force-reindex", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Force a full reindex, discarding any incremental state.", + "validations": null + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of documents per bulk request.", + "validations": [ + { + "kind": "range", + "min": "1", + "max": "10000", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "max-retries", + "shortName": null, + "kind": "Primitive:int?", + "required": false, + "summary": "Number of retry attempts for failed bulk items.", + "validations": [ + { + "kind": "range", + "min": "0", + "max": "20", + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "debug-mode", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "kind": "Uri:Uri", + "required": false, + "summary": "Route requests through this proxy URL.", + "validations": [ + { + "kind": "uriScheme", + "min": null, + "max": null, + "pattern": null, + "values": [ + "http", + "https" + ] + } + ] + }, + { + "role": "flag", + "name": "proxy-username", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server username.", + "validations": null + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Proxy server password.", + "validations": null + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate.", + "validations": null + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to a PEM or DER certificate file for SSL validation.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "pem", + "der", + "crt", + "cer" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "certificate-not-root", + "shortName": null, + "kind": "Primitive:bool?", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root.", + "validations": null + } + ] + }, + { + "path": [ + "codex" + ], + "name": "serve", + "summary": "Serve the built portal at http://localhost:4000.", + "notes": "Run after codex build. Does not rebuild on file changes.", + "usage": "docs-builder codex serve [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "port", + "shortName": null, + "kind": "Primitive:int", + "required": false, + "summary": "Port to listen on. Default: 4000.", + "validations": null + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "kind": "DirectoryInfo:DirectoryInfo", + "required": false, + "summary": "Path to the portal output. Defaults to .artifacts/codex/docs/.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + }, + { + "path": [ + "codex" + ], + "name": "update-redirects", + "summary": "Push the codex redirects mapping to CloudFront\u0027s KeyValueStore.", + "notes": "Run after codex build produces a redirects.json.", + "usage": "docs-builder codex update-redirects --_ \u003Cstring\u003E \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "_", + "shortName": null, + "kind": "Primitive:string", + "required": true, + "summary": null, + "validations": null + }, + { + "role": "positional", + "name": "config", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": true, + "summary": "Path to the codex.yml configuration file (used to resolve the environment).", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "yml", + "yaml" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Named deployment target. Defaults to the value in codex.yml or the ENVIRONMENT env var.", + "validations": null + }, + { + "role": "flag", + "name": "redirects-file", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to redirects.json. Defaults to .artifacts/codex/docs/redirects.json.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "json" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + } + ] + } + ], + "namespaces": [] + }, + { + "segment": "inbound-links", + "summary": "Validate cross-doc-set links against the published link registry.", + "notes": "Every documentation set publishes a links.json file containing the URLs of all its pages.\nThese files are aggregated into a shared link registry. Inbound-links commands validate that\ncross-links between documentation sets resolve to real pages in the registry.", + "options": [], + "defaultCommand": null, + "commands": [ + { + "path": [ + "inbound-links" + ], + "name": "validate", + "summary": "Validate all cross-links originating from or targeting a specific repository.", + "notes": null, + "usage": "docs-builder inbound-links validate [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "from", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Only check links published by this repository slug.", + "validations": null + }, + { + "role": "flag", + "name": "to", + "shortName": null, + "kind": "Primitive:string", + "required": false, + "summary": "Only check links that point to this repository slug.", + "validations": null + } + ] + }, + { + "path": [ + "inbound-links" + ], + "name": "validate-all", + "summary": "Validate all cross-links across every published links.json in the registry.", + "notes": null, + "usage": "docs-builder inbound-links validate-all", + "examples": [], + "parameters": [] + }, + { + "path": [ + "inbound-links" + ], + "name": "validate-link-reference", + "summary": "Validate a locally built links.json against the published link registry.", + "notes": "Use this to verify cross-links before publishing. The local links.json is checked against\nall currently published registries to ensure every outbound cross-link resolves.", + "usage": "docs-builder inbound-links validate-link-reference [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "file", + "shortName": null, + "kind": "FileInfo:FileInfo", + "required": false, + "summary": "Path to links.json. Defaults to .artifacts/docs/html/links.json.", + "validations": [ + { + "kind": "rejectSymbolicLinks", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "existing", + "min": null, + "max": null, + "pattern": null, + "values": null + }, + { + "kind": "fileExtensions", + "min": null, + "max": null, + "pattern": null, + "values": [ + "json" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "kind": "Primitive:string", + "required": false, + "summary": "Root of the documentation source. Defaults to cwd.", + "validations": null + } + ] + } + ], + "namespaces": [] + } + ] +} \ No newline at end of file diff --git a/docs/cli/assembler/assemble.md b/docs/cli/assembler/assemble.md deleted file mode 100644 index 13058aa0cb..0000000000 --- a/docs/cli/assembler/assemble.md +++ /dev/null @@ -1,91 +0,0 @@ -# assemble - -Do a full assembler clone, build and optional serving of the full documentation in one swoop - -## Usage - -``` -docs-builder assemble [options...] [-h|--help] [--version] -``` - - - -## Usage examples - -The following will clone the repository, build the documentation and serve it on port 4000 using the embedded configuration inside the `docs-builder` binary. - -```bash -docs-builder assemble --serve -``` - -This single command is equivalent to the following commands: - -```bash -docs-builder assembler clone -docs-builder assembler build -docs-builder assembler serve -``` - -### Using a local workspace for assembler builds - -Where this command really shines is when you want to create a temporary workspace folder to validate: - -* changes to [site wide configuration](../../configure/site/index.md). -* changes to one or more repositories and their effect on the assembler build. - -To do that inside an empty folder, call: - -```bash -docs-builder assembler config init --local -docs-builder assemble --serve -``` - -This will source the latest configuration from [The `config` folder on the `main` branch of `docs-builder`](https://github.com/elastic/docs-builder/tree/main/config) -and place them inside the `$(pwd)/config` folder. - -Now when you call `docs-builder assemble` rather than using the embedded configuration, it will use local one that one you just created. -You can be explicit about the configuration source to use: - -```bash -docs-builder assembler config init --local -docs-builder assemble --serve -c local -``` - -## Options - -`--strict` `` -: Treat warnings as errors and fail the build on warnings (optional) - -`--environment` `` -: The environment to build (optional) defaults to 'dev' - -`--fetch-latest` `` -: If true, fetch the latest commit of the branch instead of the link registry entry ref (optional) - -`--assume-cloned` `` -: If true, assume the repository folder already exists on disk assume it's cloned already, primarily used for testing (optional) - -`--metadata-only` `` -: Only emit documentation metadata to output, ignored if 'exporters' is also set (optional) - -`--show-hints` `` -: Show hints from all documentation sets during assembler build (optional) - -`--exporters` `` -: Set available exporters: - - * html - * es, - * config, - * links, - * state, - * llm, - * redirect, - * metadata, - * default - * none. - - Defaults to (html, llm, config, links, state, redirect) or 'default'. (optional) - -`--serve` -: Serve the documentation on port 4000 after successful build (Optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-bloom-filter-create.md b/docs/cli/assembler/assembler-bloom-filter-create.md deleted file mode 100644 index cda2ec4a21..0000000000 --- a/docs/cli/assembler/assembler-bloom-filter-create.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -navigation_title: "bloom-filter create" ---- - -# assembler bloom-filter create - -Generates a bloom filter that gets embedded into the `docs-builder` binary. - -This bloom filter is used to determine whether a document's `mapped_page` in the frontmatter exists in - -the project of [legacy-url-mappings](../../configure/site/legacy-url-mappings.md) - -The existence determines how the document history selector should be populated. - -## Usage - -``` -docs-builder assembler bloom-filter create [options...] [-h|--help] [--version] -``` - -## Options - -`--built-docs-dir` `` -: The local dir of local elastic/built-docs repository (Required) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-bloom-filter-lookup.md b/docs/cli/assembler/assembler-bloom-filter-lookup.md deleted file mode 100644 index bdb3027121..0000000000 --- a/docs/cli/assembler/assembler-bloom-filter-lookup.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -navigation_title: "bloom-filter lookup" ---- -# assembler bloom-filter lookup - -Test command to assert if an old V2 url matches with our bloom filter - -## Usage - -``` -docs-builder assembler bloom-filter lookup [options...] [-h|--help] [--version] -``` - -## Options - -`--path` `` -: The local dir of local elastic/built-docs repository (Required) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-build.md b/docs/cli/assembler/assembler-build.md deleted file mode 100644 index 6134277a1a..0000000000 --- a/docs/cli/assembler/assembler-build.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -navigation_title: "build" ---- - -# assembler build - -:::note -This command requires that you've previously ran `docs-builder assembler clone` to clone the documentation sets. -If you clone using a certain `--environment` you must also use that same `--environment` when building. -::: - -Builds all the documentation sets and assembles them into an assembled complete documentation site that's ready to be deployed. - -It uses [the site configuration files](../../configure/site/index.md) to direct how the documentation sets should be assembled. - -## Usage - -``` -docs-builder assembler build [options...] [-h|--help] [--version] -``` - -## Options - -`--strict` `` -: Treat warnings as errors and fail the build on warnings (optional) - -`--environment` `` -: The environment to build (optional) - -`--metadata-only` `` -: Only emit documentation metadata to output, ignored if 'exporters' is also set (optional) - -`--show-hints` `` -: Show hints from all documentation sets during assembler build (optional) - -`--exporters` `` -: Set available exporters: - - * html - * es, - * config, - * links, - * state, - * llm, - * redirect, - * metadata, - * default - * none. - - Defaults to (html, llm, config, links, state, redirect) or 'default'. (optional) - diff --git a/docs/cli/assembler/assembler-clone.md b/docs/cli/assembler/assembler-clone.md deleted file mode 100644 index 0a555990f8..0000000000 --- a/docs/cli/assembler/assembler-clone.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -navigation_title: "clone" ---- - -# assembler clone - -Clones all repositories. Defaults to `$(pwd)/.artifacts/checkouts/{content_source}`. - -The `content_source` is the `content_source` of the `--environment` option as configured in `assembly.yaml` - -## Usage - -``` -docs-builder assembler clone [options...] [-h|--help] [--version] -``` - -## Options - -`--strict` `` -: Treat warnings as errors and fail the build on warnings (optional) - -`--environment` `` -: The environment to build (optional) - -`--fetch-latest` `` -: If true, fetch the latest commit of the branch instead of the link registry entry ref (optional) - -`--assume-cloned` `` -: If true, assume the repository folder already exists on disk assume it's cloned already, primarily used for testing (optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-config-init.md b/docs/cli/assembler/assembler-config-init.md deleted file mode 100644 index d1c5e4eb49..0000000000 --- a/docs/cli/assembler/assembler-config-init.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -navigation_title: "config init" ---- - -# assembler config init - -Sources the configuration from [The `config` folder on the `main` branch of `docs-builder`](https://github.com/elastic/docs-builder/tree/main/config) - -By default, the configuration is placed in a special application folder as its main usages is to be used by CI environments. - -* OSX: `~/Library/Application Support/docs-builder` [NSApplicationSupportDirectory](https://developer.apple.com/documentation/foundation/filemanager/searchpathdirectory/applicationsupportdirectory). -* Linux: `~/.config/docs-builder`. -* {icon}`logo_windows` Windows: `%APPDATA%\docs-builder`. - -You can also use the `--local` option to save the configuration locally in the current working directory. This exposes a great way to assemble the full documentation locally in an empty directory. - -See [using assemble to create local workspaces](assemble.md#using-a-local-workspace-for-assembler-builds) for more information. - -## Usage - -``` -docs-builder assembler config init [options...] [-h|--help] [--version] -``` - -## Options - -`--git-ref` `` -: The git reference of the config, defaults to 'main' (optional) - -`--local` -: Save the remote configuration locally in the pwd so later commands can pick it up as local (Optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-content-source-match.md b/docs/cli/assembler/assembler-content-source-match.md deleted file mode 100644 index ab955ff325..0000000000 --- a/docs/cli/assembler/assembler-content-source-match.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -navigation_title: "content-source match" ---- - -# assembler content-source match - -This command is used to match a repository and branch to a content source it will emit the following `$GITHUB_OUTPUT`: - -* `content-source-match` - whether the branch is a configured content source. -* `content-source-next` - whether the branch is the next content source. -* `content-source-current` - whether the branch is the current content source. -* `content-source-speculative` - whether the branch is a speculative content source. - -#### Speculative builds - -If branches follow semantic versioning, if a branch is cut that is greater than the current version, it will be considered a speculative build. -`docs-builer`'s shared workflow will trigger even if it's not specified as a content source in `assembler.yml`. - -This allows a branch `links.json` to be published to the `Link Service` a head of time before it's configured as a content source. - -## Usage - -``` -docs-builder assembler content-source match [-h|--help] [--version] -``` - -## Arguments - -` -: The name of the `elastic/` repository you want to match if it should be build on CI - -` -: The branch you want to match if it should be build on CI` \ No newline at end of file diff --git a/docs/cli/assembler/assembler-content-source-validate.md b/docs/cli/assembler/assembler-content-source-validate.md deleted file mode 100644 index 661269a812..0000000000 --- a/docs/cli/assembler/assembler-content-source-validate.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -navigation_title: "content-source validate" ---- - -# assembler content-source validate - -Validates that the configured content source branches are publishing succesfully to the `Links Service`. - -## Usage - -``` -docs-builder assembler content-source validate [-h|--help] [--version] -``` \ No newline at end of file diff --git a/docs/cli/assembler/assembler-deploy-apply.md b/docs/cli/assembler/assembler-deploy-apply.md deleted file mode 100644 index 4e745387ad..0000000000 --- a/docs/cli/assembler/assembler-deploy-apply.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -navigation_title: "deploy apply" ---- - -# assembler deploy apply - -Applies an incremental synchronization plan created by [`docs-builder assembler deploy plan`](./assembler-deploy-plan.md). - -## Usage - -``` -docs-builder assembler deploy apply [options...] [-h|--help] [--version] -``` - -## Options - -`--environment` `` -: The environment to build (Required) - -`--s3-bucket-name` `` -: The S3 bucket name to deploy to (Required) - -`--plan-file` `` -: The file path to the plan file to apply (Required) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-deploy-plan.md b/docs/cli/assembler/assembler-deploy-plan.md deleted file mode 100644 index cf4e5c5e7b..0000000000 --- a/docs/cli/assembler/assembler-deploy-plan.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -navigation_title: "deploy plan" ---- - -# assembler deploy plan - -Creates an incremental synchronization plan by comparing the reote `--s3-bucket-name` with the local output of the build. - -## Usage - -``` -docs-builder assembler deploy plan [options...] [-h|--help] [--version] -``` - -## Options - -`--environment` `` -: The environment to build (Required) - -`--s3-bucket-name` `` -: The S3 bucket name to deploy to (Required) - -`--out` `` -: The file to write the plan to (Default: "") - -`--delete-threshold` `` -: The percentage of deletions allowed in the plan as float (optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-deploy-update-redirects.md b/docs/cli/assembler/assembler-deploy-update-redirects.md deleted file mode 100644 index 5a811df776..0000000000 --- a/docs/cli/assembler/assembler-deploy-update-redirects.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -navigation_title: "deploy update-redirects" ---- - -# assembler deploy update-redirects - -Refreshes the redirects mapping in Cloudfront's KeyValueStore - -## Usage - -``` -docs-builder assembler deploy update-redirects [options...] [-h|--help] [--version] -``` - -## Options - -`--environment` `` -: The environment to build (Required) - -`--redirects-file` `` -: Path to the redirects mapping pre-generated by docs-builder assemble (optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-index.md b/docs/cli/assembler/assembler-index.md deleted file mode 100644 index 8ae72ddcda..0000000000 --- a/docs/cli/assembler/assembler-index.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -navigation_title: "index" ---- - -# assembler index - -Index documentation to Elasticsearch, calls `docs-builder assembler build --exporters elasticsearch`. Exposes more options - -## Usage - -``` -docs-builder assembler index [options...] [-h|--help] [--version] -``` - -## Options - -`-es|--endpoint ` -: Elasticsearch endpoint, alternatively set env DOCUMENTATION_ELASTIC_URL (optional) - -`--environment` `` -: The --environment used to clone ends up being part of the index name (optional) - -`--api-key` `` -: Elasticsearch API key, alternatively set env DOCUMENTATION_ELASTIC_APIKEY (optional) - -`--username` `` -: Elasticsearch username (basic auth), alternatively set env DOCUMENTATION_ELASTIC_USERNAME (optional) - -`--password` `` -: Elasticsearch password (basic auth), alternatively set env DOCUMENTATION_ELASTIC_PASSWORD (optional) - -`--search-num-threads` `` -: The number of search threads the inference endpoint should use. Defaults: 8 (optional) - -`--index-num-threads` `` -: The number of index threads the inference endpoint should use. Defaults: 8 (optional) - -`--bootstrap-timeout` `` -: Timeout in minutes for the inference endpoint creation. Defaults: 4 (optional) - -`--index-name-prefix` `` -: The prefix for the computed index/alias names. Defaults: semantic-docs (optional) - -`--force-reindex` `` -: Force reindex strategy to semantic index, by default, we multiplex writes if the semantic index does not exist yet (optional) - -`--buffer-size` `` -: The number of documents to send to ES as part of the bulk. Defaults: 100 (optional) - -`--max-retries` `` -: The number of times failed bulk items should be retried. Defaults: 3 (optional) - -`--debug-mode` `` -: Buffer ES request/responses for better error messages and pass ?pretty to all requests (optional) - -`--proxy-address` `` -: Route requests through a proxy server (optional) - -`--proxy-password` `` -: Proxy server password (optional) - -`--proxy-username` `` -: Proxy server username (optional) - -`--disable-ssl-verification` `` -: Disable SSL certificate validation (EXPERT OPTION) (optional) - -`--certificate-fingerprint` `` -: Pass a self-signed certificate fingerprint to validate the SSL connection (optional) - -`--certificate-path` `` -: Pass a self-signed certificate to validate the SSL connection (optional) - -`--certificate-not-root` `` -: If the certificate is not root but only part of the validation chain pass this (optional) \ No newline at end of file diff --git a/docs/cli/assembler/assembler-navigation-validate-link-reference.md b/docs/cli/assembler/assembler-navigation-validate-link-reference.md deleted file mode 100644 index 678724217e..0000000000 --- a/docs/cli/assembler/assembler-navigation-validate-link-reference.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -navigation_title: "navigation validate-link-reference" ---- - -# assembler navigation validate-link-reference - -Validate all published links in links.json do not collide with navigation path_prefixes and all urls are unique. - -Read more about [navigation](../../configure/site/navigation.md). - -## Usage - -``` -docs-builder assembler navigation validate-link-reference [arguments...] [-h|--help] [--version] -``` - -## Arguments - -`[0] ` -: Path to `links.json` defaults to '.artifacts/docs/html/links.json' \ No newline at end of file diff --git a/docs/cli/assembler/assembler-navigation-validate.md b/docs/cli/assembler/assembler-navigation-validate.md deleted file mode 100644 index 4e897d41e2..0000000000 --- a/docs/cli/assembler/assembler-navigation-validate.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -navigation_title: "navigation validate" ---- - -# assembler navigation validate - -Validates [navigation.yml](../../configure/site/navigation.md) does not contain colliding path prefixes and all urls are unique - -## Usage - -``` -docs-builder assembler navigation validate [-h|--help] [--version] -``` \ No newline at end of file diff --git a/docs/cli/assembler/assembler-serve.md b/docs/cli/assembler/assembler-serve.md deleted file mode 100644 index 805d1b438b..0000000000 --- a/docs/cli/assembler/assembler-serve.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -navigation_title: "serve" ---- - -# assembler serve - -Serve the output of an assembler build on `http://localhost:4000/` - -## Usage - -``` -docs-builder assembler serve [options...] [-h|--help] [--version] -``` - -## Options - -`--port` `` -: Port to serve the documentation. (Default: 4000) - -`--path` `` -: (optional) \ No newline at end of file diff --git a/docs/cli/assembler/index.md b/docs/cli/assembler/index.md deleted file mode 100644 index 6c92bac9e1..0000000000 --- a/docs/cli/assembler/index.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -navigation_title: "assembler" ---- - -# Assembler commands - -Assembler builds bring together all isolated builds and turn them into the overall documentation that gets published. - -If you want to build the latest documentation, you can do so using the following commands - -:::{note} -When assembling using the `config init --local` option, it's advised to create an empty directory to run these commands in. -This creates a dedicated workspace for the assembler build and any local changes that you might want to test. -::: - -```bash -docs-builder assembler config init --local -docs-builder assemble --serve -``` - -The full assembled documentation should now be running at http://localhost:4000. - -The [assemble](assemble.md) command is syntactic sugar over the following commands: - -```bash -docs-builder assembler config init --local -docs-builder assembler clone -docs-builder assembler build -docs-builder assembler serve -``` - -Which may be more appropriate to call in isolation depending on the workflow you are going for. - -All `assembler` commans take an `--environment ` argument that defaults to 'dev' but can be set e.g to 'prod' to -build the production documentation. See [assembler.yml](../../configure/site/index.md) configuration for which environments are -available - -## Build commands - -- [assemble](assemble.md) -- [assembler build](assembler-build.md) -- [assembler clone](assembler-clone.md) -- [assembler config init](assembler-config-init.md) -- [assembler index](assembler-index.md) -- [assembler serve](assembler-serve.md) - -## Specialized build commands - -- [assembler bloom-filter create](assembler-bloom-filter-create.md) -- [assembler bloom-filter lookup](assembler-bloom-filter-lookup.md) - -## Validation commands - -- [assembler content-source match](assembler-content-source-match.md) -- [assembler content-source validate](assembler-content-source-validate.md) -- [assembler navigation validate](assembler-navigation-validate.md) -- [assembler navigation validate-link-reference](assembler-navigation-validate-link-reference.md) - -## Deploy commands - -- [assembler deploy apply](assembler-deploy-apply.md) -- [assembler deploy plan](assembler-deploy-plan.md) -- [assembler deploy update-redirects](assembler-deploy-update-redirects.md) - diff --git a/docs/cli/changelog/add.md b/docs/cli/changelog/add.md deleted file mode 100644 index 710fcb9f2f..0000000000 --- a/docs/cli/changelog/add.md +++ /dev/null @@ -1,240 +0,0 @@ -# changelog add - -Create a changelog file that describes a single item in the release documentation. -For details and examples, go to [](/contribute/create-changelogs.md). - -## Usage - -```sh -docs-builder changelog add [options...] [-h|--help] -``` - -## Options - -`--action ` -: Optional: What users must do to mitigate. -: If the content contains any special characters such as backquotes(`), you must precede it with a backslash escape character (`\`). - -`--areas ` -: Optional: Areas affected (comma-separated or specify multiple times). - -`--concise` -: Optional: Omit schema reference comments from the generated YAML files. Useful in CI workflows to produce compact output. - -`--config ` -: Optional: Path to the changelog.yml configuration file. Defaults to `docs/changelog.yml`. - -`--description ` -: Optional: Additional information about the change (max 600 characters). -: If the content contains any special characters such as backquotes, you must precede it with a backslash escape character (`\`). - -`--no-extract-release-notes` -: Optional: Turn off extraction of release notes from PR or issue descriptions. -: By default, the behavior is determined by the [extract.release_notes](/contribute/configure-changelogs-ref.md#extract) changelog configuration setting. Release notes are extracted when using `--prs` or `--report` (and from issues when using `--issues`). - -`--feature-id ` -: Optional: Feature flag ID - -`--highlight ` -: Optional: Include in release highlights. - -`--impact ` -: Optional: How the user's environment is affected. -: If the content contains any special characters such as backquotes, you must precede it with a backslash escape character (`\`). - -`--issues ` -: Optional: Issue URL(s) or number(s) (comma-separated), or a path to a newline-delimited file containing issue URLs or numbers. Can be specified multiple times. -: Each occurrence can be either comma-separated issues (for example `--issues "https://github.com/owner/repo/issues/123,456"`) or a file path (for example `--issues /path/to/file.txt`). -: When specifying issues directly, provide comma-separated values. -: When specifying a file path, provide a single value that points to a newline-delimited file. -: If `--owner` and `--repo` are provided, issue numbers can be used instead of URLs. -: If specified, `--title` can be derived from the issue. -: Creates one changelog file per issue. -: Mutually exclusive with `--report`. - -`--no-extract-issues` -: Optional: Turn off extraction of linked references. -: When using `--prs` or `--report`: turns off extraction of linked issues from the PR body (for example, "Fixes #123"). -: When using `--issues`: turns off extraction of linked PRs from the issue body (for example, "Fixed by #123"). -: By default, the behavior is determined by the `extract.issues` changelog configuration setting. - -`--output ` -: Optional: Output directory for the changelog fragment. Falls back to `bundle.directory` in `changelog.yml` when not specified. If that value is also absent, defaults to current directory. - -`--owner ` -: Optional: GitHub repository owner (used when `--prs` or `--issues` contains just numbers, or when using `--release-version`). Not required when `--prs` or `--report` supplies only fully-qualified pull request URLs. -: Falls back to `bundle.owner` in `changelog.yml` when not specified. If that value is also absent, defaults to `elastic`. - -`--products >` -: Products affected in format "product target lifecycle, ..." (for example, `"elasticsearch 9.2.0 ga, cloud-serverless 2025-08-05"`). -: The valid product identifiers are listed in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml). -: The valid lifecycles are listed in [ChangelogConfiguration.cs](https://github.com/elastic/docs-builder/blob/main/src/services/Elastic.Documentation.Services/Changelog/ChangelogConfiguration.cs). -: For more information about the valid product and lifecycle values, go to [Product format](#product-format). - -`--prs ` -: Optional: Pull request URLs or numbers (comma-separated), or a path to a newline-delimited file containing PR URLs or numbers. Can be specified multiple times. -: Each occurrence can be either comma-separated PRs (for example `--prs "https://github.com/owner/repo/pull/123,6789"`) or a file path (for example `--prs /path/to/file.txt`). -: When specifying PRs directly, provide comma-separated values. -: When specifying a file path, provide a single value that points to a newline-delimited file. -: If `--owner` and `--repo` are provided, PR numbers can be used instead of URLs. -: If specified, `--title` can be derived from the PR. -: If mappings are configured, `--areas`, `--type`, and `--products` can also be derived from the PR labels. -: Creates one changelog file per PR. -: If there are `rules.create` definitions in the changelog configuration file and a PR has a blocking label for the resolved products, that PR is skipped and no changelog file is created for it. -: Mutually exclusive with `--report`. - -`--report ` -: Optional: URL or path to a promotion report HTML document (for example a Buildkite promotion report). The command extracts GitHub pull request URLs from the HTML and creates one changelog file per PR, using the same parsing rules as [`changelog bundle --report`](/cli/changelog/bundle.md). -: Mutually exclusive with `--prs`, `--issues`, and `--release-version`. -: For a plain newline-delimited list of fully-qualified PR URLs, use `--prs` with a file path instead of `--report`. -: When the value is an `https://` URL, only hosts allowed by the parser (such as `github.com` and `buildkite.com`) are supported, and the CLI needs network access to fetch the report. -`--release-version ` -: Optional: GitHub release tag to use as a source of pull requests (for example, `"v9.2.0"` or `"latest"`). -: When specified, the command fetches the release from GitHub, parses PR references from the release notes, and creates one changelog file per PR — without creating a bundle. Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -: Use `docs-builder changelog gh-release` instead if you also want a bundle. -: Requires `--repo` (or `bundle.repo` in `changelog.yml`). -: Set to `latest` to use the most recent release. -: Mutually exclusive with `--report`, `--prs`, and `--issues`. - -`--repo ` -: Optional: GitHub repository name (used when `--prs`, `--issues`, `--report`, or `--release-version` is specified). Falls back to `bundle.repo` in `changelog.yml` when not specified. - -`--strip-title-prefix` -: Optional: When used with `--prs`, `--issues`, or `--report`, remove square brackets and text within them from the beginning of PR or issue titles, remove a colon if it follows the closing bracket, and remove a single ASCII hyphen when it's immediately after that prefix and followed by whitespace. -: For example, if a PR title is `"[Discover][ESQL]: Fix filtering by multiline string fields"` it becomes `"Fix filtering by multiline string fields"`. -: Likewise `"[Cases] - Enable numerical id service"` becomes `"Enable numerical id service"`. -: When a derived title still begins with `-`, `*`, `+`, an en dash, or an em dash, the emitted YAML uses a quoted `title` value so it is valid and unambiguous. -: This option applies only when the title is derived from GitHub (when `--title` is not explicitly provided). -: By default, the behavior is determined by the `extract.strip_title_prefix` changelog configuration setting (which defaults to `false`). - -`--subtype ` -: Optional: Subtype for breaking changes (for example, `api`, `behavioral`, or `configuration`). -: The valid subtypes are listed in [ChangelogConfiguration.cs](https://github.com/elastic/docs-builder/blob/main/src/services/Elastic.Documentation.Services/Changelog/ChangelogConfiguration.cs). - -`--title ` -: A short, user-facing title (max 80 characters) -: Required if none of `--prs`, `--issues`, or `--report` is specified. -: If both `--prs` and `--title` are specified, the latter value is used instead of what exists in the PR. -: If the content contains any special characters such as backquotes, you must precede it with a backslash escape character (`\`). - -`--type ` -: Required if none of `--prs`, `--issues`, or `--report` is specified. Type of change (for example, `feature`, `enhancement`, `bug-fix`, or `breaking-change`). -: If mappings are configured, type can be derived from the PR or issue. -: The valid types are listed in [ChangelogConfiguration.cs](https://github.com/elastic/docs-builder/blob/main/src/services/Elastic.Documentation.Services/Changelog/ChangelogConfiguration.cs). - -`--use-pr-number` -: Optional: Use PR numbers for filenames instead of the configured `filename` strategy. -: Requires `--prs`, `--issues`, or `--report`. -: Mutually exclusive with `--use-issue-number`. -: Refer to [](#filenames). - -`--use-issue-number` -: Optional: Use issue numbers for filenames instead of the configured `filename` strategy. -: Requires `--prs` or `--issues`. -: Mutually exclusive with `--use-pr-number`. -: Refer to [](#filenames). - -## Filenames - -By default, output files are named according to the `filename` strategy in `changelog.yml`: - -| Strategy | Example filename | Description | -|---|---|---| -| `timestamp` (default) | `1735689600-fixes-enrich-and-lookup-join-resolution.yaml` | Uses a Unix timestamp with a sanitized title slug. | -| `pr` | `137431.yaml` | Uses the PR number. | -| `issue` | `2571.yaml` | Uses the issue number. | - -Refer to [changelog.example.yml](https://github.com/elastic/docs-builder/blob/main/config/changelog.example.yml) or [](/contribute/configure-changelogs-ref.md). - -You can override those settings with the `--use-pr-number` or `--use-issue-number` CLI flags: - -```sh -docs-builder changelog add \ - --prs 1234 \ - --products "elasticsearch 9.2.3" \ - --use-pr-number - -docs-builder changelog add \ - --issues 4567 \ - --products "elasticsearch 9.3.0" \ - --use-issue-number -``` - -:::{important} -`--use-pr-number` and `--use-issue-number` are mutually exclusive; specify only one. `--use-pr-number` requires `--prs`, `--issues`, or `--report`. `--use-issue-number` requires `--prs` or `--issues`. The numbers are extracted from the URLs or identifiers you provide or from linked references in the issue or PR body when extraction is enabled. - -**Precedence**: CLI flags (`--use-pr-number` / `--use-issue-number`) > `filename` in `changelog.yml` > default (`timestamp`). -::: - -## Product format and resolution [product-format] - -The `--products` command option accepts values with the format `"product target lifecycle, ..."` where: - -- `product` is a product ID that exists in [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml) (required) -- `target` is the target version or date (optional) -- `lifecycle` exists in [Lifecycle.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/Lifecycle.cs) (optional) - -You can further limit the possible values with the [products](/contribute/configure-changelogs-ref.md#products) and [lifecycles](/contribute/configure-changelogs-ref.md#lifecycles) options in the changelog configuration file. - -For example: - -- `"kibana 9.2.0 ga"` -- `"cloud-serverless 2025-08-05"` -- `"cloud-enterprise 4.0.3, cloud-hosted 2025-10-31"` - -The `changelog add` command resolves product values in the following order: - -1. The `--products` CLI option always takes priority. -1. If `pivot.products` is defined in the changelog configuration file and the PR or issue has labels that match, those products are used. Multiple matching entries are all applied. -1. If `products.default` is defined in the changelog configuration file, those default products are used. -1. If `--repo` is specified (or `bundle.repo` is set in the changelog configuration file), the repository name is matched against known product IDs in `products.yml` and the derived value is used. - -The same order applies when using `--report` (after PR URLs are resolved from the promotion report), and when using batch `--prs` with multiple pull requests. - -If none of these steps yield at least one product, the command returns an error. - -## Configuration checks - -By default, the command checks the following path for a configuration file: `docs/changelog.yml`. -You can specify a different path with the `--config` command option. - -If a configuration file exists, the command validates its values before generating changelog files: - -- If the configuration file contains `lifecycles`, `products`, `subtype`, or `type` values that don't match the values in `ChangelogEntryType.cs`, `ChangelogEntrySubtype.cs`, or `Lifecycle.cs`, validation fails. -- If the configuration file contains `areas` values and they don't match what you specify in the `--areas` command option, validation fails. -- If the configuration file contains `lifecycles` or `products` values that are a subset of the available values and you try to create a changelog with values outside that subset, validation fails. - -In each of these cases where validation fails, a changelog file is not created. - -If the configuration file contains `rules.create` definitions and a PR or issue has a blocking label, that PR is skipped and no changelog file is created for it. -For more information, refer to [Rules for creation and publishing](/contribute/configure-changelogs.md#rules). - -## CI auto-detection [ci-auto-detection] - -When running inside GitHub Actions, `changelog add` automatically reads the following environment variables to fill in arguments that were not provided on the command line: - -| Environment variable | Fills | Set from | -| --- | --- | --- | -| `CHANGELOG_PR_NUMBER` | `--prs` | `github.event.pull_request.number` | -| `CHANGELOG_TITLE` | `--title` | `steps.evaluate.outputs.title` | -| `CHANGELOG_DESCRIPTION` | `--description` | `steps.evaluate.outputs.description` | -| `CHANGELOG_TYPE` | `--type` | `steps.evaluate.outputs.type` | -| `CHANGELOG_PRODUCTS` | `--products` | `steps.evaluate.outputs.products` | -| `CHANGELOG_OWNER` | `--owner` | `github.repository_owner` | -| `CHANGELOG_REPO` | `--repo` | `github.event.repository.name` | - -**Precedence**: explicit CLI arguments always take priority over environment variables. Environment variables are only used when the corresponding CLI argument is not provided. - -`CHANGELOG_DESCRIPTION` has additional precedence rules related to release note extraction: - -- If `--description` is provided on the command line, it always wins. -- If `--no-extract-release-notes` is passed (or `extract.release_notes: false` is set in the changelog configuration), `CHANGELOG_DESCRIPTION` is ignored. This prevents a description that was extracted by `evaluate-pr` from being applied when extraction has been disabled. -- Otherwise, `CHANGELOG_DESCRIPTION` fills `--description` when it is not set on the command line. - -The filename strategy is controlled by the `filename` option in `changelog.yml` (defaulting to `timestamp`). Refer to [changelog.example.yml](https://github.com/elastic/docs-builder/blob/main/config/changelog.example.yml) for details. - -This allows the CI action to invoke `changelog add` with a minimal command line: - -```sh -docs-builder changelog add --config docs/changelog.yml --output /tmp/staging --concise --strip-title-prefix -``` diff --git a/docs/cli/changelog/bundle-amend.md b/docs/cli/changelog/bundle-amend.md deleted file mode 100644 index 03ccb615b7..0000000000 --- a/docs/cli/changelog/bundle-amend.md +++ /dev/null @@ -1,125 +0,0 @@ -# changelog bundle-amend - -Amend a bundle with additional changelog entries. -Amend bundles follow a specific naming convention: `{parent-bundle-name}.amend-{N}.yaml` where `{N}` is a sequence number. - -To create a bundle, use [](/cli/changelog/bundle.md). -For details and examples, go to [](/contribute/bundle-changelogs.md). - -## Usage - -```sh -docs-builder changelog bundle-amend [arguments...] [options...] [-h|--help] -``` - -## Arguments - -`` -: Required: Path to the original bundle file to amend. - -## Options - -`--add ` -: Required: Path(s) to changelog YAML file(s) to add as comma-separated values. Supports tilde (~) expansion and relative paths. - -`--no-resolve`: -: Optional: Explicitly turn off resolve (overrides inference from original bundle). - -`--resolve` -: Optional: Copy the contents of each changelog file into the entries array. Defaults to false. - -## Resolve behaviour - -By default, the `bundle-amend` command **infers** whether to resolve entries from the original bundle. -If the original bundle contains resolved entries (with inline `title`, `type`, and so on), the amend file will also be resolved. -If the original bundle contains only file references, the amend file will also contain only file references. - -This inference ensures that amend files are portable—they contain everything needed to be understood alongside the original bundle, even when copied to another repository. - -You can override this behaviour: - -- `--resolve`: Force entries to be resolved (inline content), regardless of the original bundle. -- `--no-resolve`: Force entries to contain only file references, regardless of the original bundle. - -## Output - -Amend bundles contain only the additional entries, they are not a full repetition of the original bundle. -For example: - -```yaml -# 9.3.0.amend-1.yaml -entries: -- file: - name: late-addition.yaml - checksum: abc123def456 -``` - -When bundles are loaded (either via the `changelog render` command or the `{changelog}` directive), amend files are **automatically merged** with their parent bundles. -The entries from all matching amend files are combined with the parent bundle's entries, and the result is rendered as a single release. - -:::{note} -Amend bundles do not need to include `products` or `hide-features` fields—they inherit these from their parent bundle. If an amend bundle is found without a matching parent bundle, it remains standalone. - -`rules.bundle` filtering does not apply to `changelog bundle-amend`. The command is designed as a direct-injection escape hatch: the files you specify with `--add` are always included regardless of any product, type, or area filter configuration. Filtering only applies during the initial `changelog bundle` or `changelog gh-release` run. -::: -## Examples - -### Add a single changelog to a bundle - -```sh -docs-builder changelog bundle-amend \ - ./docs/changelog/bundles/9.3.0.yaml \ - --add ./docs/changelog/138723.yaml -``` - -The new bundle automatically matches the resolve style of the original bundle. - -### Add multiple changelogs to a bundle - -Specify multiple files as comma-separated values: - -```sh -docs-builder changelog bundle-amend \ - ./docs/changelog/bundles/9.3.0.yaml \ - --add "./docs/changelog/138723.yaml,./docs/changelog/1770424335.yaml" -``` - -### Using different path styles - -The command supports tilde expansion, relative paths, and absolute paths: - -```sh -# With tilde expansion -docs-builder changelog bundle-amend \ - ~/docs/changelog/bundles/9.3.0.yaml \ - --add "~/docs/changelog/138723.yaml,~/docs/changelog/1770424335.yaml" - -# With relative paths -docs-builder changelog bundle-amend \ - ./bundles/9.3.0.yaml \ - --add "./138723.yaml,./1770424335.yaml" - -# With absolute paths -docs-builder changelog bundle-amend \ - /path/to/bundles/9.3.0.yaml \ - --add "/path/to/138723.yaml,/path/to/1770424335.yaml" -``` - -### Resolving changelog contents - -Use `--resolve` to copy the full contents of each changelog file into the new bundle even if the original bundle is unresolved: - -```sh -docs-builder changelog bundle-amend \ - ./docs/changelog/bundles/9.3.0.yaml \ - --add "./docs/changelog/138723.yaml,./docs/changelog/1770424335.yaml" \ - --resolve -``` - -Likewise, you can force file-only references even if the original bundle is resolved: - -```sh -docs-builder changelog bundle-amend 9.3.0.yaml \ - --add ./docs/changelog/late-addition.yaml \ - --no-resolve -``` diff --git a/docs/cli/changelog/bundle.md b/docs/cli/changelog/bundle.md deleted file mode 100644 index ec73417875..0000000000 --- a/docs/cli/changelog/bundle.md +++ /dev/null @@ -1,686 +0,0 @@ -# changelog bundle - -Bundle changelog files. - -To create the changelogs, use [](/cli/changelog/add.md). -For details and examples, go to [](/contribute/changelog.md). - -## Usage - -```sh -docs-builder changelog bundle [arguments...] [options...] [-h|--help] -``` - -`changelog bundle` supports two mutually exclusive invocation modes: - -- **Profile-based**: All paths and filters come from the changelog configuration file. No other options are allowed. For example, `bundle `. -- **Option-based**: You supply all filter and output options directly. For example, `bundle --all` (or `--input-products`, `--prs`, `--issues`). - -You cannot mix the two modes. Passing any option-based flag together with a profile returns an error. - -Profile-based commands discover the changelog configuration automatically (no `--config` flag): they look for `changelog.yml` in the current directory, then `docs/changelog.yml`. -If neither file is found, the command returns an error with instructions to run `docs-builder changelog init` or to re-run from the folder where the file exists. - -Option-based commands ignore the `bundle.profiles` section of the changelog configuration file. - -## Arguments - -These arguments apply to profile-based bundling: - -`[0] ` -: Profile name from `bundle.profiles` in the changelog configuration file. -: For example, "elasticsearch-release". -: When specified, the second argument is the version, promotion report URL, or URL list file. - -`[1] ` -: Version number, promotion report URL/path, or URL list file. -: For example, `9.2.0`, `https://buildkite.../promotion-report.html`, or `/path/to/prs.txt`. - -`[2] ` -: Optional: Promotion report URL/path or URL list file when the second argument is a version string. -: When provided, `[1]` must be a version string and `[2]` is the PR/issue filter source. -: For example, `docs-builder changelog bundle serverless-release 2026-02 ./promotion-report.html`. - -:::{note} -The third argument (`[2]`) is required when your profile uses `{version}` placeholders in `output` or `output_products` patterns and you also want to filter by a promotion report or URL list. Without it, the version defaults to `"unknown"`. -::: - -### Profile argument types - -The second argument (`[1]`) and optional third argument (`[2]`) accept the following: - -- **Version string** — Used for `{version}` substitution in profile patterns. For example, `9.2.0` or `2026-02`. -- **Promotion report URL** — A URL to an HTML promotion report. PR URLs are extracted from it. -- **Promotion report file** — A path to a downloaded `.html` file containing a promotion report. -- **URL list file** — A path to a plain-text file containing one fully-qualified GitHub PR or issue URL per line. For example, `https://github.com/elastic/elasticsearch/pull/123`. The file must contain only PR URLs or only issue URLs, not a mix. Bare numbers and short forms such as `owner/repo#123` are not allowed. - -## General options - -These options work with both profile-based and option-based modes. - -`--plan` -: Output a structured set of CI step outputs (`needs_network`, `needs_github_token`, `output_path`) describing Docker flags, network requirements, and the resolved output path, then exit without generating the bundle. Intended for CI actions that need to determine container configuration before running the actual bundle step. When running outside GitHub Actions, the output is written to stdout. - -## Options - -The following options are only valid in option-based mode (no profile argument). -Using any of them with a profile returns an error. -You must choose one method for determining what's in the bundle (`--all`, `--input-products`, `--prs`, `--issues`, `--release-version`, or `--report`). - -`--all` -: Include all changelogs from the directory. - -`--config ` -: Optional: Path to the changelog.yml configuration file. -: Defaults to `docs/changelog.yml`. - -`--directory ` -: Optional: The directory that contains the changelog YAML files. -: When not specified, falls back to `bundle.directory` from the changelog configuration, then the current working directory. See [Output files](#output-files) for the full resolution order. - -`--description ` -: Optional: Bundle description text with placeholder support. -: Supports `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` placeholders. Overrides `bundle.description` from config. -: When using `{version}` or `{lifecycle}` placeholders, predictable substitution values are required: -: - **Option-based mode**: Requires `--output-products` to be explicitly specified -: - **Profile-based mode**: Requires either a version argument OR `output_products` in the profile configuration - -`--hide-features ` -: Optional: A list of feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. -: Can be specified multiple times. -: Adds a `hide-features` list to the bundle. -: When the bundle is rendered (by the `changelog render` command or `{changelog}` directive), changelogs with matching `feature-id` values will be commented out of the documentation. - -`--input-products ?>` -: Filter by products in the format "product target lifecycle, ...". -: For more information about the valid product and lifecycle values, go to [Product format](#product-format). -: When specified, all three parts (product, target, lifecycle) are required but can be wildcards (`*`). Multiple comma-separated values are combined with OR: a changelog is included if it matches any of the specified product/target/lifecycle combinations. For example: - -- `"cloud-serverless 2025-12-02 ga, cloud-serverless 2025-12-06 beta"` — include changelogs for either cloud-serverless 2025-12-02 ga or cloud-serverless 2025-12-06 beta -- `"cloud-serverless 2025-12-02 *"` - match cloud-serverless 2025-12-02 with any lifecycle -- `"elasticsearch * *"` - match all elasticsearch changelogs -- `"* 9.3.* *"` - match any product with target starting with "9.3." -- `"* * *"` - match all changelogs (equivalent to `--all`) - -:::{note} -The `--input-products` option determines which changelog files are gathered for consideration. **`rules.bundle` is not disabled** when you use `--input-products` — global `include_products` / `exclude_products`, type/area rules, and (when configured) per-product rules still run **after** matching, unless your configuration is in no-filtering mode per [Bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle). The only “mutually exclusive” pairing on this command is **profile-based** bundling versus **option-based** flags (see [Usage](#usage)), not `--input-products` versus `rules.bundle`. -::: - -`--issues ` -: Filter by issue URLs (comma-separated), or a path to a newline-delimited file. Can be specified multiple times. -: Each occurrence can be either comma-separated issues ( `--issues "https://github.com/owner/repo/issues/123,456"`) or a file path (for example `--issues /path/to/file.txt`). -: When using a file, every line must be a fully-qualified GitHub issue URL such as `https://github.com/owner/repo/issues/123`. Bare numbers and short forms are not allowed in files. - -`--no-resolve` -: Optional: Explicitly turn off the `resolve` option if it's specified in the changelog configuration file. - -`--output ` -: Optional: The output path for the bundle. -: Can be either (1) a directory path, in which case `changelog-bundle.yaml` is created in that directory, or (2) a file path ending in `.yml` or `.yaml`. -: When not specified, falls back to `bundle.output_directory` from the changelog configuration, then the input directory (which is itself resolved from `--directory`, `bundle.directory`, or the current working directory). See [Output files](#output-files) for the full resolution order. - -`--output-products ?>` -: Optional: Explicitly set the products array in the output file in format "product target lifecycle, ...". -: This value replaces information that would otherwise be derived from changelogs. -: For more information about the valid product and lifecycle values, go to [Product format](#product-format). -: When `rules.bundle.products` per-product overrides are configured, `--output-products` also supplies the product IDs used to choose the **rule context product** (first alphabetically) for Mode 3. To use a different product's rules, run a separate bundle with only that product in `--output-products`. For details, refer to [Product-specific bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle-products). - -`--no-release-date` -: Optional: Skip auto-population of release date in the bundle. -: By default, bundles are created with a `release-date` field set to today's date (UTC) or the GitHub release published date when using `--release-version`. -: Mutually exclusive with `--release-date`. -: **Not available in profile mode** — use bundle configuration instead. - -`--release-date ` -: Optional: Explicit release date for the bundle in YYYY-MM-DD format. -: Overrides the default auto-population behavior (today's date or GitHub release published date). -: Mutually exclusive with `--no-release-date`. -: **Not available in profile mode** — use bundle configuration instead. - -`--owner ` -: Optional: The GitHub repository owner, required when pull requests or issues are specified as numbers. -: Precedence: `--owner` flag > `bundle.owner` in `changelog.yml` > `elastic`. - -`--prs ` -: Filter by pull request URLs (comma-separated) or a path to a newline-delimited file. Can be specified multiple times. -: Each occurrence can be either comma-separated PRs (for example `--prs "https://github.com/owner/repo/pull/123,6789"`) or a file path (for example `--prs /path/to/file.txt`). -: When using a file, every line must be a fully-qualified GitHub PR URL such as `https://github.com/owner/repo/pull/123`. Bare numbers and short forms are not allowed in files. - -`--release-version ` -: GitHub release tag to use as a source of pull requests (for example, `"v9.2.0"` or `"latest"`). -: When specified, the command fetches the release from GitHub, parses PR references from the release notes, and uses them as the bundle filter. Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -: Requires repo (`--repo` or `bundle.repo` in `changelog.yml`) and owner (`--owner` flag > `bundle.owner` in `changelog.yml` > `elastic`) details. -: When `--output-products` is not specified, the products array in the bundle is derived from the matched changelog files' own `products` fields, consistent with all other filter options. - -`--repo ` -: Optional: The GitHub repository name. -: Falls back to `bundle.repo` in `changelog.yml` when not specified; if that is also absent, the product ID is used. - -`--report ` -: Filter by pull requests extracted from a promotion report. Accepts a URL or a local file path. -: The report can be an HTML page from Buildkite or any file containing GitHub PR URLs. - -`--resolve` -: Optional: Copy the contents of each changelog file into the entries array. -: By default, the bundle contains only the file names and checksums. - -## Output files - -Both modes use the same ordered fallback to determine where to write the bundle. The first value that is set wins: - -**Output directory** (where the bundle file is placed): - -| Priority | Profile-based | Option-based | -|----------|---------------|--------------| -| 1 | — | `--output` (explicit file or directory path) | -| 2 | `bundle.output_directory` in `changelog.yml` | `bundle.output_directory` in `changelog.yml` | -| 3 | `bundle.directory` in `changelog.yml` | `--directory` CLI option | -| 4 | Current working directory | `bundle.directory` in `changelog.yml` | -| 5 | — | Current working directory | - -**Input directory** (where changelog YAML files are read from) follows the same fallback for both modes, minus the explicit CLI override that is forbidden in profile mode: - -| Priority | Profile-based | Option-based | -|----------|---------------|--------------| -| 1 | `bundle.directory` in `changelog.yml` | `--directory` CLI option | -| 2 | Current working directory | `bundle.directory` in `changelog.yml` | -| 3 | — | Current working directory | - -**Bundle filename** is determined by the `bundle.profiles..output` setting (profile-based) or defaults to `changelog-bundle.yaml` (both modes). -The profile `output` setting can include additional path segments. For example: `"stack/kibana-{version}.yaml"`. - -In option-based mode, when you specify `--output`, it supports two formats: - -1. **Directory path**: If you specify a directory path (without a filename), the command creates `changelog-bundle.yaml` in that directory: - - ```sh - docs-builder changelog bundle --all --output /path/to/output/dir - # Creates /path/to/output/dir/changelog-bundle.yaml - ``` - -2. **File path**: If you specify a file path ending in `.yml` or `.yaml`, the command uses that exact path: - - ```sh - docs-builder changelog bundle --all --output /path/to/custom-bundle.yaml - # Creates /path/to/custom-bundle.yaml - ``` - -If you specify a file path with a different extension (not `.yml` or `.yaml`), the command returns an error. - -:::{note} -"Current working directory" means the directory you are in when you run the command (`pwd`). -Setting `bundle.directory` and `bundle.output_directory` in `changelog.yml` is recommended so you don't need to rely on running the command from a specific directory. -::: - -## Product format - -The `changelog bundle` command has `--input-products` and `--output-products` options that accept values with the format `"product target lifecycle, ..."` where: - -- `product` is the product ID from [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml) (required) -- `target` is the target version or date (optional) -- `lifecycle` exists in [Lifecycle.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/Lifecycle.cs) (optional) - -You can further limit the possible values with the [products](/contribute/configure-changelogs-ref.md#products) and [lifecycles](/contribute/configure-changelogs-ref.md#lifecycles) options in the changelog configuration file. - -For example: - -- `"kibana 9.2.0 ga"` -- `"cloud-serverless 2025-08-05"` -- `"cloud-enterprise 4.0.3, cloud-hosted 2025-10-31"` - -## Repository name in bundles [changelog-bundle-repo] - -The repository name is stored in each bundle product entry to ensure that PR and issue links are generated correctly when the bundle is rendered. -It can be set in three ways, in order of precedence: - -1. **`--repo` option** (option-based mode only) -2. **`repo` field in the profile** (profile-based mode only; overrides the bundle-level default) -3. **`bundle.repo` in `changelog.yml`** (applies to both modes as a default when neither of the above is set) - -Setting `bundle.repo` and `bundle.owner` in your configuration means you rarely need to pass `--repo` and `--owner` on the command line: - -```yaml -bundle: - repo: elasticsearch - owner: elastic -``` - -You can still override them per profile if a project has multiple products with different repos. - -The bundle output includes a `repo` field in each product: - -```yaml -products: -- product: cloud-serverless - target: 2025-12-02 - repo: elasticsearch - owner: elastic -entries: -- file: - name: 1765495972-new-feature.yaml - checksum: 6c3243f56279b1797b5dfff6c02ebf90b9658464 -``` - -When rendering, pull request and issue links use `https://github.com/elastic/elasticsearch/...` instead of the product ID. - -:::{note} -If no `repo` is set at any level, the product ID is used as a fallback for link generation. -This may result in broken links if the product ID doesn't match the GitHub repository name (for example, `cloud-serverless` product ID in the `elasticsearch` repo). -::: - -## Rules for filtered bundles [changelog-bundle-rules] - -The `rules.bundle` section in the changelog configuration file lets you filter entries during bundling. It applies to both `changelog bundle` and `changelog gh-release`, after entries are matched by the primary filter (`--prs`, `--issues`, `--all`, **`--input-products`**, and so on) and before the bundle is written. - -Which `rules.bundle` fields take effect depends on the bundle rule modes (no filtering, global rules against each changelog’s content, or per-product rule context). Input stage (gathering entries) and bundle filtering stage (filtering for output) are conceptually separate. For more information, refer to [bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle). - -The following fields are supported: - -`exclude_products` -: A product ID or list of product IDs to exclude from the bundle. Cannot be combined with `include_products`. - -`include_products` -: A product ID or list of product IDs to include in the bundle (all others are excluded). Cannot be combined with `exclude_products`. - -`match_products` -: Match mode for the product filter (`any`, `all`, or `conjunction`). Inherits from `rules.match` when not specified. - -`exclude_types` -: A changelog type or list of types to exclude from the bundle. - -`include_types` -: Only changelogs with these types are kept; all others are excluded. - -`exclude_areas` -: A changelog area or list of areas to exclude from the bundle. - -`include_areas` -: Only changelogs with these areas are kept; all others are excluded. - -`match_areas` -: Match mode for the area filter (`any`, `all`, or `conjunction`). Inherits from `rules.match` when not specified. - -`products` -: Per-product filter overrides for **all filter types** (product, type, area). Keys are product IDs (or comma-separated lists). -: When this map is **non-empty**, the bundler uses **per-product rule context** mode: global `rules.bundle` product/type/area fields are **not** used for filtering (repeat constraints under each product key if you still need them). -: For details, refer to [Bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle). - -```yaml -rules: - bundle: - exclude_products: cloud-enterprise - exclude_types: deprecation - exclude_areas: - - Internal - products: - cloud-serverless: - include_areas: - - "Search" - - "Monitoring" -``` - -## PR and issue link allowlist [link-allowlist] - -A changelog in a public repository might contain links to pull requests or issues in repositories that should not appear in published documentation. - -Set `bundle.link_allow_repos` in `changelog.yml` to an explicit list of `owner/repo` strings (for example, `elastic/elasticsearch`). When this key is present (including as an empty list), PR and issue references are filtered at bundle time: only links whose resolved repository is in the list are kept; others are rewritten to quoted `# PRIVATE:` sentinel strings in the bundle YAML. - -:::{important} -`bundle.link_allow_repos` requires a **resolved** bundle. Set `bundle.resolve: true` or pass `--resolve`. Unresolved bundles that only store `file:` pointers are not rewritten. -::: - -When [`assembler.yml`](/configure/site/content.md) is available, docs-builder emits **warnings** (non-fatal) if an allowlisted repo is missing from `references` or is marked `private: true`, so you can verify the registry before publishing. - -The `changelog bundle`, `changelog gh-release`, and `changelog bundle-amend` commands apply the same rules. The changelog directive and `changelog render` command omit `# PRIVATE:` sentinels from rendered documentation. - -:::{warning} -Sentinel values are omitted from rendered documentation but remain in bundle files; they are not cryptographic redaction. -::: - -`bundle.repo` must name a **single** GitHub repository (do not use `repo1+repo2` merged-repo syntax). - -## Option-based examples - -### Bundle by report or URL list - -You can use `--report` to filter by a promotion report: - -```sh -# Extract PRs from a downloaded report and use them as the filter -docs-builder changelog bundle \ - --report ./promotion-report.html \ - --directory ./docs/changelog \ - --output ./docs/releases/bundle.yaml -``` - -By default all changelogs that match PRs in the promotion report are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules). - -### Bundle by GitHub release [changelog-bundle-release-version] - -You can use `--release-version` to fetch pull request references directly from GitHub release notes and use them as the bundle filter. -This is equivalent to building a PR list file manually and passing it with `--prs`, but without any file management. - -:::{important} -Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -::: - -```sh -docs-builder changelog bundle \ - --release-version v1.34.0 \ - --repo apm-agent-dotnet \ <1> - --owner elastic <2> -``` - -1. You must specify `--repo` or set `bundle.repo` in the changelog configuration file. -2. If you don't specify `--owner`, it uses `bundle.owner` in the changelog configuration or else defaults to `elastic`. - -Without `--output-products`, the products array in the bundle is derived from the matched changelog files' own `products` fields — the same behavior as `--prs`, `--issues`, `--report`, and `--all`. -Use `--output-products` when you need a single, authoritative product entry that reflects the release identity rather than the diverse metadata across individual changelog files. -For example: - -```sh -docs-builder changelog bundle \ - --release-version v1.34.0 \ - --output-products "apm-agent-dotnet 1.34.0 ga" -``` - -:::{note} -`--release-version` requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable (or an active `gh` login) to fetch release details from the GitHub API. -::: - -By default all changelogs that match PRs in the GitHub release notes are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules). - -### Bundle with description - -You can add a description to bundles using the `--description` option. For simple descriptions, use regular quotes: - -```sh -docs-builder changelog bundle \ - --all \ - --description "This release includes new features and bug fixes." -``` - -For multiline descriptions with multiple paragraphs, lists, and links, use ANSI-C quoting (`$'...'`) with `\n` for line breaks: - -```sh -docs-builder changelog bundle \ - --all \ - --description $'This release includes significant improvements:\n\n- Enhanced performance\n- Bug fixes and stability improvements\n\nFor security updates, go to [security announcements](https://example.com/docs).' -``` - -When using placeholders in option-based mode, you must explicitly specify `--output-products` for predictable substitution: - -```sh -docs-builder changelog bundle \ - --all \ - --output-products "elasticsearch 9.1.0 ga" \ - --description "Elasticsearch {version} includes performance improvements. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}" -``` - -### Bundle with release date - -You can add a `release-date` field directly to a bundle YAML file. This field is optional and purely informative for end-users. It is especially useful for components released outside the usual stack lifecycle, such as APM agents and EDOT agents. - -```yaml -products: - - product: apm-agent-dotnet - target: 1.34.0 -release-date: "April 9, 2026" -description: | - This release includes tracing improvements and bug fixes. -entries: - - file: - name: tracing-improvement.yaml - checksum: abc123 -``` - -When the bundle is rendered (by the `changelog render` command or `{changelog}` directive), the release date appears immediately after the version heading as italicized text: `_Released: April 9, 2026_`. - -## Profile-based examples - -When the changelog configuration file defines `bundle.profiles`, you can use those profiles with the `changelog bundle` command. - -### Profile configuration fields [changelog-bundle-profile-config] - -If you're using profile-based commands, they're affected by the following fields in the `bundle.profiles` section of the changelog configuration file: - -`source` -: Optional. When set to `github_release`, the PR list is fetched automatically from the GitHub release identified by the version argument. Requires `repo` to be set at the profile or `bundle` level. Mutually exclusive with `products`. -: Example: `source: github_release` - -`products` -: Required when filtering by product metadata (equivalent to the `--input-products` command option). -: The value `"* * *"` is equivalent to the `--all` command option. -: Not used when the filter comes from a promotion report, URL list file, or `source: github_release` — in those cases the PR or issue list determines what's included and `products` is ignored. -: Supports `{version}` and `{lifecycle}` placeholders that are substituted at runtime. -: Example: `"elasticsearch {version} {lifecycle}"` -: Refer to [](#product-format). - -:::{note} -The `products` field determines which changelog files are gathered for consideration. **`rules.bundle` still applies** afterward (see the note under [`--input-products`](#options)). Input stage and bundle filtering stage are conceptually separate. -::: - -`output` -: Optional. The output filename pattern for the bundle file. Supports `{version}` and `{lifecycle}` placeholders. -: When not set, the output path falls back in order to: `bundle.output_directory/changelog-bundle.yaml` (if `bundle.output_directory` is configured), then `changelog-bundle.yaml` in the input directory. -: Setting this is recommended so each profile produces a distinctly named file rather than overwriting the default. -: Example: `"elasticsearch-{version}.yaml"` - -`output_products` -: Optional. Overrides the products array written to the bundle output. Supports `{version}` and `{lifecycle}` placeholders. -: When **not set**, the products array is derived from the individual changelog files matched by the filter. This often produces multiple product entries (one per unique product/target/lifecycle combination across all matched files), which may not reflect a single clean release identity. -: When **set**, the products array in the bundle is exactly the value you specify, replacing anything that would be derived from the matched changelogs. Use this to publish a single, authoritative product entry with a specific version and lifecycle. -: The `{lifecycle}` placeholder is substituted at runtime with the inferred lifecycle. For `source: github_release` profiles this comes from the release tag suffix. For standard profiles it comes from the version argument. Refer to [](#changelog-bundle-standard-profile-lifecycle) and [](#changelog-bundle-github-release-profile) for details. -: If you omit lifecycle from the pattern (for example, `"elasticsearch {version}"`), the lifecycle field is omitted from the products array entirely. -: Example: `"elasticsearch {version} {lifecycle}"` or `"elasticsearch {version} ga"` to hardcode GA regardless of tag. -: Refer to [](#product-format). - -`repo` -: Optional. The GitHub repository name written to each product entry in the bundle. Used by the `{changelog}` directive to generate correct PR/issue links. Only needed when the product ID doesn't match the GitHub repository name. Overrides `bundle.repo` when set. Required when `source: github_release` is used and no `bundle.repo` default is set. -: Example: `repo: elasticsearch`. - -`owner` -: Optional. The GitHub owner written to each product entry in the bundle. Overrides `bundle.owner` when set. -: Example: `owner: elastic` - -`hide_features` -: Optional. Feature IDs to mark as hidden in the bundle output (string or list). When the bundle is rendered, entries with matching `feature-id` values are commented out. - -### Lifecycle inference for standard profiles [changelog-bundle-standard-profile-lifecycle] - -If your configuration file defines a standard profile (that is to say, not a GitHub release profile), the lifecycle is inferred from the version string you pass as the second argument: - -| Version argument | Inferred lifecycle | -|------------------|--------------------| -| `9.2.0` | `ga` | -| `9.2.0-rc.1` | `ga` | -| `9.2.0-beta.1` | `beta` | -| `9.2.0-alpha.1` | `preview` | -| `9.2.0-preview.1` | `preview` | - -For more information about acceptable product and lifecycle values, go to [Product format](#product-format). - -You can invoke those profiles with commands like this: - -```sh -# Bundle changelogs for a GA release ({lifecycle} → "ga" inferred from "9.2.0") -docs-builder changelog bundle elasticsearch-release 9.2.0 - -# Bundle changelogs for a beta release ({lifecycle} → "beta" inferred from "9.2.0-beta.1") -docs-builder changelog bundle elasticsearch-release 9.2.0-beta.1 - -# Bundle changelogs with partial dates -docs-builder changelog bundle serverless-monthly 2026-02 - -# Bundle changelogs that match a list of PRs in a downloaded promotion report -# (version used for {version} substitution; report used as PR filter) -docs-builder changelog bundle serverless-report 2026-02-13 ./promotion-report.html - -# Same using a URL list file instead of an HTML promotion report -docs-builder changelog bundle serverless-report 2026-02-13 ./prs.txt - -# Bundle changelogs using the PR list from a GitHub release (source: github_release) -docs-builder changelog bundle elasticsearch-gh-release 9.2.0 - -# Use "latest" to fetch the most recent release -docs-builder changelog bundle elasticsearch-gh-release latest -``` - -:::{warning} -**Placeholder validation**: If your profile uses `{version}` or `{lifecycle}` placeholders in the description, you must ensure predictable substitution values: - -```sh -# ✅ Good: Version provided for placeholder substitution -docs-builder changelog bundle elasticsearch-release 9.2.0 ./report.html - -# ❌ Bad: No version, placeholders will fail unless profile has output_products -docs-builder changelog bundle elasticsearch-release ./report.html -``` - -To fix the second case, either provide a version argument or add an `output_products` pattern to your profile: - -```yaml -bundle: - profiles: - elasticsearch-release: - products: "elasticsearch * *" - output_products: "elasticsearch {version}" # Enables placeholder substitution - description: "Download: https://github.com/{owner}/{repo}/releases/tag/v{version}" -``` -::: - -### Bundle by product - -You can create profiles that are equivalent to the `--input-products` filter option, that is to say the bundle will contain only changelogs with matching `products`. -For example: - -```yaml -bundle: - # Input directory containing changelog YAML files - directory: docs/changelog - # Output directory for bundles - output_directory: docs/releases - # Whether to resolve (copy contents) by default - resolve: true - repo: elasticsearch <1> - owner: elastic - profiles: - # Collect all changelogs - release-all: - products: "* * *" <2> - output: "all.yaml" - # Find changelogs with any lifecycle and a partial date - serverless-monthly: - products: "cloud-serverless {version}-* *" <3> - output: "serverless-{version}.yaml" - output_products: "cloud-serverless {version}" - - # Find changelogs with a specific lifecycle - elasticsearch-ga-only: - products: "elasticsearch {version} ga" <4> - output: "elasticsearch-{version}.yaml" - - # Infer the lifecycle from the version - elasticsearch-release: - hide_features: <5> - - feature-flag-1 - - feature-flag-2 - products: "elasticsearch {version} {lifecycle}" <6> - output: "elasticsearch-{version}.yaml" - output_products: "elasticsearch {version}" -``` - -1. Bundle-level defaults that apply to all profiles. Individual profiles can override these. -2. Collects all changelogs from the `directory`. This is equivalent to the `--all` command. -3. Collects any changelogs that have `product: cloud-serverless`, any lifecycle, and the date partially specified in the command. This is equivalent to the `--input-products` command option's support for wildcards. -4. Collects any changelogs that have `product: elasticsearch`, `lifecycle: ga`, and the version specified in the command. -5. Adds a `hide-features` array in the bundle. This is equivalent to the `--hide-features` command option. -6. In this case, the lifecycle is inferred from the version string passed as the second command argument (for example, `9.2.0-beta.1` → `beta`). - -`output_products: "elasticsearch {version} {lifecycle}"` produces a single, authoritative product entry in the bundle derived from the release tag — for example, tag `v9.2.0` gives `elasticsearch 9.2.0 ga` and tag `v9.2.0-beta.1` gives `elasticsearch 9.2.0 beta`. Without `output_products`, the bundle products array is instead derived from the matched changelog files' own `products` fields, which is the consistent fallback for all profile types. Set `output_products` when you need a single clean product entry that reflects the release identity rather than the diverse metadata across individual changelog files. - -:::{note} -The `products` field determines which changelog files are gathered for consideration. **`rules.bundle` still applies** afterward (see the note under [`--input-products`](#options)). Input stage and bundle filtering stage are conceptually separate. -::: - -For profiles that use static patterns (without `{version}` or `{lifecycle}` placeholders), the second argument is still required but serves no functional purpose. You can pass any placeholder value. For example: - -```sh -# Profile with static patterns - second argument unused but required -docs-builder changelog bundle release-all '*' -docs-builder changelog bundle release-all 'unused' -docs-builder changelog bundle release-all 'none' -``` - -If you are using the `{version}` placeholder in the `output_products` or `output` fields, you must provide an appropriate value even though it's not used by the `products` filter. - -### Bundle by report or URL list [profile-bundle-report-examples] - -You can also create profiles that are equivalent to the `--prs`, `--issues`, and `--report` filter options. -That is to say you can create bundles that contain only changelogs with matching `prs` or `issues`. -For example: - -```yaml -bundle: - repo: elasticsearch <1> - owner: elastic - profiles: - # Find changelogs that match a list of PRs - serverless-report: <2> - output: "serverless-{version}.yaml" - output_products: "cloud-serverless {version}" -``` - -1. Bundle-level defaults that apply to all profiles. Individual profiles can override these. -2. If a profile is intended for use with a promotion report or a newline delimited file that lists the issues or pull requests, it does not need a `products` filter. If the `output` and `output_products` are omitted, the default path and file names are used. This example shows how you can use a `{version}` variable to customize the bundle's filename and product metadata. - -By default all changelogs that match PRs or issues in the list or report are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules). - -### Bundle by GitHub release profiles [changelog-bundle-github-release-profile] - -To make bundling by GitHub release more easily repeatable, create a profile with `source: github_release` in your changelog configuration file. -For example: - -```yaml -bundle: - profiles: - # Fetch the PR list directly from a GitHub release - agent-gh-release: - source: github_release <1> - repo: apm-agent-dotnet <2> - output: "agent-{version}.yaml" - output_products: "apm-agent-dotnet {version} {lifecycle}" -``` - -1. Instead of filtering pre-existing changelog files by product, this profile fetches the PR list from the GitHub release notes for the given version. Mutually exclusive with `products`. -2. The repository to fetch the release from. Overrides `bundle.repo` for this profile. - -For `source: github_release` profiles, the `{lifecycle}` placeholder in `output` and `output_products` is inferred from the **release tag** returned by GitHub (not the argument you pass to the command). -This means the pre-release suffix on the tag drives the lifecycle value: - -| Release tag | `{version}` | `{lifecycle}` | -|-------------|-------------|---------------| -| `v9.2.0` | `9.2.0` | `ga` | -| `v9.2.0-beta.1` | `9.2.0` | `beta` | -| `v9.2.0-preview.1` | `9.2.0` | `preview` | -| `v1.34.1` | `1.34.1` | `ga` | -| `v1.34.1-preview.1` | `1.34.1` | `preview` | - -This differs from standard profiles, where lifecycle is inferred from the version argument you type. For `source: github_release`, the `{version}` placeholder always uses the clean base version (stripped of any pre-release suffix), while `{lifecycle}` reflects the actual tag format. - -If the lifecycle you want to advertise cannot be inferred from the tag format — for example, because your team uses clean tags like `v1.34.1` even for pre-releases — hardcode the lifecycle directly in `output_products` instead of using the `{lifecycle}` placeholder: - -```yaml -# Instead of relying on {lifecycle} inference, hardcode the lifecycle -gh-release: - source: github_release - repo: apm-agent-dotnet - output: "apm-agent-dotnet-{version}.yaml" - output_products: "apm-agent-dotnet {version} preview" -``` - -By default all changelogs that match PRs in the GitHub release notes are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules). diff --git a/docs/cli/changelog/cmd-bundle.md b/docs/cli/changelog/cmd-bundle.md new file mode 100644 index 0000000000..d4b612aa24 --- /dev/null +++ b/docs/cli/changelog/cmd-bundle.md @@ -0,0 +1,84 @@ +Aggregates changelog YAML files matching a filter into a single bundle file. The bundle is the artifact used by the `{changelog}` directive and `docs-builder changelog render` to produce release notes. + +The command has **two mutually exclusive modes**. You cannot mix them: supplying a profile name on the command line disables all filter and output flags. + +## Profile-based mode + +Define reusable profiles in `changelog.yml` and invoke by name. This is the recommended approach for release workflows because the filter, output path, and product metadata are all captured in configuration and don't need to be specified on the command line. + +```sh +# Bundle using a named profile (version inferred for {lifecycle} placeholder) +docs-builder changelog bundle elasticsearch-release 9.2.0 + +# Bundle using a profile with a promotion report as the filter source +docs-builder changelog bundle elasticsearch-release 9.2.0 ./promotion-report.html +``` + +The second positional argument accepts: +- A version string (e.g. `9.2.0`, `9.2.0-beta.1`) — lifecycle is inferred automatically (`ga`, `beta`, `rc`) +- A promotion report URL or file path +- A plain-text URL list file (one fully-qualified GitHub URL per line) + +When your profile uses `{version}` in its output pattern and you also want to filter by a report, pass both arguments. + +Example profile in `changelog.yml`: + +```yaml +bundle: + repo: elasticsearch + owner: elastic + directory: docs/changelog + output_directory: docs/releases + profiles: + elasticsearch-release: + products: "elasticsearch {version} {lifecycle}" + output: "elasticsearch/{version}.yaml" + output_products: "elasticsearch {version}" +``` + +## Option-based mode + +Supply filter flags directly when you don't have a profile configured or need a one-off bundle. + +Exactly one of the following filter flags is required: + +- `--all` — include every changelog in the directory +- `--input-products` — match by product, target date, and lifecycle (e.g. `"elasticsearch * *"`) +- `--prs` — filter by PR URLs or a newline-delimited file of PR URLs +- `--issues` — filter by issue URLs or a newline-delimited file of issue URLs +- `--release-version` — fetch PR references from a GitHub release tag (e.g. `v9.2.0` or `latest`) +- `--report` — filter by PRs referenced in a promotion report (URL or local file) + +```sh +# Bundle all changelogs in docs/changelog/ +docs-builder changelog bundle --all --directory docs/changelog + +# Bundle changelogs for a specific product release +docs-builder changelog bundle \ + --input-products "elasticsearch 9.2.0 ga" \ + --output docs/releases/9.2.0.yaml + +# Bundle from a GitHub release +docs-builder changelog bundle \ + --release-version v9.2.0 \ + --repo elasticsearch \ + --owner elastic +``` + +## Resolved vs. reference bundles + +By default the bundle contains only file names and checksums — the original changelog files must remain on disk for rendering. Add `--resolve` (or set `bundle.resolve: true` in `changelog.yml`) to embed the full entry content inside the bundle. A resolved bundle is: + +- Required when using the `{changelog}` directive after deleting the source changelog files +- Required when `link_allow_repos` is configured (private-link scrubbing only runs during resolve) +- Necessary to regenerate rendered Markdown or AsciiDoc after the source files are removed + +:::{tip} +For most release workflows, use `--resolve`. It makes the bundle self-contained and allows you to clean up the changelog files with `docs-builder changelog remove` immediately after bundling. +::: + +## CI usage + +Pass `--plan` to emit GitHub Actions step outputs (`needs_network`, `needs_github_token`, `output_path`) without generating the bundle. Use this in a planning step to decide whether subsequent steps require a GitHub token or network access. + +For full configuration reference, see [Bundle changelogs](/contribute/bundle-changelogs.md). diff --git a/docs/cli/changelog/evaluate-pr.md b/docs/cli/changelog/evaluate-pr.md deleted file mode 100644 index 87cc8067fb..0000000000 --- a/docs/cli/changelog/evaluate-pr.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -navigation_title: "changelog evaluate-pr" ---- - -# changelog evaluate-pr - -:::{note} -This command is intended for CI automation. It is used internally by the changelog GitHub Actions and is not typically invoked directly by users. -::: - -Evaluate a pull request for changelog generation eligibility. -Performs pre-flight checks (body-only edit, bot loop detection, manual edit detection), loads the changelog configuration, checks label-based creation rules, resolves the PR title and type, and sets GitHub Actions outputs for downstream steps. - -## Usage - -```sh -docs-builder changelog evaluate-pr [options...] [-h|--help] -``` - -## Options - -`--config ` -: Path to the `changelog.yml` configuration file. - -`--owner ` -: GitHub repository owner. - -`--repo ` -: GitHub repository name. - -`--pr-number ` -: Pull request number. - -`--pr-title ` -: Pull request title. - -`--pr-labels ` -: Comma-separated PR labels. - -`--head-ref ` -: PR head branch ref. - -`--head-sha ` -: PR head commit SHA. - -`--event-action ` -: GitHub event action (e.g., `opened`, `synchronize`, `edited`). - -`--title-changed` -: Whether the PR title changed (for `edited` events). -: Default: `false` - -`--strip-title-prefix` -: Remove square-bracket prefixes from the PR title (for example, `[Inference API] Title` becomes `Title`), strip an optional colon after the prefix, and strip an ASCII ` - `-style separator after the prefix when the hyphen is followed by whitespace. -: Titles that still start with `-`, `*`, `+`, an en dash, or an em dash are surrounded by quotes to avoid rendering problems. -: Default: `false` - -`--bot-name ` -: Bot login name for loop detection. -: Default: `github-actions[bot]` - -## GitHub Actions outputs - -| Output | Description | -|--------|-------------| -| `status` | Evaluation result: `skipped`, `manually-edited`, `no-title`, `no-label`, or `proceed` | -| `should-generate` | `true` if `changelog add` should run | -| `should-upload` | `true` if the artifact should be uploaded | -| `title` | Resolved PR title | -| `description` | Release note extracted from the PR body (when `extract.release_notes` is enabled and a release note is found). Long or multi-line release notes (>120 characters) are placed here. Passed downstream as `CHANGELOG_DESCRIPTION` for `changelog add`. | -| `type` | Resolved changelog type | -| `products` | Comma-separated product specs resolved from PR labels via `pivot.products` mappings (e.g., `cloud-hosted, cloud-serverless`) | -| `label-table` | Markdown table of configured label-to-type mappings | -| `product-label-table` | Markdown table of configured label-to-product mappings | -| `existing-changelog-filename` | Filename of a previously committed changelog for this PR (if any) | - -## Environment variables - -| Variable | Purpose | -|----------|---------| -| `GITHUB_TOKEN` | GitHub API authentication for bot-commit and manual-edit detection | - -## Examples - -Evaluate PR #42 in the `elastic/elasticsearch` repository: - -```sh - docs-builder changelog evaluate-pr \ - --config docs/changelog.yml \ - --owner elastic \ - --repo elasticsearch \ - --pr-number 42 \ - --pr-title "Add new feature" \ - --pr-labels "enhancement,Team:Core" \ - --head-ref feature-branch \ - --head-sha abc123 \ - --event-action opened \ - --strip-title-prefix -``` diff --git a/docs/cli/changelog/gh-release.md b/docs/cli/changelog/gh-release.md deleted file mode 100644 index 2a8ef7ba82..0000000000 --- a/docs/cli/changelog/gh-release.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -navigation_title: "changelog gh-release" ---- - -# changelog gh-release - -Create changelog files and a bundle from a GitHub release by parsing pull request references from the release notes. - -:::{important} -Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -::: - -For general information about changelogs, go to [](/contribute/changelog.md). - -## Usage - -```sh -docs-builder changelog gh-release [version] [options...] [-h|--help] -``` - -## Arguments - -`repo` -: Required: GitHub repository in `owner/repo` format (for example, `elastic/elasticsearch`) or just the repository name (for example, `elasticsearch`), which defaults to `elastic` as the owner. - -`version` -: Optional: The release tag to fetch (for example, `v9.2.0` or `9.2.0`). Defaults to `latest`. - -## Options - -`--config ` -: Optional: Path to the changelog.yml configuration file. Defaults to `docs/changelog.yml`. - -`--description ` -: Optional: Bundle description text with placeholder support. -: Supports `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` placeholders. Overrides `bundle.description` from config. - -`--output ` -: Optional: Output directory for the generated changelog files. Falls back to `bundle.directory` in `changelog.yml` when not specified. Defaults to `./changelogs`. - -`--release-date ` -: Optional: Explicit release date for the bundle in YYYY-MM-DD format. -: By default, the bundle uses the GitHub release's published date. This option overrides that behavior. -: If the GitHub release has no published date, falls back to today's date (UTC). - -`--strip-title-prefix` -: Optional: Remove square brackets and the text within them from the beginning of pull request titles, remove a colon or a single ASCII hyphen if it follows the closing bracket and is followed by whitespace. -: Multiple bracket prefixes are also supported (for example, `"[Discover][ESQL] - Fix filtering"` becomes `"Fix filtering"`). -: When the title still begins with `-`, `*`, `+`, an en dash, or an em dash, it's surrounded by quotes. -: By default, the behavior is determined by the `extract.strip_title_prefix` changelog configuration setting (which defaults to `false`). - -`--warn-on-type-mismatch` -: Optional: Warn when the type inferred from Release Drafter section headers (for example, "Bug Fixes") doesn't match the type derived from the pull request's labels. Defaults to `true`. - -## Output - -The command creates two types of output in the directory specified by `--output`: - -- One YAML changelog file per pull request found in the release notes. -- A bundle file at `{output}/bundles/{version}-{product}-bundle.yml` that references all created changelog files. - -The product, target version, and lifecycle are inferred automatically from the release tag and the repository name (via [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml)). For example, a tag of `v9.2.0` on `elastic/elasticsearch` creates changelogs with `product: elasticsearch`, `target: 9.2.0`, and `lifecycle: ga`. - -## Configuration - -The `rules.bundle` section of your `changelog.yml` applies to bundles created by this command (after changelog files are gathered from the release). -For details, refer to [Rules for filtered bundles](/cli/changelog/bundle.md#changelog-bundle-rules). - -## Examples - -### Create changelogs from the latest release - -```sh -docs-builder changelog gh-release elastic/elasticsearch -``` - -### Create changelogs from a specific version tag - -```sh -docs-builder changelog gh-release elastic/elasticsearch v9.2.0 -``` - -### Use a short repository name - -```sh -docs-builder changelog gh-release elasticsearch v9.2.0 -``` - -### Specify a custom output directory - -```sh -docs-builder changelog gh-release elasticsearch v9.2.0 \ - --output ./docs/changelog \ - --config ./docs/changelog.yml -``` - -### Add description with placeholders - -```sh -docs-builder changelog gh-release elasticsearch v9.2.0 \ - --description "Elasticsearch {version} includes new features and fixes. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}" -``` \ No newline at end of file diff --git a/docs/cli/changelog/index.md b/docs/cli/changelog/index.md index 7efbfc40fe..0460cd28c9 100644 --- a/docs/cli/changelog/index.md +++ b/docs/cli/changelog/index.md @@ -1,15 +1,12 @@ ---- -navigation_title: "changelog" ---- +The `changelog` commands manage a file-per-change workflow that produces release notes with a consistent layout across all your products. Each developer creates a small YAML file per pull request; you later bundle those files into a release artifact and render it into Markdown or AsciiDoc. -# Changelog commands +## Typical workflow -These commands are associated with product release documentation. +1. **Configure** — create `docs/changelog.yml` with label mappings and bundle profiles: `docs-builder changelog init` +2. **Create** — add a changelog YAML for each notable PR: `docs-builder changelog add` +3. **Bundle** — aggregate entries for a release: `docs-builder changelog bundle` +4. **Publish** — render the bundle to a release notes page: `docs-builder changelog render` -- [changelog add](add.md) - Create a changelog file -- [changelog bundle](bundle.md) - Create a changelog bundle file -- [changelog gh-release](gh-release.md) - Create changelogs and a bundle from a GitHub release -- [changelog init](init.md) - Initialize changelog configuration and folder structure -- [changelog bundle-amend](bundle-amend.md) - Add entries to an existing bundle -- [changelog render](render.md) - Generate markdown output from changelog bundle files -- [changelog evaluate-pr](evaluate-pr.md) - (CI) Evaluate a PR for changelog generation eligibility +When working in CI, `docs-builder changelog evaluate-pr` inspects an open pull request and decides whether it needs a changelog file, then sets GitHub Actions outputs so your workflow can gate on the result. + +See [Create release notes from changelogs](/contribute/changelog.md) for the end-to-end guide. diff --git a/docs/cli/changelog/init.md b/docs/cli/changelog/init.md deleted file mode 100644 index f4db0efd18..0000000000 --- a/docs/cli/changelog/init.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -navigation_title: "changelog init" ---- - -# changelog init - -Initialize changelog configuration and folder structure for a repository. - -If a docs folder that contains `docset.yml` exists (in the repository root or `docs/` directory), the command uses that folder. -If a `docs` folder exists without `docset.yml`, the command uses it. -If no docs folder exists, the command creates `{path}/docs` and places `changelog.yml` there. - -The command creates a `changelog.yml` configuration file (from the built-in template) and `changelog` and `releases` subdirectories in the `docs` folder. -When `--changelog-dir` or `--bundles-dir` is specified, the corresponding `bundle.directory` and `bundle.output_directory` values in `changelog.yml` are set or updated (whether creating a new file or the file already exists). - -When the template is written for the first time, the command can **seed** `bundle.owner`, `bundle.repo`, and `bundle.link_allow_repos` so PR and issue links resolve under the explicit link allowlist in [changelog.example.yml](https://github.com/elastic/docs-builder/blob/main/config/changelog.example.yml) (there is no implicit allow for your own repository). Seeding runs when `git` remote `origin` points at **github.com** and/or when you pass `--owner` and/or `--repo`. CLI values override values inferred from `git`. If you pass `--repo` without `--owner` and `git` does not supply an owner, the owner defaults to `elastic`. If neither `git` nor CLI provides enough information, the placeholder line is removed from the template and you can set bundle fields manually. - -## Usage - -```sh -docs-builder changelog init [options...] [-h|--help] -``` - -## Options - -`--path ` -: Optional: Repository root path. -: Defaults to the output of `pwd` (current directory). The docs folder is `{path}/docs`, created if it does not exist. - -`--changelog-dir ` -: Optional: Path to the changelog directory. -: Defaults to `{docsFolder}/changelog`. - -`--bundles-dir ` -: Optional: Path to the bundles output directory. -: Defaults to `{docsFolder}/releases`. - -`--owner ` -: Optional: GitHub organization or user for `bundle.owner` and for seeding `bundle.link_allow_repos` when creating `changelog.yml`. Overrides the owner parsed from `git` remote `origin`. - -`--repo ` -: Optional: GitHub repository name for `bundle.repo` and for seeding `bundle.link_allow_repos` when creating `changelog.yml`. Overrides the repository name parsed from `git` remote `origin`. - -## Examples - -Initialize changelog (creates or uses docs folder, places `changelog.yml` there, plus `changelog` and `releases` subdirectories): - -```sh -docs-builder changelog init -``` - -Initialize when run from a subdirectory, specifying the root path: - -```sh -docs-builder changelog init --path /path/to/my-repo -``` - -Use custom changelog and bundles directories. -Sets or updates `bundle.directory` and `bundle.output_directory` in `changelog.yml` (creating the file if it does not exist): - -```sh -docs-builder changelog init \ - --changelog-dir ./my-changelogs \ - --bundles-dir ./my-releases -``` - -Initialize without relying on `git` (for example in a clean checkout or CI), setting the GitHub owner and repository used to seed bundle defaults and `link_allow_repos`: - -```sh -docs-builder changelog init --owner elastic --repo kibana -``` diff --git a/docs/cli/changelog/remove.md b/docs/cli/changelog/remove.md deleted file mode 100644 index ddaa310ac5..0000000000 --- a/docs/cli/changelog/remove.md +++ /dev/null @@ -1,286 +0,0 @@ -# changelog remove - -Remove changelog YAML files from a directory. - -You can use either profile-based or command-option-based removal: - -- **Profile-based**: `docs-builder changelog remove ` — uses the same `bundle.profiles` configuration as [`changelog bundle`](/cli/changelog/bundle.md) to determine which changelogs to remove. -- **Option-based**: `docs-builder changelog remove --products "..." ` (or `--prs`, `--issues`, `--all`, `--release-version`, `--report`) — specify the filter directly. - -These modes are mutually exclusive. You can't combine a profile argument with the command filter options. - -Before deleting anything, the command checks whether any of the matching files are referenced by unresolved bundles, to prevent silently breaking the `{changelog}` directive. - -For more context, go to [](/contribute/bundle-changelogs.md#changelog-remove). - -## Usage - -```sh -docs-builder changelog remove [arguments...] [options...] [-h|--help] -``` - -## Arguments - -These arguments apply to profile-based removal: - -`[0] ` -: Profile name from `bundle.profiles` in the changelog configuration file. -: For example, "elasticsearch-release". -: When specified, the second argument is the version, promotion report URL, or URL list file. - -`[1] ` -: Version number, promotion report URL/path, or URL list file. -: For example, `9.2.0`, `https://buildkite.../promotion-report.html`, or `/path/to/prs.txt`. -: See [Profile argument types](/cli/changelog/bundle.md#profile-argument-types) for details on accepted formats. - -`[2] ` -: Optional: Promotion report URL/path or URL list file when the second argument is a version string. -: When provided, `[1]` must be a version string and `[2]` is the PR/issue filter source. -: For example, `docs-builder changelog remove serverless-release 2026-02 ./promotion-report.html`. - -## Options - -For command-option-based removal, only one filter option can be specified: `--all`, `--products`, `--prs`, `--issues`, `--release-version`, or `--report`. - -`--all` -: Remove all changelog files in the directory. -: Cannot be combined with a profile argument. - -`--bundles-dir ` -: Optional: Override the directory scanned for bundles during the dependency check. -: When not specified, the directory is resolved in order: `bundle.output_directory` from the changelog configuration, then `{changelog-dir}/bundles`, then `{changelog-dir}/../bundles`. -: Not allowed with a profile argument. In profile mode, the same automatic discovery applies. - -`--config ` -: Optional: Path to the changelog configuration file. -: Defaults to `docs/changelog.yml`. -: Not allowed with a profile argument. In profile mode, the configuration is discovered automatically. - -`--directory ` -: Optional: The directory that contains the changelog YAML files. -: When not specified, falls back to `bundle.directory` from the changelog configuration, then the current working directory. -: Not allowed with a profile argument. In profile mode, the same fallback applies (starting from `bundle.directory`). - -`--dry-run` -: Print the files that would be removed and any bundle dependency conflicts, without deleting anything. -: Valid in both profile and command-option-based mode. - -`--force` -: Proceed with removal even when files are referenced by unresolved bundles. -: Emits a warning per dependency instead of blocking. -: Valid in both profile and command-option-based mode. - -`--issues ` -: Filter by issue URLs (comma-separated), or a path to a newline-delimited file. -: Can be specified multiple times. -: When using a file, every line must be a fully-qualified GitHub issue URL. Bare numbers and short forms are not allowed in files. -: Cannot be combined with a profile argument. - -`--owner ` -: Optional: The GitHub repository owner, which is used when pull requests or issues are specified as numbers. -: Precedence: `--owner` flag > `bundle.owner` in `changelog.yml` > `elastic`. -: Cannot be combined with a profile argument. - -`--products ?>` -: Filter by products in format `"product target lifecycle, ..."` -: Cannot be combined with a profile argument. -: All three parts (product, target, lifecycle) are required but can be wildcards (`*`). Multiple comma-separated values are combined with OR: a changelog is removed if it matches any of the specified product/target/lifecycle combinations. For example: - -- `"elasticsearch 9.3.0 ga"` — exact match -- `"cloud-serverless 2025-12-02 ga, cloud-serverless 2025-12-06 beta"` — remove changelogs for either cloud-serverless 2025-12-02 ga or cloud-serverless 2025-12-06 beta -- `"elasticsearch * *"` — all elasticsearch changelogs -- `"* 9.3.* *"` — any product with a target starting with `9.3.` -- `"* * *"` — all changelogs (equivalent to `--all`) - -`--prs ` -: Filter by pull request URLs (comma-separated), or a path to a newline-delimited file. -: Can be specified multiple times. -: When using a file, every line must be a fully-qualified GitHub PR URL. Bare numbers and short forms are not allowed in files. -: Cannot be combined with a profile argument. - -`--release-version ` -: GitHub release tag to use as a source of pull requests (for example, `"v9.2.0"` or `"latest"`). -: When specified, the command fetches the release from GitHub, parses PR references from the release notes, and use it as the removal filter. Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -: Requires repo (`--repo` or `bundle.repo` in `changelog.yml`) and owner (`--owner` flag > `bundle.owner` in `changelog.yml` > `elastic`) details. -: Requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable (or an active `gh` login). - -`--repo ` -: The GitHub repository name, which is required when pull requests or issues are specified as numbers or when using `--release-version`. -: Precedence: `--repo` flag > `bundle.repo` in `changelog.yml`. -: Cannot be combined with a profile argument. - -`--report ` -: Filter by pull requests extracted from a promotion report. Accepts a URL or a local file path. -: Exactly one filter option must be specified: `--all`, `--products`, `--prs`, `--issues`, or `--report`. -: Not allowed with a profile argument. - -## Directory resolution [changelog-remove-dirs] - -Both modes use the same ordered fallback to locate changelog YAML files and existing bundles. - -**Changelog files directory** (where changelog YAML files are read from): - -| Priority | Profile-based | Option-based | -|----------|---------------|--------------| -| 1 | `bundle.directory` in `changelog.yml` | `--directory` CLI option | -| 2 | Current working directory | `bundle.directory` in `changelog.yml` | -| 3 | — | Current working directory | - -**Bundles directory** (scanned for existing bundles during the dependency check): - -| Priority | Both modes | -|----------|------------| -| 1 | `--bundles-dir` CLI option (command-option-based only) | -| 2 | `bundle.output_directory` in `changelog.yml` | -| 3 | `{changelog-dir}/bundles` | -| 4 | `{changelog-dir}/../bundles` | - -:::{note} -"Current working directory" means the directory you are in when you run the command (`pwd`). -Setting `bundle.directory` and `bundle.output_directory` in `changelog.yml` is recommended so you don't need to rely on running the command from a specific directory. -::: - -## Option-based examples - -### Remove by GitHub release [changelog-remove-release-version] - -You can use `--release-version` to fetch pull request references directly from GitHub release notes and use them as the removal filter. - -:::{important} -Only automated GitHub release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format) are supported at this time. -::: - -This mirrors the equivalent [`--release-version` option on `changelog bundle`](/cli/changelog/bundle.md#changelog-bundle-release-version) and is useful when cleaning up after a release-based bundle. -For example: - -```sh -docs-builder changelog remove \ - --release-version v1.34.0 \ - --repo apm-agent-dotnet --owner elastic -``` - -The repo and owner used to fetch the release follow the same precedence as `changelog bundle`: - -- Repo: `--repo` flag > `bundle.repo` in `changelog.yml` (one source is required) -- Owner: `--owner` flag > `bundle.owner` in `changelog.yml` > `elastic` - -Use `--dry-run` to preview which files would be deleted before committing: - -```sh -docs-builder changelog remove \ - --release-version v1.34.0 \ - --dry-run -``` - -Pass `latest` to target the most recent release: - -```sh -docs-builder changelog remove \ - --release-version latest \ - --dry-run -``` - -:::{note} -`--release-version` requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable (or an active `gh` login) to fetch release details from the GitHub API. -::: - -## Profile-based examples [changelog-remove-profile] - -When a `changelog.yml` configuration file defines `bundle.profiles`, you can use those same profiles with `changelog remove` to remove exactly the changelogs that would be included in a matching bundle. - -Profile-based commands discover the changelog configuration automatically (no `--config` flag): they look for `changelog.yml` in the current directory, then `docs/changelog.yml`. If neither file is found, the command returns an error with instructions to run `docs-builder changelog init` or to re-run from the folder where the file exists. - -### Profile fields - -The `changelog remove` command reads the same `bundle.profiles` configuration as `changelog bundle`, but only a subset of fields are relevant to removal: - -| Field | Used by `changelog remove`? | Notes | -|---|---|---| -| `products` | Yes, when filtering by product | Required when the profile argument is a version string and no `source: github_release` is set. Not needed when the filter comes from a promotion report, URL list file, or `source: github_release`. | -| `source` | Yes | `source: github_release` fetches the PR list from the GitHub release to use as the removal filter. | -| `repo` | Yes, with `source: github_release` | Identifies the GitHub repository to fetch the release from. | -| `owner` | Yes, with `source: github_release` | Identifies the GitHub repository owner. | -| `output` | No | Ignored — removal does not write any output files. | -| `output_products` | No | Ignored. | -| `hide_features` | No | Ignored. | -| `rules.bundle` | No | Ignored — bundle-time product filtering is not applied during removal. | - -### Remove by product - -You can create profiles that are equivalent to the `--products` filter option, that is to say the removal will affect only changelogs with matching `products`. - -```yaml -bundle: - profiles: - elasticsearch-release: - products: "elasticsearch {version} {lifecycle}" - output: "elasticsearch-{version}.yaml" -``` - -You can remove the matching changelogs with: - -```sh -docs-builder changelog remove elasticsearch-release 9.2.0 --dry-run -``` - -This removes changelogs for `elasticsearch 9.2.0 ga` — the same set that `docs-builder changelog bundle elasticsearch-release 9.2.0` would include. The lifecycle is inferred from the version string: `9.2.0` → `ga`, `9.2.0-beta.1` → `beta`. Refer to [Lifecycle inference for standard profiles](/cli/changelog/bundle.md#changelog-bundle-standard-profile-lifecycle) for details. - -### Remove by report or URL list - -You can also create profiles that are equivalent to the `--prs`, `--issues`, or `--report` filter options. -That is to say the removal will affect only changelogs with matching `prs` or `issues`. - -For these profile-based commands, you can pass a promotion report URL, a local `.html` file, or a URL list file as the second argument. The command removes changelogs whose `prs` field matches the PR URLs extracted from the report or file. The following commands perform the same task with and without a profile: - -```sh -docs-builder changelog remove serverless-report ./promotion-report.html - -docs-builder changelog remove \ - --report ./promotion-report.html -``` - -Alternatively, use a newline-delimited text file that lists pull request or issue URLs: - -```sh -docs-builder changelog remove serverless-report ./prs.txt -``` - -When you want to use both a version (for `{version}` substitution in the output filename) and a report as the filter, pass both as separate arguments: - -```sh -docs-builder changelog remove serverless-report 2026-02-13 ./promotion-report.html -``` - -### Remove by GitHub release profiles [changelog-remove-github-release-profile] - -To make removal by GitHub release more easily repeatable, create a profile with `source: github_release` in your changelog configuration file. -For example: - -```yaml -bundle: - profiles: - agent-gh-release: - source: github_release - repo: apm-agent-dotnet - owner: elastic - output: "agent-{version}.yaml" -``` - -You can remove the matching changelogs with: - -```sh -docs-builder changelog remove agent-gh-release 1.34.0 -``` - -Use `--dry-run` to preview the files that would be deleted before committing: - -```sh -docs-builder changelog remove agent-gh-release 1.34.0 --dry-run -``` - -:::{note} -`source: github_release` profiles require a `GITHUB_TOKEN` or `GH_TOKEN` environment variable (or an active `gh` login) to fetch release details from the GitHub API. -The `repo` and `owner` used to identify the release follow the same precedence as bundling: profile-level `repo`/`owner` override `bundle.repo`/`bundle.owner`, which in turn override the default owner `elastic`. -::: - -For the full list of profile configuration fields, go to [Profile configuration fields](/cli/changelog/bundle.md#changelog-bundle-profile-config). diff --git a/docs/cli/changelog/render.md b/docs/cli/changelog/render.md deleted file mode 100644 index 9ad7ac3434..0000000000 --- a/docs/cli/changelog/render.md +++ /dev/null @@ -1,143 +0,0 @@ -# changelog render - -Generate markdown or asciidoc files from changelog bundle files. - -To create the bundle files, use [](/cli/changelog/bundle.md). -For details and examples, go to [](/contribute/publish-changelogs.md). - -## Usage - -```sh -docs-builder changelog render [options...] [-h|--help] -``` - -## Options - -`--config ` -: Optional: Path to the changelog.yml configuration file. -: Defaults to `docs/changelog.yml`. -: Note: The `changelog render` command does not use `rules.publish` for filtering. Filtering must be done at bundle time using `rules.bundle`. - -`--hide-features ` -: Optional: Filter by feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. -: Each occurrence can be either comma-separated feature IDs (e.g., `--hide-features "feature:new-search-api,feature:enhanced-analytics"`) or a file path (e.g., `--hide-features /path/to/file.txt`). -: When specifying feature IDs directly, provide comma-separated values. -: When specifying a file path, provide a single value that points to a newline-delimited file. The file should contain one feature ID per line. -: Entries with matching `feature-id` values will be commented out in the output and a warning will be emitted. -: If the bundle contains `hide-features` values (that is to say, it was created with the `--hide-features` option), those values are merged with this list and are also hidden. - -`--input ` -: One or more bundle input files. -: Each bundle is specified as "bundle-file-path|changelog-file-path|repo|link-visibility" using pipe (`|`) as delimiter. -: To merge multiple bundles, separate them with commas: `--input "bundle1|dir1|repo1|keep-links,bundle2|dir2|repo2|hide-links"`. -: For example, `--input "/path/to/changelog-bundle.yaml|/path/to/changelogs|elasticsearch|keep-links"`. -: Only `bundle-file-path` is required for each bundle. -: Use `repo` if your changelogs do not contain full URLs for the pull requests or issues; otherwise they will be incorrectly derived with "elastic/elastic" in the URL by default. -: Use `link-visibility` to control whether PR/issue links are shown or hidden for entries from this bundle. Valid values are `keep-links` (default) or `hide-links`. Use `hide-links` for bundles from private repositories. When `hide-links` is set, all links are hidden for each affected entry — changelog entries can contain multiple PR links (`prs`) and issue links (`issues`), and all of them are hidden or shown together. -: Paths support tilde (`~`) expansion and relative paths. - -:::{note} -The `render` command automatically discovers and merges `.amend-*.yaml` files with their parent bundle. For more information about amended bundles, go to [](bundle-amend.md). -::: - -`--file-type ` -: Optional: Output file type. Valid values: `"markdown"` or `"asciidoc"`. -: Defaults to `"markdown"`. -: When `"markdown"` is specified, the command generates multiple markdown files (index.md, breaking-changes.md, deprecations.md, known-issues.md). -: When `"asciidoc"` is specified, the command generates a single asciidoc file with all sections. - -`--output ` -: Optional: The output directory for rendered files. -: Defaults to current directory. - -`--subsections` -: Optional: Group entries by area in subsections. -: Defaults to false. -: When enabled, entries are grouped by their area within each section. The first area from each entry's areas list is used for grouping. - -`--title ` -: Optional: The title to use for section headers, directories, and anchors in output files. -: Defaults to the version in the first bundle. -: If the string contains spaces, they are replaced with dashes when used in directory names and anchors. - -The `changelog render` command does **not** use `rules.publish` for filtering. Filtering must be done at bundle time using `rules.bundle`. For more information, refer to [](/contribute/publish-changelogs.md). For how the directive differs, see the [{changelog} directive syntax reference](/syntax/changelog.md). - -## Output formats - -### Markdown format - -When `--file-type markdown` is specified (the default), the command generates multiple markdown files: - -- `index.md` - Contains features, enhancements, bug fixes, security updates, documentation changes, regressions, and other changes -- `breaking-changes.md` - Contains breaking changes -- `deprecations.md` - Contains deprecations -- `known-issues.md` - Contains known issues -- `highlights.md` - Contains highlighted entries (only created when at least one entry has `highlight: true`) - -### Asciidoc format - -When `--file-type asciidoc` is specified, the command generates a single asciidoc file with all sections: - -- Security updates -- Bug fixes -- Highlights (only included when at least one entry has `highlight: true`) -- New features and enhancements -- Breaking changes -- Deprecations -- Known issues -- Documentation -- Regressions -- Other changes - -The asciidoc output uses attribute references for links (for example, `{repo-pull}NUMBER[#NUMBER]`). - -### Multiple PR and issue links - -Changelog entries can reference multiple pull requests and issues using the `prs` and `issues` array fields. When an entry has multiple links, all of them are rendered inline for that entry: - -```md -* Fix ML calendar event update scalability issues. [#136886](https://github.com/elastic/elastic/pull/136886) [#136900](https://github.com/elastic/elastic/pull/136900) -``` - -## Examples - -### Render a single bundle - -```sh -docs-builder changelog render \ - --input "./docs/changelog/bundles/9.3.0.yaml" \ - --output ./release-notes -``` - -### Render with tilde expansion - -```sh -docs-builder changelog render \ - --input "~/docs/changelog/bundles/9.3.0.yaml|~/docs/changelog|elasticsearch" \ - --output ~/release-notes -``` - -### Render with relative paths - -```sh -docs-builder changelog render \ - --input "./bundles/9.3.0.yaml|./changelog|elasticsearch|keep-links" \ - --file-type markdown \ - --output ./output -``` - -### Merge multiple bundles - -```sh -docs-builder changelog render \ - --input "./bundles/elasticsearch-9.3.0.yaml|./changelog|elasticsearch,./bundles/kibana-9.3.0.yaml|./changelog|kibana" \ - --output ./merged-release-notes -``` - -### Hide links from private repository bundles - -```sh -docs-builder changelog render \ - --input "./public-bundle.yaml|./changelog|elasticsearch|keep-links,./private-bundle.yaml|./private-changelog|internal-repo|hide-links" \ - --output ./release-notes -``` diff --git a/docs/cli/cli-reference-how-to.md b/docs/cli/cli-reference-how-to.md new file mode 100644 index 0000000000..57fa63020f --- /dev/null +++ b/docs/cli/cli-reference-how-to.md @@ -0,0 +1,134 @@ +--- +navigation_title: Automated Reference +--- + +# Automated CLI reference docs + +`docs-builder` can generate a complete CLI reference section from a JSON schema file that describes your tool's commands, namespaces, flags, and arguments. The generated pages render usage synopses, parameter definitions, and examples directly from that schema — no hand-maintained markdown required. + +The schema format is documented at [argh-cli-schema.json](https://github.com/nullean/argh/blob/main/schema/argh-cli-schema.json). + +:::{note} +`docs-builder` supports automatic schema generation through the `__schema` meta-command, a built-in feature of the [Nullean.Argh](https://github.com/nullean/argh) CLI framework. +For other frameworks, add a command or script to your build tooling that writes an equivalent JSON file. +::: + +:::::{stepper} + +::::{step} Create a schema file + +Add a mechanism to your CLI that outputs a schema JSON matching the [argh-cli-schema.json](https://github.com/nullean/argh/blob/main/schema/argh-cli-schema.json) format. The schema describes your CLI's structure: top-level commands, namespaces, nested sub-namespaces, per-parameter types and descriptions, usage synopses, and examples. + +Once you have a way to generate the schema, write it to a file in your docs repository and commit it: + +```bash +# Example — replace with whatever generates your schema +my-tool export-schema > docs/cli-schema.json +``` + +Commit that file. It is the source of truth for the generated reference section. + +:::{tip} +Add a CI step that regenerates the schema and fails if the checked-in copy has drifted: + +```yaml +- name: Check CLI schema is up to date + run: | + my-tool export-schema > docs/cli-schema.json.tmp + diff docs/cli-schema.json docs/cli-schema.json.tmp || \ + (echo "cli-schema.json is out of date — regenerate and commit it" && exit 1) + rm docs/cli-schema.json.tmp +``` +::: + +:::: + +::::{step} Add a cli: entry to docset.yml + +In your `docset.yml`, add a `cli:` entry to the `toc:` section pointing at the schema file: + +```yaml +toc: + - cli: cli-schema.json +``` + +That's the minimal setup. `docs-builder` generates a navigation subtree and a page for every namespace and command. + +To give the section a stable URL prefix and a home for supplemental docs, also set `folder:` to the directory name you want to use: + +```yaml +toc: + - cli: cli-schema.json + folder: cli-reference +``` + +Use `children:` to prepend hand-written pages — installation guides, conceptual overviews, or quick-start tutorials — before the auto-generated reference. All schema-generated pages follow the listed children: + +```yaml +toc: + - cli: cli-schema.json + folder: cli-reference + children: + - file: installation.md + - file: getting-started.md +``` + +:::: + +::::{step} Write supplemental content for namespaces and commands + +The supplemental folder lets you replace the auto-generated summary on any namespace or command page with your own prose. The parameter table, usage synopsis, and examples are always appended from the schema — you're only replacing the introductory description. + +Two naming conventions are supported. Both can coexist in the same folder. + +**Hierarchy style** — mirrors the CLI namespace structure, works well alongside the `index.md` convention already common in documentation repos: + +``` +cli-reference/ + database/ + index.md ← intro for the database namespace + cmd-migrate.md ← intro for database migrate command + cmd-deploy.md ← intro for the root deploy command +``` + +**Flat prefix style** — all files at the folder root, full path encoded in the name: + +``` +cli-reference/ + ns-database.md ← intro for the database namespace + cmd-database-migrate.md ← intro for database migrate command + cmd-deploy.md ← intro for the root deploy command +``` + +Any supplemental file that doesn't match a known namespace or command produces a build error. Renamed commands don't leave orphaned docs silently. + +:::: + +::::{step} Done + +Your CLI reference section is live. As your CLI evolves, regenerate the schema and commit — the docs update automatically on the next build. + +**Navigation indicators** — generated pages show a `ns` (purple) or `cmd` (amber) badge in the sidebar, making it easy to see at a glance which pages come from the schema and which are hand-written. + +**Schema version** — the JSON includes a `schemaVersion` field. Check the [schema spec](https://github.com/nullean/argh/blob/main/schema/argh-cli-schema.json) for the current version when updating your schema generator. + +:::: + +::::: + +## Reference + +| docset.yml key | Description | +|---|---| +| `cli: ` | Path to the schema JSON, relative to `docset.yml` | +| `folder: ` | Supplemental docs folder; also sets the URL prefix | +| `children:` | Regular toc items prepended before generated pages | + +| Supplemental file | Matches | +|---|---| +| `ns-root.md` | Root CLI overview page | +| `/index.md` | Namespace `` (hierarchy style) | +| `ns-.md` | Namespace `` (flat style) | +| `/cmd-.md` | Command `` inside namespace `` | +| `cmd--.md` | Same, flat style | +| `cmd-.md` | Root-level command `` | diff --git a/docs/cli/docset/build.md b/docs/cli/docset/build.md deleted file mode 100644 index 3de45a1f65..0000000000 --- a/docs/cli/docset/build.md +++ /dev/null @@ -1,65 +0,0 @@ -# build - -Builds a local documentation set folder. - -Repeated invocations will do incremental builds of only the changed files unless: - -* The base branch has changed. -* The state file in the output folder has been removed. -* The `--force` option is specified. - -## Usage - -``` -docs-builder [command] [options...] [-h|--help] [--version] -``` - -## Global Options - -- `--log-level` `level` - -## Options - -`-p|--path ` -: Defaults to the`{pwd}/docs` folder (optional) - -`-o|--output ` -: Defaults to `.artifacts/html` (optional) - -`--path-prefix` `` -: Specifies the path prefix for urls (optional) - -`--force` `` -: Force a full rebuild of the destination folder (optional) - -`--strict` `` -: Treat warnings as errors and fail the build on warnings (optional) - -`--allow-indexing` `` -: Allow indexing and following of HTML files (optional) - -`--metadata-only` `` -: Only emit documentation metadata to output, ignored if 'exporters' is also set (optional) - -`--exporters` `` -: Set available exporters: - - * html - * es, - * config, - * links, - * state, - * llm, - * redirect, - * metadata, - * default - * none. - - Defaults to (html, llm, config, links, state, redirect) or 'default'. (optional) - - -`--canonical-base-url` `` -: The base URL for the canonical url tag (optional) - -`--skip-api` -: Skip [API Explorer](../../configure/content-set/api-explorer.md) generation for faster builds. (optional) \ No newline at end of file diff --git a/docs/cli/docset/diff-validate.md b/docs/cli/docset/diff-validate.md deleted file mode 100644 index 59513c5f90..0000000000 --- a/docs/cli/docset/diff-validate.md +++ /dev/null @@ -1,18 +0,0 @@ -# diff validate - -Gathers the local changes by inspecting the git log, stashed and unstashed changes. - -It currently validates the following: - -* Ensures that renames and deletions are reflected in [redirects.yml](../../contribute/redirects.md). - -## Usage - -``` -docs-builder diff validate [options...] [-h|--help] [--version] -``` - -## Options - -`-p|--path ` -: Defaults to the`{pwd}/docs` folder (optional) \ No newline at end of file diff --git a/docs/cli/docset/format.md b/docs/cli/docset/format.md deleted file mode 100644 index ba20a1fe9c..0000000000 --- a/docs/cli/docset/format.md +++ /dev/null @@ -1,145 +0,0 @@ -# format - -Format documentation files by fixing common issues like irregular space - -## Usage - -``` -docs-builder format --check [options...] -docs-builder format --write [options...] -``` - -## Options - -`--check` -: Check if files need formatting without modifying them. Exits with code 1 if formatting is needed, 0 if all files are properly formatted. (required, mutually exclusive with --write) - -`--write` -: Write formatting changes to files. (required, mutually exclusive with --check) - -`-p|--path` `` -: Path to the documentation folder, defaults to pwd. (optional) - -## Description - -The `format` command automatically detects and fixes formatting issues in your documentation files. The command only processes Markdown files (`.md`) that are included in your `_docset.yml` table of contents, ensuring that only intentional documentation files are modified. - -You must specify exactly one of `--check` or `--write`: -- `--check` validates formatting without modifying files, useful for CI/CD pipelines -- `--write` applies formatting changes to files - -Currently, it handles irregular space characters that may impair Markdown rendering. - -### Irregular Space Detection - -The format command intelligently handles irregular space characters by categorizing them into three groups: - -#### Characters removed entirely - -These characters are removed completely as they serve no visual purpose and can cause rendering issues: - -- Line Tabulation (U+000B) -- Form Feed (U+000C) -- Next Line (U+0085) -- Ogham Space Mark (U+1680) -- Mongolian Vowel Separator (U+180E) -- Zero Width No-Break Space/BOM (U+FEFF) -- Zero Width Space (U+200B) -- Line Separator (U+2028) -- Paragraph Separator (U+2029) - -#### Characters preserved - -These characters are preserved as they serve important typographic or functional purposes: - -- No-Break Space (U+00A0) - Prevents line breaks -- Figure Space (U+2007) - Aligns numbers in tables -- Narrow No-Break Space (U+202F) - French typography -- Medium Mathematical Space (U+205F) - Mathematical expressions - -#### Characters replaced with regular spaces - -These characters are replaced with standard spaces (U+0020) as they can cause inconsistent rendering: - -- En Quad (U+2000) -- Em Quad (U+2001) -- En Space (U+2002) -- Em Space (U+2003) -- Tree-Per-Em (U+2004) -- Four-Per-Em (U+2005) -- Six-Per-Em (U+2006) -- Punctuation Space (U+2008) -- Thin Space (U+2009) -- Hair Space (U+200A) -- Ideographic Space (U+3000) - -These characters can cause unexpected rendering issues in Markdown and are often introduced accidentally through copy-paste operations from other applications. - -## Examples - -### Check if formatting is needed (CI/CD) - -```bash -docs-builder format --check -``` - -Exit codes: -- `0`: All files are properly formatted -- `1`: Some files need formatting - -### Apply formatting changes - -```bash -docs-builder format --write -``` - -### Check specific documentation folder - -```bash -docs-builder format --check --path /path/to/docs -``` - -### Format specific documentation folder - -```bash -docs-builder format --write --path /path/to/docs -``` - -## Output - -### Check mode output - -When using `--check`, the command reports which files need formatting: - -``` -Checking documentation in: /path/to/docs - -Formatting needed: - Files needing formatting: 2 - irregular space fixes needed: 3 - -Run 'docs-builder format --write' to apply changes -``` - -### Write mode output - -When using `--write`, the command reports the changes made: - -``` -Formatting documentation in: /path/to/docs -Formatted index.md (2 change(s)) - -Formatting complete: - Files processed: 155 - Files modified: 1 - irregular space fixes: 2 -``` - -## Future Enhancements - -The format command is designed to be extended with additional formatting capabilities in the future, such as: - -- Line ending normalization -- Trailing whitespace removal -- Consistent heading spacing -- And other formatting fixes diff --git a/docs/cli/docset/index-command.md b/docs/cli/docset/index-command.md deleted file mode 100644 index 00e28cf1c8..0000000000 --- a/docs/cli/docset/index-command.md +++ /dev/null @@ -1,71 +0,0 @@ -# index - -Index a single documentation set to Elasticsearch, calls `docs-builder --exporters elasticsearch`. Exposes more options - -## Usage - -``` -docs-builder index [options...] [-h|--help] [--version] -``` - -## Options - -`-es|--endpoint ` -: Elasticsearch endpoint, alternatively set env DOCUMENTATION_ELASTIC_URL (optional) - -`--path` `` -: path to the documentation folder, defaults to pwd. (optional) - -`--api-key` `` -: Elasticsearch API key, alternatively set env DOCUMENTATION_ELASTIC_APIKEY (optional) - -`--username` `` -: Elasticsearch username (basic auth), alternatively set env DOCUMENTATION_ELASTIC_USERNAME (optional) - -`--password` `` -: Elasticsearch password (basic auth), alternatively set env DOCUMENTATION_ELASTIC_PASSWORD (optional) - -`--search-num-threads` `` -: The number of search threads the inference endpoint should use. Defaults: 8 (optional) - -`--index-num-threads` `` -: The number of index threads the inference endpoint should use. Defaults: 8 (optional) - -`--bootstrap-timeout` `` -: Timeout in minutes for the inference endpoint creation. Defaults: 4 (optional) - -`--index-name-prefix` `` -: The prefix for the computed index/alias names. Defaults: semantic-docs (optional) - -`--force-reindex` `` -: Force reindex strategy to semantic index, by default, we multiplex writes if the semantic index does not exist yet (optional) - -`--buffer-size` `` -: The number of documents to send to ES as part of the bulk. Defaults: 100 (optional) - -`--max-retries` `` -: The number of times failed bulk items should be retried. Defaults: 3 (optional) - -`--debug-mode` `` -: Buffer ES request/responses for better error messages and pass ?pretty to all requests (optional) - -`--proxy-address` `` -: Route requests through a proxy server (optional) - -`--proxy-password` `` -: Proxy server password (optional) - -`--proxy-username` `` -: Proxy server username (optional) - -`--disable-ssl-verification` `` -: Disable SSL certificate validation (EXPERT OPTION) (optional) - -`--certificate-fingerprint` `` -: Pass a self-signed certificate fingerprint to validate the SSL connection (optional) - -`--certificate-path` `` -: Pass a self-signed certificate to validate the SSL connection (optional) - -`--certificate-not-root` `` -: If the certificate is not root but only part of the validation chain pass this (optional) \ No newline at end of file diff --git a/docs/cli/docset/index.md b/docs/cli/docset/index.md deleted file mode 100644 index d3e794c053..0000000000 --- a/docs/cli/docset/index.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -navigation_title: "documentation set" ---- - -# Documentation Set Commands - -An isolated build means building a single documentation set. - -A `Documentation Set` is defined as a folder containing a [docset.yml](../../configure/content-set/index.md) file. - -These commands are typically what you interface with when you are working on the documentation of a single repository locally. - -## Isolated build commands - -`build` is the default command so you can just run `docs-builder` to build a single documentation set. `docs-builder` will -locate the `docset.yml` anywhere in the directory tree automatically and build the documentation. - -- [build](build.md) - build a single documentation set (incrementally) -- [serve](serve.md) - partial build and serve documentation as needed at http://localhost:3000 -- [index](index-command.md) - ingest a single documentation set to an Elasticsearch index. - -## Refactor commands - -- [mv](mv.md) - move a file or folder to a new location. This will rewrite all links in all files too. -- [diff validate](diff-validate.md) - validate that local changes are reflected in [redirects.yml](../../contribute/redirects.md) - diff --git a/docs/cli/docset/mv.md b/docs/cli/docset/mv.md deleted file mode 100644 index 173563b1ad..0000000000 --- a/docs/cli/docset/mv.md +++ /dev/null @@ -1,25 +0,0 @@ -# mv - -Move a file or folder from one location to another and update all links in the documentation - -## Usage - -``` -docs-builder mv [arguments...] [options...] [-h|--help] [--version] -``` - -## Arguments - -`[0] ` -: The source file or folder path to move from (required) - -`[1] ` -: The target file or folder path to move to (required) - -## Options - -`--dry-run` `` -: Dry run the move operation (optional) - -`-p|--path ` -: Defaults to the`{pwd}` folder (optional) \ No newline at end of file diff --git a/docs/cli/docset/serve.md b/docs/cli/docset/serve.md deleted file mode 100644 index cab49ffeae..0000000000 --- a/docs/cli/docset/serve.md +++ /dev/null @@ -1,33 +0,0 @@ -# serve - -Continuously serve a documentation folder at http://localhost:3000. - -When running `docs-builder serve`, the documentation is not built in full. -Each page will be build on the fly continuously when requested in the browser. - -The `serve` command is also `live reload` enabled so that file systems changes will be reflected without having to restart the server. -This includes changes to the documentation files, the navigation, or the configuration files. - -## Usage - -``` -docs-builder serve [options...] [-h|--help] [--version] -``` - -## Options - -`-p|--path ` -: Path to serve the documentation. Defaults to the`{pwd}/docs` folder (optional) - -`--port` `` -: Port to serve the documentation. (Default: 3000) - -## API documentation - -When your content set includes an [API Explorer configuration](../../configure/content-set/api-explorer.md) in `docset.yml`, `docs-builder serve` generates API documentation on startup and serves it under `/api/{product-key}/`. For example, an `elasticsearch` key produces pages at `http://localhost:3000/api/elasticsearch/`. - -API spec files are watched for changes and regenerated automatically when they're updated. - -:::{note} -API generation is skipped when running `docs-builder serve --watch`. This is a performance optimization for `dotnet watch` workflows. Run `serve` without `--watch` to include API docs in your local preview. -::: \ No newline at end of file diff --git a/docs/cli/index.md b/docs/cli/index.md deleted file mode 100644 index 55f5242de0..0000000000 --- a/docs/cli/index.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -navigation_title: CLI (docs-builder) ---- - -# Command line interface - -`docs-builder` is the binary used to invoke various commands. -These commands can be roughly grouped into four main categories - -- [Documentation Set commands](#documentation-set-commands) -- [Link commands](#link-commands) -- [Assembler commands](#assembler-commands) -- [Changelog commands](#changelog-commands) - -### Global options - -The following options are available for all commands: - -`--log-level ` -: Change the log level one of ( `trace`, `debug`, `info`, `warn`, `error`, `critical`). Defaults to `info` - -`--config-source` or `-c` -: Explicitly set the configuration source one of `local`, `remote` or `embedded`. Defaults to `local` if available - other wise `embedded` - -## Documentation set commands - -Commands that operate over a single documentation set. - -A `Documentation Set` is defined as a folder containing a [docset.yml](../configure/content-set/index.md) file. - -These commands are typically what you interface with when you are working on the documentation of a single repository locally. - -[See available CLI commands for documentation sets](docset/index.md) - -## Link commands - -Outbound links, those going from the documentation set to other sources, are validated as part of the build process. - -Inbound links, those going from other sources to the documentation set, are validated using specialized commands. - -[See available CLI commands for inbound links](links/index.md) - -## Assembler commands - -Assembler builds bring together all isolated documentation set builds and turn them into the overall documentation that gets published. - -[See available CLI commands for assembler](assembler/index.md) - -## Changelog commands - -Commands that pertain to creating and publishing product release documentation. - -[See available CLI commands for release docs](changelog/index.md) diff --git a/docs/cli/installation.md b/docs/cli/installation.md new file mode 100644 index 0000000000..e402e516ab --- /dev/null +++ b/docs/cli/installation.md @@ -0,0 +1,39 @@ +# Installation + +## Automated install (recommended) + +The quickest way to get started on Linux and macOS is the one-line installer: + +```bash +curl -sL https://ela.st/docs-builder-install | sh +``` + +On Windows, run this in PowerShell: + +```ps1 +iex (New-Object System.Net.WebClient).DownloadString('https://ela.st/docs-builder-install-win') +``` + +Both scripts download the latest release binary and add it to your system `PATH`. + +## Manual install + +Download the binary directly from the [Releases page](https://github.com/elastic/docs-builder/releases), extract it, and place it somewhere on your `PATH`. + +## Build from source + +You need [.NET 10](https://dotnet.microsoft.com/download) installed. + +```bash +git clone https://github.com/elastic/docs-builder +cd docs-builder +./build.sh publishbinaries +``` + +The compiled binary is written to `.artifacts/publish/docs-builder/release/docs-builder`. + +## Verify the installation + +```bash +docs-builder --version +``` diff --git a/docs/cli/links/inbound-links-validate-all.md b/docs/cli/links/inbound-links-validate-all.md deleted file mode 100644 index 3563db79d0..0000000000 --- a/docs/cli/links/inbound-links-validate-all.md +++ /dev/null @@ -1,9 +0,0 @@ -# inbound-links validate-all - -Validate all published cross_links in all published links.json files. - -## Usage - -``` -docs-builder inbound-links validate-all [-h|--help] [--version] -``` \ No newline at end of file diff --git a/docs/cli/links/inbound-links-validate-link-reference.md b/docs/cli/links/inbound-links-validate-link-reference.md deleted file mode 100644 index b9eaca3c26..0000000000 --- a/docs/cli/links/inbound-links-validate-link-reference.md +++ /dev/null @@ -1,17 +0,0 @@ -# inbound-links validate-link-reference - -Validate a locally published links.json file against all published links.json files in the registry - -## Usage - -``` -docs-builder inbound-links validate-link-reference [options...] [-h|--help] [--version] -``` - -## Options - -`--file` `` -: Path to `links.json` defaults to '.artifacts/docs/html/links.json' (optional) - -`-p|--path ` -: Defaults to the `{pwd}` folder (optional) \ No newline at end of file diff --git a/docs/cli/links/inbound-links-validate.md b/docs/cli/links/inbound-links-validate.md deleted file mode 100644 index 80036eec2a..0000000000 --- a/docs/cli/links/inbound-links-validate.md +++ /dev/null @@ -1,17 +0,0 @@ -# inbound-links validate - -Validate all published cross_links in all published links.json files. - -## Usage - -``` -docs-builder inbound-links validate [options...] [-h|--help] [--version] -``` - -## Options - -`--from` `` -: (optional) - -`--to` `` -: (optional) \ No newline at end of file diff --git a/docs/cli/links/index.md b/docs/cli/links/index.md deleted file mode 100644 index 002dbfbe79..0000000000 --- a/docs/cli/links/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -navigation_title: links ---- - -# Inbound Links - -Outbound links, those going from the documentation set to other sources, are validated as part of the build process. - -Inbound links, those going from other sources to the documentation set, are validated using specialized commands. - -### Inbound link validation commands - -- [inbound-links validate-all](inbound-links-validate-all.md) - validate all inbounds links as published to the links registry. -- [inbound-links validate](inbound-links-validate.md) - validate inbound links from and to specific repositories -- [inbound-links validate-link-reference](inbound-links-validate-link-reference.md) - validate a local link reference artifact from [build](../docset/build.md) with the published registry diff --git a/docs/cli/shell-autocompletion.md b/docs/cli/shell-autocompletion.md new file mode 100644 index 0000000000..1eaa3d8575 --- /dev/null +++ b/docs/cli/shell-autocompletion.md @@ -0,0 +1,40 @@ +# Shell autocompletion + +`docs-builder` ships with built-in tab completion for subcommands, namespaces, and flags. No extra packages are needed — the completions are generated at build time and are trimming- and AOT-safe. + +Run the following once to install completions for your shell, then open a new terminal session. + +## Bash + +Add to `~/.bashrc`: + +```bash +eval "$(docs-builder __completion bash)" +``` + +## Zsh + +Add to `~/.zshrc`: + +```zsh +source <(docs-builder __completion zsh) +``` + +## Fish + +```fish +mkdir -p ~/.config/fish/completions +docs-builder __completion fish > ~/.config/fish/completions/docs-builder.fish +``` + +Requires Fish 3.4 or later. + +## Inspect the generated script + +You can print the raw completion script for any shell without installing it: + +```bash +docs-builder __completion bash +docs-builder __completion zsh +docs-builder __completion fish +``` diff --git a/docs/contribute/bundle-changelogs.md b/docs/contribute/bundle-changelogs.md index facc3071ce..47ba24540f 100644 --- a/docs/contribute/bundle-changelogs.md +++ b/docs/contribute/bundle-changelogs.md @@ -3,7 +3,7 @@ You can use the `docs-builder changelog bundle` command to create a YAML file that lists multiple changelogs. The command has two modes of operation: you can specify all the command options or you can define "profiles" in the changelog configuration file. The latter is more convenient and consistent for repetitive workflows. -For up-to-date details, use the `-h` option or refer to [](/cli/changelog/bundle.md). +For up-to-date details, use the `-h` option or refer to [](/cli/changelog/cmd-bundle.md). The command supports two mutually exclusive usage modes: @@ -13,7 +13,7 @@ The command supports two mutually exclusive usage modes: You cannot mix these two modes: when you use a profile name, no filter or output options are accepted on the command line. :::{tip} -Alternatively, if you already have automated release notes for GitHub releases, you can use the `docs-builder changelog gh-release` command to create changelog files and a bundle from your GitHub release notes. Refer to [](/cli/changelog/gh-release.md). +Alternatively, if you already have automated release notes for GitHub releases, you can use the `docs-builder changelog gh-release` command to create changelog files and a bundle from your GitHub release notes. Refer to [](/cli/changelog/cmd-gh-release.md). ::: ## Option-based bundling [changelog-bundle-options] @@ -81,7 +81,7 @@ Top-level `bundle` fields: | `repo` | Default GitHub repository name applied to all profiles. Falls back to product ID if not set at any level. | | `owner` | Default GitHub repository owner applied to all profiles. | | `resolve` | When `true`, embeds full changelog entry content in the bundle (same as `--resolve`). Required when `link_allow_repos` is set. | -| `link_allow_repos` | When set (including an empty list), only PR/issue links whose resolved repository is in this `owner/repo` list are kept; others are rewritten to `# PRIVATE:` sentinels in bundle YAML. When absent, no link filtering is applied. Requires `resolve: true`. Refer to [PR and issue link allowlist](/cli/changelog/bundle.md#link-allowlist). | +| `link_allow_repos` | When set (including an empty list), only PR/issue links whose resolved repository is in this `owner/repo` list are kept; others are rewritten to `# PRIVATE:` sentinels in bundle YAML. When absent, no link filtering is applied. Requires `resolve: true`. Refer to [PR and issue link allowlist](/cli/changelog/cmd-bundle.md). | Profile configuration fields in `bundle.profiles`: @@ -261,7 +261,7 @@ docs-builder changelog bundle \ --input-products "cloud-serverless 2025-12-02 ga, cloud-serverless 2025-12-06 beta" <1> ``` -1. Include all changelogs that have the `cloud-serverless` product identifier with target dates of either December 2 2025 (lifecycle `ga`) or December 6 2025 (lifecycle `beta`). For more information about product values, refer to [Product format](/cli/changelog/bundle.md#product-format). +1. Include all changelogs that have the `cloud-serverless` product identifier with target dates of either December 2 2025 (lifecycle `ga`) or December 6 2025 (lifecycle `beta`). For more information about product values, refer to [Product format](/cli/changelog/cmd-bundle.md). You can use wildcards in any of the three parts: @@ -385,7 +385,7 @@ You can also combine multiple `--prs` options: 4. Optionally include the contents of each changelog in the output file. :::{tip} -You can use these files with profile-based bundling too. Refer to [](/cli/changelog/bundle.md). +You can use these files with profile-based bundling too. Refer to [](/cli/changelog/cmd-bundle.md). ::: If you have changelog files that reference those pull requests, the command creates a file like this: @@ -436,7 +436,7 @@ The bundle's product metadata is inferred automatically from the release tag and :::{tip} If you are not creating changelogs when you create your pull requests, consider the `docs-builder changelog gh-release` command as a one-shot alternative to the `changelog add` and `changelog bundle` commands. -It parses the release notes, creates one changelog file per pull request found, and creates a `changelog-bundle.yaml` file — all in a single step. Refer to [](/cli/changelog/gh-release.md) +It parses the release notes, creates one changelog file per pull request found, and creates a `changelog-bundle.yaml` file — all in a single step. Refer to [](/cli/changelog/cmd-gh-release.md) ::: ## Hide features [changelog-bundle-hide-features] @@ -455,7 +455,7 @@ docs-builder changelog bundle \ The bundle output will include a `hide-features` field: @@ -483,9 +483,9 @@ The `--hide-features` option on the `render` command and the `hide-features` fie A changelog can reference multiple pull requests and issues in the `prs` and `issues` array fields. -To comment out links that are not in your allowlist in all changelogs in your bundles, refer to [changelog bundle](/cli/changelog/bundle.md#link-allowlist). +To comment out links that are not in your allowlist in all changelogs in your bundles, refer to [changelog bundle](/cli/changelog/cmd-bundle.md). -If you are working in a private repo and do not want any pull request or issue links to appear (even if they target a public repo), you also have the option to configure link visibiblity in the [changelog directive](/syntax/changelog.md) and [changelog render](/cli/changelog/render.md) command. +If you are working in a private repo and do not want any pull request or issue links to appear (even if they target a public repo), you also have the option to configure link visibiblity in the [changelog directive](/syntax/changelog.md) and [changelog render](/cli/changelog/cmd-render.md) command. :::{tip} You must run the `docs-builder changelog bundle` command with the `--resolve` option or set `bundle.resolve` to `true` in the changelog configuration file (so that bundle files are self-contained) in order to hide the private links. @@ -503,7 +503,7 @@ The changelogs from all matching amend files are combined with the parent bundle If you explicitly list the amend bundles in the `--input` option of the `docs-builder changelog render` command, you'll get duplicate entries in the output files. List only the original bundles. ::: -For more details and examples, go to [](/cli/changelog/bundle-amend.md). +For more details and examples, go to [](/cli/changelog/cmd-bundle-amend.md). ## Remove changelog files [changelog-remove] @@ -593,5 +593,5 @@ When using a file for `--prs` or `--issues`, every line must be a fully-qualifie docs-builder changelog remove --products "elasticsearch 9.3.0 *" --dry-run ``` -For full option details, go to [](/cli/changelog/remove.md). +For full option details, go to [](/cli/changelog/cmd-remove.md). diff --git a/docs/contribute/changelog.md b/docs/contribute/changelog.md index f1bdc47bc7..d4ae1b5a3e 100644 --- a/docs/contribute/changelog.md +++ b/docs/contribute/changelog.md @@ -6,7 +6,7 @@ To use the `docs-builder changelog` commands in your development workflow: 1. [Configure changelogs](/contribute/configure-changelogs.md): Create a configuration file, map labels, and define rules for creation and bundling. 1. [Create changelogs](/contribute/create-changelogs.md) with the `docs-builder changelog add` command. - - Alternatively, if you already have automated release notes for GitHub releases, you can use the `docs-builder changelog gh-release` command to create changelog files and a bundle from your GitHub release notes. Refer to [](/cli/changelog/gh-release.md). + - Alternatively, if you already have automated release notes for GitHub releases, you can use the `docs-builder changelog gh-release` command to create changelog files and a bundle from your GitHub release notes. Refer to [](/cli/changelog/cmd-gh-release.md). 1. [Bundle changelogs](/contribute/bundle-changelogs.md) with the `docs-builder changelog bundle` command. For example, create a bundle for the pull requests that are included in a product release. When changelogs are no longer needed in the repo, [remove changelog files](/contribute/bundle-changelogs.md#changelog-remove) with `docs-builder changelog remove`. 1. [Publish release notes](/contribute/publish-changelogs.md): Use the `{changelog}` directive in docs or `docs-builder changelog render` to produce release documentation. diff --git a/docs/contribute/configure-changelogs.md b/docs/contribute/configure-changelogs.md index 7dfc230b27..cae46c150f 100644 --- a/docs/contribute/configure-changelogs.md +++ b/docs/contribute/configure-changelogs.md @@ -56,7 +56,7 @@ Only one configuration file is required for each repository. You must maintain the file if your repo labels change over time. ::: -You can use the [changelog init](/cli/changelog/init.md) command to create the changelog configuration file and folder structure automatically. +You can use the [changelog init](/cli/changelog/cmd-init.md) command to create the changelog configuration file and folder structure automatically. For example, run the following command in your GitHub repo's root directory: diff --git a/docs/contribute/create-changelogs.md b/docs/contribute/create-changelogs.md index 34251447b3..1a2c612f77 100644 --- a/docs/contribute/create-changelogs.md +++ b/docs/contribute/create-changelogs.md @@ -12,8 +12,8 @@ Refer to [](/contribute/configure-changelogs.md). ## Create changelog files from command line [command-line] -These steps describe how to use the [changelog add](/cli/changelog/add.md) command. -If you already have automated release notes for GitHub releases, you can use the [changelog gh-release](/cli/changelog/gh-release.md) command instead. +These steps describe how to use the [changelog add](/cli/changelog/cmd-add.md) command. +If you already have automated release notes for GitHub releases, you can use the [changelog gh-release](/cli/changelog/cmd-gh-release.md) command instead. 1. If you're accessing private repositories or creating a large number of changelogs, log into GitHub or set the `GITHUB_TOKEN` (or `GH_TOKEN` ) environment variable with a sufficient personal access token. Refer to [Authorization](/contribute/configure-changelogs.md#authorization). @@ -46,7 +46,7 @@ If you already have automated release notes for GitHub releases, you can use the It derives the title from the pull request or issue title, extracts linked references, and derives the areas, products, and type from labels (if mappings are defined in the configuration file). To control what information is extracted, refer to the [extract](/contribute/configure-changelogs-ref.md#extract) and [pivot](/contribute/configure-changelogs-ref.md#pivot) sections of the changelog configuration file. - For the most up-to-date command syntax, use the `-h` option or refer to [](/cli/changelog/add.md). + For the most up-to-date command syntax, use the `-h` option or refer to [](/cli/changelog/cmd-add.md). 1. [Review the output file](#review). @@ -60,7 +60,7 @@ When automated via the [changelog GitHub Actions](https://github.com/elastic/doc The `description` output from step 1 contains the release note extracted from the PR body (when `extract.release_notes` is enabled). If extraction is disabled (either by setting `extract.release_notes: false` in `changelog.yml` or by passing `--no-extract-release-notes` to `changelog add`), the `CHANGELOG_DESCRIPTION` environment variable is ignored and the extracted description is not written to the changelog. -Refer to [CI auto-detection](/cli/changelog/add.md#ci-auto-detection) for the full list of environment variables and precedence rules. +Refer to [CI auto-detection](/cli/changelog/cmd-add.md) for the full list of environment variables and precedence rules. ## Review the content [review] @@ -68,7 +68,7 @@ Refer to [CI auto-detection](/cli/changelog/add.md#ci-auto-detection) for the fu You can specify the file location with command options (`--output`) or configuration options (`bundle.directory`). Likewise you can control the file names with command options (`--use-issue-number` or `--use-pr-number`) or the `filename` configuration option. - Refer to the [Filenames](/cli/changelog/add.md#filenames). + Refer to the [Filenames](/cli/changelog/cmd-add.md). 1. Verify that the files contain content that is accurate and user-friendly. This review is especially important when you're pulling content from GitHub, since there might be some missing or extraneous information. @@ -169,4 +169,4 @@ The option precedence is: CLI option > `changelog.yml` bundle section > built-in ::: You can use the `docs-builder changelog gh-release` command as a one-shot alternative to `changelog add` and `changelog bundle` commands. -The command parses the release notes, creates one changelog file per pull request found, and creates a `changelog-bundle.yaml` file — all in a single step. Refer to [](/cli/changelog/gh-release.md) \ No newline at end of file +The command parses the release notes, creates one changelog file per pull request found, and creates a `changelog-bundle.yaml` file — all in a single step. Refer to [](/cli/changelog/cmd-gh-release.md) \ No newline at end of file diff --git a/docs/contribute/publish-changelogs.md b/docs/contribute/publish-changelogs.md index 6aa62ab0cb..7ed47665e8 100644 --- a/docs/contribute/publish-changelogs.md +++ b/docs/contribute/publish-changelogs.md @@ -28,7 +28,7 @@ All product-specific filtering must be configured at bundle time via `rules.bund ## Generate markdown or asciidoc [render-changelogs] The `docs-builder changelog render` command creates markdown or asciidoc files from changelog bundles for documentation purposes. -For up-to-date details, use the `-h` command option or refer to [](/cli/changelog/render.md). +For up-to-date details, use the `-h` command option or refer to [](/cli/changelog/cmd-render.md). Before you can use this command you must create changelog files and collect them into bundles. For example, the `docs-builder changelog bundle` command creates a file like this: diff --git a/docs/syntax/changelog.md b/docs/syntax/changelog.md index 16c752d840..44604e8cfd 100644 --- a/docs/syntax/changelog.md +++ b/docs/syntax/changelog.md @@ -160,10 +160,10 @@ Both explicit and auto-discovered paths must resolve within the repository check You can filter changelog entries at bundle time using the `rules.bundle` configuration in your `changelog.yml` file. This is evaluated during `changelog bundle` and `changelog gh-release`, before the bundle is written. Entries that don't match are excluded from the bundle entirely. -The `{changelog}` directive and the `changelog render` command both do not apply `rules.publish`. To filter entries, use `rules.bundle` at bundle time so entries are excluded before bundling. Both receive only the bundled entries. See the [changelog bundle documentation](/cli/changelog/bundle.md#changelog-bundle-rules) for full syntax. +The `{changelog}` directive and the `changelog render` command both do not apply `rules.publish`. To filter entries, use `rules.bundle` at bundle time so entries are excluded before bundling. Both receive only the bundled entries. See the [changelog bundle documentation](/cli/changelog/cmd-bundle.md) for full syntax. `rules.bundle` supports product, type, and area filtering, and per-product overrides. -For full syntax, refer to the [rules for filtered bundles](/cli/changelog/bundle.md#changelog-bundle-rules). +For full syntax, refer to the [rules for filtered bundles](/cli/changelog/cmd-bundle.md). ## Hiding features @@ -277,7 +277,7 @@ Download the release binaries: https://github.com/elastic/elasticsearch/releases ... ``` -When present, the `release-date` field is rendered immediately after the version heading as italicized text (e.g., `_Released: April 9, 2026_`). This is purely informative for end-users and is especially useful for components released outside the usual stack lifecycle, such as APM agents and EDOT agents. If the `release-date` field is present in a bundle, it is always displayed. To control release dates, set `release_dates: false` at the bundle or profile level in the configuration (see [profile configuration](/cli/changelog/bundle.md)); when false, this prevents the date from being written to the bundle during bundling. Defaults to true when omitted. +When present, the `release-date` field is rendered immediately after the version heading as italicized text (e.g., `_Released: April 9, 2026_`). This is purely informative for end-users and is especially useful for components released outside the usual stack lifecycle, such as APM agents and EDOT agents. If the `release-date` field is present in a bundle, it is always displayed. To control release dates, set `release_dates: false` at the bundle or profile level in the configuration (see [profile configuration](/cli/changelog/cmd-bundle.md)); when false, this prevents the date from being written to the bundle during bundling. Defaults to true when omitted. Bundle descriptions are rendered when present in the bundle YAML file. The description appears after the release date (if any) but before any entry sections. Descriptions support Markdown formatting including links, lists, and multiple paragraphs. @@ -345,12 +345,12 @@ The following renders all changelog bundles from the default `changelog/bundles/ | Generating static markdown files for external use | `changelog render` command | | Selective rendering of specific versions | `changelog render` command | -The `{changelog}` directive is ideal for release notes pages that should always show the complete changelog history. For more selective workflows or external publishing, use the [`changelog render`](/cli/changelog/render.md) command. +The `{changelog}` directive is ideal for release notes pages that should always show the complete changelog history. For more selective workflows or external publishing, use the [`changelog render`](/cli/changelog/cmd-render.md) command. ## Related - [Create and bundle changelogs](/contribute/changelog.md) — Overview, workflow, and links to detailed guides -- [`changelog add`](/cli/changelog/add.md) — CLI command to create changelog entries -- [`changelog bundle`](/cli/changelog/bundle.md) — CLI command to bundle changelog entries -- [`changelog remove`](/cli/changelog/remove.md) — CLI command to remove changelog files -- [`changelog render`](/cli/changelog/render.md) — CLI command to render changelogs to markdown files +- [`changelog add`](/cli/changelog/cmd-add.md) — CLI command to create changelog entries +- [`changelog bundle`](/cli/changelog/cmd-bundle.md) — CLI command to bundle changelog entries +- [`changelog remove`](/cli/changelog/cmd-remove.md) — CLI command to remove changelog files +- [`changelog render`](/cli/changelog/cmd-render.md) — CLI command to render changelogs to markdown files diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs new file mode 100644 index 0000000000..ef299b8182 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs @@ -0,0 +1,63 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using System.Text.Json; + +namespace Elastic.Documentation.Configuration.Toc.CliReference; + +public record ArghCliSchema( + int SchemaVersion, + string EntryAssembly, + string? Description, + List GlobalOptions, + CliDefaultSchema? RootDefault, + List Commands, + List Namespaces +) +{ + public static ArghCliSchema Load(IFileInfo schemaFile) + { + var json = schemaFile.FileSystem.File.ReadAllText(schemaFile.FullName); + return JsonSerializer.Deserialize(json, ArghSchemaJsonContext.Default.ArghCliSchema) + ?? throw new InvalidOperationException($"Failed to deserialize CLI schema from {schemaFile.FullName}"); + } +} + +public record CliCommandSchema( + string[] Path, + string Name, + string? Summary, + string? Notes, + string? Usage, + string[]? Examples, + List Parameters +); + +public record CliNamespaceSchema( + string Segment, + string? Summary, + string? Notes, + List Options, + CliDefaultSchema? DefaultCommand, + List Commands, + List Namespaces +); + +public record CliParamSchema( + string Role, + string Name, + string? ShortName, + string Kind, + bool Required, + string? Summary +); + +public record CliDefaultSchema( + string? Summary, + string? Notes, + string? Usage, + string[]? Examples, + List Parameters +); diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs new file mode 100644 index 0000000000..02dc75c21d --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs @@ -0,0 +1,11 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text.Json.Serialization; + +namespace Elastic.Documentation.Configuration.Toc.CliReference; + +[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] +[JsonSerializable(typeof(ArghCliSchema))] +internal sealed partial class ArghSchemaJsonContext : JsonSerializerContext; diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs new file mode 100644 index 0000000000..f67f023a2c --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliReferenceRef.cs @@ -0,0 +1,21 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +namespace Elastic.Documentation.Configuration.Toc.CliReference; + +/// +/// Represents a CLI reference entry in the table of contents, parsed from: +/// +/// - cli: schema/cli.json +/// folder: cli-reference/ +/// +/// +public record CliReferenceRef( + string SchemaPath, + string? SupplementalFolder, + string PathRelativeToDocumentationSet, + string PathRelativeToContainer, + string Context, + IReadOnlyCollection Children +) : ITableOfContentsItem; diff --git a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs index ddbd2928b0..e171422653 100644 --- a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs +++ b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs @@ -4,6 +4,7 @@ using System.IO.Abstractions; using Elastic.Documentation.Configuration.Products; +using Elastic.Documentation.Configuration.Toc.CliReference; using Elastic.Documentation.Configuration.Toc.DetectionRules; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Extensions; @@ -82,6 +83,8 @@ public static FileRef[] GetFileRefs(ITableOfContentsItem item) return tocRef.Children.SelectMany(GetFileRefs).ToArray(); if (item is CrossLinkRef) return []; + if (item is CliReferenceRef cliRef2) + return cliRef2.Children.SelectMany(GetFileRefs).ToArray(); throw new Exception($"Unexpected item type {item.GetType().Name}"); } @@ -163,6 +166,7 @@ private static TableOfContents ResolveTableOfContents( { IsolatedTableOfContentsRef tocRef => ResolveIsolatedToc(collector, tocRef, baseDirectory, fileSystem, parentPath, containerPath, context, suppressDiagnostics), DetectionRuleOverviewRef ruleOverviewReference => ResolveRuleOverviewReference(collector, ruleOverviewReference, baseDirectory, fileSystem, parentPath, containerPath, context, suppressDiagnostics), + CliReferenceRef cliRef => ResolveCliReference(collector, cliRef, baseDirectory, fileSystem, parentPath, containerPath, context), FileRef fileRef => ResolveFileRef(collector, fileRef, baseDirectory, fileSystem, parentPath, containerPath, context, suppressDiagnostics), FolderRef folderRef => ResolveFolderRef(collector, folderRef, baseDirectory, fileSystem, parentPath, containerPath, context, suppressDiagnostics), CrossLinkRef crossLink => ResolveCrossLinkRef(collector, crossLink, baseDirectory, fileSystem, parentPath, containerPath, context), @@ -455,6 +459,66 @@ private static ITableOfContentsItem ResolveRuleOverviewReference(IDiagnosticsCol } + private static ITableOfContentsItem? ResolveCliReference( + IDiagnosticsCollector collector, + CliReferenceRef cliRef, + IDirectoryInfo baseDirectory, + IFileSystem fileSystem, + string parentPath, + string containerPath, + string context) + { + // Resolve schema path relative to docset root (context-relative for paths with '/') + string schemaFullPath; + if (cliRef.SchemaPath.Contains('/')) + { + var contextDir = fileSystem.Path.GetDirectoryName(context) ?? ""; + var contextRelativePath = fileSystem.Path.GetRelativePath(baseDirectory.FullName, contextDir); + if (contextRelativePath == ".") + contextRelativePath = ""; + schemaFullPath = string.IsNullOrEmpty(contextRelativePath) + ? cliRef.SchemaPath + : $"{contextRelativePath}/{cliRef.SchemaPath}"; + } + else + { + schemaFullPath = string.IsNullOrEmpty(parentPath) + ? cliRef.SchemaPath + : $"{parentPath}/{cliRef.SchemaPath}"; + } + + var schemaFileInfo = fileSystem.FileInfo.New(fileSystem.Path.Join(baseDirectory.FullName, schemaFullPath)); + if (!schemaFileInfo.Exists) + { + collector.EmitError(context, $"CLI schema file not found: {cliRef.SchemaPath}"); + return null; + } + + // Derive virtual root: use SupplementalFolder if set, otherwise schema path without extension + var virtualRoot = cliRef.SupplementalFolder is not null + ? cliRef.SupplementalFolder.TrimEnd('/') + : Path.ChangeExtension(schemaFullPath, null); + + var fullVirtualRoot = string.IsNullOrEmpty(parentPath) ? virtualRoot : $"{parentPath}/{virtualRoot}"; + var pathRelativeToContainer = string.IsNullOrEmpty(containerPath) + ? fullVirtualRoot + : fullVirtualRoot[(containerPath.Length + 1)..]; + + if (cliRef.SupplementalFolder is not null) + { + var supplementalDirPath = fileSystem.Path.Join(baseDirectory.FullName, cliRef.SupplementalFolder); + if (!fileSystem.Directory.Exists(supplementalDirPath)) + collector.EmitWarning(context, $"CLI supplemental docs folder not found: {cliRef.SupplementalFolder}"); + } + + // Resolve explicit children (regular docs + namespace/folder refs) relative to the virtual root + var resolvedChildren = cliRef.Children.Count > 0 + ? ResolveTableOfContents(collector, cliRef.Children, baseDirectory, fileSystem, fullVirtualRoot, containerPath, context) + : []; + + return new CliReferenceRef(schemaFullPath, cliRef.SupplementalFolder, fullVirtualRoot, pathRelativeToContainer, context, resolvedChildren); + } + /// /// Resolves a FolderRef by prepending the parent path to the folder path and recursively resolving children. /// If no children are defined, auto-discovers .md files in the folder directory. diff --git a/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs b/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs index 428f6003a2..9fe6de839c 100644 --- a/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs +++ b/src/Elastic.Documentation.Configuration/Toc/TableOfContentsYamlConverters.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using Elastic.Documentation.Configuration.Toc.CliReference; using Elastic.Documentation.Configuration.Toc.DetectionRules; using YamlDotNet.Core; using YamlDotNet.Core.Events; @@ -111,6 +112,14 @@ public class TocItemYamlConverter : IYamlTypeConverter // Capture exclude list for folder auto-discovery var exclude = dictionary.TryGetValue("exclude", out var excludeObj) && excludeObj is string[] excludeArr ? excludeArr : null; + // Check for CLI reference (cli: schema.json, optional folder: supplemental-docs/) + // Must come before the folder+file check to prevent folder: from being consumed by that branch + if (dictionary.TryGetValue("cli", out var cliSchemaPath) && cliSchemaPath is string cliSchema) + { + var supplementalFolder = dictionary.TryGetValue("folder", out var f) && f is string fStr ? fStr : null; + return new CliReferenceRef(cliSchema, supplementalFolder, cliSchema, cliSchema, placeholderContext, children); + } + // Check for folder+file combination (e.g., folder: getting-started, file: getting-started.md) // This represents a folder with a specific index file // The file becomes a child of the folder (as FolderIndexFileRef), and user-specified children follow diff --git a/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs b/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs index a211094019..e4928df058 100644 --- a/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs +++ b/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO.Abstractions; using Elastic.Documentation.Configuration.Toc; +using Elastic.Documentation.Configuration.Toc.CliReference; using Elastic.Documentation.Configuration.Toc.DetectionRules; using Elastic.Documentation.Extensions; using Elastic.Documentation.Links.CrossLinks; @@ -174,6 +175,7 @@ INavigationHomeAccessor homeAccessor CrossLinkRef crossLinkRef => CreateCrossLinkNavigation(crossLinkRef, index, parent, homeAccessor), FolderRef folderRef => CreateFolderNavigation(folderRef, index, context, parent, homeAccessor), IsolatedTableOfContentsRef tocRef => CreateTocNavigation(tocRef, index, context, parent, homeAccessor), + CliReferenceRef cliRef => CreateCliReferenceNavigation(cliRef, index, context, parent, homeAccessor), _ => null }; @@ -439,4 +441,165 @@ INavigationHomeAccessor homeAccessor return tocNavigation; } + private INavigationItem? CreateCliReferenceNavigation( + CliReferenceRef cliRef, + int index, + IDocumentationSetContext context, + INodeNavigationItem? parent, + INavigationHomeAccessor homeAccessor + ) + { + var schemaFileInfo = context.ReadFileSystem.FileInfo.New( + context.ReadFileSystem.Path.Join(context.DocumentationSourceDirectory.FullName, cliRef.SchemaPath)); + + ArghCliSchema schema; + try + { + schema = ArghCliSchema.Load(schemaFileInfo); + } + catch (Exception ex) + { + context.EmitError(context.ConfigurationPath, $"Failed to load CLI schema from {cliRef.SchemaPath}: {ex.Message}"); + return null; + } + + var virtualRoot = cliRef.PathRelativeToDocumentationSet; + var docSourceDir = context.DocumentationSourceDirectory.FullName; + + // Root folder navigation + var folderNavigation = new FolderNavigation(virtualRoot, parent, homeAccessor) { NavigationIndex = index }; + var children = new List(); + var childIndex = 0; + + // Root index file + var rootNav = MakeFileLeaf(docSourceDir, virtualRoot, [], isNamespace: true, childIndex++, folderNavigation, homeAccessor, context); + if (rootNav is not null) + children.Add(rootNav); + + // Explicit children are prepended before the schema-generated pages + foreach (var child in cliRef.Children) + { + var childNav = ConvertToNavigationItem(child, childIndex++, context, folderNavigation, homeAccessor); + if (childNav is not null) + children.Add(childNav); + } + + // All root commands + namespaces from the schema always follow + foreach (var cmd in schema.Commands) + { + var cmdNav = MakeFileLeaf(docSourceDir, virtualRoot, [cmd.Name], isNamespace: false, childIndex++, folderNavigation, homeAccessor, context); + if (cmdNav is not null) + children.Add(cmdNav); + } + foreach (var ns in schema.Namespaces) + { + var nsNav = BuildNamespaceNavigation(docSourceDir, virtualRoot, ns, [ns.Segment], childIndex++, folderNavigation, homeAccessor, context); + if (nsNav is not null) + children.Add(nsNav); + } + + if (children.Count == 0) + { + context.Collector.EmitError(cliRef.Context, $"CLI reference '{cliRef.SchemaPath}' produced no navigation items"); + return null; + } + + folderNavigation.SetNavigationItems(children); + return folderNavigation; + } + + private INavigationItem? BuildNamespaceNavigation( + string docSourceDir, + string virtualRoot, + CliNamespaceSchema ns, + string[] segments, + int index, + INodeNavigationItem parent, + INavigationHomeAccessor homeAccessor, + IDocumentationSetContext context + ) + { + // Create folder node for the namespace + var nsPath = string.Join("/", segments.Select(s => s)); + var nsFolderPath = $"{virtualRoot}/{nsPath}"; + var nsFolderNav = new FolderNavigation(nsFolderPath, parent, homeAccessor) { NavigationIndex = index }; + var children = new List(); + var childIndex = 0; + + // Namespace index file + var nsIndexNav = MakeFileLeaf(docSourceDir, virtualRoot, segments, isNamespace: true, childIndex++, nsFolderNav, homeAccessor, context); + if (nsIndexNav is not null) + children.Add(nsIndexNav); + + // Namespace commands + foreach (var cmd in ns.Commands) + { + var cmdSegments = segments.Append(cmd.Name).ToArray(); + var cmdNav = MakeFileLeaf(docSourceDir, virtualRoot, cmdSegments, isNamespace: false, childIndex++, nsFolderNav, homeAccessor, context); + if (cmdNav is not null) + children.Add(cmdNav); + } + + // Sub-namespaces + foreach (var subNs in ns.Namespaces) + { + var subSegments = segments.Append(subNs.Segment).ToArray(); + var subNav = BuildNamespaceNavigation(docSourceDir, virtualRoot, subNs, subSegments, childIndex++, nsFolderNav, homeAccessor, context); + if (subNav is not null) + children.Add(subNav); + } + + if (children.Count == 0) + return null; + + nsFolderNav.SetNavigationItems(children); + return nsFolderNav; + } + + private INavigationItem? MakeFileLeaf( + string docSourceDir, + string virtualRoot, + string[] segments, + bool isNamespace, + int index, + INodeNavigationItem parent, + INavigationHomeAccessor homeAccessor, + IDocumentationSetContext context + ) + { + var syntheticPath = SyntheticRelativePath(virtualRoot, segments, isNamespace); + var absolutePath = Path.GetFullPath(Path.Join(docSourceDir, syntheticPath)); + var fileInfo = context.ReadFileSystem.FileInfo.New(absolutePath); + + var docFile = _factory.TryCreateDocumentationFile(fileInfo, context.ReadFileSystem); + if (docFile is null) + { + context.EmitError(context.ConfigurationPath, + $"CLI reference: could not create documentation file for '{syntheticPath}'"); + return null; + } + + var args = new FileNavigationArgs(syntheticPath, syntheticPath, false, index, parent, homeAccessor); + return DocumentationNavigationFactory.CreateFileNavigationLeaf(docFile, fileInfo, args); + } + + // Relative path within doc source directory (used for file lookup and URL generation) + // Commands always use cmd- prefix to avoid collisions with namespace index.md files. + private static string SyntheticRelativePath(string virtualRoot, string[] segments, bool isNamespace) + { + if (segments.Length == 0) + return $"{virtualRoot}/index.md"; + if (isNamespace) + { + var joined = string.Join("/", segments); + return $"{virtualRoot}/{joined}/index.md"; + } + else + { + var cmdName = $"cmd-{segments[^1]}"; + var parentPath = segments.Length > 1 ? string.Join("/", segments[..^1]) + "/" : string.Empty; + return $"{virtualRoot}/{parentPath}{cmdName}.md"; + } + } + } diff --git a/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml b/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml index 050262b088..cbd1dbfaa5 100644 --- a/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml +++ b/src/Elastic.Documentation.Site/Navigation/_TocTreeNav.cshtml @@ -6,6 +6,20 @@ @{ var isTopLevel = Model.Level == 0; } +@functions { + static (string? badge, string label) ParseNavTitle(string raw) + { + if (raw.StartsWith("[ns]", StringComparison.Ordinal)) return ("ns", raw[4..]); + if (raw.StartsWith("[cmd]", StringComparison.Ordinal)) return ("cmd", raw[5..]); + return (null, raw); + } + + // Inline styles to avoid Tailwind purge — very muted tints, subtle border for definition + const string NsStyle = "background:#f5f3ff;color:#7c3aed;border:1px solid #ddd6fe;"; + const string CmdStyle = "background:#fffbeb;color:#b45309;border:1px solid #fde68a;"; + + static string BadgeStyle(string? badge) => badge == "ns" ? NsStyle : badge == "cmd" ? CmdStyle : ""; +} @if (isTopLevel && !Model.IsGlobalAssemblyBuild && !Model.IsPrimaryNavEnabled && !Model.SubTree.Index.Hidden) { var idx = Model.SubTree.Index; @@ -32,13 +46,16 @@ } if (item is INodeNavigationItem { NavigationItems.Count: 0 } group) { + var (groupBadge, groupLabel) = ParseNavTitle(group.NavigationTitle);
  • - @group.NavigationTitle + @groupLabel + @if (groupBadge == "ns") { ns } + else if (groupBadge == "cmd") { cmd }
  • } @@ -46,13 +63,16 @@ { var g = folder; var allHidden = folder.NavigationItems.All(n => n.Hidden); + var (folderBadge, folderLabel) = ParseNavTitle(g.NavigationTitle);
  • - @(g.NavigationTitle) + class="sidebar-link pr-2 content-center @(isTopLevel ? "font-semibold" : "") group-[.current]/li:text-blue-elastic! flex items-center gap-1"> + @folderLabel + @if (folderBadge == "ns") { ns } + else if (folderBadge == "cmd") { cmd } @if (!allHidden) { @@ -95,13 +115,16 @@ else if (item is ILeafNavigationItem leaf) { var hasSameTopLevelGroup = true; + var (leafBadge, leafLabel) = ParseNavTitle(leaf.NavigationTitle);
  • - @leaf.NavigationTitle + @leafLabel + @if (leafBadge == "ns") { ns } + else if (leafBadge == "cmd") { cmd }
  • } diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs new file mode 100644 index 0000000000..c1bd8fc629 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Toc.CliReference; +using Elastic.Markdown.Myst; +using Markdig.Syntax; + +namespace Elastic.Markdown.Extensions.CliReference; + +public record CliCommandFile : IO.MarkdownFile +{ + private readonly CliCommandSchema _command; + private readonly IFileInfo? _supplementalDoc; + + public CliCommandFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + CliCommandSchema command, + IFileInfo? supplementalDoc + ) : base(sourceFile, rootPath, parser, build) + { + _command = command; + _supplementalDoc = supplementalDoc; + Title = command.Name; + } + + public override string NavigationTitle => $"[cmd]{_command.Name}"; + + protected override Task GetMinimalParseDocumentAsync(Cancel ctx) + { + Title = _command.Name; + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null)); + } + + protected override Task GetParseDocumentAsync(Cancel ctx) + { + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.ParseStringAsync(markdown, SourceFile, null)); + } + + private string BuildMarkdown() + { + var supplemental = _supplementalDoc?.Exists == true + ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) + : null; + return CliMarkdownGenerator.CommandPage(_command, supplemental); + } +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs new file mode 100644 index 0000000000..24855448d2 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs @@ -0,0 +1,346 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text; +using System.Text.RegularExpressions; +using Elastic.Documentation.Configuration.Toc.CliReference; + +namespace Elastic.Markdown.Extensions.CliReference; + +internal static partial class CliMarkdownGenerator +{ + public static string RootPage(ArghCliSchema schema, string? supplementalContent) + { + var sb = new StringBuilder(); + _ = sb.AppendLine($"# {schema.EntryAssembly}"); + _ = sb.AppendLine(); + + if (supplementalContent is not null) + _ = sb.AppendLine(supplementalContent.Trim()); + else if (!string.IsNullOrWhiteSpace(schema.Description)) + _ = sb.AppendLine(schema.Description.Trim()); + + _ = sb.AppendLine(); + + if (schema.GlobalOptions.Count > 0) + { + _ = sb.AppendLine("## Global Options"); + _ = sb.AppendLine(); + AppendParameters(sb, schema.GlobalOptions); + } + + if (schema.Commands.Count > 0) + { + _ = sb.AppendLine("## Commands"); + _ = sb.AppendLine(); + foreach (var cmd in schema.Commands) + { + _ = sb.AppendLine($"`{cmd.Name}`"); + var summary = string.IsNullOrWhiteSpace(cmd.Summary) ? string.Empty : CleanSummary(cmd.Summary).description; + _ = sb.AppendLine($": {summary}"); + _ = sb.AppendLine(); + } + } + + if (schema.Namespaces.Count > 0) + { + _ = sb.AppendLine("## Namespaces"); + _ = sb.AppendLine(); + foreach (var ns in schema.Namespaces) + { + _ = sb.AppendLine($"`{ns.Segment}`"); + var summary = string.IsNullOrWhiteSpace(ns.Summary) ? string.Empty : ns.Summary; + _ = sb.AppendLine($": {summary}"); + _ = sb.AppendLine(); + } + } + + return sb.ToString(); + } + + public static string NamespacePage(CliNamespaceSchema ns, string? supplementalContent) + { + var sb = new StringBuilder(); + _ = sb.AppendLine($"# {ns.Segment}"); + _ = sb.AppendLine(); + + if (supplementalContent is not null) + _ = sb.AppendLine(supplementalContent.Trim()); + else if (!string.IsNullOrWhiteSpace(ns.Summary)) + _ = sb.AppendLine(ns.Summary.Trim()); + + _ = sb.AppendLine(); + + if (ns.Commands.Count > 0) + { + _ = sb.AppendLine("## Commands"); + _ = sb.AppendLine(); + foreach (var cmd in ns.Commands) + { + _ = sb.AppendLine($"`{cmd.Name}`"); + var summary = string.IsNullOrWhiteSpace(cmd.Summary) ? string.Empty : CleanSummary(cmd.Summary).description; + _ = sb.AppendLine($": {summary}"); + _ = sb.AppendLine(); + } + } + + if (ns.Namespaces.Count > 0) + { + _ = sb.AppendLine("## Sub-namespaces"); + _ = sb.AppendLine(); + foreach (var sub in ns.Namespaces) + { + _ = sb.AppendLine($"`{sub.Segment}`"); + var summary = string.IsNullOrWhiteSpace(sub.Summary) ? string.Empty : sub.Summary; + _ = sb.AppendLine($": {summary}"); + _ = sb.AppendLine(); + } + } + + if (ns.Options.Count > 0) + { + _ = sb.AppendLine("## Namespace Flags"); + _ = sb.AppendLine(); + AppendParameters(sb, ns.Options); + } + + return sb.ToString(); + } + + public static string CommandPage(CliCommandSchema cmd, string? supplementalContent) + { + var sb = new StringBuilder(); + _ = sb.AppendLine($"# {cmd.Name}"); + _ = sb.AppendLine(); + + if (!string.IsNullOrWhiteSpace(cmd.Usage)) + { + _ = sb.AppendLine("```bash"); + _ = sb.AppendLine(FormatUsage(cmd.Usage)); + _ = sb.AppendLine("```"); + _ = sb.AppendLine(); + } + + if (supplementalContent is not null) + _ = sb.AppendLine(supplementalContent.Trim()); + else if (!string.IsNullOrWhiteSpace(cmd.Summary)) + _ = sb.AppendLine(CleanSummary(cmd.Summary).description.Trim()); + + _ = sb.AppendLine(); + + if (cmd.Parameters.Count > 0) + { + var positionals = cmd.Parameters.Where(p => p.Role == "positional").ToList(); + var flags = cmd.Parameters.Where(p => p.Role != "positional").ToList(); + + if (positionals.Count > 0) + { + _ = sb.AppendLine("## Arguments"); + _ = sb.AppendLine(); + AppendParameters(sb, positionals); + } + + if (flags.Count > 0) + { + _ = sb.AppendLine("## Options"); + _ = sb.AppendLine(); + AppendParameters(sb, flags); + } + } + + if (cmd.Examples is { Length: > 0 }) + { + _ = sb.AppendLine("## Examples"); + _ = sb.AppendLine(); + foreach (var example in cmd.Examples) + { + if (string.IsNullOrWhiteSpace(example)) + continue; + _ = sb.AppendLine("```"); + _ = sb.AppendLine(example.Trim()); + _ = sb.AppendLine("```"); + _ = sb.AppendLine(); + } + } + + if (!string.IsNullOrWhiteSpace(cmd.Notes)) + { + _ = sb.AppendLine("## Notes"); + _ = sb.AppendLine(); + _ = sb.AppendLine(cmd.Notes.Trim()); + _ = sb.AppendLine(); + } + + return sb.ToString(); + } + + private static void AppendParameters(StringBuilder sb, IEnumerable parameters) + { + foreach (var p in parameters.Where(p => p.Name != "_")) + { + var isBool = IsBoolFlag(p.Kind); + var flagName = FormatFlagName(p); + var typeHint = isBool ? string.Empty : $" `{FormatTypeHint(p.Kind)}`"; + var requiredMarker = p.Required ? " **required**" : string.Empty; + + _ = sb.AppendLine($"{flagName}{typeHint}{requiredMarker}"); + + var (description, values, defaultValue) = CleanSummary(p.Summary); + + _ = sb.AppendLine($": {description.Trim()}"); + + // Blank line + 4-space-indented continuation paragraphs keep Values/Default inside the definition item + if (!string.IsNullOrWhiteSpace(values)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Values:** {values.Trim()}"); + } + + if (!string.IsNullOrWhiteSpace(defaultValue)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Default:** `{defaultValue.Trim()}`"); + } + + _ = sb.AppendLine(); + } + } + + private static string FormatFlagName(CliParamSchema p) + { + if (p.Role == "positional") + return $"`<{p.Name}>`"; + + var isBool = IsBoolFlag(p.Kind); + var prefix = isBool ? "`--[no-]" : "`--"; + var shortPart = p.ShortName is not null ? $"`-{p.ShortName}` " : string.Empty; + + return $"{shortPart}{prefix}{p.Name}`"; + } + + // Parses optional "Values: ..." and "Default: ..." lines that argh embeds in summary text. + private static (string description, string values, string defaultValue) CleanSummary(string? raw) + { + if (string.IsNullOrWhiteSpace(raw)) + return (string.Empty, string.Empty, string.Empty); + + // Collapse whitespace produced by XML doc indentation (newlines + leading spaces) + var normalized = WhitespaceRegex().Replace(raw.Trim(), " "); + + // Argh embeds "Values: X, Y. Default: A, B." at the end of summary text. + // Split on " Values: " first, then on " Default: " within the remainder. + const string valuesSep = " Values: "; + const string defaultSep = " Default: "; + + var valuesIdx = normalized.IndexOf(valuesSep, StringComparison.OrdinalIgnoreCase); + if (valuesIdx < 0) + { + // No Values/Default section; check for standalone Default + var defIdx = normalized.IndexOf(defaultSep, StringComparison.OrdinalIgnoreCase); + if (defIdx < 0) + return (normalized, string.Empty, string.Empty); + + return ( + normalized[..defIdx].Trim(), + string.Empty, + normalized[(defIdx + defaultSep.Length)..].Trim().TrimEnd('.') + ); + } + + var description = normalized[..valuesIdx].Trim(); + var remainder = normalized[(valuesIdx + valuesSep.Length)..]; + + var defInRemainder = remainder.IndexOf(defaultSep, StringComparison.OrdinalIgnoreCase); + if (defInRemainder < 0) + return (description, remainder.Trim().TrimEnd('.'), string.Empty); + + var values = remainder[..defInRemainder].Trim().TrimEnd('.'); + var defaultValue = remainder[(defInRemainder + defaultSep.Length)..].Trim().TrimEnd('.'); + return (description, values, defaultValue); + } + + private static bool IsBoolFlag(string kind) => + kind.StartsWith("Primitive:bool", StringComparison.OrdinalIgnoreCase) || + kind.Equals("Primitive", StringComparison.OrdinalIgnoreCase); + + private static string FormatTypeHint(string kind) + { + // "Primitive:string" → "string", "Enum:LogLevel" → "LogLevel", + // "Collection" → "enum[]", "FileInfo:..." → "path" + var colon = kind.IndexOf(':'); + var left = colon >= 0 ? kind[..colon] : kind; + var right = colon >= 0 ? kind[(colon + 1)..] : string.Empty; + + return left switch + { + "Collection" or "Collection" => "enum[]", + "Collection" => "string[]", + "Collection" or "Collection" => "int[]", + "Enum" => right.Contains('.') ? right[(right.LastIndexOf('.') + 1)..] : right, + "Primitive" => right switch + { + "string" or "string?" => "string", + "int" or "int?" or "Int32" or "Int32?" => "int", + _ => string.Empty + }, + "FileInfo" => "path", + "DirectoryInfo" => "path", + _ when left.StartsWith("Collection<") => left["Collection<".Length..].TrimEnd('>') + "[]", + _ => left + }; + } + + // Wraps a usage line to multiline bash continuation format when it exceeds 80 chars. + // Groups flag+value pairs ("--flag ") together on the same line. + private static string FormatUsage(string usage) + { + if (usage.Length <= 80) + return usage; + + var tokens = usage.Split(' '); + var groups = new List(); + var i = 0; + + // Collect the command prefix (everything before the first flag or bracket) + var prefixParts = new List(); + while (i < tokens.Length && !tokens[i].StartsWith('-') && !tokens[i].StartsWith('[') && !tokens[i].StartsWith('<')) + { + prefixParts.Add(tokens[i]); + i++; + } + groups.Add(string.Join(" ", prefixParts)); + + // Group remaining tokens: --flag pairs stay together + while (i < tokens.Length) + { + var token = tokens[i]; + if ((token.StartsWith("--") || (token.StartsWith('-') && token.Length == 2)) + && i + 1 < tokens.Length + && (tokens[i + 1].StartsWith('<') || tokens[i + 1].StartsWith("[<"))) + { + groups.Add(token + " " + tokens[i + 1]); + i += 2; + } + else + { + groups.Add(token); + i++; + } + } + + var result = new StringBuilder(); + _ = result.Append(groups[0]); + for (var g = 1; g < groups.Count; g++) + { + _ = result.Append(" \\"); + _ = result.AppendLine(); + _ = result.Append(" "); + _ = result.Append(groups[g]); + } + return result.ToString(); + } + + [GeneratedRegex(@"\s{2,}")] + private static partial Regex WhitespaceRegex(); +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs new file mode 100644 index 0000000000..cda4c19411 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Toc.CliReference; +using Elastic.Markdown.Myst; +using Markdig.Syntax; + +namespace Elastic.Markdown.Extensions.CliReference; + +public record CliNamespaceFile : IO.MarkdownFile +{ + private readonly CliNamespaceSchema _namespace; + private readonly IFileInfo? _supplementalDoc; + + public CliNamespaceFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + CliNamespaceSchema @namespace, + IFileInfo? supplementalDoc + ) : base(sourceFile, rootPath, parser, build) + { + _namespace = @namespace; + _supplementalDoc = supplementalDoc; + Title = @namespace.Segment; + } + + public override string NavigationTitle => $"[ns]{_namespace.Segment}"; + + protected override Task GetMinimalParseDocumentAsync(Cancel ctx) + { + Title = _namespace.Segment; + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null)); + } + + protected override Task GetParseDocumentAsync(Cancel ctx) + { + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.ParseStringAsync(markdown, SourceFile, null)); + } + + private string BuildMarkdown() + { + var supplemental = _supplementalDoc?.Exists == true + ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) + : null; + return CliMarkdownGenerator.NamespacePage(_namespace, supplemental); + } +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs new file mode 100644 index 0000000000..7ecc1fbad0 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -0,0 +1,337 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Toc; +using Elastic.Documentation.Configuration.Toc.CliReference; +using Elastic.Documentation.Navigation; +using Elastic.Markdown.Exporters; +using Elastic.Markdown.IO; +using Elastic.Markdown.Myst; + +namespace Elastic.Markdown.Extensions.CliReference; + +internal sealed record CliEntityInfo( + ArghCliSchema Schema, + object Entity, // ArghCliSchema | CliNamespaceSchema | CliCommandSchema + IFileInfo? SupplementalDoc +); + +public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension +{ + private BuildContext Build { get; } = build; + + private Dictionary? _syntheticFiles; + private List? _syntheticFileInfos; + + // Must be called before CreateMarkdownFile or CreateDocumentationFile can match anything. + // ScanDocumentationFiles calls this; CreateMarkdownFile also triggers it because the main + // directory scan runs before ScanDocumentationFiles, so index.md files are encountered first. + private void EnsureSyntheticFilesBuilt() + { + if (_syntheticFiles is not null) + return; + _syntheticFiles = []; + _syntheticFileInfos = BuildSyntheticFiles(); + } + + public IDocumentationFileExporter? FileExporter => null; + + public DocumentationFile? CreateDocumentationFile(IFileInfo file, MarkdownParser markdownParser) + { + EnsureSyntheticFilesBuilt(); + if (!_syntheticFiles!.TryGetValue(file.FullName, out var info)) + return null; + + return info.Entity switch + { + ArghCliSchema schema => new CliRootFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), + CliNamespaceSchema ns => new CliNamespaceFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc), + CliCommandSchema cmd => new CliCommandFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc), + _ => null + }; + } + + public MarkdownFile? CreateMarkdownFile(IFileInfo file, IDirectoryInfo sourceDirectory, MarkdownParser markdownParser) + { + // Physical CLI supplemental docs (index.md for namespaces, cmd-*.md for commands) live at the same + // path as their synthetic CLI page. EnsureSyntheticFilesBuilt() is needed here because + // CreateMarkdownFile is called during the main directory scan, before ScanDocumentationFiles runs. + var name = file.Name; + if (name != "index.md" && !name.StartsWith("cmd-", StringComparison.OrdinalIgnoreCase)) + return null; + EnsureSyntheticFilesBuilt(); + var fullPath = Path.GetFullPath(file.FullName); + if (!_syntheticFiles!.TryGetValue(fullPath, out var info)) + return null; + return info.Entity switch + { + ArghCliSchema schema => new CliRootFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), + CliNamespaceSchema ns => new CliNamespaceFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc), + CliCommandSchema cmd => new CliCommandFile(file, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc), + _ => null + }; + } + + public void VisitNavigation(INavigationItem navigation, IDocumentationFile model) { } + + public bool TryGetDocumentationFileBySlug(DocumentationSet documentationSet, string slug, out DocumentationFile? documentationFile) + { + documentationFile = null; + return false; + } + + public IReadOnlyCollection<(IFileInfo, DocumentationFile)> ScanDocumentationFiles(Func defaultFileHandling) + { + EnsureSyntheticFilesBuilt(); + if (_syntheticFileInfos is not { Count: > 0 }) + return []; + + var results = new List<(IFileInfo, DocumentationFile)>(); + foreach (var fileInfo in _syntheticFileInfos) + { + // When a supplemental index.md physically exists at the synthetic path (e.g. changelog/index.md), + // skip it here — the factory's directory scan will find the real file and call CreateMarkdownFile, + // which picks up the CliNamespaceFile from _syntheticFiles. Registering both would cause duplicate keys. + if (fileInfo.Exists) + continue; + + // defaultFileHandling calls extension.CreateDocumentationFile(file, markdownParser) + // which routes back to our CreateDocumentationFile above — now with the MarkdownParser available + var doc = defaultFileHandling(fileInfo, Build.DocumentationSourceDirectory); + results.Add((fileInfo, doc)); + } + return results; + } + + private List BuildSyntheticFiles() + { + var cliRefs = FindCliReferenceRefs(Build.ConfigurationYaml.TableOfContents); + var fileInfos = new List(); + + foreach (var cliRef in cliRefs) + { + var schemaFileInfo = Build.ReadFileSystem.FileInfo.New( + Build.ReadFileSystem.Path.Join(Build.DocumentationSourceDirectory.FullName, cliRef.SchemaPath)); + + if (!schemaFileInfo.Exists) + continue; + + ArghCliSchema schema; + try + { + schema = ArghCliSchema.Load(schemaFileInfo); + } + catch (Exception ex) + { + Build.Collector.EmitError(schemaFileInfo, $"Failed to load CLI schema: {ex.Message}"); + continue; + } + + var virtualRoot = cliRef.PathRelativeToDocumentationSet; + var supplementalDirPath = cliRef.SupplementalFolder is not null + ? Build.ReadFileSystem.Path.Join(Build.DocumentationSourceDirectory.FullName, cliRef.SupplementalFolder) + : null; + + var matched = new HashSet(StringComparer.OrdinalIgnoreCase); + + // Root page + var rootSupplemental = FindSupplemental(supplementalDirPath, [], isNamespace: true, matched); + var rootSyntheticPath = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [], isNamespace: true); + var rootFileInfo = Build.ReadFileSystem.FileInfo.New(rootSyntheticPath); + _syntheticFiles![rootSyntheticPath] = new CliEntityInfo(schema, schema, rootSupplemental); + fileInfos.Add(rootFileInfo); + + // Root commands + foreach (var cmd in schema.Commands) + { + var path = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [cmd.Name], isNamespace: false); + var fileInfo = Build.ReadFileSystem.FileInfo.New(path); + var supplemental = FindSupplemental(supplementalDirPath, [cmd.Name], isNamespace: false, matched); + _syntheticFiles[path] = new CliEntityInfo(schema, cmd, supplemental); + fileInfos.Add(fileInfo); + } + + // Namespaces (recursive) + CollectNamespaceFiles(Build.DocumentationSourceDirectory.FullName, virtualRoot, supplementalDirPath, schema.Namespaces, [], matched, fileInfos, schema); + + // Validate supplemental files + if (supplementalDirPath is not null && Build.ReadFileSystem.Directory.Exists(supplementalDirPath)) + ValidateSupplementalFiles(supplementalDirPath, matched, cliRef.Context); + } + + return fileInfos; + } + + private void CollectNamespaceFiles( + string docSourceDir, + string virtualRoot, + string? supplementalDirPath, + IReadOnlyCollection namespaces, + string[] nsPath, + HashSet matched, + List fileInfos, + ArghCliSchema schema) + { + foreach (var ns in namespaces) + { + var fullNsPath = nsPath.Append(ns.Segment).ToArray(); + + var nsFilePath = SyntheticPath(docSourceDir, virtualRoot, fullNsPath, isNamespace: true); + var nsFileInfo = Build.ReadFileSystem.FileInfo.New(nsFilePath); + var nsSupplemental = FindSupplemental(supplementalDirPath, fullNsPath, isNamespace: true, matched); + _syntheticFiles![nsFilePath] = new CliEntityInfo(schema, ns, nsSupplemental); + fileInfos.Add(nsFileInfo); + + foreach (var cmd in ns.Commands) + { + var cmdSegments = fullNsPath.Append(cmd.Name).ToArray(); + var cmdPath = SyntheticPath(docSourceDir, virtualRoot, cmdSegments, isNamespace: false); + var cmdFileInfo = Build.ReadFileSystem.FileInfo.New(cmdPath); + var cmdSupplemental = FindSupplemental(supplementalDirPath, cmdSegments, isNamespace: false, matched); + _syntheticFiles[cmdPath] = new CliEntityInfo(schema, cmd, cmdSupplemental); + fileInfos.Add(cmdFileInfo); + } + + CollectNamespaceFiles(docSourceDir, virtualRoot, supplementalDirPath, ns.Namespaces, fullNsPath, matched, fileInfos, schema); + } + } + + // Absolute synthetic path: docSourceDir/virtualRoot/segments.../index.md (namespace) or .../cmd-.md (command) + // Commands always use the cmd- prefix to avoid collisions with namespace index.md files. + // GetFullPath normalizes any ".." segments so the key matches IFileInfo.FullName lookups. + internal static string SyntheticPath(string docSourceDir, string virtualRoot, string[] segments, bool isNamespace) + { + if (segments.Length == 0) + return Path.GetFullPath(Path.Join(docSourceDir, virtualRoot, "index.md")); + + if (isNamespace) + { + var joined = Path.Combine([.. segments]); + return Path.GetFullPath(Path.Join(docSourceDir, virtualRoot, joined, "index.md")); + } + else + { + // Command pages: all parent segments as path, final name prefixed with cmd- + var cmdName = $"cmd-{segments[^1]}"; + var parentSegments = segments.Length > 1 ? segments[..^1] : []; + var parentPath = parentSegments.Length > 0 ? Path.Combine([.. parentSegments]) : string.Empty; + return string.IsNullOrEmpty(parentPath) + ? Path.GetFullPath(Path.Join(docSourceDir, virtualRoot, cmdName + ".md")) + : Path.GetFullPath(Path.Join(docSourceDir, virtualRoot, parentPath, cmdName + ".md")); + } + } + + private IFileInfo? FindSupplemental(string? supplementalDirPath, string[] segments, bool isNamespace, HashSet matched) + { + if (supplementalDirPath is null) + return null; + + var candidates = isNamespace + ? HierarchyCandidates(segments, isNamespace: true).Concat(FlatPrefixCandidates(segments, isNamespace: true)) + : HierarchyCandidates(segments, isNamespace: false).Concat(FlatPrefixCandidates(segments, isNamespace: false)); + + foreach (var candidate in candidates) + { + var fullPath = Path.Join(supplementalDirPath, candidate); + var fileInfo = Build.ReadFileSystem.FileInfo.New(fullPath); + if (!fileInfo.Exists) + continue; + _ = matched.Add(fullPath); + return fileInfo; + } + return null; + } + + // hierarchy style for namespaces: changelog/index.md (natural folder layout) then changelog/ns-changelog.md + // hierarchy style for commands: assembler/cmd-build.md + private static IEnumerable HierarchyCandidates(string[] segments, bool isNamespace) + { + if (segments.Length == 0) + { + yield return "ns-root.md"; + yield break; + } + + if (isNamespace) + { + // index.md inside a subfolder named after the namespace path (e.g. changelog/index.md) + var joinedPath = string.Join("/", segments); + yield return $"{joinedPath}/index.md"; + } + + var prefix = isNamespace ? "ns-" : "cmd-"; + var lastName = segments[^1]; + var folder = segments.Length > 1 ? string.Join("/", segments[..^1]) + "/" : string.Empty; + yield return $"{folder}{prefix}{lastName}.md"; + } + + // flat style: ns-assembler-content-source.md, cmd-assembler-build.md + private static IEnumerable FlatPrefixCandidates(string[] segments, bool isNamespace) + { + if (segments.Length == 0) + yield break; + + var prefix = isNamespace ? "ns-" : "cmd-"; + var joined = string.Join("-", segments); + yield return $"{prefix}{joined}.md"; + } + + private void ValidateSupplementalFiles(string supplementalDirPath, HashSet matched, string context) + { + foreach (var file in Build.ReadFileSystem.Directory + .EnumerateFiles(supplementalDirPath, "*.md", SearchOption.AllDirectories)) + { + var name = Path.GetFileName(file); + var relPath = Path.GetRelativePath(supplementalDirPath, file); + + if (name == "index.md") + { + // index.md at the supplemental root is not valid — must be inside a namespace subfolder + if (Path.GetDirectoryName(relPath) is "" or null or ".") + Build.Collector.EmitError(context, $"CLI supplemental docs folder must not contain a root-level index.md"); + else if (!matched.Contains(file)) + Build.Collector.EmitError(context, $"CLI supplemental 'index.md' at '{relPath}' does not match any CLI namespace (expected a subfolder named after the namespace path)"); + continue; + } + + if (!name.StartsWith("ns-", StringComparison.OrdinalIgnoreCase) && + !name.StartsWith("cmd-", StringComparison.OrdinalIgnoreCase)) + continue; + + if (!matched.Contains(file)) + Build.Collector.EmitError(context, $"CLI supplemental file '{relPath}' does not match any CLI namespace or command"); + } + } + + private static IReadOnlyCollection FindCliReferenceRefs(IReadOnlyCollection items) + { + var found = new List(); + Traverse(items, found); + return found; + + static void Traverse(IReadOnlyCollection items, List found) + { + foreach (var item in items) + { + if (item is CliReferenceRef cliRef) + { + found.Add(cliRef); + continue; + } + + var children = item switch + { + FileRef f => f.Children, + FolderRef f => f.Children, + IsolatedTableOfContentsRef t => t.Children, + _ => null + }; + if (children is { Count: > 0 }) + Traverse(children, found); + } + } + } +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs new file mode 100644 index 0000000000..a860221d3a --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs @@ -0,0 +1,52 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using Elastic.Documentation.Configuration; +using Elastic.Documentation.Configuration.Toc.CliReference; +using Elastic.Markdown.Myst; +using Markdig.Syntax; + +namespace Elastic.Markdown.Extensions.CliReference; + +public record CliRootFile : IO.MarkdownFile +{ + private readonly ArghCliSchema _schema; + private readonly IFileInfo? _supplementalDoc; + + public CliRootFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + ArghCliSchema schema, + IFileInfo? supplementalDoc + ) : base(sourceFile, rootPath, parser, build) + { + _schema = schema; + _supplementalDoc = supplementalDoc; + Title = schema.EntryAssembly; + } + + protected override Task GetMinimalParseDocumentAsync(Cancel ctx) + { + Title = _schema.EntryAssembly; + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.MinimalParseStringAsync(markdown, SourceFile, null)); + } + + protected override Task GetParseDocumentAsync(Cancel ctx) + { + var markdown = BuildMarkdown(); + return Task.FromResult(MarkdownParser.ParseStringAsync(markdown, SourceFile, null)); + } + + private string BuildMarkdown() + { + var supplemental = _supplementalDoc?.Exists == true + ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) + : null; + return CliMarkdownGenerator.RootPage(_schema, supplemental); + } +} diff --git a/src/Elastic.Markdown/IO/DocumentationSet.cs b/src/Elastic.Markdown/IO/DocumentationSet.cs index 910678741d..9068220898 100644 --- a/src/Elastic.Markdown/IO/DocumentationSet.cs +++ b/src/Elastic.Markdown/IO/DocumentationSet.cs @@ -10,12 +10,15 @@ using Elastic.Documentation; using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.Builder; +using Elastic.Documentation.Configuration.Toc; +using Elastic.Documentation.Configuration.Toc.CliReference; using Elastic.Documentation.Links; using Elastic.Documentation.Links.CrossLinks; using Elastic.Documentation.Navigation; using Elastic.Documentation.Navigation.Isolated.Node; using Elastic.Documentation.Site.Navigation; using Elastic.Markdown.Extensions; +using Elastic.Markdown.Extensions.CliReference; using Elastic.Markdown.Extensions.DetectionRules; using Elastic.Markdown.Myst; using Microsoft.Extensions.Logging; @@ -296,6 +299,30 @@ private IReadOnlyCollection InstantiateExtensions() } } + // Auto-enable CLI reference extension when the TOC contains a cli: entry + if (HasCliReferenceRef(Context.ConfigurationYaml.TableOfContents)) + list.Add(new CliReferenceDocsBuilderExtension(Context)); + return list.AsReadOnly(); } + + private static bool HasCliReferenceRef(IReadOnlyCollection items) + { + foreach (var item in items) + { + if (item is CliReferenceRef) + return true; + + var children = item switch + { + FileRef f => f.Children, + FolderRef f => f.Children, + IsolatedTableOfContentsRef t => t.Children, + _ => null + }; + if (children is { Count: > 0 } && HasCliReferenceRef(children)) + return true; + } + return false; + } } diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index bd60000099..86333aba6a 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -76,7 +76,7 @@ protected set public string? Description { get; private set; } [field: AllowNull, MaybeNull] - public string NavigationTitle + public virtual string NavigationTitle { get => !string.IsNullOrEmpty(field) ? field : Title ?? string.Empty; private set => field = value.StripMarkdown(); diff --git a/src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml b/src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml index 0ee990d0be..d239d1a963 100644 --- a/src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml +++ b/src/Elastic.Markdown/Layout/_Breadcrumbs.cshtml @@ -1,4 +1,9 @@ @inherits RazorSlice +@functions { + static string StripBadge(string t) => + t.StartsWith("[ns]", StringComparison.Ordinal) ? t[4..] : + t.StartsWith("[cmd]", StringComparison.Ordinal) ? t[5..] : t; +} @{ var targets = Model.Breadcrumbs; var crumbs = targets.ToList(); @@ -16,7 +21,7 @@ href="@item.Url" @(i == 0 ? "hx-disable=true" : "") @Model.Htmx.GetHxAttributes(Model.CurrentNavigationItem?.NavigationRoot.Id == item.NavigationRoot.Id)> - @item.NavigationTitle + @StripBadge(item.NavigationTitle) @if (i < crumbs.Count - 1) { diff --git a/src/Elastic.Markdown/Layout/_PrevNextNav.cshtml b/src/Elastic.Markdown/Layout/_PrevNextNav.cshtml index e4b92fb47d..1f51d69090 100644 --- a/src/Elastic.Markdown/Layout/_PrevNextNav.cshtml +++ b/src/Elastic.Markdown/Layout/_PrevNextNav.cshtml @@ -1,4 +1,9 @@ @inherits RazorSlice +@functions { + static string StripBadge(string t) => + t.StartsWith("[ns]", StringComparison.Ordinal) ? t[4..] : + t.StartsWith("[cmd]", StringComparison.Ordinal) ? t[5..] : t; +}
    public static ScopedFileSystem InMemory() => new(new MockFileSystem(), WorkingDirectoryReadOptions); + /// + /// Like but additionally scopes the mock filesystem to 's + /// git root. Use when serving docs from a directory outside the current working tree so that the + /// in-memory output path (<source>/.artifacts/docs/html) passes scope validation. + /// + public static ScopedFileSystem InMemoryForPath(string? path) + { + if (path is null) + return InMemory(); + var root = Paths.FindGitRoot(path); + if (root == Paths.WorkingDirectoryRoot.FullName) + return InMemory(); + return new(new MockFileSystem(), new ScopedFileSystemOptions( + [Paths.WorkingDirectoryRoot.FullName, Paths.ApplicationData.FullName, root]) + { + AllowedHiddenFolderNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".artifacts" }, + AllowedHiddenFileNames = new HashSet(StringComparer.OrdinalIgnoreCase) { ".git", ".doc.state" } + }); + } + /// /// Scopes to and /// for reading. Use when the inner FS contains files diff --git a/src/Elastic.Documentation.Site/Assets/styles.css b/src/Elastic.Documentation.Site/Assets/styles.css index 0d2231ba81..ab502704e2 100644 --- a/src/Elastic.Documentation.Site/Assets/styles.css +++ b/src/Elastic.Documentation.Site/Assets/styles.css @@ -477,3 +477,35 @@ dl dt[id] { opacity: 1; } } + +/* CLI page type badge in

    — right-aligned span, colored by type */ +h1:has(.cli-badge-ns, .cli-badge-cmd) { + display: flex; + align-items: center; +} + +.cli-badge-ns, +.cli-badge-cmd { + margin-left: auto; + flex-shrink: 0; + font-size: 0.34em; + font-family: var(--font-body, sans-serif); + font-weight: 700; + padding: 1px 6px; + border-radius: 3px; + letter-spacing: 0.04em; + text-transform: lowercase; + line-height: 1.5; +} + +.cli-badge-ns { + background: #f5f3ff; + color: #7c3aed; + border: 1px solid #ddd6fe; +} + +.cli-badge-cmd { + background: #fffbeb; + color: #b45309; + border: 1px solid #fde68a; +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs index c1bd8fc629..4438f1cccc 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs @@ -14,6 +14,9 @@ public record CliCommandFile : IO.MarkdownFile { private readonly CliCommandSchema _command; private readonly IFileInfo? _supplementalDoc; + private readonly string? _binaryName; + + private readonly string[] _fullPath; public CliCommandFile( IFileInfo sourceFile, @@ -21,11 +24,15 @@ public CliCommandFile( MarkdownParser parser, BuildContext build, CliCommandSchema command, - IFileInfo? supplementalDoc + IFileInfo? supplementalDoc, + string[]? fullPath = null, + string? binaryName = null ) : base(sourceFile, rootPath, parser, build) { _command = command; _supplementalDoc = supplementalDoc; + _fullPath = fullPath ?? [command.Name]; + _binaryName = binaryName; Title = command.Name; } @@ -49,6 +56,6 @@ private string BuildMarkdown() var supplemental = _supplementalDoc?.Exists == true ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) : null; - return CliMarkdownGenerator.CommandPage(_command, supplemental); + return CliMarkdownGenerator.CommandPage(_command, supplemental, _fullPath, _binaryName); } } diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs index 327ba22b3d..1843235758 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs @@ -50,10 +50,17 @@ public static string RootPage(ArghCliSchema schema, string? supplementalContent) return sb.ToString(); } - public static string NamespacePage(CliNamespaceSchema ns, string? supplementalContent) + public static string NamespacePage(CliNamespaceSchema ns, string? supplementalContent, string[]? fullPath = null, string? binaryName = null) { var sb = new StringBuilder(); - _ = sb.AppendLine($"# {ns.Segment}"); + var heading = fullPath is { Length: > 0 } ? string.Join(" ", fullPath) : ns.Segment; + _ = sb.AppendLine($"# {heading} cli namespace"); + _ = sb.AppendLine(); + + // Usage codeblock: binary full-path --help + _ = sb.AppendLine("```bash"); + _ = sb.AppendLine($"{binaryName ?? heading} {heading} --help"); + _ = sb.AppendLine("```"); _ = sb.AppendLine(); if (supplementalContent is not null) @@ -90,19 +97,21 @@ public static string NamespacePage(CliNamespaceSchema ns, string? supplementalCo return sb.ToString(); } - public static string CommandPage(CliCommandSchema cmd, string? supplementalContent) + public static string CommandPage(CliCommandSchema cmd, string? supplementalContent, string[]? fullPath = null, string? binaryName = null) { var sb = new StringBuilder(); - _ = sb.AppendLine($"# {cmd.Name}"); + var heading = fullPath is { Length: > 0 } ? string.Join(" ", fullPath) : cmd.Name; + _ = sb.AppendLine($"# {heading} cli command"); _ = sb.AppendLine(); - if (!string.IsNullOrWhiteSpace(cmd.Usage)) - { - _ = sb.AppendLine("```bash"); - _ = sb.AppendLine(FormatUsage(cmd.Usage)); - _ = sb.AppendLine("```"); - _ = sb.AppendLine(); - } + var usage = !string.IsNullOrWhiteSpace(cmd.Usage) + ? cmd.Usage + : GenerateUsage(cmd, fullPath, binaryName); + + _ = sb.AppendLine("```bash"); + _ = sb.AppendLine(FormatUsage(usage)); + _ = sb.AppendLine("```"); + _ = sb.AppendLine(); if (supplementalContent is not null) _ = sb.AppendLine(supplementalContent.Trim()); @@ -214,6 +223,38 @@ private static void AppendParameters(StringBuilder sb, IEnumerable(); + if (!string.IsNullOrWhiteSpace(binaryName)) + parts.Add(binaryName); + if (fullPath is { Length: > 0 }) + parts.AddRange(fullPath); + else + parts.Add(cmd.Name); + + var visible = cmd.Parameters.Where(p => p.Name != "_" && !p.Hidden).ToList(); + var positionals = visible.Where(p => p.Role == "positional").ToList(); + var requiredFlags = visible.Where(p => p.Role != "positional" && p.Required).ToList(); + var optionalFlags = visible.Where(p => p.Role != "positional" && !p.Required).ToList(); + + foreach (var p in positionals) + parts.Add(p.Required ? $"<{p.Name}>" : $"[<{p.Name}>]"); + + foreach (var p in requiredFlags) + { + if (IsBoolFlag(p.Type)) + parts.Add($"--{p.Name}"); + else + parts.Add($"--{p.Name} <{p.Name}>"); + } + + if (optionalFlags.Count > 0) + parts.Add("[options]"); + + return string.Join(" ", parts); + } + private static string FormatFlagName(CliParamSchema p) { if (p.Role == "positional") diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs index cda4c19411..dea2f84eb6 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs @@ -14,6 +14,9 @@ public record CliNamespaceFile : IO.MarkdownFile { private readonly CliNamespaceSchema _namespace; private readonly IFileInfo? _supplementalDoc; + private readonly string? _binaryName; + + private readonly string[] _fullPath; public CliNamespaceFile( IFileInfo sourceFile, @@ -21,11 +24,15 @@ public CliNamespaceFile( MarkdownParser parser, BuildContext build, CliNamespaceSchema @namespace, - IFileInfo? supplementalDoc + IFileInfo? supplementalDoc, + string[]? fullPath = null, + string? binaryName = null ) : base(sourceFile, rootPath, parser, build) { _namespace = @namespace; _supplementalDoc = supplementalDoc; + _fullPath = fullPath ?? [@namespace.Segment]; + _binaryName = binaryName; Title = @namespace.Segment; } @@ -49,6 +56,6 @@ private string BuildMarkdown() var supplemental = _supplementalDoc?.Exists == true ? _supplementalDoc.FileSystem.File.ReadAllText(_supplementalDoc.FullName) : null; - return CliMarkdownGenerator.NamespacePage(_namespace, supplemental); + return CliMarkdownGenerator.NamespacePage(_namespace, supplemental, _fullPath, _binaryName); } } diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs index 24a11d7057..adba6c2c78 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -18,7 +18,9 @@ internal sealed record CliEntityInfo( object Entity, // ArghCliSchema | CliNamespaceSchema | CliCommandSchema IFileInfo? SupplementalDoc, /// The clean synthetic file (no cmd- prefix) — used as the MarkdownFile source for correct URL generation. - IFileInfo? CleanSyntheticFile = null + IFileInfo? CleanSyntheticFile = null, + /// Full path segments used to build the page heading (e.g. ["assembler", "bloom-filter"]). + string[]? FullPath = null ); public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension @@ -108,8 +110,8 @@ private void EnsureSyntheticFilesBuilt() info.Entity switch { ArghCliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), - CliNamespaceSchema ns => new CliNamespaceFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc), - CliCommandSchema cmd => new CliCommandFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc), + CliNamespaceSchema ns => new CliNamespaceFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc, info.FullPath ?? [ns.Segment], info.Schema.Name), + CliCommandSchema cmd => new CliCommandFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc, info.FullPath ?? [cmd.Name], info.Schema.Name), _ => null }; @@ -191,7 +193,7 @@ private List BuildSyntheticFiles() var path = SyntheticPath(Build.DocumentationSourceDirectory.FullName, virtualRoot, [cmd.Name], isNamespace: false); var fileInfo = Build.ReadFileSystem.FileInfo.New(path); var supplemental = FindSupplemental(supplementalDirPath, [cmd.Name], isNamespace: false, matched); - var cmdInfo = new CliEntityInfo(schema, cmd, supplemental, fileInfo); + var cmdInfo = new CliEntityInfo(schema, cmd, supplemental, fileInfo, FullPath: [cmd.Name]); _syntheticFiles[path] = cmdInfo; if (supplemental != null) _supplementalFiles![supplemental.FullName] = cmdInfo; @@ -226,7 +228,7 @@ private void CollectNamespaceFiles( var nsFilePath = SyntheticPath(docSourceDir, virtualRoot, fullNsPath, isNamespace: true); var nsFileInfo = Build.ReadFileSystem.FileInfo.New(nsFilePath); var nsSupplemental = FindSupplemental(supplementalDirPath, fullNsPath, isNamespace: true, matched); - var nsInfo = new CliEntityInfo(schema, ns, nsSupplemental, nsFileInfo); + var nsInfo = new CliEntityInfo(schema, ns, nsSupplemental, nsFileInfo, FullPath: fullNsPath); _syntheticFiles![nsFilePath] = nsInfo; if (nsSupplemental != null) _supplementalFiles![nsSupplemental.FullName] = nsInfo; @@ -238,7 +240,7 @@ private void CollectNamespaceFiles( var cmdPath = SyntheticPath(docSourceDir, virtualRoot, cmdSegments, isNamespace: false); var cmdFileInfo = Build.ReadFileSystem.FileInfo.New(cmdPath); var cmdSupplemental = FindSupplemental(supplementalDirPath, cmdSegments, isNamespace: false, matched); - var cmdInfo = new CliEntityInfo(schema, cmd, cmdSupplemental, cmdFileInfo); + var cmdInfo = new CliEntityInfo(schema, cmd, cmdSupplemental, cmdFileInfo, FullPath: cmdSegments); _syntheticFiles[cmdPath] = cmdInfo; if (cmdSupplemental != null) _supplementalFiles![cmdSupplemental.FullName] = cmdInfo; diff --git a/src/tooling/docs-builder/Commands/ServeCommand.cs b/src/tooling/docs-builder/Commands/ServeCommand.cs index 10f55ed6ec..12bf06ea4d 100644 --- a/src/tooling/docs-builder/Commands/ServeCommand.cs +++ b/src/tooling/docs-builder/Commands/ServeCommand.cs @@ -23,7 +23,7 @@ internal sealed class ServeCommand(ILoggerFactory logFactory, IConfigurationCont [CommandName("serve")] public async Task Serve(GlobalCliOptions _, [Existing, ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? path = null, int port = 3000, bool watch = false, CancellationToken ct = default) { - var host = new DocumentationWebHost(logFactory, path?.FullName, port, FileSystemFactory.RealGitRootForPath(path?.FullName), FileSystemFactory.InMemory(), configurationContext, watch); + var host = new DocumentationWebHost(logFactory, path?.FullName, port, FileSystemFactory.RealGitRootForPath(path?.FullName), FileSystemFactory.InMemoryForPath(path?.FullName), configurationContext, watch); await host.RunAsync(ct); _logger.LogInformation("Find your documentation at http://localhost:{Port}/{Path}", port, host.GeneratorState.Generator.DocumentationSet.FirstInterestingUrl.TrimStart('/') diff --git a/src/tooling/docs-builder/Http/InMemoryBuildState.cs b/src/tooling/docs-builder/Http/InMemoryBuildState.cs index 628b752b67..6533e1d549 100644 --- a/src/tooling/docs-builder/Http/InMemoryBuildState.cs +++ b/src/tooling/docs-builder/Http/InMemoryBuildState.cs @@ -53,8 +53,10 @@ public class InMemoryBuildState(ILoggerFactory loggerFactory, IConfigurationCont private readonly Lock _diagnosticsLock = new(); private readonly List _diagnostics = []; - // Reuse MockFileSystem across builds to benefit from caching - private readonly ScopedFileSystem _writeFs = FileSystemFactory.InMemory(); + // Reuse MockFileSystem across builds to benefit from caching. + // Initialized lazily on first ExecuteBuildAsync so we can scope it to the source path. + private ScopedFileSystem? _writeFs; + private string? _writeFsPath; // Broadcast: maintain list of connected client channels private readonly Lock _clientsLock = new(); @@ -170,6 +172,11 @@ private async Task ExecuteBuildAsync(string sourcePath, Cancel ct) var streamingCollector = new StreamingDiagnosticsCollector(_loggerFactory, this); var readFs = FileSystemFactory.RealGitRootForPath(sourcePath); + if (_writeFs is null || _writeFsPath != sourcePath) + { + _writeFs = FileSystemFactory.InMemoryForPath(sourcePath); + _writeFsPath = sourcePath; + } var service = new IsolatedBuildService(_loggerFactory, _configurationContext, new NullCoreService(), SystemEnvironmentVariables.Instance); _logger.LogInformation("Starting in-memory validation build for {Path}", sourcePath); @@ -188,7 +195,7 @@ private async Task ExecuteBuildAsync(string sourcePath, Cancel ct) SkipApi = true, SkipCrossLinks = false }, - _writeFs, // reuse MockFileSystem across builds for caching + _writeFs, // reuse MockFileSystem across builds for caching; initialized above ct ); From 9c3fb99402ccc65c399bdcd71709783b8917b2e4 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 30 Apr 2026 21:01:29 +0200 Subject: [PATCH 09/18] feat(cli-docs): allow index.md as CLI root supplemental; nav title shows "{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) --- .../CliReference/CliReferenceDocsBuilderExtension.cs | 8 +++----- .../Extensions/CliReference/CliRootFile.cs | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs index adba6c2c78..97700ac28a 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -305,6 +305,7 @@ private static IEnumerable HierarchyCandidates(string[] segments, bool i { if (segments.Length == 0) { + yield return "index.md"; yield return "ns-root.md"; yield break; } @@ -343,11 +344,8 @@ private void ValidateSupplementalFiles(string supplementalDirPath, HashSet $"{_schema.Name} CLI"; + protected override Task GetMinimalParseDocumentAsync(Cancel ctx) { Title = _schema.Name; From c0a701e1dfbe654185a61151928b8643d0aba9fe Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 30 Apr 2026 21:38:59 +0200 Subject: [PATCH 10/18] docs(syntax): add page-card directive reference page Co-Authored-By: Claude Sonnet 4.6 (1M context) --- docs/_docset.yml | 1 + docs/syntax/directives.md | 1 + docs/syntax/page-card.md | 119 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 docs/syntax/page-card.md diff --git a/docs/_docset.yml b/docs/_docset.yml index 7a7f0b8274..d2bc1c11b5 100644 --- a/docs/_docset.yml +++ b/docs/_docset.yml @@ -150,6 +150,7 @@ toc: - file: line_breaks.md - file: links.md - file: list-sub-pages.md + - file: page-card.md - file: passthrough.md - file: sidebars.md - file: stepper.md diff --git a/docs/syntax/directives.md b/docs/syntax/directives.md index 556953a045..b3754b6c6e 100644 --- a/docs/syntax/directives.md +++ b/docs/syntax/directives.md @@ -75,6 +75,7 @@ The following directives are available: - [Include](file_inclusion.md) - Include content from other files - [List sub-pages](list-sub-pages.md) - List sibling pages in the current section - [Math](math.md) - Mathematical expressions and equations +- [Page cards](page-card.md) - Full-width clickable navigation rows - [Settings](automated_settings.md) - Configuration blocks - [Stepper](stepper.md) - Step-by-step content - [Tabs](tabs.md) - Tabbed content organization diff --git a/docs/syntax/page-card.md b/docs/syntax/page-card.md new file mode 100644 index 0000000000..133da106e2 --- /dev/null +++ b/docs/syntax/page-card.md @@ -0,0 +1,119 @@ +# Page cards + +Page cards are full-width, clickable navigation rows that link to another page in the docs. Use them to build index-style landing pages where you want each destination to be visually prominent and immediately navigable. + +:::{page-card} [Admonitions](admonitions.md) +Callout boxes for notes, warnings, tips, and other asides. +::: + +## Basic usage + +A page card takes a single Markdown link as its argument. The description body is optional. + +:::::::{tab-set} +::::::{tab-item} Output +:::{page-card} [Code blocks](code.md) +Syntax-highlighted code with copy button, callouts, and console output support. +::: +:::::: + +::::::{tab-item} Markdown +```markdown +:::{page-card} [Code blocks](code.md) +Syntax-highlighted code with copy button, callouts, and console output support. +::: +``` +:::::: +::::::: + +## Without a description + +:::::::{tab-set} +::::::{tab-item} Output +:::{page-card} [Tables](tables.md) +::: +:::::: + +::::::{tab-item} Markdown +```markdown +:::{page-card} [Tables](tables.md) +::: +``` +:::::: +::::::: + +## Stacking cards + +Consecutive page cards stack into a list automatically — no container directive required. + +:::::::{tab-set} +::::::{tab-item} Output +:::{page-card} [Admonitions](admonitions.md) +Callout boxes for notes, warnings, tips, and important asides. +::: + +:::{page-card} [Tabs](tabs.md) +Organise related content into selectable tab panels. +::: + +:::{page-card} [Stepper](stepper.md) +Step-by-step instructions with numbered visual progression. +::: +:::::: + +::::::{tab-item} Markdown +```markdown +:::{page-card} [Admonitions](admonitions.md) +Callout boxes for notes, warnings, tips, and important asides. +::: + +:::{page-card} [Tabs](tabs.md) +Organise related content into selectable tab panels. +::: + +:::{page-card} [Stepper](stepper.md) +Step-by-step instructions with numbered visual progression. +::: +``` +:::::: +::::::: + +## Link types + +### Local links + +The most common use — a relative path to another `.md` file in the same documentation set: + +```markdown +:::{page-card} [Configuration](./configuration.md) +How to configure contexts and credentials. +::: +``` + +### Cross-repository links + +Page cards support [cross-repository links](links.md#cross-repository-links) using the `scheme://path` syntax: + +```markdown +:::{page-card} [Getting Started](docs-content://get-started/introduction.md) +Learn the basics of the Elastic Stack. +::: +``` + +### Absolute URLs are not allowed + +Page cards are for in-docs navigation only. Absolute `http://` or `https://` URLs are rejected at build time: + +```markdown +:::{page-card} [Elastic website](https://elastic.co) ← build error +::: +``` + +Use a standard Markdown link or a [button](buttons.md) for external destinations. + +## Reference + +| Part | Required | Description | +|------|----------|-------------| +| Argument | Yes | A Markdown link `[Title](url)` — title becomes the card heading, url must be a local `.md` path or a crosslink. | +| Body | No | One or more lines of description text rendered below the title. | From bd8c56001d2d1e950794bc0228217e22fcb06d69 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 4 May 2026 21:02:15 +0200 Subject: [PATCH 11/18] fix(ci): regenerate cli-schema.json; fix import ordering and CSS formatting - 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) --- docs/cli-schema.json | 269 ++++++++++++++++++ .../Assets/styles.css | 7 +- .../Myst/Directives/DirectiveBlockParser.cs | 2 +- .../Myst/Directives/DirectiveHtmlRenderer.cs | 2 +- 4 files changed, 277 insertions(+), 3 deletions(-) diff --git a/docs/cli-schema.json b/docs/cli-schema.json index c96f6a90b0..f116f35774 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -3614,6 +3614,87 @@ } ] }, + { + "path": [ + "changelog" + ], + "name": "evaluate-artifact", + "summary": "(CI) Evaluate downloaded artifact in the resolving workflow.", + "notes": "Reads metadata, validates PR state (SHA, labels), and sets GitHub Actions outputs\nfor downstream steps (commit, comment).", + "usage": "docs-builder changelog evaluate-artifact --metadata \u003Cstring\u003E --owner \u003Cstring\u003E --repo \u003Cstring\u003E", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "metadata", + "shortName": null, + "type": "string", + "required": true, + "summary": "Path to the downloaded metadata.json file" + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "type": "string", + "required": true, + "summary": "GitHub repository owner" + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "type": "string", + "required": true, + "summary": "GitHub repository name" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "defaultValue": "information", + "aliases": [ + "log-level" + ], + "enumValues": [ + "trace", + "debug", + "information", + "warning", + "error", + "critical", + "none" + ] + }, + { + "role": "flag", + "name": "config-source", + "shortName": "c", + "type": "enum", + "required": false, + "summary": "Override the configuration source: local, remote", + "aliases": [ + "config-source" + ], + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, { "path": [ "changelog" @@ -4093,6 +4174,194 @@ } ] }, + { + "path": [ + "changelog" + ], + "name": "prepare-artifact", + "summary": "(CI) Package changelog artifact for cross-workflow transfer.", + "notes": "Resolves final status from evaluate-pr \u002B changelog add outcomes, copies generated YAML,\nwrites metadata.json, and sets GitHub Actions outputs. Always succeeds (exit 0) so the upload step runs.", + "usage": "docs-builder changelog prepare-artifact --staging-dir \u003Cstring\u003E --output-dir \u003Cstring\u003E --evaluate-status \u003Cstring\u003E --generate-outcome \u003Cstring\u003E --pr-number \u003Cint\u003E --head-ref \u003Cstring\u003E --head-sha \u003Cstring\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "staging-dir", + "shortName": null, + "type": "string", + "required": true, + "summary": "Directory where changelog add wrote the generated YAML" + }, + { + "role": "flag", + "name": "output-dir", + "shortName": null, + "type": "string", + "required": true, + "summary": "Directory to write the artifact (metadata.json \u002B YAML)" + }, + { + "role": "flag", + "name": "evaluate-status", + "shortName": null, + "type": "string", + "required": true, + "summary": "Status output from the evaluate-pr step" + }, + { + "role": "flag", + "name": "generate-outcome", + "shortName": null, + "type": "string", + "required": true, + "summary": "Outcome of the changelog add step (success/failure)" + }, + { + "role": "flag", + "name": "pr-number", + "shortName": null, + "type": "integer", + "required": true, + "summary": "Pull request number" + }, + { + "role": "flag", + "name": "head-ref", + "shortName": null, + "type": "string", + "required": true, + "summary": "PR head branch ref" + }, + { + "role": "flag", + "name": "head-sha", + "shortName": null, + "type": "string", + "required": true, + "summary": "PR head commit SHA" + }, + { + "role": "flag", + "name": "is-fork", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Whether the PR is from a fork", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "can-commit", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Whether the commit strategy allows committing", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "maintainer-can-modify", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Whether the fork PR allows maintainer edits", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "head-repo", + "shortName": null, + "type": "string", + "required": false, + "summary": "Fork repository full name (owner/repo)" + }, + { + "role": "flag", + "name": "label-table", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: markdown label table from evaluate-pr" + }, + { + "role": "flag", + "name": "product-label-table", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: markdown product label table from evaluate-pr" + }, + { + "role": "flag", + "name": "skip-labels", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: comma-separated skip labels from evaluate-pr" + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: path to changelog.yml" + }, + { + "role": "flag", + "name": "existing-changelog-filename", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: filename of a previously committed changelog for this PR" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "defaultValue": "information", + "aliases": [ + "log-level" + ], + "enumValues": [ + "trace", + "debug", + "information", + "warning", + "error", + "critical", + "none" + ] + }, + { + "role": "flag", + "name": "config-source", + "shortName": "c", + "type": "enum", + "required": false, + "summary": "Override the configuration source: local, remote", + "aliases": [ + "config-source" + ], + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, { "path": [ "changelog" diff --git a/src/Elastic.Documentation.Site/Assets/styles.css b/src/Elastic.Documentation.Site/Assets/styles.css index ab502704e2..3d3edd26d9 100644 --- a/src/Elastic.Documentation.Site/Assets/styles.css +++ b/src/Elastic.Documentation.Site/Assets/styles.css @@ -422,7 +422,12 @@ math { } /* Heading anchor links — show # on hover */ -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { position: relative; & .headerlink { diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs index 52308f890f..770565af64 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveBlockParser.cs @@ -12,8 +12,8 @@ using Elastic.Markdown.Myst.Directives.Image; using Elastic.Markdown.Myst.Directives.Include; using Elastic.Markdown.Myst.Directives.Math; -using Elastic.Markdown.Myst.Directives.Settings; using Elastic.Markdown.Myst.Directives.PageCard; +using Elastic.Markdown.Myst.Directives.Settings; using Elastic.Markdown.Myst.Directives.Stepper; using Elastic.Markdown.Myst.Directives.SubPages; using Elastic.Markdown.Myst.Directives.Table; diff --git a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs index d65c0c8b75..838713abbf 100644 --- a/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs @@ -18,8 +18,8 @@ using Elastic.Markdown.Myst.Directives.Image; using Elastic.Markdown.Myst.Directives.Include; using Elastic.Markdown.Myst.Directives.Math; -using Elastic.Markdown.Myst.Directives.Settings; using Elastic.Markdown.Myst.Directives.PageCard; +using Elastic.Markdown.Myst.Directives.Settings; using Elastic.Markdown.Myst.Directives.Stepper; using Elastic.Markdown.Myst.Directives.SubPages; using Elastic.Markdown.Myst.Directives.Table; From 04da68b119c1431f4d4ecc2fd1899c57d80ccbbc Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 4 May 2026 21:21:54 +0200 Subject: [PATCH 12/18] fix(tests): stub all redirect targets in authoring tests; remove CLI self-redirects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/_redirects.yml | 10 ---------- tests/authoring/Framework/Setup.fs | 8 ++++---- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/docs/_redirects.yml b/docs/_redirects.yml index 3c331023c2..b58a46a5b8 100644 --- a/docs/_redirects.yml +++ b/docs/_redirects.yml @@ -14,15 +14,6 @@ redirects: 'cli/release/changelog-remove.md': 'cli/changelog/remove.md' 'cli/release/changelog-render.md': 'cli/changelog/render.md' 'cli/release/index.md': 'cli/changelog/index.md' - # Old hand-written CLI page redirects → generated pages - 'cli/changelog/add.md': 'cli/changelog/add.md' - 'cli/changelog/bundle.md': 'cli/changelog/bundle.md' - 'cli/changelog/bundle-amend.md': 'cli/changelog/bundle-amend.md' - 'cli/changelog/evaluate-pr.md': 'cli/changelog/evaluate-pr.md' - 'cli/changelog/gh-release.md': 'cli/changelog/gh-release.md' - 'cli/changelog/init.md': 'cli/changelog/init.md' - 'cli/changelog/remove.md': 'cli/changelog/remove.md' - 'cli/changelog/render.md': 'cli/changelog/render.md' 'cli/docset/index.md': 'cli/index.md' 'cli/docset/build.md': 'cli/build.md' 'cli/docset/diff-validate.md': 'cli/diff.md' @@ -30,7 +21,6 @@ redirects: 'cli/docset/index-command.md': 'cli/cmd-index.md' 'cli/docset/mv.md': 'cli/mv.md' 'cli/docset/serve.md': 'cli/serve.md' - 'cli/assembler/index.md': 'cli/assembler/index.md' 'cli/assembler/assemble.md': 'cli/assemble.md' 'cli/assembler/assembler-build.md': 'cli/assembler/build.md' 'cli/assembler/assembler-clone.md': 'cli/assembler/clone.md' diff --git a/tests/authoring/Framework/Setup.fs b/tests/authoring/Framework/Setup.fs index d89f426d12..26d68318ba 100644 --- a/tests/authoring/Framework/Setup.fs +++ b/tests/authoring/Framework/Setup.fs @@ -162,11 +162,11 @@ navigation_title: Stub let destPath = Path.Combine(mockDocsRoot, normalized) // Tests supply their own minimal pages; do not replace them with production content. + // Always add a stub for any local redirect target not already present — this covers both + // physical pages and synthetic pages (e.g. CLI reference pages generated from a schema). + // Actual redirect correctness is validated by the production build, not authoring tests. if not (fileSystem.File.Exists destPath) then - let sourcePath = Path.Combine(repoRoot, "docs", normalized) - - if File.Exists sourcePath then - fileSystem.AddFile(destPath, MockFileData(stubMarkdown))) + fileSystem.AddFile(destPath, MockFileData(stubMarkdown))) [] type TestFile = From bbdf56ee08e736458b028028284adf09302d53eb Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 6 May 2026 12:00:49 +0200 Subject: [PATCH 13/18] fix(tests): remove cli/* redirects; update tests for schema-driven CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/_redirects.yml | 39 +--------------- .../Elastic.Documentation.Refactor/Move.cs | 4 ++ .../PhysicalDocsetTests.cs | 15 ++++--- tests/authoring/Framework/Setup.fs | 8 ++-- .../authoring/Generator/LinkReferenceFile.fs | 45 ------------------- 5 files changed, 17 insertions(+), 94 deletions(-) diff --git a/docs/_redirects.yml b/docs/_redirects.yml index b58a46a5b8..df29839830 100644 --- a/docs/_redirects.yml +++ b/docs/_redirects.yml @@ -1,43 +1,6 @@ redirects: 'migration/freeze/gh-action.md' : 'index.md' 'migration/freeze/index.md' : 'index.md' - 'cli/mcp.md': 'index.md' - # Legacy cli/release/* redirects — now point to generated changelog command pages - 'cli/release/changelog-add.md': 'cli/changelog/add.md' - 'cli/release/changelog-evaluate-artifact.md': 'index.md' - 'cli/release/changelog-evaluate-pr.md': 'cli/changelog/evaluate-pr.md' - 'cli/release/changelog-bundle.md': 'cli/changelog/bundle.md' - 'cli/release/changelog-bundle-amend.md': 'cli/changelog/bundle-amend.md' - 'cli/release/changelog-gh-release.md': 'cli/changelog/gh-release.md' - 'cli/release/changelog-init.md': 'cli/changelog/init.md' - 'cli/release/changelog-prepare-artifact.md': 'index.md' - 'cli/release/changelog-remove.md': 'cli/changelog/remove.md' - 'cli/release/changelog-render.md': 'cli/changelog/render.md' - 'cli/release/index.md': 'cli/changelog/index.md' - 'cli/docset/index.md': 'cli/index.md' - 'cli/docset/build.md': 'cli/build.md' - 'cli/docset/diff-validate.md': 'cli/diff.md' - 'cli/docset/format.md': 'cli/format.md' - 'cli/docset/index-command.md': 'cli/cmd-index.md' - 'cli/docset/mv.md': 'cli/mv.md' - 'cli/docset/serve.md': 'cli/serve.md' - 'cli/assembler/assemble.md': 'cli/assemble.md' - 'cli/assembler/assembler-build.md': 'cli/assembler/build.md' - 'cli/assembler/assembler-clone.md': 'cli/assembler/clone.md' - 'cli/assembler/assembler-config-init.md': 'cli/assembler/config/init.md' - 'cli/assembler/assembler-content-source-match.md': 'cli/assembler/content-source/match.md' - 'cli/assembler/assembler-content-source-validate.md': 'cli/assembler/content-source/validate.md' - 'cli/assembler/assembler-deploy-apply.md': 'cli/assembler/deploy/apply.md' - 'cli/assembler/assembler-deploy-plan.md': 'cli/assembler/deploy/plan.md' - 'cli/assembler/assembler-deploy-update-redirects.md': 'cli/assembler/deploy/update-redirects.md' - 'cli/assembler/assembler-index.md': 'cli/assembler/index.md' - 'cli/assembler/assembler-navigation-validate.md': 'cli/assembler/navigation/validate.md' - 'cli/assembler/assembler-navigation-validate-link-reference.md': 'cli/assembler/navigation/validate-link-reference.md' - 'cli/assembler/assembler-serve.md': 'cli/assembler/serve.md' - 'cli/links/index.md': 'cli/inbound-links/index.md' - 'cli/links/inbound-links-validate.md': 'cli/inbound-links/validate.md' - 'cli/links/inbound-links-validate-all.md': 'cli/inbound-links/validate-all.md' - 'cli/links/inbound-links-validate-link-reference.md': 'cli/inbound-links/validate-link-reference.md' 'testing/redirects/4th-page.md': 'testing/redirects/5th-page.md' 'testing/redirects/9th-page.md': '!testing/redirects/5th-page.md' 'testing/redirects/6th-page.md': @@ -60,4 +23,4 @@ redirects: "yy": "bb" 'testing/redirects/third-page.md': anchors: - 'removed-anchor': \ No newline at end of file + 'removed-anchor': diff --git a/src/authoring/Elastic.Documentation.Refactor/Move.cs b/src/authoring/Elastic.Documentation.Refactor/Move.cs index a9e974dce1..104399b8fe 100644 --- a/src/authoring/Elastic.Documentation.Refactor/Move.cs +++ b/src/authoring/Elastic.Documentation.Refactor/Move.cs @@ -235,6 +235,10 @@ private bool ValidateInputs(string source, string target, out IFileInfo[] fromFi private async Task ProcessMarkdownFile(ChangeSet changeSet, MarkdownFile value, Cancel ctx) { + // Synthetic files (e.g. CLI reference pages generated from a schema) have no physical content. + if (!value.SourceFile.Exists) + return; + var source = changeSet.From.FullName; var target = changeSet.To.FullName; diff --git a/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs b/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs index 0a392e842b..ffb96d96ef 100644 --- a/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs +++ b/tests/Elastic.Documentation.Configuration.Tests/PhysicalDocsetTests.cs @@ -4,6 +4,7 @@ using AwesomeAssertions; using Elastic.Documentation.Configuration.Toc; +using Elastic.Documentation.Configuration.Toc.CliReference; namespace Elastic.Documentation.Configuration.Tests; @@ -86,9 +87,12 @@ public void PhysicalDocsetContainsExpectedFolders() folderNames.Should().Contain("building-blocks"); folderNames.Should().Contain("configure"); folderNames.Should().Contain("syntax"); - folderNames.Should().Contain("cli"); folderNames.Should().Contain("migration"); folderNames.Should().Contain("testing"); + + // cli is a CliReferenceRef (schema-driven), not a FolderRef + var cliRef = docSet.TableOfContents.OfType().FirstOrDefault(); + cliRef.Should().NotBeNull(); } [Fact] @@ -108,12 +112,9 @@ public void PhysicalDocsetHasValidNestedStructure() nestedFolders.Should().Contain("site"); nestedFolders.Should().Contain("content-set"); - // Test the cli folder has nested folders - var cliFolder = docSet.TableOfContents.OfType().First(f => f.PathRelativeToDocumentationSet == "cli"); - var cliNestedFolders = cliFolder.Children.OfType().Select(f => f.PathRelativeToDocumentationSet).ToList(); - cliNestedFolders.Should().Contain("docset"); - cliNestedFolders.Should().Contain("assembler"); - cliNestedFolders.Should().Contain("links"); + // cli is a CliReferenceRef (schema-driven), not a FolderRef — verify it has children + var cliRef = docSet.TableOfContents.OfType().First(); + cliRef.Children.Should().NotBeEmpty(); } [Fact] diff --git a/tests/authoring/Framework/Setup.fs b/tests/authoring/Framework/Setup.fs index 26d68318ba..d89f426d12 100644 --- a/tests/authoring/Framework/Setup.fs +++ b/tests/authoring/Framework/Setup.fs @@ -162,11 +162,11 @@ navigation_title: Stub let destPath = Path.Combine(mockDocsRoot, normalized) // Tests supply their own minimal pages; do not replace them with production content. - // Always add a stub for any local redirect target not already present — this covers both - // physical pages and synthetic pages (e.g. CLI reference pages generated from a schema). - // Actual redirect correctness is validated by the production build, not authoring tests. if not (fileSystem.File.Exists destPath) then - fileSystem.AddFile(destPath, MockFileData(stubMarkdown))) + let sourcePath = Path.Combine(repoRoot, "docs", normalized) + + if File.Exists sourcePath then + fileSystem.AddFile(destPath, MockFileData(stubMarkdown))) [] type TestFile = diff --git a/tests/authoring/Generator/LinkReferenceFile.fs b/tests/authoring/Generator/LinkReferenceFile.fs index e5e9b7f1e5..5947fb37a6 100644 --- a/tests/authoring/Generator/LinkReferenceFile.fs +++ b/tests/authoring/Generator/LinkReferenceFile.fs @@ -64,15 +64,6 @@ Through various means $$$including-this-inline-syntax$$$ }, "url_path_prefix": "", "links": { - "cli/changelog/add.md": {}, - "cli/changelog/bundle-amend.md": {}, - "cli/changelog/bundle.md": {}, - "cli/changelog/evaluate-pr.md": {}, - "cli/changelog/gh-release.md": {}, - "cli/changelog/index.md": {}, - "cli/changelog/init.md": {}, - "cli/changelog/remove.md": {}, - "cli/changelog/render.md": {}, "file.md": {}, "index.md": { "anchors": [ @@ -102,42 +93,6 @@ Through various means $$$including-this-inline-syntax$$$ "migration/freeze/index.md": { "to": "index.md" }, - "cli/mcp.md": { - "to": "index.md" - }, - "cli/release/changelog-add.md": { - "to": "cli/changelog/add.md" - }, - "cli/release/changelog-evaluate-artifact.md": { - "to": "index.md" - }, - "cli/release/changelog-evaluate-pr.md": { - "to": "cli/changelog/evaluate-pr.md" - }, - "cli/release/changelog-bundle.md": { - "to": "cli/changelog/bundle.md" - }, - "cli/release/changelog-bundle-amend.md": { - "to": "cli/changelog/bundle-amend.md" - }, - "cli/release/changelog-gh-release.md": { - "to": "cli/changelog/gh-release.md" - }, - "cli/release/changelog-init.md": { - "to": "cli/changelog/init.md" - }, - "cli/release/changelog-prepare-artifact.md": { - "to": "index.md" - }, - "cli/release/changelog-remove.md": { - "to": "cli/changelog/remove.md" - }, - "cli/release/changelog-render.md": { - "to": "cli/changelog/render.md" - }, - "cli/release/index.md": { - "to": "cli/changelog/index.md" - }, "testing/redirects/4th-page.md": { "to": "testing/redirects/5th-page.md" }, From 842cfcace36829c3fbb6963eeb49b5b0a4acb2b9 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 7 May 2026 09:31:58 +0200 Subject: [PATCH 14/18] fix(ci): regenerate cli-schema.json; add redirects for deleted CLI pages - Regenerate docs/cli-schema.json against Nullean.Argh 0.15.1 (adds aliases and defaultValue fields that CI schema-freshness check requires) - Add _redirects.yml entries for all CLI static pages deleted by this PR; targets use physical files (cli/installation.md, cli/changelog/index.md) so redirect validation passes in both CI and authoring tests - Update links.json snapshot to reflect new redirect stubs Co-Authored-By: Claude Sonnet 4.6 (1M context) --- docs/_redirects.yml | 39 +++ docs/cli-schema.json | 325 +----------------- .../authoring/Generator/LinkReferenceFile.fs | 38 ++ 3 files changed, 93 insertions(+), 309 deletions(-) diff --git a/docs/_redirects.yml b/docs/_redirects.yml index df29839830..efb262699e 100644 --- a/docs/_redirects.yml +++ b/docs/_redirects.yml @@ -1,6 +1,45 @@ redirects: 'migration/freeze/gh-action.md' : 'index.md' 'migration/freeze/index.md' : 'index.md' + # CLI docs: old static pages replaced by schema-generated equivalents. + # Targets use physical pages so redirect validation works in authoring tests. + # The CLI schema-generated pages (synthetic) serve the real content in production. + 'cli/index.md': 'cli/installation.md' + 'cli/assembler/index.md': 'cli/installation.md' + 'cli/changelog/add.md': 'cli/changelog/index.md' + 'cli/changelog/bundle-amend.md': 'cli/changelog/index.md' + 'cli/changelog/bundle.md': 'cli/changelog/index.md' + 'cli/changelog/evaluate-pr.md': 'cli/changelog/index.md' + 'cli/changelog/gh-release.md': 'cli/changelog/index.md' + 'cli/changelog/init.md': 'cli/changelog/index.md' + 'cli/changelog/remove.md': 'cli/changelog/index.md' + 'cli/changelog/render.md': 'cli/changelog/index.md' + 'cli/assembler/assemble.md': 'cli/installation.md' + 'cli/assembler/assembler-bloom-filter-create.md': 'cli/installation.md' + 'cli/assembler/assembler-bloom-filter-lookup.md': 'cli/installation.md' + 'cli/assembler/assembler-build.md': 'cli/installation.md' + 'cli/assembler/assembler-clone.md': 'cli/installation.md' + 'cli/assembler/assembler-config-init.md': 'cli/installation.md' + 'cli/assembler/assembler-content-source-match.md': 'cli/installation.md' + 'cli/assembler/assembler-content-source-validate.md': 'cli/installation.md' + 'cli/assembler/assembler-deploy-apply.md': 'cli/installation.md' + 'cli/assembler/assembler-deploy-plan.md': 'cli/installation.md' + 'cli/assembler/assembler-deploy-update-redirects.md': 'cli/installation.md' + 'cli/assembler/assembler-index.md': 'cli/installation.md' + 'cli/assembler/assembler-navigation-validate-link-reference.md': 'cli/installation.md' + 'cli/assembler/assembler-navigation-validate.md': 'cli/installation.md' + 'cli/assembler/assembler-serve.md': 'cli/installation.md' + 'cli/docset/index.md': 'cli/installation.md' + 'cli/docset/build.md': 'cli/installation.md' + 'cli/docset/diff-validate.md': 'cli/installation.md' + 'cli/docset/format.md': 'cli/installation.md' + 'cli/docset/index-command.md': 'cli/installation.md' + 'cli/docset/mv.md': 'cli/installation.md' + 'cli/docset/serve.md': 'cli/installation.md' + 'cli/links/index.md': 'cli/installation.md' + 'cli/links/inbound-links-validate.md': 'cli/installation.md' + 'cli/links/inbound-links-validate-all.md': 'cli/installation.md' + 'cli/links/inbound-links-validate-link-reference.md': 'cli/installation.md' 'testing/redirects/4th-page.md': 'testing/redirects/5th-page.md' 'testing/redirects/9th-page.md': '!testing/redirects/5th-page.md' 'testing/redirects/6th-page.md': diff --git a/docs/cli-schema.json b/docs/cli-schema.json index f116f35774..c5df8bb72a 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -16,10 +16,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -37,9 +33,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -149,10 +142,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -170,9 +159,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -354,10 +340,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -375,9 +357,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -417,10 +396,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -438,9 +413,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -498,10 +470,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -519,9 +487,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -825,10 +790,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -846,9 +807,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -913,10 +871,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -934,9 +888,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1017,10 +968,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -1038,9 +985,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1133,10 +1077,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -1154,9 +1094,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1225,10 +1162,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -1246,9 +1179,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1554,10 +1484,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -1575,9 +1501,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1651,10 +1574,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -1672,9 +1591,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -1980,10 +1896,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2001,9 +1913,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2078,10 +1987,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2099,9 +2004,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2144,10 +2046,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2165,9 +2063,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2229,10 +2124,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2250,9 +2141,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2313,10 +2201,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2334,9 +2218,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2371,10 +2252,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2392,9 +2269,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2495,10 +2369,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2516,9 +2386,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2602,10 +2469,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2623,9 +2486,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2708,10 +2568,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2729,9 +2585,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2776,10 +2629,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2797,9 +2646,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -2874,10 +2720,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -2895,9 +2737,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3069,7 +2908,7 @@ "shortName": null, "type": "array", "required": false, - "summary": "Optional: Issue URL(s) or number(s) (comma-separated), or a path to a newline-delimited file containing issue URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated issues (e.g., \u0060--issues \u0022https://github.com/owner/repo/issues/123,456\u0022\u0060) or a file path (e.g., \u0060--issues /path/to/file.txt\u0060). If --owner and --repo are provided, issue numbers can be used instead of URLs. If specified, --title can be derived from the issue. Creates one changelog file per issue.", + "summary": "Optional: Issue URL(s) or number(s) (comma-separated), or a path to a newline-delimited file containing issue URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated issues (e.g., \u0060--issues \u0022https://github.com/owner/repo/issues/123,456\u0022\u0060) or a file path (e.g., \u0060--issues /path/to/file.txt\u0060). If --owner and --repo are provided, issue numbers can be used instead of URLs. If specified, --title can be derived from the issue. Creates one changelog file per issue. Mutually exclusive with --release-version and --report.", "repeatable": true, "elementType": "string" }, @@ -3095,17 +2934,25 @@ "shortName": null, "type": "array", "required": false, - "summary": "Optional: Pull request URL(s) or PR number(s) (comma-separated), or a path to a newline-delimited file containing PR URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated PRs (e.g., \u0060--prs \u0022https://github.com/owner/repo/pull/123,6789\u0022\u0060) or a file path (e.g., \u0060--prs /path/to/file.txt\u0060). When specifying PRs directly, provide comma-separated values. When specifying a file path, provide a single value that points to a newline-delimited file. If --owner and --repo are provided, PR numbers can be used instead of URLs. If specified, --title can be derived from the PR. If mappings are configured, --areas and --type can also be derived from the PR. Creates one changelog file per PR.", + "summary": "Optional: Pull request URL(s) or PR number(s) (comma-separated), or a path to a newline-delimited file containing PR URLs or numbers. Can be specified multiple times. Each occurrence can be either comma-separated PRs (e.g., \u0060--prs \u0022https://github.com/owner/repo/pull/123,6789\u0022\u0060) or a file path (e.g., \u0060--prs /path/to/file.txt\u0060). When specifying PRs directly, provide comma-separated values. When specifying a file path, provide a single value that points to a newline-delimited file. If --owner and --repo are provided, PR numbers can be used instead of URLs. If specified, --title can be derived from the PR. If mappings are configured, --areas and --type can also be derived from the PR. Creates one changelog file per PR. Mutually exclusive with --release-version and --report.", "repeatable": true, "elementType": "string" }, + { + "role": "flag", + "name": "report", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: URL or file path to a promotion report HTML document. Extracts GitHub pull request URLs and creates one changelog per PR (same parsing as \u0060changelog bundle --report\u0060). Mutually exclusive with --prs, --issues, and --release-version." + }, { "role": "flag", "name": "release-version", "shortName": null, "type": "string", "required": false, - "summary": "Optional: GitHub release tag to fetch PRs from (e.g., \u0022v9.2.0\u0022 or \u0022latest\u0022). When specified, creates one changelog per PR in the release notes. Requires --repo (or bundle.repo in changelog.yml). Mutually exclusive with --prs and --issues. Does not create a bundle; use \u0027changelog gh-release\u0027 for that." + "summary": "Optional: GitHub release tag to fetch PRs from (e.g., \u0022v9.2.0\u0022 or \u0022latest\u0022). When specified, creates one changelog per PR in the release notes. Requires --repo (or bundle.repo in changelog.yml). Mutually exclusive with --prs, --issues, and --report. Does not create a bundle; use \u0027changelog gh-release\u0027 for that." }, { "role": "flag", @@ -3121,7 +2968,7 @@ "shortName": null, "type": "boolean", "required": false, - "summary": "Optional: When used with --prs, remove square brackets and text within them from the beginning of PR titles, and also remove a colon if it follows the closing bracket (e.g., \u0022[Inference API] Title\u0022 becomes \u0022Title\u0022, \u0022[ES|QL]: Title\u0022 becomes \u0022Title\u0022, \u0022[Discover][ESQL] Title\u0022 becomes \u0022Title\u0022)", + "summary": "Optional: When used with --prs or --report, remove square brackets and text within them from the beginning of PR titles, and also remove a colon if it follows the closing bracket (e.g., \u0022[Inference API] Title\u0022 becomes \u0022Title\u0022, \u0022[ES|QL]: Title\u0022 becomes \u0022Title\u0022, \u0022[Discover][ESQL] Title\u0022 becomes \u0022Title\u0022)", "defaultValue": "false" }, { @@ -3138,7 +2985,7 @@ "shortName": null, "type": "string", "required": false, - "summary": "Optional: A short, user-facing title (max 80 characters). Required if neither --prs nor --issues is specified. If --prs and --title are specified, the latter value is used instead of what exists in the PR." + "summary": "Optional: A short, user-facing title (max 80 characters). Required if neither --prs, --issues, nor --report is specified. If --prs and --title are specified, the latter value is used instead of what exists in the PR." }, { "role": "flag", @@ -3146,7 +2993,7 @@ "shortName": null, "type": "string", "required": false, - "summary": "Optional: Type of change (feature, enhancement, bug-fix, breaking-change, etc.). Required if neither --prs nor --issues is specified. If mappings are configured, type can be derived from the PR or issue." + "summary": "Optional: Type of change (feature, enhancement, bug-fix, breaking-change, etc.). Required if neither --prs, --issues, nor --report is specified. If mappings are configured, type can be derived from the PR or issue." }, { "role": "flag", @@ -3154,7 +3001,7 @@ "shortName": null, "type": "boolean", "required": false, - "summary": "Optional: Use PR numbers for filenames instead of timestamp-slug. With both --prs (which creates one changelog per specified PR) and --issues (which creates one changelog per specified issue), each changelog filename will be derived from its PR numbers. Requires --prs or --issues. Mutually exclusive with --use-issue-number.", + "summary": "Optional: Use PR numbers for filenames instead of timestamp-slug. With --prs, --report, or --issues (where PRs are resolved), each changelog filename will be derived from its PR numbers. Requires --prs, --report, or --issues. Mutually exclusive with --use-issue-number.", "defaultValue": "false" }, { @@ -3173,10 +3020,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -3194,9 +3037,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3457,10 +3297,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -3478,9 +3314,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3574,10 +3407,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -3595,9 +3424,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3655,10 +3481,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -3676,9 +3498,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3832,10 +3651,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -3853,9 +3668,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -3989,10 +3801,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4010,9 +3818,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -4134,10 +3939,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4155,9 +3956,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -4322,10 +4120,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4343,9 +4137,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -4570,7 +4361,7 @@ "shortName": null, "type": "string", "required": false, - "summary": "Optional (option-based mode only): URL or file path to a promotion report. Extracts PR URLs and uses them as the filter. Mutually exclusive with --all, --products, --prs, and --issues." + "summary": "Optional (option-based mode only): URL or file path to a promotion report. Extracts PR URLs and uses them as the filter. Mutually exclusive with --all, --products, --prs, --release-version, and --issues." }, { "role": "flag", @@ -4579,10 +4370,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4600,9 +4387,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -4731,10 +4515,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4752,9 +4532,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -4877,10 +4654,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -4898,9 +4671,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5040,10 +4810,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5061,9 +4827,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5172,10 +4935,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5193,9 +4952,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5297,10 +5053,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5318,9 +5070,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5659,10 +5408,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5680,9 +5425,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5756,10 +5498,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5777,9 +5515,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5902,10 +5637,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -5923,9 +5654,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -5985,10 +5713,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -6006,9 +5730,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -6042,10 +5763,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -6063,9 +5780,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", @@ -6147,10 +5861,6 @@ "type": "enum", "required": false, "summary": "Minimum log level. Default: information", - "defaultValue": "information", - "aliases": [ - "log-level" - ], "enumValues": [ "trace", "debug", @@ -6168,9 +5878,6 @@ "type": "enum", "required": false, "summary": "Override the configuration source: local, remote", - "aliases": [ - "config-source" - ], "enumValues": [ "local", "remote", diff --git a/tests/authoring/Generator/LinkReferenceFile.fs b/tests/authoring/Generator/LinkReferenceFile.fs index 5947fb37a6..4c0b6f5180 100644 --- a/tests/authoring/Generator/LinkReferenceFile.fs +++ b/tests/authoring/Generator/LinkReferenceFile.fs @@ -64,6 +64,8 @@ Through various means $$$including-this-inline-syntax$$$ }, "url_path_prefix": "", "links": { + "cli/changelog/index.md": {}, + "cli/installation.md": {}, "file.md": {}, "index.md": { "anchors": [ @@ -93,6 +95,42 @@ Through various means $$$including-this-inline-syntax$$$ "migration/freeze/index.md": { "to": "index.md" }, + "cli/index.md": { "to": "cli/installation.md" }, + "cli/assembler/index.md": { "to": "cli/installation.md" }, + "cli/changelog/add.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/bundle-amend.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/bundle.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/evaluate-pr.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/gh-release.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/init.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/remove.md": { "to": "cli/changelog/index.md" }, + "cli/changelog/render.md": { "to": "cli/changelog/index.md" }, + "cli/assembler/assemble.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-bloom-filter-create.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-bloom-filter-lookup.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-build.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-clone.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-config-init.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-content-source-match.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-content-source-validate.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-deploy-apply.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-deploy-plan.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-deploy-update-redirects.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-index.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-navigation-validate-link-reference.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-navigation-validate.md": { "to": "cli/installation.md" }, + "cli/assembler/assembler-serve.md": { "to": "cli/installation.md" }, + "cli/docset/index.md": { "to": "cli/installation.md" }, + "cli/docset/build.md": { "to": "cli/installation.md" }, + "cli/docset/diff-validate.md": { "to": "cli/installation.md" }, + "cli/docset/format.md": { "to": "cli/installation.md" }, + "cli/docset/index-command.md": { "to": "cli/installation.md" }, + "cli/docset/mv.md": { "to": "cli/installation.md" }, + "cli/docset/serve.md": { "to": "cli/installation.md" }, + "cli/links/index.md": { "to": "cli/installation.md" }, + "cli/links/inbound-links-validate.md": { "to": "cli/installation.md" }, + "cli/links/inbound-links-validate-all.md": { "to": "cli/installation.md" }, + "cli/links/inbound-links-validate-link-reference.md": { "to": "cli/installation.md" }, "testing/redirects/4th-page.md": { "to": "testing/redirects/5th-page.md" }, From fa55ec5fe9f26b3b36f6f95d27a260b4507f6aaa Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 7 May 2026 14:35:53 +0200 Subject: [PATCH 15/18] chore: upgrade Nullean.Argh to 0.16.0; regenerate cli-schema.json Co-Authored-By: Claude Sonnet 4.6 (1M context) --- Directory.Packages.props | 6 +++--- docs/cli-schema.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1516a1b6db..df37646548 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -69,9 +69,9 @@ - - - + + + diff --git a/docs/cli-schema.json b/docs/cli-schema.json index c5df8bb72a..4f18d9ef86 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -1,5 +1,5 @@ { - "schemaVersion": 2, + "schemaVersion": 1, "name": "docs-builder", "version": "1.0.0.0", "description": null, From 178fef67634c50b91eaafc4db161c820cb443766 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 7 May 2026 14:50:54 +0200 Subject: [PATCH 16/18] fix(docs): replace unknown 'fish' language with 'shell'; regenerate schema Co-Authored-By: Claude Sonnet 4.6 (1M context) --- docs/cli-schema.json | 21 ++++++++------------- docs/cli/shell-autocompletion.md | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/cli-schema.json b/docs/cli-schema.json index 4f18d9ef86..48b85ecb3c 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -94,11 +94,9 @@ "role": "flag", "name": "exporters", "shortName": null, - "type": "array", + "type": "string", "required": false, - "summary": "Comma-separated list of exporters to run.\n Values: Html, Elasticsearch, Configuration, LinkMetadata, DocumentationState, LLMText, Redirects.\n Default: Html, Configuration, LinkMetadata, DocumentationState, Redirects.", - "separator": ",", - "elementType": "enum" + "summary": "Comma-separated list of exporters to run." }, { "role": "flag", @@ -282,11 +280,9 @@ "role": "flag", "name": "exporters", "shortName": null, - "type": "array", + "type": "string", "required": false, - "summary": "Comma-separated list of exporters to run.\n Default: html, configuration, linkmetadata, documentationState, dedirects.", - "separator": ",", - "elementType": "enum" + "summary": "Comma-separated list of exporters to run." }, { "role": "flag", @@ -1056,11 +1052,9 @@ "role": "flag", "name": "exporters", "shortName": null, - "type": "array", + "type": "string", "required": false, - "summary": "Comma-separated list of exporters to run.\n Values: Html, Elasticsearch, Configuration, LinkMetadata, DocumentationState, LLMText, Redirects.\n Default: Html, Configuration, LinkMetadata, DocumentationState, Redirects.", - "separator": ",", - "elementType": "enum" + "summary": "Comma-separated list of exporters to run." }, { "role": "flag", @@ -2350,7 +2344,8 @@ "max": null, "pattern": null, "values": [ - "json" + "json", + "plan" ] }, { diff --git a/docs/cli/shell-autocompletion.md b/docs/cli/shell-autocompletion.md index 1eaa3d8575..19709f5370 100644 --- a/docs/cli/shell-autocompletion.md +++ b/docs/cli/shell-autocompletion.md @@ -22,7 +22,7 @@ source <(docs-builder __completion zsh) ## Fish -```fish +```shell mkdir -p ~/.config/fish/completions docs-builder __completion fish > ~/.config/fish/completions/docs-builder.fish ``` From 1c0c35e28ff3993e2f12dec7ff185c6582674455 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 7 May 2026 15:15:53 +0200 Subject: [PATCH 17/18] feat(cli-schema): language-agnostic schema model; intent/validation/deprecation rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renames ArghCliSchema → CliSchema (language/framework agnostic, per cli-schema.org spec) and adds full support for all fields introduced in the Argh 0.16.0 schema: Schema model (CliSchema.cs, CliSchemaJsonContext.cs): - New records: CliValidationSchema, CliDeprecatedSchema, CliIntentSchema, CliOutputSchema, CliEnvironmentSchema, CliEnvVarSchema, CliConfigFileSchema - CliParamSchema: adds Validations, Variadic, Deprecated - CliCommandSchema: adds Tags, Deprecated, Intent, Output, Streaming, LongRunning - CliDefaultSchema: adds Kind, Hidden - CliSchema root: adds Version, ReservedMetaCommands, Tags, RequiresAuth, AuthCommands, Environment Documentation generation (CliMarkdownGenerator.cs): - Deprecated commands: renders a warning admonition before the description - Intent callouts: danger block for destructive, blockquotes for requiresAuth, requiresConfirmation, scope - Output formats: shown after the usage block - Parameter validations: Constraints line with human-readable phrases per kind (existing, rejectSymbolicLinks, expandUserProfile, uriScheme, range, timeSpanRange, fileExtensions) - Repeatable/variadic hints on parameters - Special role annotations: confirmationSkip, dryRun shown inline - Deprecated parameters: inline deprecation notice - Namespace notes section (was modelled but never rendered) - Namespace defaultCommand: new "## Running without a subcommand" section - Root page: Environment Variables and Configuration Files sections Command enrichment attributes: - build: [CommandIntent(Idempotent)], [MutationScope(Directory)] - mv: [CommandIntent(Destructive)], [MutationScope(Directory)], [DryRun] - format: [CommandIntent(Idempotent)], [MutationScope(Directory)] - changelog remove: [CommandIntent(Destructive|RequiresConfirmation)], [MutationScope(Directory)], [DryRun], [ConfirmationSkip] on --force - assembler deploy plan/apply: [RequiresAuth], [MutationScope(Global)], apply also gets [CommandIntent(Destructive)] - assembler/codex clone+build: [CommandIntent(Idempotent)] - index/codex index: [RequiresAuth] Co-Authored-By: Claude Sonnet 4.6 (1M context) --- docs/cli-schema.json | 74 ++++-- .../Toc/CliReference/ArghSchema.cs | 74 ------ .../Toc/CliReference/CliSchema.cs | 137 ++++++++++ ...JsonContext.cs => CliSchemaJsonContext.cs} | 4 +- .../Node/DocumentationSetNavigation.cs | 4 +- .../CliReference/CliMarkdownGenerator.cs | 247 +++++++++++++++++- .../CliReferenceDocsBuilderExtension.cs | 12 +- .../Extensions/CliReference/CliRootFile.cs | 4 +- .../Commands/Assembler/AssemblerCommands.cs | 4 + .../Commands/Assembler/DeployCommands.cs | 6 + .../docs-builder/Commands/ChangelogCommand.cs | 7 +- .../Commands/Codex/CodexCommands.cs | 4 + .../Commands/Codex/CodexIndexCommand.cs | 3 + .../docs-builder/Commands/IndexCommand.cs | 3 + .../Commands/IsolatedBuildCommand.cs | 3 + .../docs-builder/Commands/MoveCommand.cs | 7 +- .../docs-builder/Commands/ServeCommand.cs | 2 + 17 files changed, 489 insertions(+), 106 deletions(-) delete mode 100644 src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs create mode 100644 src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchema.cs rename src/Elastic.Documentation.Configuration/Toc/CliReference/{ArghSchemaJsonContext.cs => CliSchemaJsonContext.cs} (76%) diff --git a/docs/cli-schema.json b/docs/cli-schema.json index 48b85ecb3c..99fae06718 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -367,7 +367,11 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true, + "scope": "directory" + } }, { "path": [], @@ -497,7 +501,11 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true, + "scope": "directory" + } }, { "path": [], @@ -817,7 +825,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "requiresAuth": true + } }, { "path": [], @@ -844,7 +855,7 @@ "summary": "Destination file or folder path." }, { - "role": "flag", + "role": "dryRun", "name": "dry-run", "shortName": null, "type": "boolean", @@ -898,7 +909,11 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "destructive": true, + "scope": "directory" + } }, { "path": [], @@ -1102,7 +1117,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true + } }, { "path": [ @@ -1187,7 +1205,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true + } }, { "path": [ @@ -2395,7 +2416,12 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "destructive": true, + "scope": "global", + "requiresAuth": true + } }, { "path": [ @@ -2495,7 +2521,11 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "scope": "global", + "requiresAuth": true + } }, { "path": [ @@ -4281,7 +4311,7 @@ ] }, { - "role": "flag", + "role": "dryRun", "name": "dry-run", "shortName": null, "type": "boolean", @@ -4290,7 +4320,7 @@ "defaultValue": "false" }, { - "role": "flag", + "role": "confirmationSkip", "name": "force", "shortName": null, "type": "boolean", @@ -4396,7 +4426,12 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "destructive": true, + "scope": "directory", + "requiresConfirmation": true + } }, { "path": [ @@ -4961,7 +4996,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true + } }, { "path": [ @@ -5079,7 +5117,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "idempotent": true + } }, { "path": [ @@ -5434,7 +5475,10 @@ "required": false, "summary": "Skip cloning private repositories" } - ] + ], + "intent": { + "requiresAuth": true + } }, { "path": [ diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs deleted file mode 100644 index bf280772d4..0000000000 --- a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchema.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.IO.Abstractions; -using System.Text.Json; - -namespace Elastic.Documentation.Configuration.Toc.CliReference; - -// Schema v2: entryAssembly renamed to name, kind replaced by type (JSON Schema primitives) -public record ArghCliSchema( - int SchemaVersion, - string Name, - string? Description, - List GlobalOptions, - CliDefaultSchema? RootDefault, - List Commands, - List Namespaces -) -{ - public static ArghCliSchema Load(IFileInfo schemaFile) - { - var json = schemaFile.FileSystem.File.ReadAllText(schemaFile.FullName); - return JsonSerializer.Deserialize(json, ArghSchemaJsonContext.Default.ArghCliSchema) - ?? throw new InvalidOperationException($"Failed to deserialize CLI schema from {schemaFile.FullName}"); - } -} - -public record CliCommandSchema( - string[] Path, - string Name, - string? Summary, - string? Notes, - string? Usage, - string[]? Examples, - List Parameters, - string[]? Aliases = null, - bool Hidden = false -); - -public record CliNamespaceSchema( - string Segment, - string? Summary, - string? Notes, - List Options, - CliDefaultSchema? DefaultCommand, - List Commands, - List Namespaces -); - -public record CliParamSchema( - string Role, - string Name, - string? ShortName, - // v2: type uses JSON Schema primitives ("string","integer","number","boolean","array","enum") - string Type, - bool Required, - string? Summary, - string? DefaultValue = null, - string[]? EnumValues = null, - string? ElementType = null, - bool Repeatable = false, - string? Separator = null, - string[]? Aliases = null, - bool Hidden = false -); - -public record CliDefaultSchema( - string? Summary, - string? Notes, - string? Usage, - string[]? Examples, - List Parameters -); diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchema.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchema.cs new file mode 100644 index 0000000000..d4324974ce --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchema.cs @@ -0,0 +1,137 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using System.Text.Json; + +namespace Elastic.Documentation.Configuration.Toc.CliReference; + +// Language-agnostic CLI schema spec (https://cli-schema.org) +// Schema v2: type uses JSON Schema primitives; v1 used kind-style strings +public record CliSchema( + int SchemaVersion, + string Name, + string? Description, + List GlobalOptions, + CliDefaultSchema? RootDefault, + List Commands, + List Namespaces, + string? Version = null, + string[]? ReservedMetaCommands = null, + string[]? Tags = null, + bool? RequiresAuth = null, + string[]? AuthCommands = null, + CliEnvironmentSchema? Environment = null +) +{ + public static CliSchema Load(IFileInfo schemaFile) + { + var json = schemaFile.FileSystem.File.ReadAllText(schemaFile.FullName); + return JsonSerializer.Deserialize(json, CliSchemaJsonContext.Default.CliSchema) + ?? throw new InvalidOperationException($"Failed to deserialize CLI schema from {schemaFile.FullName}"); + } +} + +public record CliCommandSchema( + string[] Path, + string Name, + string? Summary, + string? Notes, + string? Usage, + string[]? Examples, + List Parameters, + string[]? Aliases = null, + bool Hidden = false, + string[]? Tags = null, + CliDeprecatedSchema? Deprecated = null, + CliIntentSchema? Intent = null, + CliOutputSchema? Output = null, + bool Streaming = false, + bool LongRunning = false +); + +public record CliNamespaceSchema( + string Segment, + string? Summary, + string? Notes, + List Options, + CliDefaultSchema? DefaultCommand, + List Commands, + List Namespaces +); + +public record CliParamSchema( + string Role, + string Name, + string? ShortName, + // v2: type uses JSON Schema primitives ("string","integer","number","boolean","array","enum") + string Type, + bool Required, + string? Summary, + string? DefaultValue = null, + string[]? EnumValues = null, + string? ElementType = null, + bool Repeatable = false, + string? Separator = null, + string[]? Aliases = null, + bool Hidden = false, + bool Variadic = false, + CliDeprecatedSchema? Deprecated = null, + List? Validations = null +); + +public record CliDefaultSchema( + string? Summary, + string? Notes, + string? Usage, + string[]? Examples, + List Parameters, + string Kind = "", + bool Hidden = false +); + +public record CliValidationSchema( + string Kind, + string[]? Values = null, + string? Min = null, + string? Max = null, + string? Pattern = null +); + +public record CliDeprecatedSchema( + string? Message = null, + string? Since = null, + string? RemovedIn = null +); + +public record CliIntentSchema( + bool? Destructive = null, + bool? Idempotent = null, + string? Scope = null, + bool? RequiresConfirmation = null, + bool? RequiresAuth = null +); + +public record CliOutputSchema( + string[]? Formats = null, + string? FormatFlag = null +); + +public record CliEnvironmentSchema( + List? Variables = null, + List? ConfigFiles = null +); + +public record CliEnvVarSchema( + string Name, + string? Description = null, + bool Required = false, + string? DefaultValue = null +); + +public record CliConfigFileSchema( + string Path, + string? Description = null, + bool Required = false +); diff --git a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchemaJsonContext.cs similarity index 76% rename from src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs rename to src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchemaJsonContext.cs index 02dc75c21d..672aa7bff1 100644 --- a/src/Elastic.Documentation.Configuration/Toc/CliReference/ArghSchemaJsonContext.cs +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchemaJsonContext.cs @@ -7,5 +7,5 @@ namespace Elastic.Documentation.Configuration.Toc.CliReference; [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] -[JsonSerializable(typeof(ArghCliSchema))] -internal sealed partial class ArghSchemaJsonContext : JsonSerializerContext; +[JsonSerializable(typeof(CliSchema))] +internal sealed partial class CliSchemaJsonContext : JsonSerializerContext; diff --git a/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs b/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs index 17fb804d9d..b1746079a0 100644 --- a/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs +++ b/src/Elastic.Documentation.Navigation/Isolated/Node/DocumentationSetNavigation.cs @@ -452,10 +452,10 @@ INavigationHomeAccessor homeAccessor var schemaFileInfo = context.ReadFileSystem.FileInfo.New( context.ReadFileSystem.Path.Join(context.DocumentationSourceDirectory.FullName, cliRef.SchemaPath)); - ArghCliSchema schema; + CliSchema schema; try { - schema = ArghCliSchema.Load(schemaFileInfo); + schema = CliSchema.Load(schemaFileInfo); } catch (Exception ex) { diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs index 1843235758..0ee7bade33 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs @@ -10,7 +10,7 @@ namespace Elastic.Markdown.Extensions.CliReference; internal static partial class CliMarkdownGenerator { - public static string RootPage(ArghCliSchema schema, string? supplementalContent) + public static string RootPage(CliSchema schema, string? supplementalContent) { var sb = new StringBuilder(); _ = sb.AppendLine($"# {schema.Name}"); @@ -47,6 +47,37 @@ public static string RootPage(ArghCliSchema schema, string? supplementalContent) AppendPageCard(sb, ns.Segment, $"./{ns.Segment}/index.md", ns.Summary); } + if (schema.Environment?.Variables is { Count: > 0 } envVars) + { + _ = sb.AppendLine("## Environment Variables"); + _ = sb.AppendLine(); + foreach (var v in envVars) + { + var required = v.Required ? " **required**" : string.Empty; + _ = sb.AppendLine($"`{v.Name}`{required}"); + _ = sb.AppendLine($": {v.Description?.Trim() ?? string.Empty}"); + if (!string.IsNullOrWhiteSpace(v.DefaultValue)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Default:** `{v.DefaultValue.Trim()}`"); + } + _ = sb.AppendLine(); + } + } + + if (schema.Environment?.ConfigFiles is { Count: > 0 } configFiles) + { + _ = sb.AppendLine("## Configuration Files"); + _ = sb.AppendLine(); + foreach (var f in configFiles) + { + var required = f.Required ? " **required**" : string.Empty; + _ = sb.AppendLine($"`{f.Path}`{required}"); + _ = sb.AppendLine($": {f.Description?.Trim() ?? string.Empty}"); + _ = sb.AppendLine(); + } + } + return sb.ToString(); } @@ -70,6 +101,9 @@ public static string NamespacePage(CliNamespaceSchema ns, string? supplementalCo _ = sb.AppendLine(); + if (ns.DefaultCommand is { Hidden: false } defaultCmd) + AppendDefaultCommand(sb, defaultCmd, fullPath, binaryName); + var visibleCmds = ns.Commands.Where(c => !c.Hidden).ToList(); if (visibleCmds.Count > 0) { @@ -94,6 +128,14 @@ public static string NamespacePage(CliNamespaceSchema ns, string? supplementalCo AppendParameters(sb, ns.Options); } + if (!string.IsNullOrWhiteSpace(ns.Notes)) + { + _ = sb.AppendLine("## Notes"); + _ = sb.AppendLine(); + _ = sb.AppendLine(ns.Notes.Trim()); + _ = sb.AppendLine(); + } + return sb.ToString(); } @@ -113,6 +155,8 @@ public static string CommandPage(CliCommandSchema cmd, string? supplementalConte _ = sb.AppendLine("```"); _ = sb.AppendLine(); + AppendCommandCallouts(sb, cmd); + if (supplementalContent is not null) _ = sb.AppendLine(supplementalContent.Trim()); else if (!string.IsNullOrWhiteSpace(cmd.Summary)) @@ -166,6 +210,126 @@ public static string CommandPage(CliCommandSchema cmd, string? supplementalConte return sb.ToString(); } + private static void AppendCommandCallouts(StringBuilder sb, CliCommandSchema cmd) + { + if (cmd.Deprecated is not null) + { + var parts = new List { "**Deprecated**" }; + if (!string.IsNullOrWhiteSpace(cmd.Deprecated.Since)) + parts.Add($"since {cmd.Deprecated.Since}"); + if (!string.IsNullOrWhiteSpace(cmd.Deprecated.Message)) + parts.Add(cmd.Deprecated.Message.Trim().TrimEnd('.')); + if (!string.IsNullOrWhiteSpace(cmd.Deprecated.RemovedIn)) + parts.Add($"Removed in: {cmd.Deprecated.RemovedIn}"); + _ = sb.AppendLine(":::{warning}"); + _ = sb.AppendLine(string.Join(". ", parts) + "."); + _ = sb.AppendLine(":::"); + _ = sb.AppendLine(); + } + + if (cmd.Intent?.Destructive == true) + { + _ = sb.AppendLine(":::{danger}"); + _ = sb.AppendLine("This command is **destructive** — changes cannot be undone."); + _ = sb.AppendLine(":::"); + _ = sb.AppendLine(); + } + + var notes = new List(); + if (cmd.LongRunning) + notes.Add("This command may take a long time to complete."); + if (cmd.Streaming) + notes.Add("This command streams output continuously until stopped."); + if (cmd.Intent?.RequiresAuth == true) + notes.Add("Authentication is required."); + if (cmd.Intent?.RequiresConfirmation == true) + notes.Add("This command prompts for confirmation before proceeding."); + if (!string.IsNullOrWhiteSpace(cmd.Intent?.Scope)) + notes.Add($"Scope: `{cmd.Intent.Scope}`."); + + foreach (var note in notes) + { + _ = sb.AppendLine($"> {note}"); + _ = sb.AppendLine(); + } + + if (cmd.Output?.Formats is { Length: > 0 } formats) + { + _ = sb.AppendLine($"**Output formats:** {string.Join(", ", formats)}"); + _ = sb.AppendLine(); + } + } + + private static void AppendDefaultCommand(StringBuilder sb, CliDefaultSchema defaultCmd, string[]? fullPath, string? binaryName) + { + _ = sb.AppendLine("## Running without a subcommand"); + _ = sb.AppendLine(); + + if (!string.IsNullOrWhiteSpace(defaultCmd.Summary)) + { + _ = sb.AppendLine(defaultCmd.Summary.Trim()); + _ = sb.AppendLine(); + } + + var usageParts = new List(); + if (!string.IsNullOrWhiteSpace(binaryName)) + usageParts.Add(binaryName); + if (fullPath is { Length: > 0 }) + usageParts.AddRange(fullPath); + + var usageLine = !string.IsNullOrWhiteSpace(defaultCmd.Usage) + ? defaultCmd.Usage + : string.Join(" ", usageParts) + " [options]"; + + _ = sb.AppendLine("```bash"); + _ = sb.AppendLine(FormatUsage(usageLine)); + _ = sb.AppendLine("```"); + _ = sb.AppendLine(); + + if (defaultCmd.Parameters.Count > 0) + { + var positionals = defaultCmd.Parameters.Where(p => p.Role == "positional").ToList(); + var flags = defaultCmd.Parameters.Where(p => p.Role != "positional").ToList(); + + if (positionals.Count > 0) + { + _ = sb.AppendLine("### Arguments"); + _ = sb.AppendLine(); + AppendParameters(sb, positionals); + } + + if (flags.Count > 0) + { + _ = sb.AppendLine("### Options"); + _ = sb.AppendLine(); + AppendParameters(sb, flags); + } + } + + if (defaultCmd.Examples is { Length: > 0 }) + { + _ = sb.AppendLine("### Examples"); + _ = sb.AppendLine(); + foreach (var example in defaultCmd.Examples) + { + if (string.IsNullOrWhiteSpace(example)) + continue; + _ = sb.AppendLine("```"); + _ = sb.AppendLine(example.Trim()); + _ = sb.AppendLine("```"); + _ = sb.AppendLine(); + } + } + + if (!string.IsNullOrWhiteSpace(defaultCmd.Notes)) + { + _ = sb.AppendLine("### Notes"); + _ = sb.AppendLine(); + _ = sb.AppendLine(defaultCmd.Notes.Trim()); + _ = sb.AppendLine(); + } + } + // Commands named "index" keep cmd- prefix to avoid collision with namespace index.md pages private static string CommandPath(string name) => name.Equals("index", StringComparison.OrdinalIgnoreCase) ? $"cmd-{name}" : name; @@ -195,7 +359,33 @@ private static void AppendParameters(StringBuilder sb, IEnumerable { "**Deprecated**" }; + if (!string.IsNullOrWhiteSpace(p.Deprecated.Since)) + parts.Add($"since {p.Deprecated.Since}"); + if (!string.IsNullOrWhiteSpace(p.Deprecated.Message)) + parts.Add(p.Deprecated.Message.Trim().TrimEnd('.')); + if (!string.IsNullOrWhiteSpace(p.Deprecated.RemovedIn)) + parts.Add($"Removed in: {p.Deprecated.RemovedIn}"); + _ = sb.AppendLine(); + _ = sb.AppendLine($" {string.Join(". ", parts)}."); + } // Enum values: prefer schema EnumValues, fall back to legacy embedded text var values = p.EnumValues is { Length: > 0 } @@ -219,10 +409,63 @@ private static void AppendParameters(StringBuilder sb, IEnumerable? validations) + { + if (validations is not { Count: > 0 }) + return string.Empty; + + var parts = new List(); + foreach (var v in validations) + { + var phrase = v.Kind.ToLowerInvariant() switch + { + "existing" => "must exist", + "rejectsymboliclinks" => "symbolic links not allowed", + "expanduserprofile" => "supports `~` home expansion", + "urischeme" when v.Values is { Length: > 0 } => + $"must be a {string.Join(" or ", v.Values)} URI", + "range" when v.Min is not null && v.Max is not null => + $"between {v.Min} and {v.Max}", + "range" when v.Min is not null => $"minimum {v.Min}", + "range" when v.Max is not null => $"maximum {v.Max}", + "timespanrange" when v.Min is not null && v.Max is not null => + $"duration between {v.Min} and {v.Max}", + "fileextensions" when v.Values is { Length: > 0 } => + $"extensions: {string.Join(", ", v.Values)}", + "pattern" when v.Pattern is not null => $"must match `{v.Pattern}`", + _ => null + }; + if (phrase is not null) + parts.Add(phrase); + } + + return parts.Count > 0 ? string.Join(", ", parts) : string.Empty; + } + private static string GenerateUsage(CliCommandSchema cmd, string[]? fullPath, string? binaryName) { var parts = new List(); diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs index 97700ac28a..aaac32349e 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -14,8 +14,8 @@ namespace Elastic.Markdown.Extensions.CliReference; internal sealed record CliEntityInfo( - ArghCliSchema Schema, - object Entity, // ArghCliSchema | CliNamespaceSchema | CliCommandSchema + CliSchema Schema, + object Entity, // CliSchema | CliNamespaceSchema | CliCommandSchema IFileInfo? SupplementalDoc, /// The clean synthetic file (no cmd- prefix) — used as the MarkdownFile source for correct URL generation. IFileInfo? CleanSyntheticFile = null, @@ -109,7 +109,7 @@ private void EnsureSyntheticFilesBuilt() private MarkdownFile? CreateCliFileFromInfo(IFileInfo sourceFile, MarkdownParser markdownParser, CliEntityInfo info) => info.Entity switch { - ArghCliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), + CliSchema schema => new CliRootFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, schema, info.SupplementalDoc), CliNamespaceSchema ns => new CliNamespaceFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, ns, info.SupplementalDoc, info.FullPath ?? [ns.Segment], info.Schema.Name), CliCommandSchema cmd => new CliCommandFile(sourceFile, Build.DocumentationSourceDirectory, markdownParser, Build, cmd, info.SupplementalDoc, info.FullPath ?? [cmd.Name], info.Schema.Name), _ => null @@ -159,10 +159,10 @@ private List BuildSyntheticFiles() if (!schemaFileInfo.Exists) continue; - ArghCliSchema schema; + CliSchema schema; try { - schema = ArghCliSchema.Load(schemaFileInfo); + schema = CliSchema.Load(schemaFileInfo); } catch (Exception ex) { @@ -219,7 +219,7 @@ private void CollectNamespaceFiles( string[] nsPath, HashSet matched, List fileInfos, - ArghCliSchema schema) + CliSchema schema) { foreach (var ns in namespaces) { diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs index 9bcdce0b4d..9e18b3863b 100644 --- a/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs +++ b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.cs @@ -12,7 +12,7 @@ namespace Elastic.Markdown.Extensions.CliReference; public record CliRootFile : IO.MarkdownFile { - private readonly ArghCliSchema _schema; + private readonly CliSchema _schema; private readonly IFileInfo? _supplementalDoc; public CliRootFile( @@ -20,7 +20,7 @@ public CliRootFile( IDirectoryInfo rootPath, MarkdownParser parser, BuildContext build, - ArghCliSchema schema, + CliSchema schema, IFileInfo? supplementalDoc ) : base(sourceFile, rootPath, parser, build) { diff --git a/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs b/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs index 6023aeccf0..f89094b7f8 100644 --- a/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/AssemblerCommands.cs @@ -14,6 +14,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands.Assembler; @@ -109,6 +110,7 @@ IEnvironmentVariables environmentVariables /// Named deployment target. Determines which repositories and branches are cloned. /// Fetch the HEAD of each branch instead of the pinned link-registry ref. /// Skip cloning; assume repositories are already on disk. + [CommandIntent(Intent.Idempotent)] [NoOptionsInjection] public async Task Clone( bool? strict = null, @@ -136,6 +138,7 @@ static async (s, col, opts, ctx) => await s.CloneAll(col, opts, ctx) /// Run after assembler clone. Reads every cloned repository, applies the shared navigation.yml, /// and writes the unified site to .artifacts/docs/. /// + [CommandIntent(Intent.Idempotent)] [NoOptionsInjection] public async Task Build( [AsParameters] AssemblerBuildOptions options, @@ -156,6 +159,7 @@ static async (s, col, state, ctx) => await s.BuildAll(col, state.options, state. /// Run after assembler build. Does not watch for file changes. /// Port to listen on. Default: 4000. /// Path to the built site. Defaults to .artifacts/docs/. + [NoOptionsInjection] public async Task Serve(int port = 4000, [Existing, ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? path = null, CancellationToken ct = default) { diff --git a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs index 500292d4e9..b6a18a787b 100644 --- a/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs +++ b/src/tooling/docs-builder/Commands/Assembler/DeployCommands.cs @@ -12,6 +12,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands.Assembler; @@ -33,6 +34,8 @@ ICoreService githubActionsService /// S3 bucket to deploy to. /// Path to write the plan file. Defaults to stdout. /// Abort if the plan would delete more than this percentage of objects (0–100). + [RequiresAuth] + [MutationScope(MutationScope.Global)] [NoOptionsInjection] public async Task Plan(string environment, string s3BucketName, [ExpandUserProfile, RejectSymbolicLinks] FileInfo? @out = null, float? deleteThreshold = null, CancellationToken ct = default) { @@ -50,6 +53,9 @@ static async (s, collector, state, ctx) => await s.Plan(collector, state.environ /// Named deployment target. /// S3 bucket to deploy to. /// Path to the plan file produced by assembler deploy plan. + [RequiresAuth] + [CommandIntent(Intent.Destructive)] + [MutationScope(MutationScope.Global)] [NoOptionsInjection] public async Task Apply(string environment, string s3BucketName, [Existing, ExpandUserProfile, RejectSymbolicLinks, FileExtensions(Extensions = "json,plan")] FileInfo planFile, CancellationToken ct = default) { diff --git a/src/tooling/docs-builder/Commands/ChangelogCommand.cs b/src/tooling/docs-builder/Commands/ChangelogCommand.cs index 74b367d006..751ce7500d 100644 --- a/src/tooling/docs-builder/Commands/ChangelogCommand.cs +++ b/src/tooling/docs-builder/Commands/ChangelogCommand.cs @@ -24,6 +24,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands; @@ -924,6 +925,8 @@ async static (s, collector, state, ctx) => await s.BundleChangelogs(collector, s /// GitHub repository name, which is used when PRs or issues are specified as numbers or when --release-version is used. Falls back to bundle.repo in changelog.yml when not specified. If that value is also absent, the product ID is used. /// Optional (option-based mode only): URL or file path to a promotion report. Extracts PR URLs and uses them as the filter. Mutually exclusive with --all, --products, --prs, --release-version, and --issues. /// + [CommandIntent(Intent.Destructive | Intent.RequiresConfirmation)] + [MutationScope(MutationScope.Directory)] [NoOptionsInjection] public async Task Remove( [Argument] string? profile = null, @@ -933,8 +936,8 @@ public async Task Remove( [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? bundlesDir = null, [Existing, ExpandUserProfile, RejectSymbolicLinks, FileExtensions(Extensions = "yml,yaml")] FileInfo? config = null, [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? directory = null, - bool dryRun = false, - bool force = false, + [DryRun] bool dryRun = false, + [ConfirmationSkip] bool force = false, string[]? issues = null, string? owner = null, [ArgumentParser(typeof(ProductInfoParser))] ProductArgumentList? products = null, diff --git a/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs b/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs index fd5ab16bca..349cfd2a17 100644 --- a/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs +++ b/src/tooling/docs-builder/Commands/Codex/CodexCommands.cs @@ -18,6 +18,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands.Codex; @@ -119,6 +120,7 @@ public async Task CloneAndBuild( /// Treat warnings as errors. /// Fetch the HEAD of each branch instead of the pinned ref. /// Skip cloning; assume repositories are already on disk. + [CommandIntent(Intent.Idempotent)] [NoOptionsInjection] public async Task Clone( [Argument, Existing, ExpandUserProfile, RejectSymbolicLinks, FileExtensions(Extensions = "yml,yaml")] FileInfo config, @@ -167,6 +169,7 @@ public async Task Clone( /// Path to the codex.yml configuration file. /// Treat warnings as errors. /// Output directory. Defaults to .artifacts/codex/. + [CommandIntent(Intent.Idempotent)] [NoOptionsInjection] public async Task Build( [Argument, Existing, ExpandUserProfile, RejectSymbolicLinks, FileExtensions(Extensions = "yml,yaml")] FileInfo config, @@ -220,6 +223,7 @@ public async Task Build( /// Run after codex build. Does not rebuild on file changes. /// Port to listen on. Default: 4000. /// Path to the portal output. Defaults to .artifacts/codex/docs/. + [NoOptionsInjection] public async Task Serve(int port = 4000, [Existing, ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? path = null, CancellationToken ct = default) { diff --git a/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs b/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs index 3e14db4f90..185814603e 100644 --- a/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs +++ b/src/tooling/docs-builder/Commands/Codex/CodexIndexCommand.cs @@ -18,6 +18,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands.Codex; @@ -35,6 +36,8 @@ IEnvironmentVariables environmentVariables /// Run after codex build. Streams documents from all included documentation sets to the cluster. /// /// Path to the codex.yml configuration file. + + [RequiresAuth] public async Task Index( GlobalCliOptions _, [Argument, Existing, ExpandUserProfile, RejectSymbolicLinks, FileExtensions(Extensions = "yml,yaml")] FileInfo config, diff --git a/src/tooling/docs-builder/Commands/IndexCommand.cs b/src/tooling/docs-builder/Commands/IndexCommand.cs index ad0004a648..34e1489d10 100644 --- a/src/tooling/docs-builder/Commands/IndexCommand.cs +++ b/src/tooling/docs-builder/Commands/IndexCommand.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands; @@ -28,6 +29,8 @@ IEnvironmentVariables environmentVariables /// Does not write HTML to disk. Requires a running cluster and valid credentials. /// /// + + [RequiresAuth] [CommandName("index")] public async Task Index( GlobalCliOptions _, diff --git a/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs b/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs index b39cdcb43e..cc9d478428 100644 --- a/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs +++ b/src/tooling/docs-builder/Commands/IsolatedBuildCommand.cs @@ -11,6 +11,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands; @@ -27,6 +28,8 @@ IEnvironmentVariables environmentVariables /// Locates the documentation root by searching for a docset.yml file starting at .Path. /// The output directory is wiped and rebuilt on each run unless incremental build detects no changes. /// + [CommandIntent(Intent.Idempotent)] + [MutationScope(MutationScope.Directory)] [DefaultCommand] [CommandName("build")] public async Task Build( diff --git a/src/tooling/docs-builder/Commands/MoveCommand.cs b/src/tooling/docs-builder/Commands/MoveCommand.cs index 45ee274887..2f5ac7a901 100644 --- a/src/tooling/docs-builder/Commands/MoveCommand.cs +++ b/src/tooling/docs-builder/Commands/MoveCommand.cs @@ -10,6 +10,7 @@ using Elastic.Documentation.Services; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands; @@ -24,12 +25,14 @@ IConfigurationContext configurationContext /// Destination file or folder path. /// -p, Documentation root. Defaults to cwd. /// Print the changes that would be made without applying them. + [CommandIntent(Intent.Destructive)] + [MutationScope(MutationScope.Directory)] [CommandName("mv")] public async Task Move( GlobalCliOptions _, [Argument] string source, [Argument] string target, - bool? dryRun = null, + [DryRun] bool? dryRun = null, string? path = null, Cancel ct = default ) @@ -50,6 +53,8 @@ async static (s, collector, state, ctx) => await s.Move(collector, state.source, /// -p, Documentation root. Defaults to cwd. /// Report files that need formatting without modifying them. Exits 1 when any file is out of format. /// Apply formatting changes in place. + [CommandIntent(Intent.Idempotent)] + [MutationScope(MutationScope.Directory)] [CommandName("format")] public async Task Format( GlobalCliOptions _, diff --git a/src/tooling/docs-builder/Commands/ServeCommand.cs b/src/tooling/docs-builder/Commands/ServeCommand.cs index 12bf06ea4d..0d2ff3afa3 100644 --- a/src/tooling/docs-builder/Commands/ServeCommand.cs +++ b/src/tooling/docs-builder/Commands/ServeCommand.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Configuration; using Microsoft.Extensions.Logging; using Nullean.Argh; +using Nullean.Argh.Documentation; namespace Documentation.Builder.Commands; @@ -20,6 +21,7 @@ internal sealed class ServeCommand(ILoggerFactory logFactory, IConfigurationCont /// -p, Documentation source directory. Defaults to the cwd/docs folder. /// Port to serve the documentation. Default: 3000 /// Special flag for dotnet watch optimizations during development + [CommandName("serve")] public async Task Serve(GlobalCliOptions _, [Existing, ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? path = null, int port = 3000, bool watch = false, CancellationToken ct = default) { From 55e08a31defafd9ac0bc9d03b12c5222e51fe8da Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 7 May 2026 17:30:21 +0200 Subject: [PATCH 18/18] fix(cli-docs): restore product-format, lifecycle-inference, link-allowlist anchors The static cli/changelog/bundle.md was deleted (replaced by the schema-generated page) but other docs still cross-link to its anchors. Add the three referenced sections to the cmd-bundle.md supplemental so they appear in the generated page. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- docs/cli/changelog/cmd-bundle.md | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/cli/changelog/cmd-bundle.md b/docs/cli/changelog/cmd-bundle.md index d4b612aa24..6a767a008c 100644 --- a/docs/cli/changelog/cmd-bundle.md +++ b/docs/cli/changelog/cmd-bundle.md @@ -82,3 +82,57 @@ For most release workflows, use `--resolve`. It makes the bundle self-contained Pass `--plan` to emit GitHub Actions step outputs (`needs_network`, `needs_github_token`, `output_path`) without generating the bundle. Use this in a planning step to decide whether subsequent steps require a GitHub token or network access. For full configuration reference, see [Bundle changelogs](/contribute/bundle-changelogs.md). + +## Product format [product-format] + +The `changelog bundle` command has `--input-products` and `--output-products` options that accept values with the format `"product target lifecycle, ..."` where: + +- `product` is the product ID from [products.yml](https://github.com/elastic/docs-builder/blob/main/config/products.yml) (required) +- `target` is the target version or date (optional) +- `lifecycle` exists in [Lifecycle.cs](https://github.com/elastic/docs-builder/blob/main/src/Elastic.Documentation/Lifecycle.cs) (optional) + +You can further limit the possible values with the [products](/contribute/configure-changelogs-ref.md#products) and [lifecycles](/contribute/configure-changelogs-ref.md#lifecycles) options in the changelog configuration file. + +For example: + +- `"kibana 9.2.0 ga"` +- `"cloud-serverless 2025-08-05"` +- `"cloud-enterprise 4.0.3, cloud-hosted 2025-10-31"` + +If you use `"* * *"` in the `--input-products` command option or `bundle.profiles..products` configuration setting, it's equivalent to the `--all` command option. + +## Lifecycle inference [lifecycle-inference] + +The way that lifecycle values are inferred varies between GitHub release profiles and standard profiles. + +### GitHub release profiles + +For `source: github_release` profiles, the `{lifecycle}` placeholder in `output` and `output_products` is derived from the full release tag name. For example: + +| Release tag | `{version}` | `{lifecycle}` | +|-------------|-------------|---------------| +| `v1.2.3` | `1.2.3` | `ga` | +| `v1.2.3-beta.1` | `1.2.3` | `beta` | +| `v1.2.3-preview.1` | `1.2.3` | `preview` | + +### Standard profiles + +For standard profiles, `{version}` is copied verbatim from your command argument and `{lifecycle}` is derived from that value. For example: + +| Version argument | `{version}` | `{lifecycle}` | +|------------------|-------------|---------------| +| `9.2.0` | `9.2.0` | `ga` | +| `9.2.0-beta.1` | `9.2.0-beta.1` | `beta` | +| `9.2.0-preview.1` | `9.2.0-preview.1` | `preview` | + +For more information about acceptable product and lifecycle values, go to [Product format](#product-format). + +## PR and issue link allowlist [link-allowlist] + +A changelog in a public repository might contain links to pull requests or issues in repositories that should not appear in published documentation. + +Set `bundle.link_allow_repos` in `changelog.yml` to an explicit list of `owner/repo` strings. When this key is present (including as an empty list), PR and issue references are filtered at bundle time: only links whose resolved repository is in the list are kept; others are rewritten to `# PRIVATE:` sentinel strings in the bundle YAML. + +:::{important} +`bundle.link_allow_repos` requires a **resolved** bundle. Set `bundle.resolve: true` or pass `--resolve`. +:::