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
13 changes: 13 additions & 0 deletions config/changelog.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,12 @@ bundle:
output_directory: docs/releases
# Whether to resolve (copy contents) by default
resolve: true
# Optional: default description text for bundles. Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
# Use YAML literal block scalar (|) for multiline descriptions. See docs/contribute/changelog.md for examples.
# description: |
# This release includes new features and bug fixes.
#
# For more information, see the [release notes](https://www.elastic.co/docs/release-notes/product#product-{version}).
# changelog-init-bundle-seed
# PR/issue link allowlist: when set (including []), only links to these owner/repo pairs are kept
# in bundle output; others are rewritten to '# PRIVATE:' sentinels (requires resolve: true).
Expand Down Expand Up @@ -245,6 +251,13 @@ bundle:
# output: "elasticsearch-{version}.yaml"
# # Optional: override the products array written to the bundle output.
# # output_products: "elasticsearch {version}"
# # Optional: profile-specific description (overrides bundle.description)
# # description: |
# # Elasticsearch {version} includes:
# # - Performance improvements
# # - Bug fixes and stability enhancements
# #
# # Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
# Example: GitHub release profile (fetches PR list directly from a GitHub release)
# Use when you want to bundle or remove changelogs based on a published GitHub release.
# elasticsearch-gh-release:
Expand Down
57 changes: 57 additions & 0 deletions docs/cli/changelog/bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ You must choose one method for determining what's in the bundle (`--all`, `--inp
: 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 <string?>`
: 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 <string[]?>`
: Optional: A list of feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs.
: Can be specified multiple times.
Expand Down Expand Up @@ -354,6 +361,33 @@ docs-builder changelog bundle \
By default all changelogs that match PRs in the GitHub release notes are included in the bundle.
To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules).

### Bundle with description

You can add a description to bundles using the `--description` option. For simple descriptions, use regular quotes:

```sh
docs-builder changelog bundle \
--all \
--description "This release includes new features and bug fixes."
```

For multiline descriptions with multiple paragraphs, lists, and links, use ANSI-C quoting (`$'...'`) with `\n` for line breaks:

```sh
docs-builder changelog bundle \
--all \
--description $'This release includes significant improvements:\n\n- Enhanced performance\n- Bug fixes and stability improvements\n\nFor security updates, go to [security announcements](https://example.com/docs).'
```

When using placeholders in option-based mode, you must explicitly specify `--output-products` for predictable substitution:

```sh
docs-builder changelog bundle \
--all \
--output-products "elasticsearch 9.1.0 ga" \
--description "Elasticsearch {version} includes performance improvements. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
```

## Profile-based examples

When the changelog configuration file defines `bundle.profiles`, you can use those profiles with the `changelog bundle` command.
Expand Down Expand Up @@ -442,6 +476,29 @@ docs-builder changelog bundle elasticsearch-gh-release 9.2.0
docs-builder changelog bundle elasticsearch-gh-release latest
```

:::{warning}
**Placeholder validation**: If your profile uses `{version}` or `{lifecycle}` placeholders in the description, you must ensure predictable substitution values:

```sh
# ✅ Good: Version provided for placeholder substitution
docs-builder changelog bundle elasticsearch-release 9.2.0 ./report.html

# ❌ Bad: No version, placeholders will fail unless profile has output_products
docs-builder changelog bundle elasticsearch-release ./report.html
```

To fix the second case, either provide a version argument or add an `output_products` pattern to your profile:

```yaml
bundle:
profiles:
elasticsearch-release:
products: "elasticsearch * *"
output_products: "elasticsearch {version}" # Enables placeholder substitution
description: "Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
```
:::

### Bundle by product

You can create profiles that are equivalent to the `--input-products` filter option, that is to say the bundle will contain only changelogs with matching `products`.
Expand Down
11 changes: 11 additions & 0 deletions docs/cli/changelog/gh-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ docs-builder changelog gh-release <repo> [version] [options...] [-h|--help]
`--config <string?>`
: Optional: Path to the changelog.yml configuration file. Defaults to `docs/changelog.yml`.

`--description <string?>`
: Optional: Bundle description text with placeholder support.
: Supports `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` placeholders. Overrides `bundle.description` from config.

`--output <string?>`
: Optional: Output directory for the generated changelog files. Falls back to `bundle.directory` in `changelog.yml` when not specified. Defaults to `./changelogs`.

Expand Down Expand Up @@ -86,6 +90,13 @@ docs-builder changelog gh-release elasticsearch v9.2.0 \
--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}"
```

### Strip component prefixes from titles

```sh
Expand Down
69 changes: 69 additions & 0 deletions docs/contribute/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -746,11 +746,23 @@ bundle:
owner: elastic # The default repository owner for PR and issue links.
directory: docs/changelog # The directory that contains changelog files.
output_directory: docs/releases # The directory that contains changelog bundles.
# Optional: Default bundle description with placeholder support
description: |
This release includes new features and bug fixes.

For more information, see the [release notes](https://www.elastic.co/docs/release-notes/elasticsearch#elasticsearch-{version}).
profiles:
elasticsearch-release:
products: "elasticsearch {version} {lifecycle}"
output: "elasticsearch/{version}.yaml"
output_products: "elasticsearch {version}"
# Profile-specific description overrides bundle.description
description: |
Elasticsearch {version} includes:
- Performance improvements
- Bug fixes and stability enhancements

Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
hide_features:
- feature:experimental-api
serverless-release:
Expand Down Expand Up @@ -800,6 +812,63 @@ The `{version}` placeholder is substituted with the clean base version extracted

This differs from standard profiles, where `{lifecycle}` is inferred from the version string you type at the command line.

#### Bundle descriptions

You can add introductory text to bundles using the `description` field. This text appears at the top of rendered changelogs, after the release heading but before the entry sections.

**Configuration locations:**

- `bundle.description`: Default description for all profiles
- `bundle.profiles.<name>.description`: Profile-specific description (overrides the default)

**Placeholder support:**

Bundle descriptions support these placeholders:

- `{version}`: The resolved version string
- `{lifecycle}`: The resolved lifecycle (ga, beta, preview, etc.)
- `{owner}`: The GitHub repository owner
- `{repo}`: The GitHub repository name

**Important**: When using `{version}` or `{lifecycle}` placeholders, you must ensure predictable substitution values:

- **Option-based mode**: Requires `--output-products` when using placeholders
- **Profile-based mode**: Requires either a version argument (e.g., `bundle profile 9.2.0`) OR an `output_products` pattern in the profile configuration when using placeholders. If you invoke a profile with only a promotion report (e.g., `bundle profile ./report.html`), placeholders will fail unless `output_products` is configured.

**Multiline descriptions in YAML:**

For complex descriptions with multiple paragraphs, lists, and links, use YAML literal block scalars with the `|` (pipe) syntax:

```yaml
bundle:
description: |
This release includes significant improvements:

- Enhanced performance
- Bug fixes and stability improvements
- New features for better user experience

