Skip to content
Open
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
19 changes: 19 additions & 0 deletions docs/cli/changelog/bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,25 @@ docs-builder changelog bundle \
--description "Elasticsearch {version} includes performance improvements. Download: https://github.com/{owner}/{repo}/releases/tag/v{version}"
```

### Bundle with release date

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`, you can use those profiles with the `changelog bundle` command.
Expand Down
11 changes: 8 additions & 3 deletions docs/syntax/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,11 @@ 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 description and hide-features
# Example bundle with release-date, description, and hide-features
products:
- product: elasticsearch
target: 9.3.0
release-date: "2026-04-09"
description: |
This release includes new features and bug fixes.

Expand Down Expand Up @@ -227,11 +228,13 @@ The version is extracted from the first product's `target` field in each bundle

## Rendered output

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

```markdown
## 0.100.0

_Released: 2026-04-09_

This release includes new features and bug fixes.

Download the release binaries: https://github.com/elastic/elasticsearch/releases/tag/v0.100.0
Expand All @@ -246,7 +249,9 @@ Download the release binaries: https://github.com/elastic/elasticsearch/releases
...
```

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

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.

### Section types

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public sealed record BundleDto
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Optional release date for this bundle.
/// Purely informative; rendered after the release heading.
/// </summary>
[YamlMember(Alias = "release-date", ApplyNamingConventions = false)]
public string? ReleaseDate { 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 @@ -229,7 +229,20 @@ private static LoadedBundle MergeBundleGroup(IGrouping<string, LoadedBundle> gro
_ => string.Join("\n\n", descriptions)
};

var mergedData = first.Data with { Description = mergedDescription };
var releaseDates = bundlesList
.Select(b => b.Data?.ReleaseDate)
.Where(d => d.HasValue)
.Select(d => d!.Value)
.Distinct()
.ToList();

var mergedReleaseDate = releaseDates.Count switch
{
0 => (DateOnly?)null,
_ => releaseDates[0]
};

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

return new LoadedBundle(
first.Version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 System.Globalization;
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using Elastic.Documentation.Configuration.Serialization;
Expand Down Expand Up @@ -136,6 +137,7 @@ public static string SerializeBundle(Bundle bundle)
{
Products = dto.Products?.Select(ToBundledProduct).ToList() ?? [],
Description = dto.Description,
ReleaseDate = ParseReleaseDate(dto.ReleaseDate),
HideFeatures = dto.HideFeatures ?? [],
Entries = dto.Entries?.Select(ToBundledEntry).ToList() ?? []
};
Expand Down Expand Up @@ -212,6 +214,11 @@ private static ChangelogEntryType ParseEntryType(string? value)
: null;
}

private static DateOnly? ParseReleaseDate(string? value) =>
DateOnly.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
? date
: null;

// Reverse mappings (Domain → DTO) for serialization

private static ChangelogEntryDto ToDto(ChangelogEntry entry) => new()
Expand Down Expand Up @@ -241,6 +248,7 @@ private static ChangelogEntryType ParseEntryType(string? value)
{
Products = bundle.Products.Select(ToDto).ToList(),
Description = bundle.Description,
ReleaseDate = bundle.ReleaseDate?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
HideFeatures = bundle.HideFeatures.Count > 0 ? bundle.HideFeatures.ToList() : null,
Entries = bundle.Entries.Select(ToDto).ToList()
};
Expand Down
8 changes: 8 additions & 0 deletions src/Elastic.Documentation/ReleaseNotes/Bundle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ public record Bundle
/// </summary>
public string? Description { get; init; }

/// <summary>
/// Optional release date for this bundle.
/// Purely informative for end-users; rendered after the release heading.
/// Useful for components released outside the usual stack lifecycle (e.g., APM/EDOT agents).
/// Parsed from YYYY-MM-DD format in YAML; serialized back as YYYY-MM-DD.
/// </summary>
public DateOnly? ReleaseDate { 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, bundle.Data?.Description);
return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description, bundle.Data?.ReleaseDate);
}

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

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

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

// Add release date if present
if (releaseDate is { } date)
{
_ = sb.AppendLine();
_ = sb.AppendLine(CultureInfo.InvariantCulture, $"_Released: {date.ToString("MMMM d, yyyy", CultureInfo.InvariantCulture)}_");
}

// Add description if present
if (!string.IsNullOrEmpty(description))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public async Task RenderAsciidoc(ChangelogRenderContext context, Cancel ctx)
_ = sb.AppendLine(InvariantCulture, $"== {context.Title}");
_ = sb.AppendLine();

// Add release date if present
if (context.BundleReleaseDate is { } releaseDate)
{
_ = sb.AppendLine(InvariantCulture, $"_Released: {releaseDate.ToString("MMMM d, yyyy", InvariantCulture)}_");
_ = sb.AppendLine();
}

// Add description if present
if (!string.IsNullOrEmpty(context.BundleDescription))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ public record ChangelogRenderContext
/// Optional bundle-level introductory description. Only set when there's a single bundle with a description (MVP approach).
/// </summary>
public string? BundleDescription { get; init; }
/// <summary>
/// Optional release date for this bundle. Purely informative for end-users.
/// Only set when there's a single unique release date across all bundles (MVP approach).
/// </summary>
public DateOnly? BundleReleaseDate { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ Cancel ctx
var bundleDescriptions = validationResult.Bundles
.Select(b => b.Data.Description)
.Where(d => !string.IsNullOrEmpty(d))
.Distinct()
.ToList();

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

// Extract release dates from bundles for MVP support
var bundleReleaseDates = validationResult.Bundles
.Select(b => b.Data.ReleaseDate)
.Where(d => d.HasValue)
.Select(d => d!.Value)
.Distinct()
.ToList();

DateOnly? renderReleaseDate = null;
if (bundleReleaseDates.Count > 1)
{
collector.EmitWarning(string.Empty,
$"Multiple bundles contain release dates ({bundleReleaseDates.Count} found). " +
"Multi-bundle release date support is not yet implemented. Release dates will be skipped.");
}
else if (bundleReleaseDates.Count == 1)
{
renderReleaseDate = bundleReleaseDates[0];
}

// Build render context
var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription);
var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription, renderReleaseDate);

// Validate entry types
if (!ValidateEntryTypes(collector, resolvedResult.Entries, config.Types))
Expand Down Expand Up @@ -266,7 +287,8 @@ private static ChangelogRenderContext BuildRenderContext(
ResolvedEntriesResult resolved,
HashSet<string> featureIdsToHide,
ChangelogConfiguration? config,
string? description = null)
string? description = null,
DateOnly? releaseDate = null)
{
// Group entries by type
var entriesByType = resolved.Entries
Expand Down Expand Up @@ -308,7 +330,8 @@ private static ChangelogRenderContext BuildRenderContext(
EntryToOwner = entryToOwner,
EntryToHideLinks = entryToHideLinks,
Configuration = config,
BundleDescription = description
BundleDescription = description,
BundleReleaseDate = releaseDate
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ public override async Task RenderAsync(ChangelogRenderContext context, Cancel ct
var sb = new StringBuilder();
_ = sb.AppendLine(InvariantCulture, $"## {context.Title} [{context.Repo}-release-notes-{context.TitleSlug}]");

// Add release date if present
if (context.BundleReleaseDate is { } releaseDate)
{
_ = sb.AppendLine();
_ = sb.AppendLine(InvariantCulture, $"_Released: {releaseDate.ToString("MMMM d, yyyy", InvariantCulture)}_");
}

// Add description if present
if (!string.IsNullOrEmpty(context.BundleDescription))
{
Expand Down
Loading
Loading