Skip to content

Commit 75fc1d6

Browse files
lcawlcotti
andauthored
Add changelog bundle description (#3058)
Co-authored-by: Felipe Cotti <felipe.cotti@elastic.co>
1 parent 8fa0c04 commit 75fc1d6

24 files changed

Lines changed: 748 additions & 19 deletions

File tree

config/changelog.example.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,12 @@ bundle:
212212
output_directory: docs/releases
213213
# Whether to resolve (copy contents) by default
214214
resolve: true
215+
# Optional: default description text for bundles. Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
216+
# Use YAML literal block scalar (|) for multiline descriptions. See docs/contribute/changelog.md for examples.
217+
# description: |
218+
# This release includes new features and bug fixes.
219+
#
220+
# For more information, see the [release notes](https://www.elastic.co/docs/release-notes/product#product-{version}).
215221
# changelog-init-bundle-seed
216222
# PR/issue link allowlist: when set (including []), only links to these owner/repo pairs are kept
217223
# in bundle output; others are rewritten to '# PRIVATE:' sentinels (requires resolve: true).
@@ -245,6 +251,13 @@ bundle:
245251
# output: "elasticsearch-{version}.yaml"
246252
# # Optional: override the products array written to the bundle output.
247253
# # output_products: "elasticsearch {version}"
254+
# # Optional: profile-specific description (overrides bundle.description)
255+
# # description: |
256+
# # Elasticsearch {version} includes:
257+
# # - Performance improvements
258+
# # - Bug fixes and stability enhancements
259+
# #
260+
# # Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
248261
# Example: GitHub release profile (fetches PR list directly from a GitHub release)
249262
# Use when you want to bundle or remove changelogs based on a published GitHub release.
250263
# elasticsearch-gh-release:

docs/cli/changelog/bundle.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ You must choose one method for determining what's in the bundle (`--all`, `--inp
7878
: Optional: The directory that contains the changelog YAML files.
7979
: 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.
8080

81+
`--description <string?>`
82+
: Optional: Bundle description text with placeholder support.
83+
: Supports `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` placeholders. Overrides `bundle.description` from config.
84+
: When using `{version}` or `{lifecycle}` placeholders, predictable substitution values are required:
85+
: - **Option-based mode**: Requires `--output-products` to be explicitly specified
86+
: - **Profile-based mode**: Requires either a version argument OR `output_products` in the profile configuration
87+
8188
`--hide-features <string[]?>`
8289
: Optional: A list of feature IDs (comma-separated), or a path to a newline-delimited file containing feature IDs.
8390
: Can be specified multiple times.
@@ -354,6 +361,33 @@ docs-builder changelog bundle \
354361
By default all changelogs that match PRs in the GitHub release notes are included in the bundle.
355362
To apply additional filtering by the changelog type, areas, or products, add `rules.bundle` [filters](#changelog-bundle-rules).
356363

364+
### Bundle with description
365+
366+
You can add a description to bundles using the `--description` option. For simple descriptions, use regular quotes:
367+
368+
```sh
369+
docs-builder changelog bundle \
370+
--all \
371+
--description "This release includes new features and bug fixes."
372+
```
373+
374+
For multiline descriptions with multiple paragraphs, lists, and links, use ANSI-C quoting (`$'...'`) with `\n` for line breaks:
375+
376+
```sh
377+
docs-builder changelog bundle \
378+
--all \
379+
--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).'
380+
```
381+
382+
When using placeholders in option-based mode, you must explicitly specify `--output-products` for predictable substitution:
383+
384+
```sh
385+
docs-builder changelog bundle \
386+
--all \
387+
--output-products "elasticsearch 9.1.0 ga" \
388+
--description "Elasticsearch {version} includes performance improvements. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
389+
```
390+
357391
## Profile-based examples
358392

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

479+
:::{warning}
480+
**Placeholder validation**: If your profile uses `{version}` or `{lifecycle}` placeholders in the description, you must ensure predictable substitution values:
481+
482+
```sh
483+
# ✅ Good: Version provided for placeholder substitution
484+
docs-builder changelog bundle elasticsearch-release 9.2.0 ./report.html
485+
486+
# ❌ Bad: No version, placeholders will fail unless profile has output_products
487+
docs-builder changelog bundle elasticsearch-release ./report.html
488+
```
489+
490+
To fix the second case, either provide a version argument or add an `output_products` pattern to your profile:
491+
492+
```yaml
493+
bundle:
494+
profiles:
495+
elasticsearch-release:
496+
products: "elasticsearch * *"
497+
output_products: "elasticsearch {version}" # Enables placeholder substitution
498+
description: "Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
499+
```
500+
:::
501+
445502
### Bundle by product
446503

447504
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`.

docs/cli/changelog/gh-release.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ docs-builder changelog gh-release <repo> [version] [options...] [-h|--help]
3131
`--config <string?>`
3232
: Optional: Path to the changelog.yml configuration file. Defaults to `docs/changelog.yml`.
3333

34+
`--description <string?>`
35+
: Optional: Bundle description text with placeholder support.
36+
: Supports `{version}`, `{lifecycle}`, `{owner}`, and `{repo}` placeholders. Overrides `bundle.description` from config.
37+
3438
`--output <string?>`
3539
: Optional: Output directory for the generated changelog files. Falls back to `bundle.directory` in `changelog.yml` when not specified. Defaults to `./changelogs`.
3640

@@ -86,6 +90,13 @@ docs-builder changelog gh-release elasticsearch v9.2.0 \
8690
--config ./docs/changelog.yml
8791
```
8892

93+
### Add description with placeholders
94+
95+
```sh
96+
docs-builder changelog gh-release elasticsearch v9.2.0 \
97+
--description "Elasticsearch {version} includes new features and fixes. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
98+
```
99+
89100
### Strip component prefixes from titles
90101

91102
```sh

docs/contribute/changelog.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,11 +746,23 @@ bundle:
746746
owner: elastic # The default repository owner for PR and issue links.
747747
directory: docs/changelog # The directory that contains changelog files.
748748
output_directory: docs/releases # The directory that contains changelog bundles.
749+
# Optional: Default bundle description with placeholder support
750+
description: |
751+
This release includes new features and bug fixes.
752+
753+
For more information, see the [release notes](https://www.elastic.co/docs/release-notes/elasticsearch#elasticsearch-{version}).
749754
profiles:
750755
elasticsearch-release:
751756
products: "elasticsearch {version} {lifecycle}"
752757
output: "elasticsearch/{version}.yaml"
753758
output_products: "elasticsearch {version}"
759+
# Profile-specific description overrides bundle.description
760+
description: |
761+
Elasticsearch {version} includes:
762+
- Performance improvements
763+
- Bug fixes and stability enhancements
764+
765+
Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
754766
hide_features:
755767
- feature:experimental-api
756768
serverless-release:
@@ -800,6 +812,63 @@ The `{version}` placeholder is substituted with the clean base version extracted
800812

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

815+
#### Bundle descriptions
816+
817+
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.
818+
819+
**Configuration locations:**
820+
821+
- `bundle.description`: Default description for all profiles
822+
- `bundle.profiles.<name>.description`: Profile-specific description (overrides the default)
823+
824+
**Placeholder support:**
825+
826+
Bundle descriptions support these placeholders:
827+
828+
- `{version}`: The resolved version string
829+
- `{lifecycle}`: The resolved lifecycle (ga, beta, preview, etc.)
830+
- `{owner}`: The GitHub repository owner
831+
- `{repo}`: The GitHub repository name
832+
833+
**Important**: When using `{version}` or `{lifecycle}` placeholders, you must ensure predictable substitution values:
834+
835+
- **Option-based mode**: Requires `--output-products` when using placeholders
836+
- **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.
837+
838+
**Multiline descriptions in YAML:**
839+
840+
For complex descriptions with multiple paragraphs, lists, and links, use YAML literal block scalars with the `|` (pipe) syntax:
841+
842+
```yaml
843+
bundle:
844+
description: |
845+
This release includes significant improvements:
846+
847+
- Enhanced performance
848+
- Bug fixes and stability improvements
849+
- New features for better user experience
850+
851+
For security updates, go to [security announcements](https://example.com/docs).
852+
853+
Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version}
854+
```
855+
856+
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.
857+
858+
**Command line usage:**
859+
860+
For simple descriptions, use the `--description` option with regular quotes:
861+
862+
```sh
863+
docs-builder changelog bundle --all --description "This release includes new features."
864+
```
865+
866+
For multiline descriptions on the command line, use ANSI-C quoting (`$'...'`) with `\n` for line breaks:
867+
868+
```sh
869+
docs-builder changelog bundle --all --description $'Enhanced release:\n\n- Performance improvements\n- Bug fixes'
870+
```
871+
803872
`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:
804873

805874
```yaml

docs/syntax/changelog.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,14 @@ For full syntax, refer to the [rules for filtered bundles](/cli/changelog/bundle
142142
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.
143143

144144
```yaml
145-
# Example bundle with hide-features
145+
# Example bundle with description and hide-features
146146
products:
147147
- product: elasticsearch
148148
target: 9.3.0
149+
description: |
150+
This release includes new features and bug fixes.
151+
152+
For more information, see the [release notes](https://example.com/docs).
149153
hide-features:
150154
- feature:hidden-api
151155
- feature:experimental
@@ -223,10 +227,15 @@ The version is extracted from the first product's `target` field in each bundle
223227

224228
## Rendered output
225229

226-
Each bundle renders as a `## {version}` section with subsections beneath:
230+
Each bundle renders as a `## {version}` section with optional description and subsections beneath:
227231

228232
```markdown
229233
## 0.100.0
234+
235+
This release includes new features and bug fixes.
236+
237+
Download the release binaries: https://github.com/elastic/elasticsearch/releases/tag/v0.100.0
238+
230239
### Features and enhancements
231240
...
232241
### Fixes
@@ -237,6 +246,8 @@ Each bundle renders as a `## {version}` section with subsections beneath:
237246
...
238247
```
239248

249+
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.
250+
240251
### Section types
241252

242253
| Section | Entry type | Rendering |

src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public record BundleConfiguration
2727
/// </summary>
2828
public bool Resolve { get; init; } = true;
2929

30+
/// <summary>
31+
/// Default bundle description used when no profile-specific description is provided.
32+
/// Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
33+
/// </summary>
34+
public string? Description { get; init; }
35+
3036
/// <summary>
3137
/// Default GitHub repository name applied to all profiles that do not specify their own.
3238
/// Used for generating correct PR/issue links when the product ID differs from the repo name.
@@ -81,6 +87,12 @@ public record BundleProfile
8187
/// </summary>
8288
public string? OutputProducts { get; init; }
8389

90+
/// <summary>
91+
/// Profile-specific bundle description. When provided, overrides the bundle.description default.
92+
/// Supports {version}, {lifecycle}, {owner}, and {repo} placeholders.
93+
/// </summary>
94+
public string? Description { get; init; }
95+
8496
/// <summary>
8597
/// GitHub repository name stored on each product in the bundle output.
8698
/// Used for generating correct PR/issue links when the product ID differs from the repo name.

src/Elastic.Documentation.Configuration/ReleaseNotes/Bundle.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public sealed record BundleDto
1414
{
1515
public List<BundledProductDto>? Products { get; set; }
1616
/// <summary>
17+
/// Optional introductory description text for this bundle.
18+
/// </summary>
19+
public string? Description { get; set; }
20+
/// <summary>
1721
/// Feature IDs that should be hidden when rendering this bundle.
1822
/// Entries with matching feature-id values will be commented out in the output.
1923
/// </summary>

src/Elastic.Documentation.Configuration/ReleaseNotes/BundleLoader.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,25 @@ private static LoadedBundle MergeBundleGroup(IGrouping<string, LoadedBundle> gro
217217
// Use the first bundle's metadata as the base
218218
var first = bundlesList[0];
219219

220+
var descriptions = bundlesList
221+
.Select(b => b.Data?.Description)
222+
.Where(d => !string.IsNullOrEmpty(d))
223+
.ToList();
224+
225+
var mergedDescription = descriptions.Count switch
226+
{
227+
0 => null,
228+
1 => descriptions[0],
229+
_ => string.Join("\n\n", descriptions)
230+
};
231+
232+
var mergedData = first.Data with { Description = mergedDescription };
233+
220234
return new LoadedBundle(
221235
first.Version,
222236
combinedRepo,
223237
first.Owner,
224-
first.Data,
238+
mergedData,
225239
first.FilePath,
226240
mergedEntries
227241
);

src/Elastic.Documentation.Configuration/ReleaseNotes/ReleaseNotesSerialization.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public static string SerializeBundle(Bundle bundle)
135135
private static Bundle ToBundle(BundleDto dto) => new()
136136
{
137137
Products = dto.Products?.Select(ToBundledProduct).ToList() ?? [],
138+
Description = dto.Description,
138139
HideFeatures = dto.HideFeatures ?? [],
139140
Entries = dto.Entries?.Select(ToBundledEntry).ToList() ?? []
140141
};
@@ -239,6 +240,7 @@ private static ChangelogEntryType ParseEntryType(string? value)
239240
private static BundleDto ToDto(Bundle bundle) => new()
240241
{
241242
Products = bundle.Products.Select(ToDto).ToList(),
243+
Description = bundle.Description,
242244
HideFeatures = bundle.HideFeatures.Count > 0 ? bundle.HideFeatures.ToList() : null,
243245
Entries = bundle.Entries.Select(ToDto).ToList()
244246
};

src/Elastic.Documentation/ReleaseNotes/Bundle.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public record Bundle
1313
/// <summary>Products included in this bundle.</summary>
1414
public IReadOnlyList<BundledProduct> Products { get; init; } = [];
1515

16+
/// <summary>
17+
/// Optional introductory description text for this bundle.
18+
/// Rendered as introductory content after the release heading.
19+
/// </summary>
20+
public string? Description { get; init; }
21+
1622
/// <summary>
1723
/// Feature IDs that should be hidden when rendering this bundle.
1824
/// Entries with matching feature-id values will be commented out in the output.

0 commit comments

Comments
 (0)