diff --git a/config/changelog.example.yml b/config/changelog.example.yml index d1057fc161..e30a65ef75 100644 --- a/config/changelog.example.yml +++ b/config/changelog.example.yml @@ -1,324 +1,21 @@ -# Changelog Configuration -# This file configures the valid values for changelog fields using the pivot structure. -# Place this file as `changelog.yml` in the `docs/` directory -# -# NOTE: All list-like fields accept BOTH forms: -# - Comma-separated string: "value1, value2, value3" -# - YAML list: -# - value1 -# - value2 -# - value3 -# Both forms produce the same result. Choose whichever is more readable for your use case. +# Changelog configuration +# Place this file at docs/changelog.yml. -# Filename strategy for generated changelog files. -# Controls how files created by 'changelog add' are named. -# pr — use the PR number (e.g., 12345.yaml). -# issue — use the issue number (e.g., 67890.yaml). -# timestamp — use a Unix timestamp with a title slug (e.g., 1735689600-fix-search.yaml). Default. -# Can be overridden per invocation with --use-pr-number or --use-issue-number CLI flags. -filename: timestamp - -# Products configuration (optional) -# If not specified, all products from products.yml are allowed (including those -# that have 'public-reference' disabled). -products: - # List of available product IDs (empty = all from products.yml) - # Accepts string or list: "elasticsearch, kibana" or [elasticsearch, kibana] - available: [] - # Default products when --products not specified and no label maps to a product - # default: - # - product: elasticsearch - # lifecycle: ga - # Products that a label-derived product implies, keyed by product ID. - # An entry that gets 'observability' from a label also gets 'kibana' and 'cloud-serverless'. - # implies: - # observability: [kibana, cloud-serverless] - -# Extraction configuration -# Controls automatic extraction of information from PR descriptions -extract: - # Auto-extract release notes from PR descriptions (default: true) - # Can be overridden by CLI --no-extract-release-notes - release_notes: true - # Auto-extract linked references (default: true) - # When using --prs: looks for patterns like "Fixes #123", "Closes #456" in PR body to derive issues. - # When using --issues: looks for patterns like "Fixed by #123" in issue body to derive PRs. - # Can be overridden by CLI --no-extract-issues - issues: true - # Remove square-bracket prefixes from PR titles (default: false) - # When enabled (true or via --strip-title-prefix), titles like "[ES|QL] Fix bug" become "Fix bug". - # Can be overridden by CLI --strip-title-prefix - strip_title_prefix: false - -# Available lifecycle values (strongly typed: preview, beta, ga) -# Accepts string or list: "preview, beta, ga" or a YAML list -lifecycles: - - preview - - beta - - ga - - experimental - -# Pivot configuration for types, subtypes, and areas with label mappings. -# By default we render changelogs grouped by type > subtype > area. -# NOTE: subtype and area are optional and either may be omitted. -# Labels are specified in a "label -> value" format -pivot: - # Pivots that every PR must set a label for, beyond the type (which is always required): - # "areas" and/or "products". The PR check fails and the PR comment asks for the missing labels. - # A listed pivot needs labels in pivot.areas or pivot.products. For "products", a label is required - # even when products.default is set or pivot.products maps a single product. - # Without "products" here, a product label is only required when pivot.products maps two or more - # distinct products and products.default is not set. - # required: [areas, products] - - # Type definitions with optional labels - # At a minimum, feature, bug-fix, and breaking-change must be configured. - # Keys are type names, values can be: - # - simple string: comma-separated label list (e.g., ">bug, >fix") - # - YAML list: [">bug", ">fix"] - # - empty/null: no labels for this type - # - object: { labels: "...", subtypes: {...} } for breaking-change type only - # (labels and subtype values also accept string or list) - types: - # Complex object form with subtypes (ONLY allowed for breaking-change) - # Subtypes help categorize breaking changes by their nature - # Both labels and subtype values accept string or list form - breaking-change: - labels: ">breaking, >bc" - # Equivalent list form: - # labels: - # - ">breaking" - # - ">bc" - subtypes: - api: ">api-breaking" - behavioral: ">behavioral-breaking" - configuration: ">config-breaking" - dependency: ">dependency-breaking" - subscription: ">subscription-breaking" - plugin: ">plugin-breaking" - security: ">security-breaking" - other: - # Simple string form: labels as comma-separated string - bug-fix: ">bug" - deprecation: ">deprecation" - # Empty block: no labels (YAML null) - feature: - enhancement: - docs: - known-issue: - other: - regression: - security: - - # Labels that trigger the highlight flag (accepts string or list) - # String form: highlight: ">highlight, >release-highlight" - # List form: - # highlight: - # - ">highlight" - # - ">release-highlight" - - # Area definitions with labels - # Keys are area display names (can contain commas), values are label strings or lists - # Each label maps to exactly one area. - # String form: "label1, label2" | List form: [label1, label2] - # To map one label to multiple areas, repeat the label under each area name. - areas: - # Example mappings - customize based on your label naming conventions - # Autoscaling: ":Distributed Coordination/Autoscaling" - # Search: ":Search/Search" - # Security: ":Security/Security" - # Watcher: ":Data Management/Watcher" - # To map a label to multiple areas (e.g., "Team:Search" to both "Search" and "Observability"): - # Search: - # - ":Search/Search" - # - "Team:Search" - # Observability: - # - "Team:Search" - # List form example: - # Search: - # - ":Search/Search" - # - ":Search/Ranking" - - # Product definitions with labels (optional). - # Keys are product spec strings; values are label strings or lists that trigger that product. - # A product spec string is: " [] []" - # - Product ID only: "elasticsearch" - # - Product + target: "kibana 9.2.0" - # - Full spec: "cloud-serverless 2025-06 ga" - # When a PR has labels matching any product entry, all matching products are added. - # Precedence: --products CLI option > pivot.products label mapping (plus products.implies) > products.default > repo inference. - # - # products: - # 'elasticsearch': - # - ":stack/elasticsearch" - # 'kibana': - # - ":stack/kibana" - # 'cloud-serverless': - # - ":cloud/serverless" - # # Specify a target version if known: - # # 'elasticsearch 9.2.0': - # # - ":feature/new-in-9.2" - - # Feature ID definitions with labels (optional). - # Keys are feature-id values written to the changelog; values are label strings or lists. - # When a PR has a matching label and --feature-id is not set, that feature-id is applied. - # If multiple labels map to distinct feature-ids, a warning is emitted and the first match is used. - # Precedence: --feature-id CLI option > pivot.features label mapping. - # Unreleased feature IDs should also be listed under bundle.profiles..hide_features. - # - # features: - # 'feature:new-search-api': - # - "feature-flag:new-search-api" - # - ":Feature/NewSearchApi" - -# Rules configuration — controls which PRs create changelogs and which changelogs are published. -# All list-like fields (exclude, include, exclude_types) accept BOTH forms: -# - Comma-separated string: "value1, value2, value3" -# - YAML list: [value1, value2, value3] -# -# For details and examples, refer to the [rules documentation](https://github.com/elastic/docs-builder/blob/main/docs/contribute/configure-changelogs-ref.md#rules). -rules: - # match: any - - # Create — controls which PRs generate changelogs. - # create: - # exclude: ">non-issue, >test" - # # Or equivalently: - # # exclude: - # # - ">non-issue" - # # - ">test" - # # match: any - # products: - # 'elasticsearch, kibana': - # exclude: ">test" - # 'cloud-serverless': - # exclude: "ILM" - - # Bundle — filtering applied during 'changelog bundle' and 'changelog gh-release'. - # See https://github.com/elastic/docs-builder/blob/main/docs/contribute/configure-changelogs-ref.md#rules-bundle for Mode 1 / 2 / 3 behavior. - # - # Mode 2 (global content): use global lists only — no `products` key, or `products: {}`. - # Example — include changelogs that list elasticsearch OR kibana (evaluated per changelog): - # bundle: - # match_products: any - # include_products: - # - elasticsearch - # - kibana - # exclude_types: "deprecation, known-issue" - # - # Example — exclude only when the changelog lists BOTH products (conjunction); extras allowed: - # bundle: - # match_products: conjunction - # exclude_products: - # - kibana - # - observability - # - # Mode 3 (per-product context): non-empty `rules.bundle.products` — global include/exclude/type/area - # under `bundle:` are NOT applied for filtering; configure filters under each product key. - # Example: - # bundle: - # exclude_types: [docs] # ignored for filtering in Mode 3 — duplicate under each product if needed - # products: - # observability: - # exclude_products: ["cloud-enterprise"] - # exclude_types: ["docs"] - # include_areas: - # - "APM" - # - "Infrastructure monitoring" - # kibana: - # match_products: all - # include_products: ["kibana", "security"] - # exclude_types: ["docs"] - -# Bundle configuration (profiles and defaults) bundle: # Input directory containing changelog YAML files directory: docs/changelog # Output directory for bundled changelog files output_directory: docs/releases - # Optional: default description text for bundles. Supports {version}, {lifecycle}, {owner}, and {repo} placeholders. - # Use YAML literal block scalar (|) for multiline descriptions. - # 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. - # There is no implicit allow: you must list every repo whose links should appear, including your - # own (bundle.repo as owner/repo). When omitted entirely, no link filtering is applied. - # Run `changelog init` in a GitHub clone to pre-fill owner, repo, and link_allow_repos, or set - # bundle.owner, bundle.repo, and link_allow_repos manually. Example — allow only this repo: - # link_allow_repos: - # - elastic/kibana - # To allow cross-repo links, add more owner/repo entries (for example elastic/elasticsearch). - # Optional: default GitHub repo name applied to all profiles that do not specify their own. - # Used by the {changelog} directive to generate correct PR/issue links when the product ID - # differs from the GitHub repository name. Can be overridden per profile. - # repo: elasticsearch - # Optional: default GitHub owner applied to all profiles that do not specify their own. - # owner: elastic - # Optional: branch whose CDN changelog pool (changelog/{org}/{repo}/{branch}/...) is sourced from when - # bundling entries from the CDN. Defaults to "main" when unset. Can be overridden per profile. - # branch: main - # Optional: control auto-population of release-date for all profiles by default. - # When true (default), auto-populate release dates. Profiles can override this setting. - # release_dates: true - # Named bundle profiles for different release scenarios. - # Profiles can be used with both 'changelog bundle' and 'changelog remove': - # docs-builder changelog bundle elasticsearch-release 9.2.0 - # docs-builder changelog remove elasticsearch-release 9.2.0 - # When used with 'changelog remove', only the 'products' field is applied. - # The 'output_products', 'repo', 'owner', and 'hide_features' fields are - # bundle-specific and are ignored for removal. - # Bundle file names are derived by convention as '{repo}-{product}-{version}.yaml' from the - # authoring repo and the profile's primary output product; setting an explicit 'output' - # pattern is an error. If no repo can be resolved, the name falls back to '{product}-{version}.yaml'. - profiles: - # Example: Elasticsearch release profile (filter by changelog fields) - # elasticsearch-release: - # # Filter: which input changelogs to include ({version} and {lifecycle} are substituted at runtime) - # products: "elasticsearch {version} {lifecycle}" - # # The bundle's products metadata; also determines the conventional output - # # file name ({repo}-{product}-{version}.yaml from the authoring repo and the first product). - # 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} - # # Optional: control auto-population of release-date for this profile. - # # When true (default), auto-populate release dates. When false, equivalent to --no-release-date. - # # release_dates: true - # 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: - # source: github_release # Fetch PR list from GitHub release instead of filtering input changelogs - # repo: elasticsearch # GitHub repository (required if bundle.repo is not set) - # owner: elastic # GitHub owner (optional; defaults to bundle.owner or "elastic") - # output_products: "elasticsearch {version} {lifecycle}" - - # Example: Serverless release profile (filter by promotion report, PR, or issue list) - # serverless-release: - # output_products: "cloud-serverless {version}" # omit lifecycle - # # output_products: "cloud-serverless {version} {lifecycle}" # derive (ga for ISO dates) - # # output_products: "cloud-serverless {version} preview" # hardcode for exceptional releases - # # Optional: replace bundle.output_directory for this profile (same as --output as a directory). - # # The conventional {repo}-{product}-{version}.yaml name is written in this folder. - # # output_directory: docs/releases/cloud-serverless - # # Optional: GitHub repository for link and file name generation (overrides bundle.repo if set) - # repo: elasticsearch - # owner: elastic - # # Feature IDs to hide when bundling with this profile (accepts string or list) - # hide_features: - # - feature-flag-1 - # - feature-flag-2 +pivot: + # Map pull request labels to standard changelog types. + # At a minimum, feature, bug-fix, and breaking-change must be configured. + types: + feature: "feature" + bug-fix: "bug, fix" + breaking-change: "breaking" - # Example: Multi-product profile (Mode 3 rule context = first product alphabetically, e.g. kibana). - # For security-only rules, add a separate profile with output_products listing only security. - # Bundle names are derived as {repo}-{product}-{version}.yaml from the first output product. - # kibana-security-release: - # output_products: "kibana {version}, security {version}" +rules: + create: + # Labels that mean this pull request needs no changelog entry + exclude: "changelog:skip, chore, dependencies, ci" diff --git a/docs/cli-schema.json b/docs/cli-schema.json index 59b71892f2..df403bb945 100644 --- a/docs/cli-schema.json +++ b/docs/cli-schema.json @@ -4090,7 +4090,7 @@ ], "name": "init", "summary": "Create changelog.yml and the changelog/releases directory structure.", - "notes": "Discovers the docs folder via docset.yml; falls back to creating PATH/docs.\nWhen changelog.yml already exists, updates only the paths specified via or .\nSeeds bundle.owner, bundle.repo, and bundle.link_allow_repos from the git remote origin when available.", + "notes": "Discovers the docs folder via docset.yml; falls back to creating PATH/docs.\nWhen changelog.yml already exists, updates only the paths specified via or .", "usage": "docs-builder changelog init [options]", "examples": [], "parameters": [ @@ -4130,20 +4130,6 @@ } ] }, - { - "role": "flag", - "name": "owner", - "type": "string", - "required": false, - "summary": "GitHub owner for seeding bundle defaults. Overrides the value inferred from git remote origin." - }, - { - "role": "flag", - "name": "repo", - "type": "string", - "required": false, - "summary": "GitHub repository name for seeding bundle defaults. Overrides the value inferred from git remote origin." - }, { "role": "flag", "name": "log-level", diff --git a/docs/cli/changelog/cmd-init.md b/docs/cli/changelog/cmd-init.md index a8db47f0aa..26093d4fd1 100644 --- a/docs/cli/changelog/cmd-init.md +++ b/docs/cli/changelog/cmd-init.md @@ -8,9 +8,9 @@ The command locates the docs folder using the following priority: 2. A `docs` folder without `docset.yml`. 3. If no docs folder exists, creates `{path}/docs`. -The command creates a `changelog.yml` configuration file and `changelog` and `releases` subdirectories in the docs folder. +The command creates a `changelog.yml` configuration file and `changelog` and `releases` subdirectories in the docs folder. The generated `changelog.yml` contains the minimal configuration for the automated release notes path: type mappings for `feature`, `bug-fix`, and `breaking-change`, and a skip-label rule. -When the template is written for the first time, the command seeds `bundle.owner`, `bundle.repo`, and `bundle.link_allow_repos` from your `git` remote `origin` (when it points at `github.com`) and/or from `--owner` and `--repo`. CLI values override values inferred from `git`. If neither source provides enough information, the placeholder lines are removed from the template for manual editing. +When `changelog.yml` already exists, the command updates only the directory paths if `--changelog-dir` or `--bundles-dir` are specified, and leaves all other content unchanged. ## Examples @@ -25,7 +25,4 @@ docs-builder changelog init --path /path/to/my-repo docs-builder changelog init \ --changelog-dir ./my-changelogs \ --bundles-dir ./my-releases - -# Explicitly set GitHub owner and repo (useful in CI without git) -docs-builder changelog init --owner elastic --repo kibana ``` diff --git a/src/Elastic.Documentation.Configuration/ChangelogTemplateSeeder.cs b/src/Elastic.Documentation.Configuration/ChangelogTemplateSeeder.cs deleted file mode 100644 index 1ec54a711b..0000000000 --- a/src/Elastic.Documentation.Configuration/ChangelogTemplateSeeder.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// 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 - -namespace Elastic.Documentation.Configuration; - -/// -/// Applies bundle.owner, bundle.repo, and bundle.link_allow_repos seeding -/// to the changelog template placeholder. Pure string transformation with no I/O. -/// -public static class ChangelogTemplateSeeder -{ - internal const string Placeholder = " # changelog-init-bundle-seed"; - - /// - /// Replaces or removes the # changelog-init-bundle-seed placeholder in template content. - /// CLI values take precedence over git-inferred values. When only repo is known, owner defaults to elastic. - /// - public static string ApplyBundleRepoSeed(string content, string? ownerCli, string? repoCli, string? gitOwner, string? gitRepo) - { - var gitMatched = gitOwner is not null && gitRepo is not null; - - var resolvedRepo = string.IsNullOrWhiteSpace(repoCli) ? gitRepo : repoCli.Trim(); - var resolvedOwner = string.IsNullOrWhiteSpace(ownerCli) ? gitOwner : ownerCli.Trim(); - if (!string.IsNullOrWhiteSpace(resolvedRepo) && string.IsNullOrWhiteSpace(resolvedOwner)) - resolvedOwner = "elastic"; - - var shouldSeed = !string.IsNullOrWhiteSpace(resolvedOwner) - && !string.IsNullOrWhiteSpace(resolvedRepo) - && (!string.IsNullOrWhiteSpace(ownerCli) || !string.IsNullOrWhiteSpace(repoCli) || gitMatched); - - var eol = content.Contains("\r\n", StringComparison.Ordinal) ? "\r\n" : "\n"; - - var block = shouldSeed - ? $" owner: {QuoteForYaml(resolvedOwner!)}{eol} repo: {QuoteForYaml(resolvedRepo!)}{eol} link_allow_repos:{eol} - {QuoteForYaml($"{resolvedOwner}/{resolvedRepo}")}{eol}" - : ""; - - var placeholderWithEol = Placeholder + eol; - if (content.Contains(placeholderWithEol, StringComparison.Ordinal)) - return content.Replace(placeholderWithEol, block, StringComparison.Ordinal); - - return content.Replace(Placeholder, shouldSeed ? block.TrimEnd('\r', '\n') : string.Empty, StringComparison.Ordinal); - } - - internal static string QuoteForYaml(string value) => - value.Contains(':') - || value.Contains(' ') - || value.Contains('#') - || value.Contains('"') - || value.Contains('\\') - || value.Contains('\n') - || value.Contains('\r') - || value.Contains('\t') - ? $"\"{value - .Replace("\\", "\\\\") - .Replace("\"", "\\\"") - .Replace("\r", "\\r") - .Replace("\n", "\\n") - .Replace("\t", "\\t")}\"" - : value; -} diff --git a/src/tooling/docs-builder/Commands/ChangelogCommand.cs b/src/tooling/docs-builder/Commands/ChangelogCommand.cs index 6f7763722a..5f402f1f28 100644 --- a/src/tooling/docs-builder/Commands/ChangelogCommand.cs +++ b/src/tooling/docs-builder/Commands/ChangelogCommand.cs @@ -58,20 +58,15 @@ IEnvironmentVariables environmentVariables /// /// Discovers the docs folder via docset.yml; falls back to creating PATH/docs. /// When changelog.yml already exists, updates only the paths specified via or . - /// Seeds bundle.owner, bundle.repo, and bundle.link_allow_repos from the git remote origin when available. /// /// Repository root. Defaults to cwd. /// Changelog entry directory. Defaults to docs/changelog. /// Bundle output directory. Defaults to docs/releases. - /// GitHub owner for seeding bundle defaults. Overrides the value inferred from git remote origin. - /// GitHub repository name for seeding bundle defaults. Overrides the value inferred from git remote origin. [NoOptionsInjection] public Task Init( [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? path = null, [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? changelogDir = null, - [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? bundlesDir = null, - string? owner = null, - string? repo = null + [ExpandUserProfile, RejectSymbolicLinks] DirectoryInfo? bundlesDir = null ) { var rootPath = path?.FullName ?? Path.GetFullPath("."); @@ -147,8 +142,6 @@ public Task Init( content = content.Replace("output_directory: docs/releases", $"output_directory: {outputValue}"); } - content = ApplyChangelogInitBundleRepoSeed(content, owner, repo, repoRoot); - try { _fileSystem.File.WriteAllBytes(configPath, Encoding.UTF8.GetBytes(content)); @@ -2344,16 +2337,6 @@ private static string GetPathForConfig(string repoPath, string targetPath) return pathForConfig; } - private string ApplyChangelogInitBundleRepoSeed(string content, string? ownerCli, string? repoCli, string repoRoot) - { - string? gitOwner = null; - string? gitRepo = null; - if (GitRemoteConfigurationReader.TryReadOriginUrl(_fileSystem, repoRoot, out var originUrl)) - _ = GitHubRemoteParser.TryParseGitHubComOwnerRepo(originUrl, out gitOwner, out gitRepo); - - return ChangelogTemplateSeeder.ApplyBundleRepoSeed(content, ownerCli, repoCli, gitOwner, gitRepo); - } - private async Task ResolveBundleDescription( string? description, string? descriptionFile, diff --git a/tests/Elastic.Documentation.Configuration.Tests/ChangelogTemplateSeederTests.cs b/tests/Elastic.Documentation.Configuration.Tests/ChangelogTemplateSeederTests.cs deleted file mode 100644 index 5b3ebd6be7..0000000000 --- a/tests/Elastic.Documentation.Configuration.Tests/ChangelogTemplateSeederTests.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// 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 AwesomeAssertions; - -namespace Elastic.Documentation.Configuration.Tests; - -public class ChangelogTemplateSeederTests -{ - private const string Template = "bundle:\n release_dates: true\n # changelog-init-bundle-seed\n # some other comment\n"; - - private const string TemplateWindows = - "bundle:\r\n release_dates: true\r\n # changelog-init-bundle-seed\r\n # some other comment\r\n"; - - [Test] - public void ApplyBundleRepoSeed_GitOwnerAndRepo_SeedsTemplate() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: null, - repoCli: null, - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: elastic\n"); - result.Should().Contain(" repo: kibana\n"); - result.Should().Contain(" - elastic/kibana\n"); - result.Should().NotContain("changelog-init-bundle-seed"); - } - - [Test] - public void ApplyBundleRepoSeed_CliOwnerAndRepo_SeedsTemplate() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: "myorg", - repoCli: "myrepo", - gitOwner: null, - gitRepo: null - ); - - result.Should().Contain(" owner: myorg\n"); - result.Should().Contain(" repo: myrepo\n"); - result.Should().Contain(" - myorg/myrepo\n"); - } - - [Test] - public void ApplyBundleRepoSeed_CliOverridesGit() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: "override-owner", - repoCli: "override-repo", - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: override-owner\n"); - result.Should().Contain(" repo: override-repo\n"); - result.Should().Contain(" - override-owner/override-repo\n"); - } - - [Test] - public void ApplyBundleRepoSeed_CliRepoOnly_OwnerDefaultsToElastic() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: null, - repoCli: "myrepo", - gitOwner: null, - gitRepo: null - ); - - result.Should().Contain(" owner: elastic\n"); - result.Should().Contain(" repo: myrepo\n"); - result.Should().Contain(" - elastic/myrepo\n"); - } - - [Test] - public void ApplyBundleRepoSeed_CliOwnerOnly_NoRepo_RemovesPlaceholder() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed(Template, ownerCli: "myorg", repoCli: null, gitOwner: null, gitRepo: null); - - result.Should().NotContain("changelog-init-bundle-seed"); - result.Should().NotContain(" owner:"); - result.Should().NotContain(" repo:"); - } - - [Test] - public void ApplyBundleRepoSeed_NeitherCliNorGit_RemovesPlaceholder() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed(Template, ownerCli: null, repoCli: null, gitOwner: null, gitRepo: null); - - result.Should().NotContain("changelog-init-bundle-seed"); - result.Should().NotContain(" owner:"); - result.Should().NotContain(" repo:"); - result.Should().Contain(" # some other comment\n"); - } - - [Test] - public void ApplyBundleRepoSeed_WhitespaceCliValues_TreatedAsAbsent() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: " ", - repoCli: " ", - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: elastic\n"); - result.Should().Contain(" repo: kibana\n"); - } - - [Test] - public void ApplyBundleRepoSeed_WindowsLineEndings_PreservesStyle() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - TemplateWindows, - ownerCli: null, - repoCli: null, - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: elastic\r\n"); - result.Should().Contain(" repo: kibana\r\n"); - result.Should().NotContain(" owner: elastic\n repo:"); - } - - [Test] - public void ApplyBundleRepoSeed_MissingPlaceholder_ReturnsContentUnchanged() - { - var content = "bundle:\n release_dates: true\n"; - - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - content, - ownerCli: "elastic", - repoCli: "kibana", - gitOwner: null, - gitRepo: null - ); - - result.Should().Be(content); - } - - [Test] - public void ApplyBundleRepoSeed_ValuesNeedingYamlQuoting_AreQuoted() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: "my org", - repoCli: "my:repo", - gitOwner: null, - gitRepo: null - ); - - result.Should().Contain(" owner: \"my org\"\n"); - result.Should().Contain(" repo: \"my:repo\"\n"); - result.Should().Contain(" - \"my org/my:repo\"\n"); - } - - [Test] - public void ApplyBundleRepoSeed_CliRepoOverridesGitRepo_KeepsGitOwner() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: null, - repoCli: "other-repo", - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: elastic\n"); - result.Should().Contain(" repo: other-repo\n"); - result.Should().Contain(" - elastic/other-repo\n"); - } - - [Test] - public void ApplyBundleRepoSeed_PlaceholderAtEofWithoutNewline_Seeds() - { - var content = "bundle:\n release_dates: true\n # changelog-init-bundle-seed"; - - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - content, - ownerCli: null, - repoCli: null, - gitOwner: "elastic", - gitRepo: "kibana" - ); - - result.Should().Contain(" owner: elastic"); - result.Should().Contain(" repo: kibana"); - result.Should().NotContain("changelog-init-bundle-seed"); - } - - [Test] - public void ApplyBundleRepoSeed_PlaceholderAtEofWithoutNewline_RemovesWhenNoSeed() - { - var content = "bundle:\n release_dates: true\n # changelog-init-bundle-seed"; - - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed(content, ownerCli: null, repoCli: null, gitOwner: null, gitRepo: null); - - result.Should().Be("bundle:\n release_dates: true\n"); - result.Should().NotContain("changelog-init-bundle-seed"); - } - - [Test] - public void ApplyBundleRepoSeed_BackslashInValue_IsEscapedInYaml() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: @"path\org", - repoCli: "repo", - gitOwner: null, - gitRepo: null - ); - - result.Should().Contain(@" owner: ""path\\org"""); - result.Should().Contain(" repo: repo\n"); - } - - [Test] - public void ApplyBundleRepoSeed_ControlCharsInValue_AreEscapedInYaml() - { - var result = ChangelogTemplateSeeder.ApplyBundleRepoSeed( - Template, - ownerCli: "org\tname", - repoCli: "repo", - gitOwner: null, - gitRepo: null - ); - - result.Should().Contain(@" owner: ""org\tname"""); - } -}