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/Directory.Packages.props b/Directory.Packages.props index b050cc128e..df37646548 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -69,9 +69,9 @@ - - - + + + @@ -88,7 +88,7 @@ - + diff --git a/docs/_docset.yml b/docs/_docset.yml index 8c56fc0d0a..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 @@ -160,53 +161,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..efb262699e 100644 --- a/docs/_redirects.yml +++ b/docs/_redirects.yml @@ -1,18 +1,45 @@ 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' - '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 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': @@ -35,4 +62,4 @@ redirects: "yy": "bb" 'testing/redirects/third-page.md': anchors: - 'removed-anchor': \ No newline at end of file + 'removed-anchor': diff --git a/docs/building-blocks/inbound-cross-links.md b/docs/building-blocks/inbound-cross-links.md index 207aca023a..cc72e84891 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/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/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..99fae06718 --- /dev/null +++ b/docs/cli-schema.json @@ -0,0 +1,5940 @@ +{ + "schemaVersion": 1, + "name": "docs-builder", + "version": "1.0.0.0", + "description": null, + "reservedMetaCommands": [ + "__complete", + "__completion", + "__schema" + ], + "globalOptions": [ + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "strict", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors." + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "type": "string", + "required": false, + "summary": "Named deployment target, e.g. dev, staging, production. Determines which configuration branch and index names are used." + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set." + }, + { + "role": "flag", + "name": "show-hints", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Print documentation hints emitted during the build." + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "type": "string", + "required": false, + "summary": "Comma-separated list of exporters to run." + }, + { + "role": "flag", + "name": "assume-build", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip the build step when .artifacts/docs/index.html already exists. Intended for test scenarios only." + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned link-registry ref.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk. Useful for iterating on the build.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "serve", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Serve the site on port 4000 after a successful build.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": "p", + "type": "string", + "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", + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "URL path prefix prepended to every generated link." + }, + { + "role": "flag", + "name": "force", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Delete and rebuild the output folder even if nothing changed." + }, + { + "role": "flag", + "name": "strict", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors." + }, + { + "role": "flag", + "name": "allow-indexing", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Emit meta robots tags that allow search engine indexing." + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set." + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "type": "string", + "required": false, + "summary": "Comma-separated list of exporters to run." + }, + { + "role": "flag", + "name": "canonical-base-url", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Skip OpenAPI spec generation for faster builds." + }, + { + "role": "flag", + "name": "skip-cross-links", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip fetching cross-doc-set link indexes." + }, + { + "role": "flag", + "name": "in-memory", + "shortName": null, + "type": "boolean", + "required": false, + "summary": null, + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true, + "scope": "directory" + } + }, + { + "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", + "type": "string", + "required": false, + "summary": "Root of the documentation source. Defaults to cwd/docs." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": "p", + "type": "string", + "required": false, + "summary": "Documentation root. Defaults to cwd." + }, + { + "role": "flag", + "name": "check", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Report files that need formatting without modifying them. Exits 1 when any file is out of format.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "write", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Apply formatting changes in place.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true, + "scope": "directory" + } + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY." + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "type": "string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME." + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD." + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default)." + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default)." + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Force a full reindex, discarding any incremental state." + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests." + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Proxy server username." + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Proxy server password." + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments." + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "type": "string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate." + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root." + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "type": "string", + "required": false, + "summary": null + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "requiresAuth": true + } + }, + { + "path": [], + "name": "mv", + "summary": "Move a file or folder and rewrite all inbound links across the documentation set.", + "notes": null, + "usage": "docs-builder mv \u003Csource\u003E \u003Ctarget\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "source", + "shortName": null, + "type": "string", + "required": true, + "summary": "Source file or folder path." + }, + { + "role": "positional", + "name": "target", + "shortName": null, + "type": "string", + "required": true, + "summary": "Destination file or folder path." + }, + { + "role": "dryRun", + "name": "dry-run", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Print the changes that would be made without applying them.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "path", + "shortName": "p", + "type": "string", + "required": false, + "summary": "Documentation root. Defaults to cwd." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "destructive": true, + "scope": "directory" + } + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "path", + "shortName": "p", + "type": "string", + "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, + "type": "integer", + "required": false, + "summary": "Port to serve the documentation. Default: 3000", + "defaultValue": "3000" + }, + { + "role": "flag", + "name": "watch", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Special flag for dotnet watch optimizations during development", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors." + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "type": "string", + "required": false, + "summary": "Named deployment target, e.g. dev, staging, production. Determines which configuration branch and index names are used." + }, + { + "role": "flag", + "name": "metadata-only", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Write only metadata files; skip HTML generation. Ignored when --exporters is also set." + }, + { + "role": "flag", + "name": "show-hints", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Print documentation hints emitted during the build." + }, + { + "role": "flag", + "name": "exporters", + "shortName": null, + "type": "string", + "required": false, + "summary": "Comma-separated list of exporters to run." + }, + { + "role": "flag", + "name": "assume-build", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip the build step when .artifacts/docs/index.html already exists. Intended for test scenarios only." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true + } + }, + { + "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, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "type": "string", + "required": false, + "summary": "Named deployment target. Determines which repositories and branches are cloned." + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned link-registry ref.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true + } + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY." + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "type": "string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME." + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD." + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default)." + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default)." + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Force a full reindex, discarding any incremental state." + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests." + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Proxy server username." + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Proxy server password." + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments." + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "type": "string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate." + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root." + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "type": "string", + "required": false, + "summary": "Named deployment target; becomes part of the Elasticsearch index name." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "integer", + "required": false, + "summary": "Port to listen on. Default: 4000.", + "defaultValue": "4000" + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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 [options]", + "examples": [], + "parameters": [ + { + "role": "flag", + "name": "endpoint", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY." + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "type": "string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME." + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD." + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default)." + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default)." + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Force a full reindex, discarding any incremental state." + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests." + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Proxy server username." + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Proxy server password." + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments." + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "type": "string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate." + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root." + }, + { + "role": "flag", + "name": "environment", + "shortName": null, + "type": "string", + "required": false, + "summary": "Named deployment target; used to resolve the correct Elasticsearch index." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "required": true, + "summary": "URL path to look up (e.g. /guide/en/elasticsearch/reference/current/index.html)." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "required": false, + "summary": "Git ref to fetch. Defaults to main." + }, + { + "role": "flag", + "name": "local", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Write the configuration into cwd so subsequent commands treat it as a local override.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "required": false, + "summary": "Repository slug to match (e.g. elastic/elasticsearch)." + }, + { + "role": "positional", + "name": "branch-or-tag", + "shortName": null, + "type": "string", + "required": false, + "summary": "Branch name or version tag to test against." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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": [ + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "required": true, + "summary": "Named deployment target." + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "type": "string", + "required": true, + "summary": "S3 bucket to deploy to." + }, + { + "role": "flag", + "name": "plan-file", + "shortName": null, + "type": "string", + "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", + "plan" + ] + }, + { + "kind": "expandUserProfile", + "min": null, + "max": null, + "pattern": null, + "values": null + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "destructive": true, + "scope": "global", + "requiresAuth": true + } + }, + { + "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, + "type": "string", + "required": true, + "summary": "Named deployment target." + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "type": "string", + "required": true, + "summary": "S3 bucket to deploy to." + }, + { + "role": "flag", + "name": "out", + "shortName": null, + "type": "string", + "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, + "type": "number", + "required": false, + "summary": "Abort if the plan would delete more than this percentage of objects (0\u2013100).", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "scope": "global", + "requiresAuth": true + } + }, + { + "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, + "type": "string", + "required": true, + "summary": "Named deployment target." + }, + { + "role": "flag", + "name": "redirects-file", + "shortName": null, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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": [ + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "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": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "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." + }, + { + "role": "flag", + "name": "action", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: What users must do to mitigate" + }, + { + "role": "flag", + "name": "areas", + "shortName": null, + "type": "array", + "required": false, + "summary": "Optional: Area(s) affected (comma-separated or specify multiple times)", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "concise", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Optional: Omit schema reference comments from generated YAML files. Useful in CI to produce compact output.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Optional: Additional information about the change (max 600 characters)" + }, + { + "role": "flag", + "name": "no-extract-release-notes", + "shortName": null, + "type": "boolean", + "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.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "no-extract-issues", + "shortName": null, + "type": "boolean", + "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.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "feature-id", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Feature flag ID" + }, + { + "role": "flag", + "name": "highlight", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Optional: Include in release highlights", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "impact", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: How the user\u0027s environment is affected" + }, + { + "role": "flag", + "name": "issues", + "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. Mutually exclusive with --release-version and --report.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "prs", + "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. 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, --issues, and --report. Does not create a bundle; use \u0027changelog gh-release\u0027 for that." + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "type": "boolean", + "required": false, + "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" + }, + { + "role": "flag", + "name": "subtype", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Subtype for breaking changes (api, behavioral, configuration, etc.)" + }, + { + "role": "flag", + "name": "title", + "shortName": null, + "type": "string", + "required": false, + "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", + "name": "type", + "shortName": null, + "type": "string", + "required": false, + "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", + "name": "use-pr-number", + "shortName": null, + "type": "boolean", + "required": false, + "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" + }, + { + "role": "flag", + "name": "use-issue-number", + "shortName": null, + "type": "boolean", + "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.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "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." + }, + { + "role": "positional", + "name": "profile-arg", + "shortName": null, + "type": "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)" + }, + { + "role": "positional", + "name": "profile-report", + "shortName": null, + "type": "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)." + }, + { + "role": "flag", + "name": "all", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Include all changelogs in the directory.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "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, + "type": "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." + }, + { + "role": "flag", + "name": "hide-features", + "shortName": null, + "type": "array", + "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).", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "no-release-date", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Optional: Skip auto-population of release date in the bundle. Mutually exclusive with --release-date. Not available in profile mode.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "release-date", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "input-products", + "shortName": null, + "type": "string", + "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)." + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "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" + }, + { + "role": "flag", + "name": "output-products", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Explicitly set the products array in the output file in format \u0022product target lifecycle, ...\u0022. Overrides any values from changelogs." + }, + { + "role": "flag", + "name": "issues", + "shortName": null, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "plan", + "shortName": null, + "type": "boolean", + "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.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "prs", + "shortName": null, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "release-version", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "report", + "shortName": null, + "type": "string", + "required": false, + "summary": "A URL or file path to a promotion report. Extracts PR URLs and uses them as the filter." + }, + { + "role": "flag", + "name": "resolve", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Optional: Copy the contents of each changelog file into the entries array. Uses config bundle.resolve or defaults to false.", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "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, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "resolve", + "shortName": null, + "type": "boolean", + "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).", + "defaultValue": "default" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "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, + "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": "pr-number", + "shortName": null, + "type": "integer", + "required": true, + "summary": "Pull request number" + }, + { + "role": "flag", + "name": "pr-title", + "shortName": null, + "type": "string", + "required": true, + "summary": "Pull request title" + }, + { + "role": "flag", + "name": "pr-labels", + "shortName": null, + "type": "string", + "required": true, + "summary": "Comma-separated PR labels" + }, + { + "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": "event-action", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: GitHub event action (e.g., opened, synchronize, edited). When omitted, body-only-edit and bot-loop checks are skipped." + }, + { + "role": "flag", + "name": "title-changed", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Whether the PR title changed (for edited events)", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "body-changed", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Whether the PR body changed (for edited events)", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Remove square-bracket prefixes from the PR title", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "bot-name", + "shortName": null, + "type": "string", + "required": false, + "summary": "Bot login name for loop detection", + "defaultValue": "github-actions[bot]" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "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)" + }, + { + "role": "positional", + "name": "version", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Version tag to fetch (e.g., \u0022v9.0.0\u0022, \u00229.0.0\u0022). Defaults to \u0022latest\u0022", + "defaultValue": "latest" + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Optional: Bundle description text with placeholder support. Supports VERSION, LIFECYCLE, OWNER, and REPO placeholders. Overrides bundle.description from config." + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "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" + }, + { + "role": "flag", + "name": "release-date", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Explicit release date for the bundle in YYYY-MM-DD format. Overrides GitHub release published date." + }, + { + "role": "flag", + "name": "strip-title-prefix", + "shortName": null, + "type": "boolean", + "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)", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "warn-on-type-mismatch", + "shortName": null, + "type": "boolean", + "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", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "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, + "type": "string", + "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, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "GitHub owner for seeding bundle defaults. Overrides the value inferred from git remote origin." + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "type": "string", + "required": false, + "summary": "GitHub repository name for seeding bundle defaults. Overrides the value inferred from git remote origin." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "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." + }, + { + "role": "positional", + "name": "profile-arg", + "shortName": null, + "type": "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)" + }, + { + "role": "positional", + "name": "profile-report", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "all", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Remove all changelogs in the directory. Exactly one filter option must be specified: --all, --products, --prs, --issues, or --report.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "bundles-dir", + "shortName": null, + "type": "string", + "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, + "type": "string", + "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, + "type": "string", + "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": "dryRun", + "name": "dry-run", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Print the files that would be removed without deleting them. Valid in both profile and raw mode.", + "defaultValue": "false" + }, + { + "role": "confirmationSkip", + "name": "force", + "shortName": null, + "type": "boolean", + "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.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "issues", + "shortName": null, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "owner", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "products", + "shortName": null, + "type": "string", + "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 (*)." + }, + { + "role": "flag", + "name": "prs", + "shortName": null, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "release-version", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "repo", + "shortName": null, + "type": "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." + }, + { + "role": "flag", + "name": "report", + "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, --release-version, and --issues." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "destructive": true, + "scope": "directory", + "requiresConfirmation": true + } + }, + { + "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, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Optional: Output file type. Valid values: \u0022markdown\u0022 or \u0022asciidoc\u0022. Defaults to \u0022markdown\u0022", + "defaultValue": "markdown" + }, + { + "role": "flag", + "name": "hide-features", + "shortName": null, + "type": "array", + "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.", + "repeatable": true, + "elementType": "string" + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Output directory for rendered files. Defaults to current directory" + }, + { + "role": "flag", + "name": "subsections", + "shortName": null, + "type": "boolean", + "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", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "title", + "shortName": null, + "type": "string", + "required": false, + "summary": "Optional: Title to use for section headers in output files. Defaults to version from first bundle" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "required": true, + "summary": "Artifact type to upload: \u0027changelog\u0027 (individual entries) or \u0027bundle\u0027 (consolidated bundles)." + }, + { + "role": "flag", + "name": "target", + "shortName": null, + "type": "string", + "required": true, + "summary": "Upload destination: \u0027s3\u0027 or \u0027elasticsearch\u0027." + }, + { + "role": "flag", + "name": "s3-bucket-name", + "shortName": null, + "type": "string", + "required": false, + "summary": "S3 bucket name (required when target is \u0027s3\u0027)." + }, + { + "role": "flag", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned ref.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Serve the portal on port 4000 after a successful build.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + "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, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "output", + "shortName": null, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true + } + }, + { + "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, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Treat warnings as errors.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "fetch-latest", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Fetch the HEAD of each branch instead of the pinned ref.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "assume-cloned", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning; assume repositories are already on disk.", + "defaultValue": "false" + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "idempotent": true + } + }, + { + "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 \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "API key for authentication. Falls back to env DOCUMENTATION_ELASTIC_APIKEY." + }, + { + "role": "flag", + "name": "username", + "shortName": null, + "type": "string", + "required": false, + "summary": "Username for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_USERNAME." + }, + { + "role": "flag", + "name": "password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Password for basic authentication. Falls back to env DOCUMENTATION_ELASTIC_PASSWORD." + }, + { + "role": "flag", + "name": "ai-enrichment", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Enable AI enrichment of documents using LLM-generated metadata (enabled by default)." + }, + { + "role": "flag", + "name": "search-num-threads", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Use the Elastic Inference Service to bootstrap the inference endpoint (enabled by default)." + }, + { + "role": "flag", + "name": "bootstrap-timeout", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Force a full reindex, discarding any incremental state." + }, + { + "role": "flag", + "name": "buffer-size", + "shortName": null, + "type": "integer", + "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, + "type": "integer", + "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, + "type": "boolean", + "required": false, + "summary": "Log every Elasticsearch request and response body; append ?pretty to all requests." + }, + { + "role": "flag", + "name": "proxy-address", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Proxy server username." + }, + { + "role": "flag", + "name": "proxy-password", + "shortName": null, + "type": "string", + "required": false, + "summary": "Proxy server password." + }, + { + "role": "flag", + "name": "disable-ssl-verification", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Disable SSL certificate validation. Use only in controlled environments." + }, + { + "role": "flag", + "name": "certificate-fingerprint", + "shortName": null, + "type": "string", + "required": false, + "summary": "SHA-256 fingerprint of a self-signed server certificate." + }, + { + "role": "flag", + "name": "certificate-path", + "shortName": null, + "type": "string", + "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, + "type": "boolean", + "required": false, + "summary": "Set when the certificate is an intermediate CA rather than the root." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ], + "intent": { + "requiresAuth": true + } + }, + { + "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, + "type": "integer", + "required": false, + "summary": "Port to listen on. Default: 4000.", + "defaultValue": "4000" + }, + { + "role": "flag", + "name": "path", + "shortName": null, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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 \u003Cconfig\u003E [options]", + "examples": [], + "parameters": [ + { + "role": "positional", + "name": "config", + "shortName": null, + "type": "string", + "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, + "type": "string", + "required": false, + "summary": "Named deployment target. Defaults to the value in codex.yml or the ENVIRONMENT env var." + }, + { + "role": "flag", + "name": "redirects-file", + "shortName": null, + "type": "string", + "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 + } + ] + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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, + "type": "string", + "required": false, + "summary": "Only check links published by this repository slug." + }, + { + "role": "flag", + "name": "to", + "shortName": null, + "type": "string", + "required": false, + "summary": "Only check links that point to this repository slug." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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": [ + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + }, + { + "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, + "type": "string", + "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", + "type": "string", + "required": false, + "summary": "Root of the documentation source. Defaults to cwd." + }, + { + "role": "flag", + "name": "log-level", + "shortName": "l", + "type": "enum", + "required": false, + "summary": "Minimum log level. Default: information", + "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", + "enumValues": [ + "local", + "remote", + "embedded" + ] + }, + { + "role": "flag", + "name": "skip-private-repositories", + "shortName": null, + "type": "boolean", + "required": false, + "summary": "Skip cloning private repositories" + } + ] + } + ], + "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 f38a9eaf38..0000000000 --- a/docs/cli/changelog/add.md +++ /dev/null @@ -1,241 +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 [](/contribute/create-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 3a1e0628fe..0000000000 --- a/docs/cli/changelog/bundle.md +++ /dev/null @@ -1,611 +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. - -:::{note} -The `--hide-features` option on the `render` command and the `hide-features` field in bundles are **combined**. If you specify `--hide-features` on both the `bundle` and `render` commands, all specified features are hidden. The `{changelog}` directive automatically reads `hide-features` from all loaded bundles and applies them. -::: - -`--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. -Bundle rules are not turned off when you use `--input-products`-- they run **after** matching, unless your configuration is in no-filtering mode per [Bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle). -::: - -`--issues ` -: Include changelogs for the specified 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 determine the **rule context product** (if there are multiple, the first ID alphabetically is used). Refer to [Product-specific bundle rules](/contribute/configure-changelogs-ref.md#rules-bundle-products). - -:::{tip} -Though technically optional, it is strongly recommended to set `--output-products` ( or `output_products` for profiles) so that you have a single clean product entry that reflects the context of the release. -::: - -`--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`. - -`--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`. - -`--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 ` -: Include changelogs for the specified 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 ` -: Bundle changelogs for the pull requests in GitHub release notes. For example, the tag can be `"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 ` -: Include changelogs based on the pull requests in 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. - -## File paths and filenames [output-files] - -**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 | - -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 | - -**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"` - -If you use `"* * *"` in the `--input-products` command option or `bundle.profiles..products` configuration setting, it's equivalent to the `--all` command option. - -## 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). -::: - -## 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 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 \ <1> - --repo apm-agent-dotnet \ <2> - --owner elastic <3> - --output-products "apm-agent-dotnet 1.34.0 ga" <4> -``` - -1. The tag value that is used in the `GET /repos/{owner}/{repo}/releases/tags/{tag}` releases API. -2. You must specify `--repo` or set `bundle.repo` in the changelog configuration file. -3. If you don't specify `--owner`, it uses `bundle.owner` in the changelog configuration or else defaults to `elastic`. -4. The bundle's product metadata is inferred automatically from the release tag and repository name; you can override that behavior with the `--output-products` option. - -:::{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](/contribute/configure-changelogs-ref.md#rules-bundle) configuration settings. - -:::{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) -::: - -### Bundle by issues [changelog-bundle-issues] - -You can use the `--issues` option to create a bundle of changelogs that relate to those GitHub issues. -Issues can be identified by a full URL (such as `https://github.com/owner/repo/issues/123`), a short format (such as `owner/repo#123`), or just a number (in which case `--owner` and `--repo` are required — or set via `bundle.owner` and `bundle.repo` in the configuration). - -```sh -docs-builder changelog bundle --issues "12345,12346" \ - --repo elasticsearch \ - --owner elastic \ - --output-products "elasticsearch 9.2.2 ga" -``` - -Alternatively, you can specify a path to a newline-delimited file that contains the issue URLS (for example, `--issues /path/to/file.txt`). -In this case, you cannot use short URLs or numbers, each line must have a full URL. - -By default all changelogs that match issues in the list are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add [rules.bundle](/contribute/configure-changelogs-ref.md#rules-bundle) configuration settings. - - -### Bundle by pull requests [changelog-bundle-pr] - -You can use the `--prs` option to create a bundle of the changelogs that relate to those pull requests. - -Pull requests can be identified by a full URL (such as `https://github.com/owner/repo/pull/123`), a short format (such as `owner/repo#123`), or just a number. - -```sh -docs-builder changelog bundle --prs "108875,135873,136886" \ <1> - --repo elasticsearch \ <2> - --owner elastic \ <3> - --output-products "elasticsearch 9.2.2 ga" <4> -``` - -1. The comma-separated list of pull request numbers to seek. -2. The repository in the pull request URLs. Not required when using full PR URLs, or when `bundle.repo` is set in the changelog configuration. -3. The owner in the pull request URLs. Not required when using full PR URLs, or when `bundle.owner` is set in the changelog configuration. -4. The product metadata for the bundle. If it is not provided, it will be derived from all the changelog product values. - -Alternatively, you can specify a path to a newline-delimited file that contains the PR URLS (for example, `--prs /path/to/file.txt`). -In this case, you cannot use short URLs or numbers, each line must have a full URL. -For example: - -```txt -https://github.com/elastic/elasticsearch/pull/108875 -https://github.com/elastic/elasticsearch/pull/135873 -https://github.com/elastic/elasticsearch/pull/136886 -https://github.com/elastic/elasticsearch/pull/137126 -``` - -By default all changelogs that match PRs in the list are included in the bundle. -To apply additional filtering by the changelog type, areas, or products, add [rules.bundle](/contribute/configure-changelogs-ref.md#rules-bundle) configuration settings. - -If you have changelog files that reference those pull requests, the command creates a file like this: - -```yaml -products: -- product: elasticsearch - target: 9.2.2 - lifecycle: ga -entries: -- file: - name: 1765507819-fix-ml-calendar-event-update-scalability-issues.yaml - checksum: 069b59edb14594e0bc3b70365e81626bde730ab7 -- file: - name: 1765507798-convert-bytestransportresponse-when-proxying-respo.yaml - checksum: c6dbd4730bf34dbbc877c16c042e6578dd108b62 -- file: - name: 1765507839-use-ivf_pq-for-gpu-index-build-for-large-datasets.yaml - checksum: 451d60283fe5df426f023e824339f82c2900311e -``` - -### Bundle by product [changelog-bundle-product] - -You can use the `--input-products` option to create a bundle of changelogs that match the product details. -When using `--input-products`, you must provide all three parts: product, target, and lifecycle. -Each part can be a wildcard (`*`) to match any value. - -:::{tip} -If you use profile-based bundling, provide this information in the `bundle.profiles..products` field. -::: - -```sh -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). - -You can use wildcards in any of the three parts: - -```sh -# Bundle any changelogs that have exact matches for either of these clauses -docs-builder changelog bundle --input-products "cloud-serverless 2025-12-02 ga, elasticsearch 9.3.0 beta" - -# Bundle all elasticsearch changelogs regardless of target or lifecycle -docs-builder changelog bundle --input-products "elasticsearch * *" - -# Bundle all cloud-serverless 2025-12-02 changelogs with any lifecycle -docs-builder changelog bundle --input-products "cloud-serverless 2025-12-02 *" - -# Bundle any cloud-serverless changelogs with target starting with "2025-11-" and "ga" lifecycle -docs-builder changelog bundle --input-products "cloud-serverless 2025-11-* ga" - -# Bundle all changelogs (equivalent to --all) -docs-builder changelog bundle --input-products "* * *" -``` - -If you have changelog files that reference those product details, the command creates a file like this: - -```yaml -products: <1> -- product: cloud-serverless - target: 2025-12-02 -- product: cloud-serverless - target: 2025-12-06 -entries: -- file: - name: 1765495972-fixes-enrich-and-lookup-join-resolution-based-on-m.yaml - checksum: 6c3243f56279b1797b5dfff6c02ebf90b9658464 -- file: - name: 1765507778-break-on-fielddata-when-building-global-ordinals.yaml - checksum: 70d197d96752c05b6595edffe6fe3ba3d055c845 -``` - -1. By default these values match your `--input-products` (even if the changelogs have more products). -To specify different product metadata, use the `--output-products` option. - -:::{note} -When a changelog matches multiple `--input-products` filters, it appears only once in the bundle. This deduplication applies even when using `--all` or `--prs`. -::: - -### Bundle by report - -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](/contribute/configure-changelogs-ref.md#rules-bundle) configuration settings. - -### Bundle descriptions - -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 release dates - -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](/contribute/configure-changelogs-ref.md#bundle-profiles), you can use those profiles with the `changelog bundle` command. - -Refer to [](/contribute/bundle-changelogs.md#create-profiles) for examples. - -### Lifecycle inference [lifecycle-inference] - -The way that lifecycle values are inferred varies between [GitHub release profiles](#lifecycles-github) and [standard profiles](#lifecycles-standard). - -#### GitHub release profiles [lifecycles-github] - -For `source: github_release` profiles, the `{lifecycle}` placeholder in `output` and `output_products` is derived from the full release tag name and `{version}` is the base version extracted from that same tag. -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` | - -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" -``` - -You can invoke the profile with commands like this: - -```sh -# Bundle changelogs using the PR list from a GitHub release (source: github_release) -docs-builder changelog bundle gh-release v1.2.3 - -# Use "latest" to fetch the most recent release -docs-builder changelog bundle gh-release latest -``` - -#### Standard profiles [lifecycles-standard] - -If your configuration file defines a standard profile (that is to say, not a GitHub release profile), the `{version}` is copied verbatim from your command argument and the `{lifecycle}` is derived from that value. -For example: - -| Version argument | `{version}` | `{lifecycle}` | -|------------------|-------------|---------------| -| `9.2.0` | `9.2.0` | `ga` | -| `9.2.0-rc.1` | `9.2.0-rc.1` | `ga` | -| `9.2.0-beta.1` | `9.2.0-beta.1` | `beta` | -| `9.2.0-alpha.1` | `9.2.0-alpha.1` | `preview` | -| `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). - -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 -``` - -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: - -```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' -``` diff --git a/docs/cli/changelog/cmd-bundle.md b/docs/cli/changelog/cmd-bundle.md new file mode 100644 index 0000000000..6a767a008c --- /dev/null +++ b/docs/cli/changelog/cmd-bundle.md @@ -0,0 +1,138 @@ +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). + +## 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`. +::: 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 dc74d61a08..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 [](/contribute/configure-changelogs-ref.md#rules-bundle). - -## 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 cc931dc827..0000000000 --- a/docs/cli/changelog/remove.md +++ /dev/null @@ -1,203 +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 - -You can remove changelogs based on their issues, pull requests, product metadata, or remove all changelogs from a folder. -Exactly one filter option must be specified: `--all`, `--products`, `--prs`, `--issues`, `--release-version` or `--report`. -When using a file for `--prs` or `--issues`, every line must be a fully-qualified GitHub URL. - -For example: - -```sh -docs-builder changelog remove --products "elasticsearch 9.3.0 *" --dry-run -``` - -### 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. - -Refer to [](/contribute/bundle-changelogs.md#changelog-remove) for examples. 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..5a70552ce4 --- /dev/null +++ b/docs/cli/cli-reference-how-to.md @@ -0,0 +1,141 @@ +--- +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. + +**Files are discovered automatically** — drop a file into the folder following the naming convention and it is picked up on the next build with no configuration needed. + +**Validation is strict** — any supplemental file whose name doesn't match a known namespace or command in the schema produces a build error. This means renamed or removed commands can't leave orphaned docs behind silently; your CI catches it immediately. + +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 + migrate.md ← intro for database migrate command + 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 + database-migrate.md ← intro for database migrate command + deploy.md ← intro for the root deploy command +``` + +For a live example, see how `docs-builder` uses both files in its own `cli/changelog/` folder: + +- [changelog namespace intro](/cli/changelog/index.md) — `docs/cli/changelog/index.md` introduces the changelog workflow before the auto-generated commands list +- [changelog bundle command](/cli/changelog/bundle.md) — `docs/cli/changelog/bundle.md` explains the two bundling modes; the generated parameter table follows underneath + +:::: + +::::{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) | +| `/.md` | Command `` inside namespace `` | +| `-.md` | Same, flat style | +| `.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..19709f5370 --- /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 + +```shell +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 b3c175c51b..5ebafa9bd8 100644 --- a/docs/contribute/bundle-changelogs.md +++ b/docs/contribute/bundle-changelogs.md @@ -186,7 +186,12 @@ For example, if the source of truth for what was shipped in each release is: https://github.com/elastic/kibana/pull/456 ``` -- a buildkite promotion report: +| Field | Description | +|---|---| +| `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). | ```sh # Bundle changelogs from a buildkite report ({version} → "2026-02-13") @@ -236,7 +241,235 @@ It is strongly recommended to pull all of the content from each changelog into t To apply additional filtering by the changelog type, areas, or products, add [bundle rules](#rules). -If you don't want to use profiles and prefer to specify all the command options every time you run the command, refer to [Option-based examples](/cli/changelog/bundle.md#option-based-examples). +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). + +You can use wildcards in any of the three parts: + +```sh +# Bundle any changelogs that have exact matches for either of these clauses +docs-builder changelog bundle --input-products "cloud-serverless 2025-12-02 ga, elasticsearch 9.3.0 beta" + +# Bundle all elasticsearch changelogs regardless of target or lifecycle +docs-builder changelog bundle --input-products "elasticsearch * *" + +# Bundle all cloud-serverless 2025-12-02 changelogs with any lifecycle +docs-builder changelog bundle --input-products "cloud-serverless 2025-12-02 *" + +# Bundle any cloud-serverless changelogs with target starting with "2025-11-" and "ga" lifecycle +docs-builder changelog bundle --input-products "cloud-serverless 2025-11-* ga" + +# Bundle all changelogs (equivalent to --all) +docs-builder changelog bundle --input-products "* * *" +``` + +If you have changelog files that reference those product details, the command creates a file like this: + +```yaml +products: <1> +- product: cloud-serverless + target: 2025-12-02 +- product: cloud-serverless + target: 2025-12-06 +entries: +- file: + name: 1765495972-fixes-enrich-and-lookup-join-resolution-based-on-m.yaml + checksum: 6c3243f56279b1797b5dfff6c02ebf90b9658464 +- file: + name: 1765507778-break-on-fielddata-when-building-global-ordinals.yaml + checksum: 70d197d96752c05b6595edffe6fe3ba3d055c845 +``` + +1. By default these values match your `--input-products` (even if the changelogs have more products). +To specify different product metadata, use the `--output-products` option. + +## Filter by pull requests [changelog-bundle-pr] + +You can use the `--prs` option to create a bundle of the changelogs that relate to those pull requests. +You can provide either a comma-separated list of PRs (`--prs "https://github.com/owner/repo/pull/123,12345"`) or a path to a newline-delimited file (`--prs /path/to/file.txt`). +In the latter case, the file should contain one PR URL or number per line. + +Pull requests can be identified by a full URL (such as `https://github.com/owner/repo/pull/123`), a short format (such as `owner/repo#123`), or just a number (in which case you must also provide `--owner` and `--repo` options). + +```sh +docs-builder changelog bundle --prs "108875,135873,136886" \ <1> + --repo elasticsearch \ <2> + --owner elastic \ <3> + --output-products "elasticsearch 9.2.2 ga" <4> +``` + +1. The comma-separated list of pull request numbers to seek. +2. The repository in the pull request URLs. Not required when using full PR URLs, or when `bundle.repo` is set in the changelog configuration. +3. The owner in the pull request URLs. Not required when using full PR URLs, or when `bundle.owner` is set in the changelog configuration. +4. The product metadata for the bundle. If it is not provided, it will be derived from all the changelog product values. + +In Mode 3, the **rule context product** is the first alphabetically from `--output-products` (or from aggregated changelog products if omitted). To apply a different product's per-product rules, use a bundle whose `output_products` contains only that product (separate command or profile). + +If you have changelog files that reference those pull requests, the command creates a file like this: + +```yaml +products: +- product: elasticsearch + target: 9.2.2 + lifecycle: ga +entries: +- file: + name: 1765507819-fix-ml-calendar-event-update-scalability-issues.yaml + checksum: 069b59edb14594e0bc3b70365e81626bde730ab7 +- file: + name: 1765507798-convert-bytestransportresponse-when-proxying-respo.yaml + checksum: c6dbd4730bf34dbbc877c16c042e6578dd108b62 +- file: + name: 1765507839-use-ivf_pq-for-gpu-index-build-for-large-datasets.yaml + checksum: 451d60283fe5df426f023e824339f82c2900311e +``` + +## Filter by issues [changelog-bundle-issues] + +You can use the `--issues` option to create a bundle of changelogs that relate to those GitHub issues. +Provide either a comma-separated list of issues (`--issues "https://github.com/owner/repo/issues/123,456"`) or a path to a newline-delimited file (`--issues /path/to/file.txt`). +Issues can be identified by a full URL (such as `https://github.com/owner/repo/issues/123`), a short format (such as `owner/repo#123`), or just a number (in which case `--owner` and `--repo` are required — or set via `bundle.owner` and `bundle.repo` in the configuration). + +```sh +docs-builder changelog bundle --issues "12345,12346" \ + --repo elasticsearch \ + --owner elastic \ + --output-products "elasticsearch 9.2.2 ga" +``` + +## Filter by pull request or issue file [changelog-bundle-file] + +If you have a file that lists pull requests (such as PRs associated with a GitHub release), you can pass it to `--prs`. +For example, if you have a file that contains full pull request URLs like this: + +```txt +https://github.com/elastic/elasticsearch/pull/108875 +https://github.com/elastic/elasticsearch/pull/135873 +https://github.com/elastic/elasticsearch/pull/136886 +https://github.com/elastic/elasticsearch/pull/137126 +``` + +You can use the `--prs` option with the file path to create a bundle of the changelogs that relate to those pull requests. +You can also combine multiple `--prs` options: + +```sh +./docs-builder changelog bundle \ + --prs "https://github.com/elastic/elasticsearch/pull/108875,135873" \ <1> + --prs test/9.2.2.txt \ <2> + --output-products "elasticsearch 9.2.2 ga" <3> + --resolve <4> +``` + +1. Comma-separated list of pull request URLs or numbers. +2. The path for the file that lists the pull requests. If the file contains only PR numbers, you must add `--repo` and `--owner` command options. +3. The product metadata for the bundle. If it is not provided, it will be derived from all the changelog product values. +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). +::: + +If you have changelog files that reference those pull requests, the command creates a file like this: + +```yaml +products: +- product: elasticsearch + target: 9.2.2 + lifecycle: ga +entries: +- file: + name: 1765507778-break-on-fielddata-when-building-global-ordinals.yaml + checksum: 70d197d96752c05b6595edffe6fe3ba3d055c845 + type: bug-fix + title: Break on FieldData when building global ordinals + products: + - product: elasticsearch + areas: + - Aggregations + prs: + - https://github.com/elastic/elasticsearch/pull/108875 +... +``` + +:::{note} +When a changelog matches multiple `--input-products` filters, it appears only once in the bundle. This deduplication applies even when using `--all` or `--prs`. +::: + +## Filter by GitHub release notes [changelog-bundle-release-version] + +If you have GitHub releases with automated release notes (the default format or [Release Drafter](https://github.com/release-drafter/release-drafter) format), you can use the `--release-version` option to derive the PR list from those release notes. +For example: + +```sh +docs-builder changelog bundle \ + --release-version v1.34.0 \ + --repo apm-agent-dotnet --owner elastic <1> +``` + +1. The repo and repo owner are used to fetch the release and follow these rules of precedence: + +- Repo: `--repo` flag > `bundle.repo` in `changelog.yml` (one source is required) +- Owner: `--owner` flag > `bundle.owner` in `changelog.yml` > `elastic` + +This command creates a bundle of changelogs that match the list of PRs found in the `v1.34.0` GitHub release notes. + +The bundle's product metadata is inferred automatically from the release tag and repository name; you can override that behavior with the `--output-products` option. + +:::{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) +::: + +## Hide features [changelog-bundle-hide-features] + +You can use the `--hide-features` option to embed feature IDs that should be hidden when the bundle is rendered. This is useful for features that are not yet ready for public documentation. + +```sh +docs-builder changelog bundle \ + --input-products "elasticsearch 9.3.0 *" \ + --hide-features "feature:hidden-api,feature:experimental" \ <1> + --output /path/to/bundles/9.3.0.yaml +``` + +1. Feature IDs to hide. Changelogs with matching `feature-id` values will be commented out when rendered. + + + +The bundle output will include a `hide-features` field: + +```yaml +products: +- product: elasticsearch + target: 9.3.0 +hide-features: + - feature:hidden-api + - feature:experimental +entries: +- file: + name: 1765495972-new-feature.yaml + checksum: 6c3243f56279b1797b5dfff6c02ebf90b9658464 +``` + +When this bundle is rendered (either via the `changelog render` command or the `{changelog}` directive), changelogs with `feature-id` values matching any of the listed features will be commented out in the output. + +:::{note} +The `--hide-features` option on the `render` command and the `hide-features` field in bundles are **combined**. If you specify `--hide-features` on both the `bundle` and `render` commands, all specified features are hidden. The `{changelog}` directive automatically reads `hide-features` from all loaded bundles and applies them. +::: + +## Hide private links + +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). + +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. + +:::{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. +::: ## Amend bundles [changelog-bundle-amend] diff --git a/docs/contribute/create-changelogs.md b/docs/contribute/create-changelogs.md index 6ce147d8fe..3d08b10790 100644 --- a/docs/contribute/create-changelogs.md +++ b/docs/contribute/create-changelogs.md @@ -62,7 +62,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/add.md) for the full list of environment variables and precedence rules. ## Review the content [review] @@ -70,7 +70,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/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. diff --git a/docs/syntax/changelog.md b/docs/syntax/changelog.md index e102681b23..60739c657b 100644 --- a/docs/syntax/changelog.md +++ b/docs/syntax/changelog.md @@ -160,9 +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. +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) for full syntax. + `rules.bundle` supports product, type, and area filtering, and per-product overrides. -For full syntax, refer to the [](/contribute/configure-changelogs-ref.md#rules-bundle). +For full syntax, refer to the [rules for filtered bundles](/cli/changelog/bundle.md). ## Hiding features 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. | diff --git a/src/Elastic.Documentation.Configuration/FileSystemFactory.cs b/src/Elastic.Documentation.Configuration/FileSystemFactory.cs index 6bcbd362bf..9f8b55b4cc 100644 --- a/src/Elastic.Documentation.Configuration/FileSystemFactory.cs +++ b/src/Elastic.Documentation.Configuration/FileSystemFactory.cs @@ -67,6 +67,26 @@ public static class FileSystemFactory /// 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.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/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/CliSchemaJsonContext.cs b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchemaJsonContext.cs new file mode 100644 index 0000000000..672aa7bff1 --- /dev/null +++ b/src/Elastic.Documentation.Configuration/Toc/CliReference/CliSchemaJsonContext.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(CliSchema))] +internal sealed partial class CliSchemaJsonContext : JsonSerializerContext; diff --git a/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs b/src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs index 9e7c4c1013..78e024151a 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..b1746079a0 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,169 @@ 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)); + + CliSchema schema; + try + { + schema = CliSchema.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 + ) + { + // SyntheticPath in the extension now uses clean names (no cmd- prefix) + // so factory registration path and URL path are the same. + 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); + } + + // Synthetic path — clean command names (no cmd- prefix) matching CliReferenceDocsBuilderExtension.SyntheticPath + 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 + { + // Keep cmd- prefix only for "index" commands to avoid collision with namespace index.md pages + var cmdName = segments[^1].Equals("index", StringComparison.OrdinalIgnoreCase) + ? $"cmd-{segments[^1]}" + : 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/Assets/styles.css b/src/Elastic.Documentation.Site/Assets/styles.css index 40eeccd25e..3d3edd26d9 100644 --- a/src/Elastic.Documentation.Site/Assets/styles.css +++ b/src/Elastic.Documentation.Site/Assets/styles.css @@ -420,3 +420,97 @@ math { scrollbar-width: thin; scrollbar-color: rgba(113, 134, 168, 0.5) transparent; } + +/* Heading anchor links — show # on hover */ +h1, +h2, +h3, +h4, +h5, +h6 { + position: relative; + + & .headerlink { + color: inherit; + text-decoration: none; + } + + & .headerlink::after { + content: '#'; + position: absolute; + right: calc(100% + 0.4rem); + top: 50%; + transform: translateY(-50%); + font-size: 0.85em; + font-weight: 400; + color: var(--color-grey-40, #9aa5b4); + opacity: 0; + transition: opacity 0.15s; + pointer-events: none; + } + + &:hover .headerlink::after, + & .headerlink:focus::after { + opacity: 1; + } +} + +/* Definition term anchor links — whole term is the link, # appears on the left on hover */ +dl dt[id] { + & a.paramlink { + color: inherit; + text-decoration: none; + position: relative; + + &::before { + content: '#'; + position: absolute; + right: calc(100% + 0.35rem); + top: 50%; + transform: translateY(-50%); + font-size: 0.85em; + font-weight: 400; + color: var(--color-grey-40, #9aa5b4); + opacity: 0; + transition: opacity 0.15s; + pointer-events: none; + } + } + + &:hover a.paramlink::before, + & a.paramlink:focus::before { + 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.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..4438f1cccc --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliCommandFile.cs @@ -0,0 +1,61 @@ +// 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; + private readonly string? _binaryName; + + private readonly string[] _fullPath; + + public CliCommandFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + CliCommandSchema command, + 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; + } + + 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, _fullPath, _binaryName); + } +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs new file mode 100644 index 0000000000..6e3b189041 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliMarkdownGenerator.cs @@ -0,0 +1,661 @@ +// 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(CliSchema schema, string? supplementalContent) + { + var sb = new StringBuilder(); + _ = sb.AppendLine($"# {schema.Name}"); + _ = 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); + } + + var visibleCommands = schema.Commands.Where(c => !c.Hidden).ToList(); + if (visibleCommands.Count > 0) + { + _ = sb.AppendLine("## Commands"); + _ = sb.AppendLine(); + foreach (var cmd in visibleCommands) + AppendPageCard(sb, cmd.Name, $"./{CommandPath(cmd.Name)}.md", cmd.Summary); + } + + if (schema.Namespaces.Count > 0) + { + _ = sb.AppendLine("## Namespaces"); + _ = sb.AppendLine(); + foreach (var ns in schema.Namespaces) + 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(); + } + + public static string NamespacePage(CliNamespaceSchema ns, string? supplementalContent, string[]? fullPath = null, string? binaryName = null) + { + var sb = new StringBuilder(); + 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) + _ = sb.AppendLine(supplementalContent.Trim()); + else if (!string.IsNullOrWhiteSpace(ns.Summary)) + _ = sb.AppendLine(ns.Summary.Trim()); + + _ = 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) + { + _ = sb.AppendLine("## Commands"); + _ = sb.AppendLine(); + foreach (var cmd in visibleCmds) + AppendPageCard(sb, cmd.Name, $"./{CommandPath(cmd.Name)}.md", cmd.Summary); + } + + if (ns.Namespaces.Count > 0) + { + _ = sb.AppendLine("## Sub-namespaces"); + _ = sb.AppendLine(); + foreach (var sub in ns.Namespaces) + AppendPageCard(sb, sub.Segment, $"./{sub.Segment}/index.md", sub.Summary); + } + + if (ns.Options.Count > 0) + { + _ = sb.AppendLine("## Namespace Flags"); + _ = sb.AppendLine(); + AppendParameters(sb, ns.Options); + } + + if (!string.IsNullOrWhiteSpace(ns.Notes)) + { + _ = sb.AppendLine("## Notes"); + _ = sb.AppendLine(); + _ = sb.AppendLine(ns.Notes.Trim()); + _ = sb.AppendLine(); + } + + return sb.ToString(); + } + + public static string CommandPage(CliCommandSchema cmd, string? supplementalContent, string[]? fullPath = null, string? binaryName = null) + { + var sb = new StringBuilder(); + var heading = fullPath is { Length: > 0 } ? string.Join(" ", fullPath) : cmd.Name; + _ = sb.AppendLine($"# {heading} cli command"); + _ = 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(); + + AppendCommandCallouts(sb, cmd); + + 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 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(":::{warning}"); + _ = sb.AppendLine("**Destructive operation** — changes made by this command 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; + + private static void AppendPageCard(StringBuilder sb, string title, string url, string? summary) + { + var description = string.IsNullOrWhiteSpace(summary) ? string.Empty : CleanSummary(summary).description.Trim(); + _ = sb.AppendLine(":::{page-card} [" + title + "](" + url + ")"); + if (!string.IsNullOrEmpty(description)) + _ = sb.AppendLine(description); + _ = sb.AppendLine(":::"); + _ = sb.AppendLine(); + } + + private static void AppendParameters(StringBuilder sb, IEnumerable parameters) + { + foreach (var p in parameters.Where(p => p.Name != "_" && !p.Hidden)) + { + var isBool = IsBoolFlag(p.Type); + var flagName = FormatFlagName(p); + var typeHint = isBool ? string.Empty : $" `{FormatTypeHint(p)}`"; + var requiredMarker = p.Required ? " **required**" : string.Empty; + + _ = sb.AppendLine($"{flagName}{typeHint}{requiredMarker}"); + + // v2: summary may still embed "Values:" / "Default:" for legacy generators; + // prefer dedicated fields (EnumValues, DefaultValue) when present. + var (description, legacyValues, legacySummaryDefault) = CleanSummary(p.Summary); + + var descLine = description.Trim(); + + // Annotate special roles inline + if (p.Role == "confirmationSkip") + descLine = string.IsNullOrEmpty(descLine) + ? "Pass to skip the confirmation prompt." + : descLine + " (pass to skip the confirmation prompt)"; + else if (p.Role == "dryRun") + descLine = string.IsNullOrEmpty(descLine) + ? "Preview changes without applying them." + : descLine + " (preview changes without applying them)"; + + _ = sb.AppendLine($": {descLine}"); + + // Deprecated parameter + if (p.Deprecated is not null) + { + var parts = new List { "**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 } + ? string.Join(", ", p.EnumValues) + : legacyValues; + + if (!string.IsNullOrWhiteSpace(values)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Values:** {values.Trim()}"); + } + + // Default: prefer dedicated schema field, fall back to legacy embedded text + // Skip "default" as a literal value — argh emits this for nullable booleans with no meaningful default + var defaultValue = (!string.IsNullOrWhiteSpace(p.DefaultValue) && !p.DefaultValue.Equals("default", StringComparison.OrdinalIgnoreCase)) + ? p.DefaultValue + : legacySummaryDefault; + if (!string.IsNullOrWhiteSpace(defaultValue)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Default:** `{defaultValue.Trim()}`"); + } + + // Constraints from validations + var constraints = FormatConstraints(p.Validations); + if (!string.IsNullOrEmpty(constraints)) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Constraints:** {constraints}"); + } + + // Repeatable / variadic hints + if (p.Repeatable) + { + _ = sb.AppendLine(); + _ = sb.AppendLine($" **Repeatable:** pass `--{p.Name}` multiple times to supply more than one value"); + } + else if (p.Variadic) + { + _ = sb.AppendLine(); + _ = sb.AppendLine(" **Variadic:** accepts multiple values"); + } + + _ = sb.AppendLine(); + } + } + + private static string FormatConstraints(List? 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(); + 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") + return $"`<{p.Name}>`"; + + var isBool = IsBoolFlag(p.Type); + 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); + } + + // Schema v2 uses JSON Schema primitives: "boolean", "string", "integer", "number", "array", "enum" + // Schema v1 used "Primitive:bool", "Primitive:bool?", "Primitive" for booleans + private static bool IsBoolFlag(string type) => + type.Equals("boolean", StringComparison.OrdinalIgnoreCase) || + type.StartsWith("Primitive:bool", StringComparison.OrdinalIgnoreCase) || + type.Equals("Primitive", StringComparison.OrdinalIgnoreCase); + + private static string FormatTypeHint(CliParamSchema p) + { + var type = p.Type; + + // v2 JSON Schema primitives + return type.ToLowerInvariant() switch + { + "string" => "string", + "integer" => "int", + "number" => "number", + "boolean" => string.Empty, // shown as --[no-] prefix instead + "enum" => "enum", + "array" => p.ElementType switch + { + "enum" => "enum[]", + "integer" => "int[]", + _ => "string[]" + }, + // v1 fallback (kind-style strings) + _ => FormatKindV1(type) + }; + } + + private static string FormatKindV1(string kind) + { + 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..dea2f84eb6 --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliNamespaceFile.cs @@ -0,0 +1,61 @@ +// 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; + private readonly string? _binaryName; + + private readonly string[] _fullPath; + + public CliNamespaceFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + CliNamespaceSchema @namespace, + 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; + } + + 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, _fullPath, _binaryName); + } +} diff --git a/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs new file mode 100644 index 0000000000..aaac32349e --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliReferenceDocsBuilderExtension.cs @@ -0,0 +1,389 @@ +// 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( + 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, + /// Full path segments used to build the page heading (e.g. ["assembler", "bloom-filter"]). + string[]? FullPath = null +); + +public class CliReferenceDocsBuilderExtension(BuildContext build) : IDocsBuilderExtension +{ + private BuildContext Build { get; } = build; + + private Dictionary? _syntheticFiles; + private List? _syntheticFileInfos; + // Maps physical supplemental file paths (cmd-*.md, index.md) → entity info with clean synthetic path + private Dictionary? _supplementalFiles; + // Cache of created MarkdownFile instances keyed by clean synthetic path — ensures the same instance + // is returned from both CreateMarkdownFile (supplemental) and CreateDocumentationFile (synthetic), + // so NavigationDocumentationFileLookup can find the file regardless of which key is used. + private readonly Dictionary _createdFiles = []; + + // 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 = []; + _supplementalFiles = []; + _syntheticFileInfos = BuildSyntheticFiles(); + } + + public IDocumentationFileExporter? FileExporter => null; + + public DocumentationFile? CreateDocumentationFile(IFileInfo file, MarkdownParser markdownParser) + { + EnsureSyntheticFilesBuilt(); + if (!_syntheticFiles!.TryGetValue(file.FullName, out var info)) + return null; + // Use the clean synthetic file as source if available (commands registered under clean path) + var sourceFile = info.CleanSyntheticFile ?? file; + // Return cached instance if CreateMarkdownFile already created it for this path + if (_createdFiles.TryGetValue(sourceFile.FullName, out var cached)) + return cached; + var result = CreateCliFileFromInfo(sourceFile, markdownParser, info); + if (result != null) + _createdFiles[sourceFile.FullName] = result; + return result; + } + + public MarkdownFile? CreateMarkdownFile(IFileInfo file, IDirectoryInfo sourceDirectory, MarkdownParser markdownParser) + { + // Physical CLI supplemental docs (index.md for namespaces, cmd-*.md for commands) need to be + // intercepted before the factory creates a plain MarkdownFile for them. + // EnsureSyntheticFilesBuilt() is called here because CreateMarkdownFile runs during the main + // directory scan, before ScanDocumentationFiles populates the lookups. + var name = file.Name; + if (name != "index.md" && !name.StartsWith("cmd-", StringComparison.OrdinalIgnoreCase)) + return null; + EnsureSyntheticFilesBuilt(); + var fullPath = Path.GetFullPath(file.FullName); + + // index.md: file path IS the synthetic path (namespace pages) + if (_syntheticFiles!.TryGetValue(fullPath, out var info)) + { + if (_createdFiles.TryGetValue(fullPath, out var cached)) + return cached; + var result = CreateCliFileFromInfo(file, markdownParser, info); + if (result != null) + _createdFiles[fullPath] = result; + return result; + } + + // cmd-*.md: physical supplemental file — render as the associated CLI command page + // using the clean synthetic path (no cmd- prefix) as the source file so RelativePath + // and thus the output URL are both clean (e.g. apply.md → /cli/.../apply). + if (_supplementalFiles!.TryGetValue(fullPath, out var suppInfo) && suppInfo.CleanSyntheticFile is not null) + { + var cleanPath = suppInfo.CleanSyntheticFile.FullName; + if (_createdFiles.TryGetValue(cleanPath, out var cached)) + return cached; + var result = CreateCliFileFromInfo(suppInfo.CleanSyntheticFile, markdownParser, suppInfo); + if (result != null) + _createdFiles[cleanPath] = result; + return result; + } + + return null; + } + + private MarkdownFile? CreateCliFileFromInfo(IFileInfo sourceFile, MarkdownParser markdownParser, CliEntityInfo info) => + info.Entity switch + { + 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 + }; + + 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; + + CliSchema schema; + try + { + schema = CliSchema.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); + var rootInfo = new CliEntityInfo(schema, schema, rootSupplemental, rootFileInfo); + _syntheticFiles![rootSyntheticPath] = rootInfo; + if (rootSupplemental != null) + _supplementalFiles![rootSupplemental.FullName] = rootInfo; + 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); + var cmdInfo = new CliEntityInfo(schema, cmd, supplemental, fileInfo, FullPath: [cmd.Name]); + _syntheticFiles[path] = cmdInfo; + if (supplemental != null) + _supplementalFiles![supplemental.FullName] = cmdInfo; + 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, + CliSchema 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); + var nsInfo = new CliEntityInfo(schema, ns, nsSupplemental, nsFileInfo, FullPath: fullNsPath); + _syntheticFiles![nsFilePath] = nsInfo; + if (nsSupplemental != null) + _supplementalFiles![nsSupplemental.FullName] = nsInfo; + 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); + var cmdInfo = new CliEntityInfo(schema, cmd, cmdSupplemental, cmdFileInfo, FullPath: cmdSegments); + _syntheticFiles[cmdPath] = cmdInfo; + if (cmdSupplemental != null) + _supplementalFiles![cmdSupplemental.FullName] = cmdInfo; + fileInfos.Add(cmdFileInfo); + } + + CollectNamespaceFiles(docSourceDir, virtualRoot, supplementalDirPath, ns.Namespaces, fullNsPath, matched, fileInfos, schema); + } + } + + // Clean synthetic path (no cmd- prefix) — used for URL generation and Files registration. + // 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 + { + // Commands use clean name (no cmd- prefix) for URL e.g. /cli/assembler/deploy/apply. + // Exception: commands named "index" must keep cmd- prefix to avoid collision with namespace index.md pages. + var name = segments[^1].Equals("index", StringComparison.OrdinalIgnoreCase) + ? $"cmd-{segments[^1]}" + : 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, name + ".md")) + : Path.GetFullPath(Path.Join(docSourceDir, virtualRoot, parentPath, name + ".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 "index.md"; + 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") + { + if (!matched.Contains(file)) + Build.Collector.EmitError(context, $"CLI supplemental 'index.md' at '{relPath}' does not match any CLI namespace or the CLI root page"); + 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..9e18b3863b --- /dev/null +++ b/src/Elastic.Markdown/Extensions/CliReference/CliRootFile.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 CliRootFile : IO.MarkdownFile +{ + private readonly CliSchema _schema; + private readonly IFileInfo? _supplementalDoc; + + public CliRootFile( + IFileInfo sourceFile, + IDirectoryInfo rootPath, + MarkdownParser parser, + BuildContext build, + CliSchema schema, + IFileInfo? supplementalDoc + ) : base(sourceFile, rootPath, parser, build) + { + _schema = schema; + _supplementalDoc = supplementalDoc; + Title = schema.Name; + } + + public override string NavigationTitle => $"{_schema.Name} CLI"; + + protected override Task GetMinimalParseDocumentAsync(Cancel ctx) + { + Title = _schema.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.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; +}