Skip to content

Commit 08d43d9

Browse files
authored
Changelog: Add release-date field to bundles (#3066)
* Add support for release-date field * Add Distinct() checking * Use DateOnly internally
1 parent f68d68b commit 08d43d9

13 files changed

Lines changed: 416 additions & 9 deletions

File tree

docs/cli/changelog/bundle.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,25 @@ docs-builder changelog bundle \
388388
--description "Elasticsearch {version} includes performance improvements. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
389389
```
390390

391+
### Bundle with release date
392+
393+
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.
394+
395+
```yaml
396+
products:
397+
- product: apm-agent-dotnet
398+
target: 1.34.0
399+
release-date: "April 9, 2026"
400+
description: |
401+
This release includes tracing improvements and bug fixes.
402+
entries:
403+
- file:
404+
name: tracing-improvement.yaml
405+
checksum: abc123
406+
```
407+
408+
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_`.
409+
391410
## Profile-based examples
392411

393412
When the changelog configuration file defines `bundle.profiles`, you can use those profiles with the `changelog bundle` command.

docs/syntax/changelog.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ 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 description and hide-features
145+
# Example bundle with release-date, description, and hide-features
146146
products:
147147
- product: elasticsearch
148148
target: 9.3.0
149+
release-date: "2026-04-09"
149150
description: |
150151
This release includes new features and bug fixes.
151152
@@ -227,11 +228,13 @@ The version is extracted from the first product's `target` field in each bundle
227228

228229
## Rendered output
229230

230-
Each bundle renders as a `## {version}` section with optional description and subsections beneath:
231+
Each bundle renders as a `## {version}` section with optional release date, description, and subsections beneath:
231232

232233
```markdown
233234
## 0.100.0
234235
236+
_Released: 2026-04-09_
237+
235238
This release includes new features and bug fixes.
236239
237240
Download the release binaries: https://github.com/elastic/elasticsearch/releases/tag/v0.100.0
@@ -246,7 +249,9 @@ Download the release binaries: https://github.com/elastic/elasticsearch/releases
246249
...
247250
```
248251

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.
252+
When present, the `release-date` field is rendered immediately after the version heading as italicized text (e.g., `_Released: 2026-04-09_`). This is purely informative for end-users and is especially useful for components released outside the usual stack lifecycle, such as APM agents and EDOT agents.
253+
254+
Bundle descriptions are rendered when present in the bundle YAML file. The description appears after the release date (if any) but before any entry sections. Descriptions support Markdown formatting including links, lists, and multiple paragraphs.
250255

251256
### Section types
252257

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public sealed record BundleDto
1818
/// </summary>
1919
public string? Description { get; set; }
2020
/// <summary>
21+
/// Optional release date for this bundle.
22+
/// Purely informative; rendered after the release heading.
23+
/// </summary>
24+
[YamlMember(Alias = "release-date", ApplyNamingConventions = false)]
25+
public string? ReleaseDate { get; set; }
26+
/// <summary>
2127
/// Feature IDs that should be hidden when rendering this bundle.
2228
/// Entries with matching feature-id values will be commented out in the output.
2329
/// </summary>

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,20 @@ private static LoadedBundle MergeBundleGroup(IGrouping<string, LoadedBundle> gro
229229
_ => string.Join("\n\n", descriptions)
230230
};
231231

232-
var mergedData = first.Data with { Description = mergedDescription };
232+
var releaseDates = bundlesList
233+
.Select(b => b.Data?.ReleaseDate)
234+
.Where(d => d.HasValue)
235+
.Select(d => d!.Value)
236+
.Distinct()
237+
.ToList();
238+
239+
var mergedReleaseDate = releaseDates.Count switch
240+
{
241+
0 => (DateOnly?)null,
242+
_ => releaseDates[0]
243+
};
244+
245+
var mergedData = first.Data with { Description = mergedDescription, ReleaseDate = mergedReleaseDate };
233246

234247
return new LoadedBundle(
235248
first.Version,

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.Globalization;
56
using System.IO.Abstractions;
67
using System.Text.RegularExpressions;
78
using Elastic.Documentation.Configuration.Serialization;
@@ -136,6 +137,7 @@ public static string SerializeBundle(Bundle bundle)
136137
{
137138
Products = dto.Products?.Select(ToBundledProduct).ToList() ?? [],
138139
Description = dto.Description,
140+
ReleaseDate = ParseReleaseDate(dto.ReleaseDate),
139141
HideFeatures = dto.HideFeatures ?? [],
140142
Entries = dto.Entries?.Select(ToBundledEntry).ToList() ?? []
141143
};
@@ -212,6 +214,11 @@ private static ChangelogEntryType ParseEntryType(string? value)
212214
: null;
213215
}
214216

217+
private static DateOnly? ParseReleaseDate(string? value) =>
218+
DateOnly.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
219+
? date
220+
: null;
221+
215222
// Reverse mappings (Domain → DTO) for serialization
216223

217224
private static ChangelogEntryDto ToDto(ChangelogEntry entry) => new()
@@ -241,6 +248,7 @@ private static ChangelogEntryType ParseEntryType(string? value)
241248
{
242249
Products = bundle.Products.Select(ToDto).ToList(),
243250
Description = bundle.Description,
251+
ReleaseDate = bundle.ReleaseDate?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
244252
HideFeatures = bundle.HideFeatures.Count > 0 ? bundle.HideFeatures.ToList() : null,
245253
Entries = bundle.Entries.Select(ToDto).ToList()
246254
};

src/Elastic.Documentation/ReleaseNotes/Bundle.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public record Bundle
1919
/// </summary>
2020
public string? Description { get; init; }
2121

22+
/// <summary>
23+
/// Optional release date for this bundle.
24+
/// Purely informative for end-users; rendered after the release heading.
25+
/// Useful for components released outside the usual stack lifecycle (e.g., APM/EDOT agents).
26+
/// Parsed from YYYY-MM-DD format in YAML; serialized back as YYYY-MM-DD.
27+
/// </summary>
28+
public DateOnly? ReleaseDate { get; init; }
29+
2230
/// <summary>
2331
/// Feature IDs that should be hidden when rendering this bundle.
2432
/// Entries with matching feature-id values will be commented out in the output.

src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private static string RenderSingleBundle(
7979
};
8080

8181
var displayVersion = VersionOrDate.FormatDisplayVersion(bundle.Version);
82-
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description);
82+
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description, bundle.Data?.ReleaseDate);
8383
}
8484

