From fc0d32118f378adec39ec549631fb966f9471bcd Mon Sep 17 00:00:00 2001 From: lcawl Date: Fri, 10 Apr 2026 15:51:39 -0700 Subject: [PATCH 1/2] Add bundle release date command options --- config/changelog.example.yml | 5 ++ docs/cli/changelog/bundle.md | 12 +++++ docs/cli/changelog/gh-release.md | 5 ++ docs/contribute/changelog.md | 22 ++++++++ docs/syntax/changelog.md | 2 + .../Changelog/BundleConfiguration.cs | 14 +++++ .../ReleaseNotes/Bundle.cs | 6 +++ .../Changelog/ChangelogInlineRenderer.cs | 9 ++-- .../Bundling/ChangelogBundlingService.cs | 52 ++++++++++++++++++- .../ChangelogConfigurationLoader.cs | 2 + .../GitHub/GitHubReleaseService.cs | 6 ++- .../GitHub/IGitHubReleaseService.cs | 5 ++ .../GitHubReleaseChangelogService.cs | 20 ++++++- .../Asciidoc/ChangelogAsciidocRenderer.cs | 4 +- .../Rendering/ChangelogRenderContext.cs | 6 +++ .../Rendering/ChangelogRenderingService.cs | 16 ++++-- .../Markdown/IndexMarkdownRenderer.cs | 4 +- .../ChangelogConfigurationYaml.cs | 11 ++++ .../docs-builder/Commands/ChangelogCommand.cs | 48 ++++++++++++++++- 19 files changed, 232 insertions(+), 17 deletions(-) diff --git a/config/changelog.example.yml b/config/changelog.example.yml index 82c75e76c..22ca942f3 100644 --- a/config/changelog.example.yml +++ b/config/changelog.example.yml @@ -218,6 +218,9 @@ bundle: # 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}). + # Whether to show release dates in rendered changelog output (default: false) + # When true, release-date fields are displayed as "Released: date" text + # show_release_dates: false # 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). @@ -255,6 +258,8 @@ bundle: # # description: | # # Elasticsearch {version} includes: # # - Performance improvements + # # Optional: profile-specific release date display setting (overrides bundle.show_release_dates) + # # show_release_dates: false # # - Bug fixes and stability enhancements # # # # Download the release binaries: https://github.com/{owner}/{repo}/releases/tag/v{version} diff --git a/docs/cli/changelog/bundle.md b/docs/cli/changelog/bundle.md index 2b6e38f52..1c93840c3 100644 --- a/docs/cli/changelog/bundle.md +++ b/docs/cli/changelog/bundle.md @@ -124,6 +124,18 @@ The `--input-products` option determines which changelog files are gathered for : This value replaces information that would otherwise be derived from changelogs. : When `rules.bundle.products` per-product overrides are configured, `--output-products` also supplies the product IDs used to choose the **rule context product** (first alphabetically) for Mode 3. To use a different product's rules, run a separate bundle with only that product in `--output-products`. For details, refer to [Single-product rule resolution algorithm](/contribute/changelog.md#changelog-bundle-rule-resolution). +`--no-release-date` +: Optional: Skip auto-population of release date in the bundle. +: By default, bundles are created with a `release-date` field set to today's date (UTC) or the GitHub release published date when using `--release-version`. +: Mutually exclusive with `--release-date`. +: **Not available in profile mode** — use bundle configuration instead. + +`--release-date ` +: Optional: Explicit release date for the bundle in YYYY-MM-DD format. +: Overrides the default auto-population behavior (today's date or GitHub release published date). +: Mutually exclusive with `--no-release-date`. +: **Not available in profile mode** — use bundle configuration instead. + `--owner ` : Optional: The GitHub repository owner, required when pull requests or issues are specified as numbers. : Precedence: `--owner` flag > `bundle.owner` in `changelog.yml` > `elastic`. diff --git a/docs/cli/changelog/gh-release.md b/docs/cli/changelog/gh-release.md index 0c36ec80a..ba09e56c1 100644 --- a/docs/cli/changelog/gh-release.md +++ b/docs/cli/changelog/gh-release.md @@ -38,6 +38,11 @@ docs-builder changelog gh-release [version] [options...] [-h|--help] `--output ` : Optional: Output directory for the generated changelog files. Falls back to `bundle.directory` in `changelog.yml` when not specified. Defaults to `./changelogs`. +`--release-date ` +: Optional: Explicit release date for the bundle in YYYY-MM-DD format. +: By default, the bundle uses the GitHub release's published date. This option overrides that behavior. +: If the GitHub release has no published date, falls back to today's date (UTC). + `--strip-title-prefix` : Optional: Remove square brackets and the text within them from the beginning of pull request titles, and also remove a colon if it follows the closing bracket. : For example, `"[Inference API] New embedding model support"` becomes `"New embedding model support"`. diff --git a/docs/contribute/changelog.md b/docs/contribute/changelog.md index 0bc27acfa..014d253a0 100644 --- a/docs/contribute/changelog.md +++ b/docs/contribute/changelog.md @@ -821,6 +821,28 @@ You can add introductory text to bundles using the `description` field. This tex - `bundle.description`: Default description for all profiles - `bundle.profiles..description`: Profile-specific description (overrides the default) +#### Release date control + +You can control whether release dates appear in rendered changelog output using the `show_release_dates` field. + +**Configuration locations:** + +- `bundle.show_release_dates`: Default setting for all profiles (defaults to `false`) +- `bundle.profiles..show_release_dates`: Profile-specific setting (overrides the default) + +**Behavior:** + +- `false` (default): Release date fields are ignored during rendering, even if present in bundles +- `true`: Release dates are displayed when present (e.g., `_Released: April 9, 2026_`) + +**Auto-population:** + +Release dates are automatically added to bundles when using `changelog bundle` or `changelog gh-release`: + +- **Default**: Uses today's date (UTC) when bundling +- **GitHub releases**: Uses the GitHub release's published date when available +- **Override**: Use `--release-date YYYY-MM-DD` or `--no-release-date` in option mode + **Placeholder support:** Bundle descriptions support these placeholders: diff --git a/docs/syntax/changelog.md b/docs/syntax/changelog.md index ca2128c6d..a87afe4f4 100644 --- a/docs/syntax/changelog.md +++ b/docs/syntax/changelog.md @@ -251,6 +251,8 @@ Download the release binaries: https://github.com/elastic/elasticsearch/releases 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. +The `show_release_dates` setting in `changelog.yml` (boolean, defaults to `false`) controls whether release dates appear in the rendered output. When set to `false`, the `release-date` field is ignored during rendering. + 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 diff --git a/src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs b/src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs index 5282420b3..6296f4c9c 100644 --- a/src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs +++ b/src/Elastic.Documentation.Configuration/Changelog/BundleConfiguration.cs @@ -51,6 +51,13 @@ public record BundleConfiguration /// public IReadOnlyList? LinkAllowRepos { get; init; } + /// + /// Whether to show release dates in rendered changelog output. + /// When true, bundles with release-date fields will display "Released: date" text. + /// Defaults to false. + /// + public bool ShowReleaseDates { get; init; } + /// /// Named bundle profiles for different release scenarios. /// @@ -111,6 +118,13 @@ public record BundleProfile /// public IReadOnlyList? HideFeatures { get; init; } + /// + /// Whether to show release dates in rendered changelog output for this profile. + /// When provided, overrides the bundle.show_release_dates default. + /// When true, bundles with release-date fields will display "Released: date" text. + /// + public bool? ShowReleaseDates { get; init; } + /// /// Profile source type. When set to "github_release", the profile fetches /// PR references directly from a GitHub release and uses them as the bundle filter. diff --git a/src/Elastic.Documentation/ReleaseNotes/Bundle.cs b/src/Elastic.Documentation/ReleaseNotes/Bundle.cs index 70c470eb7..ccdd4848b 100644 --- a/src/Elastic.Documentation/ReleaseNotes/Bundle.cs +++ b/src/Elastic.Documentation/ReleaseNotes/Bundle.cs @@ -27,6 +27,12 @@ public record Bundle /// public DateOnly? ReleaseDate { get; init; } + /// + /// Whether to show release dates in rendered changelog output for this bundle. + /// When true, the ReleaseDate field (if present) will be displayed as "Released: date" text. + /// + public bool ShowReleaseDates { get; init; } + /// /// Feature IDs that should be hidden when rendering this bundle. /// Entries with matching feature-id values will be commented out in the output. diff --git a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs index af282fef5..86f094533 100644 --- a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs @@ -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, bundle.Data?.ReleaseDate); + return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description, bundle.Data?.ReleaseDate, bundle.Data?.ShowReleaseDates ?? false); } /// @@ -154,7 +154,8 @@ private static string GenerateMarkdown( ChangelogTypeFilter typeFilter, PublishBlocker? publishBlocker, string? description = null, - DateOnly? releaseDate = null) + DateOnly? releaseDate = null, + bool showReleaseDates = false) { var sb = new StringBuilder(); @@ -178,8 +179,8 @@ private static string GenerateMarkdown( _ = sb.AppendLine(CultureInfo.InvariantCulture, $"## {title}"); - // Add release date if present - if (releaseDate is { } date) + // Add release date if present and ShowReleaseDates is enabled + if (showReleaseDates && releaseDate is { } date) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"_Released: {date.ToString("MMMM d, yyyy", CultureInfo.InvariantCulture)}_"); diff --git a/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs b/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs index 2b4d130a3..9a9e2f522 100644 --- a/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs +++ b/src/services/Elastic.Changelog/Bundling/ChangelogBundlingService.cs @@ -91,6 +91,17 @@ public record BundleChangelogsArguments /// public string? Description { get; init; } + /// + /// Optional explicit release date for the bundle in YYYY-MM-DD format. + /// When provided, overrides auto-population behavior. + /// + public string? ReleaseDate { get; init; } + + /// + /// Whether to show release dates in rendered output. When null, inherits from config/profile. + /// + public bool? ShowReleaseDates { get; init; } + /// /// When non-null (including empty), PR/issue links are filtered to this owner/repo allowlist (from changelog.yml bundle.link_allow_repos). /// @@ -354,6 +365,36 @@ public async Task BundleChangelogs(IDiagnosticsCollector collector, Bundle } } + // Apply release date auto-population and ShowReleaseDates setting + var finalReleaseDate = bundleData.ReleaseDate; // Preserve existing date if present + if (!string.IsNullOrEmpty(input.ReleaseDate)) + { + // Explicit CLI override + if (DateOnly.TryParseExact(input.ReleaseDate, "yyyy-MM-dd", out var parsedDate)) + { + finalReleaseDate = parsedDate; + } + else + { + collector.EmitError(string.Empty, $"Invalid release date format '{input.ReleaseDate}'. Expected YYYY-MM-DD format."); + return false; + } + } + else if (finalReleaseDate == null) + { + // Auto-populate with today's date (UTC) if no existing date + finalReleaseDate = DateOnly.FromDateTime(DateTime.UtcNow); + } + + // Apply ShowReleaseDates setting (input takes precedence over config) + var showReleaseDates = input.ShowReleaseDates ?? config?.Bundle?.ShowReleaseDates ?? false; + + bundleData = bundleData with + { + ReleaseDate = finalReleaseDate, + ShowReleaseDates = showReleaseDates + }; + // Write bundle file await WriteBundleFileAsync(bundleData, outputPath, ctx); @@ -395,6 +436,7 @@ public async Task BundleChangelogs(IDiagnosticsCollector collector, Bundle string? owner = null; string[]? mergedHideFeatures = null; string? profileDescription = null; + bool? profileShowReleaseDates = null; if (config?.Bundle?.Profiles != null && config.Bundle.Profiles.TryGetValue(input.Profile!, out var profile)) { @@ -437,6 +479,9 @@ public async Task BundleChangelogs(IDiagnosticsCollector collector, Bundle owner = profile.Owner ?? config.Bundle.Owner; mergedHideFeatures = profile.HideFeatures?.Count > 0 ? [.. profile.HideFeatures] : null; + // Profile-level ShowReleaseDates takes precedence; fall back to bundle-level default + profileShowReleaseDates = profile.ShowReleaseDates ?? config.Bundle.ShowReleaseDates; + // Handle profile-specific description with placeholder substitution var descriptionTemplate = profile.Description ?? config.Bundle.Description; if (!string.IsNullOrEmpty(descriptionTemplate)) @@ -480,7 +525,8 @@ public async Task BundleChangelogs(IDiagnosticsCollector collector, Bundle Repo = repo, Owner = owner, HideFeatures = mergedHideFeatures, - Description = profileDescription + Description = profileDescription, + ShowReleaseDates = profileShowReleaseDates ?? config?.Bundle?.ShowReleaseDates }; } @@ -507,6 +553,9 @@ private BundleChangelogsArguments ApplyConfigDefaults(BundleChangelogsArguments // Apply description: CLI takes precedence; fall back to bundle-level config default var description = input.Description ?? config.Bundle.Description; + // Apply ShowReleaseDates: CLI takes precedence; fall back to bundle-level config default + var showReleaseDates = input.ShowReleaseDates ?? config.Bundle.ShowReleaseDates; + return input with { Directory = directory, @@ -515,6 +564,7 @@ private BundleChangelogsArguments ApplyConfigDefaults(BundleChangelogsArguments Repo = repo, Owner = owner, Description = description, + ShowReleaseDates = showReleaseDates, LinkAllowRepos = config.Bundle.LinkAllowRepos }; } diff --git a/src/services/Elastic.Changelog/Configuration/ChangelogConfigurationLoader.cs b/src/services/Elastic.Changelog/Configuration/ChangelogConfigurationLoader.cs index f07b82a80..054556500 100644 --- a/src/services/Elastic.Changelog/Configuration/ChangelogConfigurationLoader.cs +++ b/src/services/Elastic.Changelog/Configuration/ChangelogConfigurationLoader.cs @@ -521,6 +521,7 @@ private static PivotConfiguration ConvertPivot(PivotConfigurationYaml yamlPivot) Repo = kvp.Value.Repo, Owner = kvp.Value.Owner, HideFeatures = kvp.Value.HideFeatures?.Values, + ShowReleaseDates = kvp.Value.ShowReleaseDates, Source = kvp.Value.Source }); } @@ -534,6 +535,7 @@ private static PivotConfiguration ConvertPivot(PivotConfigurationYaml yamlPivot) Repo = yaml.Repo, Owner = yaml.Owner, LinkAllowRepos = linkAllowRepos, + ShowReleaseDates = yaml.ShowReleaseDates ?? false, Profiles = profiles }; } diff --git a/src/services/Elastic.Changelog/GitHub/GitHubReleaseService.cs b/src/services/Elastic.Changelog/GitHub/GitHubReleaseService.cs index e9985e16d..c28a0b743 100644 --- a/src/services/Elastic.Changelog/GitHub/GitHubReleaseService.cs +++ b/src/services/Elastic.Changelog/GitHub/GitHubReleaseService.cs @@ -103,7 +103,8 @@ static GitHubReleaseService() Body = releaseData.Body ?? string.Empty, Prerelease = releaseData.Prerelease, Draft = releaseData.Draft, - HtmlUrl = releaseData.HtmlUrl ?? string.Empty + HtmlUrl = releaseData.HtmlUrl ?? string.Empty, + PublishedAt = releaseData.PublishedAt }; } @@ -126,6 +127,9 @@ private sealed class GitHubReleaseResponse [JsonPropertyName("html_url")] public string? HtmlUrl { get; set; } + + [JsonPropertyName("published_at")] + public DateTimeOffset? PublishedAt { get; set; } } [JsonSerializable(typeof(GitHubReleaseResponse))] diff --git a/src/services/Elastic.Changelog/GitHub/IGitHubReleaseService.cs b/src/services/Elastic.Changelog/GitHub/IGitHubReleaseService.cs index 49281a04b..20f984ff2 100644 --- a/src/services/Elastic.Changelog/GitHub/IGitHubReleaseService.cs +++ b/src/services/Elastic.Changelog/GitHub/IGitHubReleaseService.cs @@ -38,6 +38,11 @@ public record GitHubReleaseInfo /// The URL to the release page on GitHub /// public string HtmlUrl { get; init; } = ""; + + /// + /// The date and time when this release was published on GitHub + /// + public DateTimeOffset? PublishedAt { get; init; } } /// diff --git a/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs b/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs index 97344fc15..ff4d87d7e 100644 --- a/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs +++ b/src/services/Elastic.Changelog/GithubRelease/GitHubReleaseChangelogService.cs @@ -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; using Elastic.Changelog.Bundling; @@ -60,6 +61,12 @@ public record CreateChangelogsFromReleaseArguments /// public string? Description { get; init; } + /// + /// Optional explicit release date for the bundle in YYYY-MM-DD format. + /// When provided, overrides the GitHub release published_at date. + /// + public string? ReleaseDate { get; init; } + /// /// Whether to create a bundle file after creating individual changelog files. Defaults to true. /// Set to false when called from 'changelog add --release-version' to skip bundle creation. @@ -187,7 +194,7 @@ Cancel ctx // 8. Optionally create bundle file if changelogs were created if (input.CreateBundle && createdFiles.Count > 0) { - var bundlePath = await CreateBundleViaService(collector, outputDir, createdFiles, productInfo, owner, repo, input, ctx); + var bundlePath = await CreateBundleViaService(collector, outputDir, createdFiles, productInfo, owner, repo, input, release, ctx); if (bundlePath != null) _logger.LogInformation("Created bundle file: {BundlePath}", bundlePath); } @@ -313,6 +320,7 @@ private static string GenerateYaml(ChangelogEntry data) => string owner, string repo, CreateChangelogsFromReleaseArguments input, + GitHubReleaseInfo release, Cancel ctx) { // Build the bundles subfolder path (mirrors the previous CreateBundleFile convention) @@ -333,6 +341,13 @@ private static string GenerateYaml(ChangelogEntry data) => }) .ToArray(); + // Use explicit release date if provided, otherwise GitHub release published date, otherwise fall back to auto-population + var releaseDate = input.ReleaseDate; + if (string.IsNullOrEmpty(releaseDate) && release.PublishedAt.HasValue) + { + releaseDate = DateOnly.FromDateTime(release.PublishedAt.Value.DateTime).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + } + var bundleArgs = new BundleChangelogsArguments { Directory = outputDir, @@ -342,7 +357,8 @@ private static string GenerateYaml(ChangelogEntry data) => Repo = repo, Config = input.Config, OutputProducts = [productInfo], - Description = input.Description + Description = input.Description, + ReleaseDate = releaseDate }; var success = await _bundlingService.BundleChangelogs(collector, bundleArgs, ctx); diff --git a/src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs b/src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs index e23554c59..ebd3b771c 100644 --- a/src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Asciidoc/ChangelogAsciidocRenderer.cs @@ -32,8 +32,8 @@ 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) + // Add release date if present and ShowReleaseDates is enabled + if (context.ShowReleaseDates && context.BundleReleaseDate is { } releaseDate) { _ = sb.AppendLine(InvariantCulture, $"_Released: {releaseDate.ToString("MMMM d, yyyy", InvariantCulture)}_"); _ = sb.AppendLine(); diff --git a/src/services/Elastic.Changelog/Rendering/ChangelogRenderContext.cs b/src/services/Elastic.Changelog/Rendering/ChangelogRenderContext.cs index 4fcdfbe20..33730ff05 100644 --- a/src/services/Elastic.Changelog/Rendering/ChangelogRenderContext.cs +++ b/src/services/Elastic.Changelog/Rendering/ChangelogRenderContext.cs @@ -35,4 +35,10 @@ public record ChangelogRenderContext /// Only set when there's a single unique release date across all bundles (MVP approach). /// public DateOnly? BundleReleaseDate { get; init; } + + /// + /// Whether to show release dates in the rendered output. + /// Controls whether the BundleReleaseDate (if present) is displayed. + /// + public bool ShowReleaseDates { get; init; } } diff --git a/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs b/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs index 800d64b93..9f97ca669 100644 --- a/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs +++ b/src/services/Elastic.Changelog/Rendering/ChangelogRenderingService.cs @@ -173,8 +173,16 @@ Cancel ctx renderReleaseDate = bundleReleaseDates[0]; } + // Determine ShowReleaseDates setting from bundles (all must agree, default to false) + var bundleShowReleaseDates = validationResult.Bundles + .Select(b => b.Data?.ShowReleaseDates ?? false) + .Distinct() + .ToList(); + + var renderShowReleaseDates = bundleShowReleaseDates.Count == 1 && bundleShowReleaseDates[0]; + // Build render context - var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription, renderReleaseDate); + var context = BuildRenderContext(input, outputSetup, resolvedResult, combinedHideFeatures, config, renderDescription, renderReleaseDate, renderShowReleaseDates); // Validate entry types if (!ValidateEntryTypes(collector, resolvedResult.Entries, config.Types)) @@ -288,7 +296,8 @@ private static ChangelogRenderContext BuildRenderContext( HashSet featureIdsToHide, ChangelogConfiguration? config, string? description = null, - DateOnly? releaseDate = null) + DateOnly? releaseDate = null, + bool showReleaseDates = false) { // Group entries by type var entriesByType = resolved.Entries @@ -331,7 +340,8 @@ private static ChangelogRenderContext BuildRenderContext( EntryToHideLinks = entryToHideLinks, Configuration = config, BundleDescription = description, - BundleReleaseDate = releaseDate + BundleReleaseDate = releaseDate, + ShowReleaseDates = showReleaseDates }; } diff --git a/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs b/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs index 9933eacee..02a8077d4 100644 --- a/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs +++ b/src/services/Elastic.Changelog/Rendering/Markdown/IndexMarkdownRenderer.cs @@ -52,8 +52,8 @@ 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) + // Add release date if present and ShowReleaseDates is enabled + if (context.ShowReleaseDates && context.BundleReleaseDate is { } releaseDate) { _ = sb.AppendLine(); _ = sb.AppendLine(InvariantCulture, $"_Released: {releaseDate.ToString("MMMM d, yyyy", InvariantCulture)}_"); diff --git a/src/services/Elastic.Changelog/Serialization/ChangelogConfigurationYaml.cs b/src/services/Elastic.Changelog/Serialization/ChangelogConfigurationYaml.cs index 372be436f..2c802fc0e 100644 --- a/src/services/Elastic.Changelog/Serialization/ChangelogConfigurationYaml.cs +++ b/src/services/Elastic.Changelog/Serialization/ChangelogConfigurationYaml.cs @@ -300,6 +300,11 @@ internal record BundleConfigurationYaml /// public YamlLenientList? LinkAllowRepos { get; set; } + /// + /// Whether to show release dates in rendered changelog output by default. + /// + public bool? ShowReleaseDates { get; set; } + /// /// Named bundle profiles. /// @@ -350,6 +355,12 @@ internal record BundleProfileYaml /// public YamlLenientList? HideFeatures { get; set; } + /// + /// Whether to show release dates in rendered changelog output for this profile. + /// Overrides bundle.show_release_dates when provided. + /// + public bool? ShowReleaseDates { get; set; } + /// /// Profile source type. When set to "github_release", the profile fetches /// PR references directly from a GitHub release and uses them as the bundle filter. diff --git a/src/tooling/docs-builder/Commands/ChangelogCommand.cs b/src/tooling/docs-builder/Commands/ChangelogCommand.cs index f071af8c6..c12895ce2 100644 --- a/src/tooling/docs-builder/Commands/ChangelogCommand.cs +++ b/src/tooling/docs-builder/Commands/ChangelogCommand.cs @@ -499,6 +499,8 @@ async static (s, collector, state, ctx) => await s.CreateChangelog(collector, st /// Optional: Directory containing changelog YAML files. Uses config bundle.directory or defaults to current directory /// Optional: Bundle description text with placeholder support. Supports {version}, {lifecycle}, {owner}, and {repo} placeholders. Overrides bundle.description from config. In option-based mode, placeholders require --output-products to be explicitly specified. /// Optional: Filter by feature IDs (comma-separated) or a path to a newline-delimited file containing feature IDs. Can be specified multiple times. Entries with matching feature-id values will be commented out when the bundle is rendered (by CLI render or {changelog} directive). + /// Optional: Skip auto-population of release date in the bundle. Mutually exclusive with --release-date. Not available in profile mode. + /// Optional: Explicit release date for the bundle in YYYY-MM-DD format. Overrides auto-population behavior. Mutually exclusive with --no-release-date. Not available in profile mode. /// Filter by products in format "product target lifecycle, ..." (for example, "cloud-serverless 2025-12-02 ga, cloud-serverless 2025-12-06 beta"). When specified, all three parts (product, target, lifecycle) are required but can be wildcards (*). Examples: "elasticsearch * *" matches all elasticsearch changelogs, "cloud-serverless 2025-12-02 *" matches cloud-serverless 2025-12-02 with any lifecycle, "* 9.3.* *" matches any product with target starting with "9.3.", "* * *" matches all changelogs (equivalent to --all). /// Filter by issue URLs (comma-separated), or a path to a newline-delimited file containing fully-qualified GitHub issue URLs. Can be specified multiple times. /// Optional: Output path for the bundled changelog. Can be either (1) a directory path, in which case 'changelog-bundle.yaml' is created in that directory, or (2) a file path ending in .yml or .yaml. Uses config bundle.output_directory or defaults to 'changelog-bundle.yaml' in the input directory @@ -522,6 +524,8 @@ public async Task Bundle( string? directory = null, string? description = null, string[]? hideFeatures = null, + bool noReleaseDate = false, + string? releaseDate = null, [ProductInfoParser] List? inputProducts = null, string? output = null, [ProductInfoParser] List? outputProducts = null, @@ -792,6 +796,35 @@ public async Task Bundle( return 0; } + // Validate release date flags + if (noReleaseDate && !string.IsNullOrWhiteSpace(releaseDate)) + { + collector.EmitError(string.Empty, "--no-release-date and --release-date are mutually exclusive."); + return 1; + } + + // Profile mode doesn't support release date CLI flags (use YAML configuration instead) + if (isProfileMode && (noReleaseDate || !string.IsNullOrWhiteSpace(releaseDate))) + { + var forbidden = new List(); + if (noReleaseDate) + forbidden.Add("--no-release-date"); + if (!string.IsNullOrWhiteSpace(releaseDate)) + forbidden.Add("--release-date"); + + collector.EmitError(string.Empty, + $"Profile mode does not support {string.Join(" and ", forbidden)}. " + + "Use bundle.show_release_dates configuration in changelog.yml instead."); + return 1; + } + + // Validate release date format if provided + if (!string.IsNullOrWhiteSpace(releaseDate) && !DateOnly.TryParseExact(releaseDate, "yyyy-MM-dd", out _)) + { + collector.EmitError(string.Empty, $"Invalid --release-date format '{releaseDate}'. Expected YYYY-MM-DD format."); + return 1; + } + // Determine resolve: CLI --no-resolve and --resolve override config. null = use config default. var shouldResolve = noResolve ? false : resolve; @@ -815,7 +848,8 @@ public async Task Bundle( Report = !isProfileMode ? report : null, Config = config, HideFeatures = allFeatureIdsForBundle.Count > 0 ? allFeatureIdsForBundle.ToArray() : null, - Description = description + Description = description, + ReleaseDate = noReleaseDate ? null : releaseDate }; serviceInvoker.AddCommand(service, input, @@ -1128,6 +1162,7 @@ async static (s, collector, state, ctx) => await s.RenderChangelogs(collector, s /// Optional: Path to the changelog.yml configuration file. Defaults to 'docs/changelog.yml' /// Optional: Bundle description text with placeholder support. Supports {version}, {lifecycle}, {owner}, and {repo} placeholders. Overrides bundle.description from config. /// Optional: Output directory for changelog files. Falls back to bundle.directory in changelog.yml when not specified. Defaults to './changelogs' + /// Optional: Explicit release date for the bundle in YYYY-MM-DD format. Overrides GitHub release published date. /// Optional: Remove square brackets and text within them from the beginning of PR titles (e.g., "[Inference API] Title" becomes "Title") /// Optional: Warn when the type inferred from release notes section headers doesn't match the type derived from PR labels. Defaults to true /// @@ -1138,6 +1173,7 @@ public async Task GitHubRelease( string? config = null, string? description = null, string? output = null, + string? releaseDate = null, bool stripTitlePrefix = false, bool warnOnTypeMismatch = true, Cancel ctx = default @@ -1154,6 +1190,13 @@ public async Task GitHubRelease( IGitHubPrService prService = new GitHubPrService(logFactory); var service = new GitHubReleaseChangelogService(logFactory, configurationContext, releaseService, prService); + // Validate release date format if provided + if (!string.IsNullOrWhiteSpace(releaseDate) && !DateOnly.TryParseExact(releaseDate, "yyyy-MM-dd", out _)) + { + collector.EmitError(string.Empty, $"Invalid --release-date format '{releaseDate}'. Expected YYYY-MM-DD format."); + return 1; + } + // Resolve stripTitlePrefix: CLI flag true → explicit true; otherwise null (use config default) var stripTitlePrefixResolved = stripTitlePrefix ? true : (bool?)null; @@ -1165,7 +1208,8 @@ public async Task GitHubRelease( Output = resolvedOutput, StripTitlePrefix = stripTitlePrefixResolved, WarnOnTypeMismatch = warnOnTypeMismatch, - Description = description + Description = description, + ReleaseDate = releaseDate }; serviceInvoker.AddCommand(service, input, From 4496bd2525b62f60b14756b06c0fcef29cb8cc98 Mon Sep 17 00:00:00 2001 From: lcawl Date: Fri, 10 Apr 2026 16:41:18 -0700 Subject: [PATCH 2/2] Fix Elastic.Markdown.Tests --- .../ReleaseNotes/Bundle.cs | 6 ++++++ .../ReleaseNotes/BundleLoader.cs | 12 +++++++++++- .../ReleaseNotes/ReleaseNotesSerialization.cs | 2 ++ .../Directives/ChangelogBasicTests.cs | 2 ++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Elastic.Documentation.Configuration/ReleaseNotes/Bundle.cs b/src/Elastic.Documentation.Configuration/ReleaseNotes/Bundle.cs index 11e72a87f..94ed70989 100644 --- a/src/Elastic.Documentation.Configuration/ReleaseNotes/Bundle.cs +++ b/src/Elastic.Documentation.Configuration/ReleaseNotes/Bundle.cs @@ -24,6 +24,12 @@ public sealed record BundleDto [YamlMember(Alias = "release-date", ApplyNamingConventions = false)] public string? ReleaseDate { get; set; } /// + /// Whether to show release dates in rendered changelog output for this bundle. + /// When true, the ReleaseDate field (if present) will be displayed as "Released: date" text. + /// + [YamlMember(Alias = "show-release-dates", ApplyNamingConventions = false)] + public bool? ShowReleaseDates { get; set; } + /// /// Feature IDs that should be hidden when rendering this bundle. /// Entries with matching feature-id values will be commented out in the output. /// diff --git a/src/Elastic.Documentation.Configuration/ReleaseNotes/BundleLoader.cs b/src/Elastic.Documentation.Configuration/ReleaseNotes/BundleLoader.cs index c3c1420fa..f081a75e2 100644 --- a/src/Elastic.Documentation.Configuration/ReleaseNotes/BundleLoader.cs +++ b/src/Elastic.Documentation.Configuration/ReleaseNotes/BundleLoader.cs @@ -242,7 +242,17 @@ private static LoadedBundle MergeBundleGroup(IGrouping gro _ => releaseDates[0] }; - var mergedData = first.Data with { Description = mergedDescription, ReleaseDate = mergedReleaseDate }; + var showReleaseDatesValues = bundlesList + .Select(b => b.Data?.ShowReleaseDates ?? false) + .Distinct() + .ToList(); + + // If all bundles agree on ShowReleaseDates, use that value; otherwise default to first bundle's value + var mergedShowReleaseDates = showReleaseDatesValues.Count == 1 ? showReleaseDatesValues[0] : first.Data?.ShowReleaseDates ?? false; + + var mergedData = first.Data != null + ? first.Data with { Description = mergedDescription, ReleaseDate = mergedReleaseDate, ShowReleaseDates = mergedShowReleaseDates } + : new Bundle { Description = mergedDescription, ReleaseDate = mergedReleaseDate, ShowReleaseDates = mergedShowReleaseDates }; return new LoadedBundle( first.Version, diff --git a/src/Elastic.Documentation.Configuration/ReleaseNotes/ReleaseNotesSerialization.cs b/src/Elastic.Documentation.Configuration/ReleaseNotes/ReleaseNotesSerialization.cs index 6f0690ec9..c9b794206 100644 --- a/src/Elastic.Documentation.Configuration/ReleaseNotes/ReleaseNotesSerialization.cs +++ b/src/Elastic.Documentation.Configuration/ReleaseNotes/ReleaseNotesSerialization.cs @@ -138,6 +138,7 @@ public static string SerializeBundle(Bundle bundle) Products = dto.Products?.Select(ToBundledProduct).ToList() ?? [], Description = dto.Description, ReleaseDate = ParseReleaseDate(dto.ReleaseDate), + ShowReleaseDates = dto.ShowReleaseDates ?? false, HideFeatures = dto.HideFeatures ?? [], Entries = dto.Entries?.Select(ToBundledEntry).ToList() ?? [] }; @@ -249,6 +250,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), + ShowReleaseDates = bundle.ShowReleaseDates ? bundle.ShowReleaseDates : null, HideFeatures = bundle.HideFeatures.Count > 0 ? bundle.HideFeatures.ToList() : null, Entries = bundle.Entries.Select(ToDto).ToList() }; diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index 4f2b49466..ec7c6a770 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -628,6 +628,7 @@ public ChangelogReleaseDateTests(ITestOutputHelper output) : base(output, - product: apm-agent-dotnet target: 1.34.0 release-date: "2026-04-09" + show-release-dates: true entries: - title: Add tracing improvements type: feature @@ -695,6 +696,7 @@ public ChangelogReleaseDateWithDescriptionTests(ITestOutputHelper output) : base - product: apm-agent-dotnet target: 1.34.0 release-date: "2026-04-09" + show-release-dates: true description: | This release includes tracing improvements and bug fixes. entries: