Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/cli/cli-supplemental-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Supplemental files let you enrich any auto-generated CLI reference page with con

**Validation is strict.** Any supplemental file whose name does not match a known namespace or command produces a build error, so renamed or removed commands can never leave orphaned docs behind silently.

**Frontmatter is preserved as metadata.** Add YAML frontmatter to set page metadata such as `description`, `applies_to`, or `navigation_title`. It is passed through to the generated page and is not rendered as supplemental description text.

## File naming

Two naming styles are supported and can coexist in the same folder.
Expand Down Expand Up @@ -48,6 +50,24 @@ cli/

The heading structure of a supplemental file controls what it contributes to the generated page.

### Frontmatter

Use frontmatter for page metadata:

```markdown
---
description: Use the Elastic CLI to call Elasticsearch REST APIs from the command line.
applies_to:
stack: preview
---

## Description

The `elastic stack es` command group exposes Elasticsearch REST APIs as CLI commands.
```

The metadata remains metadata. The generated page uses the `## Description` section, or the schema description if the file only contains frontmatter.

### No headings

A file with no `##` headings replaces the auto-generated description entirely:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal static partial class CliMarkdownGenerator
public static string RootPage(CliSchema schema, CliSupplementalDoc? supplemental)
{
var sb = new StringBuilder();
AppendFrontMatter(sb, supplemental);
_ = sb.AppendLine($"# {schema.Name}");
_ = sb.AppendLine();

Expand Down Expand Up @@ -108,6 +109,7 @@ public static string NamespacePage(
List<CliShortcutSchema>? shortcuts = null)
{
var sb = new StringBuilder();
AppendFrontMatter(sb, supplemental);
var heading = fullPath is { Length: > 0 } ? string.Join(" ", fullPath) : ns.Segment;
_ = sb.AppendLine($"# {heading} <span class=\"cli-badge-ns\">cli namespace</span>");
_ = sb.AppendLine();
Expand Down Expand Up @@ -198,6 +200,7 @@ public static string CommandPage(
List<CliShortcutSchema>? shortcuts = null)
{
var sb = new StringBuilder();
AppendFrontMatter(sb, supplemental);
var heading = fullPath is { Length: > 0 } ? string.Join(" ", fullPath) : cmd.Name;
_ = sb.AppendLine($"# {heading} <span class=\"cli-badge-cmd\">cli command</span>");
_ = sb.AppendLine();
Expand Down Expand Up @@ -322,6 +325,15 @@ public static string CommandPage(
return sb.ToString();
}

private static void AppendFrontMatter(StringBuilder sb, CliSupplementalDoc? supplemental)
{
if (string.IsNullOrWhiteSpace(supplemental?.FrontMatter))
return;

_ = sb.AppendLine(supplemental.FrontMatter);
_ = sb.AppendLine();
}

private static void AppendCommandModifiers(StringBuilder sb, CliCommandSchema cmd)
{
if (cmd.Deprecated is not null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Elastic.Markdown.Extensions.CliReference;

internal sealed partial record CliSupplementalDoc(
string? FrontMatter,
string? Description,
Dictionary<string, string> OptionOverrides,
Dictionary<string, string> ArgumentOverrides,
Expand All @@ -18,13 +19,14 @@ internal sealed partial record CliSupplementalDoc(
if (raw is null)
return null;

var trimmed = raw.Trim();
var (frontMatter, rawContent) = ExtractFrontMatter(raw);
var trimmed = rawContent.Trim();
if (string.IsNullOrWhiteSpace(trimmed))
return null;
return string.IsNullOrWhiteSpace(frontMatter) ? null : new CliSupplementalDoc(frontMatter, null, [], [], null);

// Backward compat: no ## headings → entire content is description
if (!trimmed.Contains("\n## ") && !trimmed.StartsWith("## ", StringComparison.Ordinal))
return new CliSupplementalDoc(trimmed, [], [], null);
return new CliSupplementalDoc(frontMatter, trimmed, [], [], null);

var sections = SplitSections(trimmed);
string? description = null;
Expand Down Expand Up @@ -55,7 +57,16 @@ internal sealed partial record CliSupplementalDoc(
}

var postContent = postParts.Count > 0 ? string.Join("\n\n", postParts) : null;
return new CliSupplementalDoc(description, optionOverrides, argumentOverrides, postContent);
return new CliSupplementalDoc(frontMatter, description, optionOverrides, argumentOverrides, postContent);
}

private static (string? FrontMatter, string Content) ExtractFrontMatter(string raw)
{
var match = FrontMatterRegex().Match(raw);
if (!match.Success)
return (null, raw);

return (match.Value.Trim(), raw[match.Length..]);
}

private static List<(string? heading, string body)> SplitSections(string text)
Expand Down Expand Up @@ -124,4 +135,7 @@ private static string NormalizeKey(string raw)
// Matches: `: `--flag`` or `: --flag` or `: <name>`
[GeneratedRegex(@"^:\s+(`[^`]+`|--[\w-]+|<[\w-]+>)")]
private static partial Regex TermLineRegex();

[GeneratedRegex(@"\A---\r?\n[\s\S]*?\r?\n---[ \t]*(?:\r?\n|$)")]
private static partial Regex FrontMatterRegex();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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 AwesomeAssertions;
using Elastic.Documentation.Configuration.Toc.CliReference;
using Elastic.Markdown.Extensions.CliReference;

namespace Elastic.Markdown.Tests.CliReference;

public class CliSupplementalDocTests
{
[Fact]
public void RootPage_PreservesFrontMatterAsMetadata()
{
var schema = CreateSchema();
const string raw = """
---
description: Use the Elastic CLI from the command line.
applies_to:
stack: preview
---
""";

var supplemental = CliSupplementalDoc.Parse(raw);
var markdown = CliMarkdownGenerator.RootPage(schema, supplemental).ReplaceLineEndings("\n");

var expectedStart = """
---
description: Use the Elastic CLI from the command line.
applies_to:
stack: preview
---

# elastic
""".ReplaceLineEndings("\n");

markdown.Should().StartWith(expectedStart);
markdown.Should().NotContain("description: Use the Elastic CLI from the command line.\n\n");
}

[Fact]
public void RootPage_StripsFrontMatterBeforeParsingDescription()
{
var schema = CreateSchema();
const string raw = """
---
description: Metadata description.
---

User-facing supplemental description.
""";

var supplemental = CliSupplementalDoc.Parse(raw);
var markdown = CliMarkdownGenerator.RootPage(schema, supplemental).ReplaceLineEndings("\n");

markdown.Should().Contain("\n# elastic\n\nUser-facing supplemental description.\n");
markdown.Should().NotContain("\nMetadata description.\n");
}

private static CliSchema CreateSchema() => new(
SchemaVersion: 1,
Name: "elastic",
Description: "Schema description.",
GlobalOptions: [],
RootDefault: null,
Commands: [],
Namespaces: []
);
}
Loading