8585
/// <summary>
@@ -153,7 +153,8 @@ private static string GenerateMarkdown(
153153
bool hideLinks,
154154
ChangelogTypeFilter typeFilter,
155155
PublishBlocker? publishBlocker,
156-
string? description = null)
156+
string? description = null,
157+
DateOnly? releaseDate = null)
157158
{
158159
var sb = new StringBuilder();
159160

@@ -177,6 +178,13 @@ private static string GenerateMarkdown(
177178

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

181+
// Add release date if present
182+
if (releaseDate is { } date)
183+
{
184+
_ = sb.AppendLine();
185+
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"_Released: {date.ToString("MMMM d, yyyy", CultureInfo.InvariantCulture)}_");
186+
}
187+
180188
// Add description if present
181189
if (!string.IsNullOrEmpty(description))
182190
{

src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ public async Task RenderAsciidoc(ChangelogRenderContext context, Cancel ctx)
3232
_ = sb.AppendLine(InvariantCulture, $"== {context.Title}");
3333
_ = sb.AppendLine();
3434

35+
// Add release date if present
36+
if (context.BundleReleaseDate is { } releaseDate)
37+
{
38+
_ = sb.AppendLine(InvariantCulture, $"_Released: {releaseDate.ToString("MMMM d, yyyy", InvariantCulture)}_");
39+
_ = sb.AppendLine();
40+
}
41+
3542
// Add description if present
3643
if (!string.IsNullOrEmpty(context.BundleDescription))
3744
{

src/services/Elastic.Changelog/Rendering/ChangelogRenderContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,9 @@ public record ChangelogRenderContext
3030
/// Optional bundle-level introductory description. Only set when there's a single bundle with a description (MVP approach).
3131
/// </summary>
3232
public string? BundleDescription { get; init; }
33+
/// <summary>
34+
/// Optional release date for this bundle. Purely informative for end-users.
35+
/// Only set when there's a single unique release date across all bundles (MVP approach).
36+
/// </summary>
37+
public DateOnly? BundleReleaseDate { get; init; }
3338
}

src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ Cancel ctx
137137
var bundleDescriptions = validationResult.Bundles
138138
.Select(b => b.Data.Description)
139139
.Where(d => !string.IsNullOrEmpty(d))
140+
.Distinct()
140141
.ToList();
141142

142143
// MVP: Check for multiple descriptions and warn
@@ -152,8 +153,28 @@ Cancel ctx
152153
renderDescription = bundleDescriptions[0];
153154
}
154155

156+
// Extract release dates from bundles for MVP support
157+
var bundleReleaseDates = validationResult.Bundles
158+
.Select(b => b.Data.ReleaseDate)
159+
.Where(d => d.HasValue)
160+
.Select(d => d!.Value)
161+
.Distinct()
162+
.ToList();
163+
164+
DateOnly? renderReleaseDate = null;
165+
if (bundleReleaseDates.Count > 1)
166+
{
167+
collector.EmitWarning(string.Empty,
168+
$"Multiple bundles contain release dates ({bundleReleaseDates.Count} found). " +
169+
"Multi-bundle release date support is not yet implemented. Release dates will be skipped.");
170+
}
171+
else if (bundleReleaseDates.Count == 1)
172+
{
173+
renderReleaseDate = bundleReleaseDates[0];
174+
}
175+
155176
// Build render context
156-
var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription);
177+
var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription, renderReleaseDate);
157178

158179
// Validate entry types
159180
if (!ValidateEntryTypes(collector, resolvedResult.Entries, config.Types))
@@ -266,7 +287,8 @@ private static ChangelogRenderContext BuildRenderContext(
266287
ResolvedEntriesResult resolved,
267288
HashSet<string> featureIdsToHide,
268289
ChangelogConfiguration? config,
269-
string? description = null)
290+
string? description = null,
291+
DateOnly? releaseDate = null)
270292
{
271293
// Group entries by type
272294
var entriesByType = resolved.Entries
@@ -308,7 +330,8 @@ private static ChangelogRenderContext BuildRenderContext(
308330
EntryToOwner = entryToOwner,
309331
EntryToHideLinks = entryToHideLinks,
310332
Configuration = config,
311-
BundleDescription = description
333+
BundleDescription = description,
334+
BundleReleaseDate = releaseDate
312335
};
313336
}
314337

0 commit comments

Comments
 (0)