diff --git a/changelog/fragments/1759172049-refine-markdown-output.yaml b/changelog/fragments/1759172049-refine-markdown-output.yaml new file mode 100644 index 0000000..7784454 --- /dev/null +++ b/changelog/fragments/1759172049-refine-markdown-output.yaml @@ -0,0 +1,46 @@ +# REQUIRED +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: enhancement + +# REQUIRED for all kinds +# Change summary; a 80ish characters long description of the change. +summary: Add logic that will add `include`s to `_snippets` files in the correct order if the `rendered_changelog_destination` in the `config.changelog.yaml` is using `release-notes/_snippets`. + +# REQUIRED for breaking-change, deprecation, known-issue +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +description: When using the `release-notes/_snippets` model, there are multiple levels of nesting to make it as easy as possible to insert new patch version sections in the correct order. The updates in this PR will (1) eliminate any manual steps in the resulting release notes PR outside of copy edits, and (2) reduce the chance of conflicts when forward porting to later minors and `main`. + +# REQUIRED for breaking-change, deprecation, known-issue +# impact: + +# REQUIRED for breaking-change, deprecation, known-issue +# action: + +# REQUIRED for all kinds +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: + +# AUTOMATED +# OPTIONAL to manually add other PR URLs +# PR URL: A link the PR that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +pr: + - https://github.com/elastic/elastic-agent-changelog-tool/pull/220 + +# AUTOMATED +# OPTIONAL to manually add other issue URLs +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +# issue: https://github.com/owner/repo/1234 diff --git a/docs/getting-started.md b/docs/getting-started.md index 476d040..673fb5a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -119,7 +119,7 @@ $ elastic-agent-changelog-tool render --version 0.1.0 --file_type markdown This will create three files: * `./changelog/0.1.0/index.md` -* `./changelog/0.1.0/breaking.md` +* `./changelog/0.1.0/breaking-changes.md` * `./changelog/0.1.0/deprecations.md` ### AsciiDoc diff --git a/docs/usage.md b/docs/usage.md index a76b15a..c0352d0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -99,7 +99,7 @@ The side effect is that the changelog will include all entries from latest stabl Depending on the specified `file_type`, this will generate the following files: * `markdown`: * Release notes: `./changelog//index.md` - * Breaking changes: `./changelog//breaking.md` + * Breaking changes: `./changelog//breaking-changes.md` * Deprecations: `./changelog//deprecations.md` * `asciidoc`: `changelog/.asciidoc` 3. Use the rendered changelog. @@ -133,19 +133,26 @@ These steps require [GitHub Authentication](./github-authentication.md). ``` $ elastic-agent-changelog-tool cleanup ``` -1. Commit the previous changes (consolidated changelod and removed files) +1. Commit the previous changes (consolidated changelog and removed files) 1. From the root folder of the repository run: ``` $ elastic-agent-changelog-tool render --version x.y.z --file_type ``` - Depending on the specified `file_type`, this will generate the following files: - * `markdown`: - * Release notes: `./changelog//index.md` - * Breaking changes: `./changelog//breaking.md` - * Deprecations: `./changelog//deprecations.md` - * `asciidoc`: `changelog/.asciidoc` -1. Integrate the generated fragment into the changelog. If the changelog is stored in the same repository, commit the changes in this same branch. + >IMPORTANT: Use `file_type` `markdown` for 9.x versions and `asciidoc` for 8.x versions. + + The files that are generated depend on the specified `file_type`. The destination directory depends on the `rendered_changelog_destination` defined in the the repo's `config.changelog.yaml`. If no `rendered_changelog_destination` is specified, it will be added to the `changelog` directory. + + * `markdown`: These files will be created: + * Release notes: `//index.md` + * Breaking changes: `//breaking-changes.md` + * Deprecations: `//deprecations.md` + + If the `rendered_changelog_destination` is set to `release-notes/_snippets`, the related `_snippets` files will automatically be updated. + + * `asciidoc`: There will be one file created, `/.asciidoc`, and you will need to integrate the generated content into the changelog. + +1. If the changelog is stored in the same repository, commit the changes in this same branch. 1. Create a PR with the changes to the `x.y` branch. diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index c56a5b8..bfa7d43 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -186,7 +186,7 @@ func (r Renderer) Render() error { if template == "markdown-index" { return path.Join(r.dest, r.changelog.Version, "index.md") } else if template == "markdown-breaking" { - return path.Join(r.dest, r.changelog.Version, "breaking.md") + return path.Join(r.dest, r.changelog.Version, "breaking-changes.md") } else if template == "markdown-deprecations" { return path.Join(r.dest, r.changelog.Version, "deprecations.md") } else { @@ -208,19 +208,35 @@ func (r Renderer) Template() ([]byte, error) { var err error if embeddedFileName, ok := assets.GetEmbeddedTemplates()[r.templ]; ok { - if r.templ == "markdown-index" { - data, err = assets.MarkdownIndexTemplate.ReadFile(embeddedFileName) - } else if r.templ == "markdown-breaking" { - data, err = assets.MarkdownBreakingTemplate.ReadFile(embeddedFileName) - } else if r.templ == "markdown-deprecations" { - data, err = assets.MarkdownDeprecationsTemplate.ReadFile(embeddedFileName) - } else if r.templ == "asciidoc-embedded" { - data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName) + var readFunc func(string) ([]byte, error) + switch r.templ { + case "markdown-index": + readFunc = assets.MarkdownIndexTemplate.ReadFile + case "markdown-breaking": + readFunc = assets.MarkdownBreakingTemplate.ReadFile + case "markdown-deprecations": + readFunc = assets.MarkdownDeprecationsTemplate.ReadFile + case "asciidoc-embedded": + readFunc = assets.AsciidocTemplate.ReadFile } - if err != nil { - return []byte{}, fmt.Errorf("cannot read embedded template: %s %w", embeddedFileName, err) + if readFunc != nil { + data, err = readFunc(embeddedFileName) + if err != nil { + return nil, fmt.Errorf("cannot read embedded template: %s %w", embeddedFileName, err) + } + // If using the snippet/include model, update the includes + if strings.Contains(r.dest, "release-notes/_snippets") { + switch r.templ { + case "markdown-index": + addInclude(r.fs, r.changelog.Version, r.dest, "index") + case "markdown-breaking": + addInclude(r.fs, r.changelog.Version, r.dest, "breaking-changes") + case "markdown-deprecations": + addInclude(r.fs, r.changelog.Version, r.dest, "deprecations") + } + } + return data, nil } - return data, nil } data, err = afero.ReadFile(r.fs, r.templ) @@ -321,3 +337,47 @@ func buildTitleByComponents(entries []Entry) string { return match } } + +func addInclude(fs afero.Fs, version, dest, templ string) { + // Extract minor version (e.g., "8.12" from "8.12.1") + minorVersion := regexp.MustCompile(`^\d+\.\d+`).FindString(version) + if minorVersion == "" { + fmt.Printf("Could not get minor version from: %v\n", version) + return + } + + // Extract include directory (e.g., "/release-notes/...") + includeDir := regexp.MustCompile(`/release-notes/.+$`).FindString(dest) + if includeDir == "" { + fmt.Printf("Could not derive include directory from: %v\n", dest) + return + } + + minorFilePath := fmt.Sprintf("%s/%s/%s.md", dest, templ, minorVersion) + templateTypeFilePath := fmt.Sprintf("%s/%s.md", dest, templ) + + // Read or create the minor file + minorFileContent, err := afero.ReadFile(fs, minorFilePath) + if err != nil { + // Create the file + if err := afero.WriteFile(fs, minorFilePath, nil, changelogFilePerm); err == nil { + fmt.Printf("Created new empty snippet file: %s\n", minorFilePath) + } + // Prepend new minor version include to the template type file (e.g. "breaking-changes") + if templateTypeFileContent, err := afero.ReadFile(fs, templateTypeFilePath); err == nil { + newMinorInclude := fmt.Sprintf(":::{include} %s/%s/%s.md\n:::", includeDir, templ, minorVersion) + newContent := fmt.Sprintf("%s\n\n%s", newMinorInclude, templateTypeFileContent) + if err := afero.WriteFile(fs, templateTypeFilePath, []byte(newContent), changelogFilePerm); err == nil { + fmt.Printf("Updated snippet file: %s\n", templateTypeFilePath) + } + } + minorFileContent = nil // ensure it's empty for next step + } + + // Prepend new patch version include to the minor file + newPatchInclude := fmt.Sprintf(":::{include} %s/%s/%s.md\n:::", includeDir, version, templ) + newContent := fmt.Sprintf("%s\n\n%s", newPatchInclude, minorFileContent) + if err := afero.WriteFile(fs, minorFilePath, []byte(newContent), changelogFilePerm); err == nil { + fmt.Printf("Updated snippet file: %s\n", minorFilePath) + } +}