For security updates, go to [security announcements](https://example.com/docs).

Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
```

The `|` (pipe) preserves line breaks and is ideal for Markdown-formatted text. Avoid using `>` (greater than) for descriptions as it folds line breaks into spaces, making lists and paragraphs difficult to format correctly.

**Command line usage:**

For simple descriptions, use the `--description` option with regular quotes:

```sh
docs-builder changelog bundle --all --description "This release includes new features."
```

For multiline descriptions on the command line, use ANSI-C quoting (`$'...'`) with `\n` for line breaks:

```sh
docs-builder changelog bundle --all --description $'Enhanced release:\n\n- Performance improvements\n- Bug fixes'
```

`output_products` is optional. When omitted, the bundle products array is derived from the matched changelog files' own `products` fields — the same fallback used by all other profile types. Set `output_products` when you want a single clean product entry that reflects the release identity rather than the diverse metadata across individual changelog files, or to hardcode a lifecycle that cannot be inferred from the tag format:

```yaml
Expand Down
15 changes: 13 additions & 2 deletions docs/syntax/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,14 @@ For full syntax, refer to the [rules for filtered bundles](/cli/changelog/bundle
When bundles contain a `hide-features` field, entries with matching `feature-id` values are automatically filtered out from the rendered output. This allows you to hide unreleased or experimental features without modifying the bundle at render time.

```yaml
# Example bundle with hide-features
# Example bundle with description and hide-features
products:
- product: elasticsearch
target: 9.3.0
description: |
This release includes new features and bug fixes.

For more information, see the [release notes](https://example.com/docs).
hide-features:
- feature:hidden-api
- feature:experimental
Expand Down Expand Up @@ -223,10 +227,15 @@ The version is extracted from the first product's `target` field in each bundle

## Rendered output

Each bundle renders as a `## {version}` section with subsections beneath:
Each bundle renders as a `## {version}` section with optional description and subsections beneath:

```markdown
## 0.100.0

This release includes new features and bug fixes.

Download the release binaries: https://github.com/elastic/elasticsearch/releases/tag/v0.100.0

### Features and enhancements
...
### Fixes
Expand All @@ -237,6 +246,8 @@ Each bundle renders as a `## {version}` section with subsections beneath:
...
```

Bundle descriptions are rendered when present in the bundle YAML file. The description appears immediately after the version heading but before any entry sections. Descriptions support Markdown formatting including links, lists, and multiple paragraphs.

### Section types

| Section | Entry type | Rendering |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ public record BundleConfiguration
/// </summary>
public bool Resolve { get; init; } = true;

/// <summary>
/// Default bundle description used when no profile-specific description is provided.
/// Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
/// </summary>
public string? Description { get; init; }

/// <summary>
/// Default GitHub repository name applied to all profiles that do not specify their own.
/// Used for generating correct PR/issue links when the product ID differs from the repo name.
Expand Down Expand Up @@ -81,6 +87,12 @@ public record BundleProfile
/// </summary>
public string? OutputProducts { get; init; }

/// <summary>
/// Profile-specific bundle description. When provided, overrides the bundle.description default.
/// Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
/// </summary>
public string? Description { get; init; }

/// <summary>
/// GitHub repository name stored on each product in the bundle output.
/// Used for generating correct PR/issue links when the product ID differs from the repo name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public sealed record BundleDto
{
public List<BundledProductDto>? Products { get; set; }
/// <summary>
/// Optional introductory description text for this bundle.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Feature IDs that should be hidden when rendering this bundle.
/// Entries with matching feature-id values will be commented out in the output.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,25 @@ private static LoadedBundle MergeBundleGroup(IGrouping<string, LoadedBundle> gro
// Use the first bundle's metadata as the base
var first = bundlesList[0];

var descriptions = bundlesList
.Select(b => b.Data?.Description)
.Where(d => !string.IsNullOrEmpty(d))
.ToList();

var mergedDescription = descriptions.Count switch
{
0 => null,
1 => descriptions[0],
_ => string.Join("\n\n", descriptions)
};

var mergedData = first.Data with { Description = mergedDescription };

return new LoadedBundle(
first.Version,
combinedRepo,
first.Owner,
first.Data,
mergedData,
first.FilePath,
mergedEntries
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public static string SerializeBundle(Bundle bundle)
private static Bundle ToBundle(BundleDto dto) => new()
{
Products = dto.Products?.Select(ToBundledProduct).ToList() ?? [],
Description = dto.Description,
HideFeatures = dto.HideFeatures ?? [],
Entries = dto.Entries?.Select(ToBundledEntry).ToList() ?? []
};
Expand Down Expand Up @@ -239,6 +240,7 @@ private static ChangelogEntryType ParseEntryType(string? value)
private static BundleDto ToDto(Bundle bundle) => new()
{
Products = bundle.Products.Select(ToDto).ToList(),
Description = bundle.Description,
HideFeatures = bundle.HideFeatures.Count > 0 ? bundle.HideFeatures.ToList() : null,
Entries = bundle.Entries.Select(ToDto).ToList()
};
Expand Down
6 changes: 6 additions & 0 deletions src/Elastic.Documentation/ReleaseNotes/Bundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ public record Bundle
/// <summary>Products included in this bundle.</summary>
public IReadOnlyList<BundledProduct> Products { get; init; } = [];

/// <summary>
/// Optional introductory description text for this bundle.
/// Rendered as introductory content after the release heading.
/// </summary>
public string? Description { get; init; }

/// <summary>
/// Feature IDs that should be hidden when rendering this bundle.
/// Entries with matching feature-id values will be commented out in the output.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static string RenderSingleBundle(
};

var displayVersion = VersionOrDate.FormatDisplayVersion(bundle.Version);
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker);
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description);
}

/// <summary>
Expand Down Expand Up @@ -152,7 +152,8 @@ private static string GenerateMarkdown(
bool subsections,
bool hideLinks,
ChangelogTypeFilter typeFilter,
PublishBlocker? publishBlocker)
PublishBlocker? publishBlocker,
string? description = null)
{
var sb = new StringBuilder();

Expand All @@ -176,6 +177,13 @@ private static string GenerateMarkdown(

_ = sb.AppendLine(CultureInfo.InvariantCulture, $"## {title}");

// Add description if present
if (!string.IsNullOrEmpty(description))
{
_ = sb.AppendLine();
_ = sb.AppendLine(description);
}

// Check if we have any content at all
var hasAnyContent = features.Count > 0 || enhancements.Count > 0 || security.Count > 0 ||
bugFixes.Count > 0 || docs.Count > 0 || regressions.Count > 0 || other.Count > 0 ||
Expand Down
Loading
Loading