From 6c380a7a12a2030c55cce65f4a357a92872ee2f8 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Fri, 15 Aug 2025 15:03:11 -0500 Subject: [PATCH 01/11] update templates --- cmd/render.go | 3 +- config.yaml | 12 +- internal/assets/assets.go | 18 ++- internal/assets/markdown-breaking-template.md | 13 +++ .../assets/markdown-deprecations-template.md | 15 +++ internal/assets/markdown-index-template.md | 19 ++++ .../assets/markdown-known-issues-template.md | 14 +++ internal/changelog/entry.go | 6 + internal/changelog/fragment/template.yaml | 23 +++- internal/changelog/renderer.go | 106 ++++++++++++------ internal/settings/settings.go | 2 +- 11 files changed, 187 insertions(+), 44 deletions(-) create mode 100644 internal/assets/markdown-breaking-template.md create mode 100644 internal/assets/markdown-deprecations-template.md create mode 100644 internal/assets/markdown-index-template.md create mode 100644 internal/assets/markdown-known-issues-template.md diff --git a/cmd/render.go b/cmd/render.go index 85490fa..0f917f1 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -35,6 +35,7 @@ func RenderCmd(fs afero.Fs) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { dest := viper.GetString("changelog_destination") renderedDest := viper.GetString("rendered_changelog_destination") + repo := viper.GetString("repo") version, err := cmd.Flags().GetString("version") if err != nil { @@ -51,7 +52,7 @@ func RenderCmd(fs afero.Fs) *cobra.Command { return fmt.Errorf("error loading changelog from file: %w", err) } - r := changelog.NewRenderer(fs, c, renderedDest, template) + r := changelog.NewRenderer(fs, c, renderedDest, template, repo) if err := r.Render(); err != nil { return fmt.Errorf("cannot build asciidoc file: %w", err) diff --git a/config.yaml b/config.yaml index c1edfdc..2cee116 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,12 @@ owner: elastic repo: elastic-agent-changelog-tool -template: asciidoc-embedded -components: [elastic-agent-changelog-tool] +template: + - asciidoc-embedded + - markdown-breaking-template + - markdown-deprecations-template + - markdown-index-template + - markdown-known-issues-template +components: + - beats + - elastic-agent + - fleet-server diff --git a/internal/assets/assets.go b/internal/assets/assets.go index 955ff42..bcd8b54 100644 --- a/internal/assets/assets.go +++ b/internal/assets/assets.go @@ -14,13 +14,29 @@ import ( // These strings can be used in the config template field or renderer template flag func GetEmbeddedTemplates() embeddedTemplates { return map[string]string{ - "asciidoc-embedded": "asciidoc-template.asciidoc", + "asciidoc-embedded": "asciidoc-template.asciidoc", + "markdown-index": "markdown-index-template.md", + "markdown-breaking": "markdown-breaking-template.md", + "markdown-deprecations": "markdown-deprecations-template.md", + "markdown-known-issues": "markdown-known-issues-template.md", } } //go:embed asciidoc-template.asciidoc var AsciidocTemplate embed.FS +//go:embed markdown-index-template.md +var MarkdownIndexTemplate embed.FS + +//go:embed markdown-breaking-template.md +var MarkdownBreakingTemplate embed.FS + +//go:embed markdown-deprecations-template.md +var MarkdownDeprecationsTemplate embed.FS + +//go:embed markdown-known-issues-template.md +var MarkdownKnownIssuesTemplate embed.FS + type embeddedTemplates map[string]string func (t embeddedTemplates) String() string { diff --git a/internal/assets/markdown-breaking-template.md b/internal/assets/markdown-breaking-template.md new file mode 100644 index 0000000..1c3c302 --- /dev/null +++ b/internal/assets/markdown-breaking-template.md @@ -0,0 +1,13 @@ +## {{.Version}} [{{.Repo}}-{{.Version}}-breaking-changes] + +{{ if .BreakingChange -}}{{ range $k, $v := .BreakingChange }}{{ range $item := $v }} +::::{dropdown} {{ $item.Summary | beautify }} +{{ if $item.Description }}{{ $item.Description }}{{ end }} + +For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}. + +{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }} + +{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} +:::: +{{- end }}{{- end }}{{- end }} diff --git a/internal/assets/markdown-deprecations-template.md b/internal/assets/markdown-deprecations-template.md new file mode 100644 index 0000000..73186e3 --- /dev/null +++ b/internal/assets/markdown-deprecations-template.md @@ -0,0 +1,15 @@ +## {{.Version}} [{{.Repo}}-{{.Version}}-deprecations] +{{ if .Deprecation -}}{{ range $k, $v := .Deprecation }}{{ range $item := $v }} + +::::{dropdown} {{ $item.Summary | beautify }} +{{ if $item.Description }}{{ $item.Description }}{{ end }} + +For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}. + +{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }} + +{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} +:::: +{{- end }}{{- end }} +{{ else }}_No deprecations._ +{{- end }} diff --git a/internal/assets/markdown-index-template.md b/internal/assets/markdown-index-template.md new file mode 100644 index 0000000..ca4cb5b --- /dev/null +++ b/internal/assets/markdown-index-template.md @@ -0,0 +1,19 @@ +## {{.Version}} [{{.Repo}}-release-notes-{{.Version}}] + +{{ if or .Feature .Enhancement }} +### Features and enhancements [{{.Repo}}-{{.Version}}-features-enhancements] +{{ if .Feature }}{{ range $k, $v := .Feature }}{{ range $item := $v }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +{{- end }}{{- end }}{{- end }}{{ if .Enhancement }}{{ range $k, $v := .Enhancement }}{{ range $item := $v }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +{{- end }}{{- end }}{{- end }} +{{- end }} + +{{ if or .Security .BugFix }} +### Fixes [{{.Repo}}-{{.Version}}-fixes] +{{ if .Security }}{{ range $k, $v := .Security }}{{ range $item := $v }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +{{- end }}{{- end }}{{- end }}{{ if .BugFix }}{{ range $k, $v := .BugFix }}{{ range $item := $v }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +{{- end }}{{- end }}{{- end }} +{{- end }} diff --git a/internal/assets/markdown-known-issues-template.md b/internal/assets/markdown-known-issues-template.md new file mode 100644 index 0000000..8dc3073 --- /dev/null +++ b/internal/assets/markdown-known-issues-template.md @@ -0,0 +1,14 @@ +{{ if .KnownIssue -}}{{ range $k, $v := .KnownIssue }}{{ range $item := $v }} + +::::{dropdown} {{ $item.Summary | beautify }} +**Applies to**: {{.Version}} + +{{ if $item.Description }}{{ $item.Description }}{{ end }} + +For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}. + +{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }} + +{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} +:::: +{{- end }}{{- end }}{{- end }} \ No newline at end of file diff --git a/internal/changelog/entry.go b/internal/changelog/entry.go index 95fde7a..aceefab 100644 --- a/internal/changelog/entry.go +++ b/internal/changelog/entry.go @@ -20,6 +20,9 @@ type Entry struct { Component string `yaml:"component"` LinkedPR []string `yaml:"pr"` LinkedIssue []string `yaml:"issue"` + Impact string `yaml:"impact"` + Action string `yaml:"action"` + Workaround string `yaml:"workaround"` Timestamp int64 `yaml:"timestamp"` File FragmentFileInfo `yaml:"file"` @@ -35,6 +38,9 @@ func EntryFromFragment(f fragment.File) Entry { Component: f.Fragment.Component, LinkedPR: []string{}, LinkedIssue: []string{}, + Impact: f.Fragment.Impact, + Action: f.Fragment.Action, + Workaround: f.Fragment.Workaround, Timestamp: f.Timestamp, File: FragmentFileInfo{ Name: f.Name, diff --git a/internal/changelog/fragment/template.yaml b/internal/changelog/fragment/template.yaml index 05b5332..85f5565 100644 --- a/internal/changelog/fragment/template.yaml +++ b/internal/changelog/fragment/template.yaml @@ -1,3 +1,4 @@ +# REQUIRED # Kind can be one of: # - breaking-change: a change to previously-documented behavior # - deprecation: functionality that is being removed in a later release @@ -10,23 +11,35 @@ # - other: does not fit into any of the other categories kind: feature +# REQUIRED for all kinds # Change summary; a 80ish characters long description of the change. summary: {{.Summary}} +# 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. -# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. -#description: +# description: +# 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: -# PR URL; optional; the PR number that added the changeset. +# 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/owner/repo/1234 +# pr: https://github.com/owner/repo/1234 +# 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 +# issue: https://github.com/owner/repo/1234 diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index 48ea049..ff2c0c6 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -9,14 +9,14 @@ import ( "fmt" "html/template" "log" + "os" "path" + "regexp" "strings" "github.com/elastic/elastic-agent-changelog-tool/internal/assets" "github.com/spf13/afero" "github.com/spf13/viper" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) type Renderer struct { @@ -25,14 +25,16 @@ type Renderer struct { // dest is the destination location where the changelog is written to dest string templ string + repo string } -func NewRenderer(fs afero.Fs, c Changelog, dest string, templ string) *Renderer { +func NewRenderer(fs afero.Fs, c Changelog, dest string, templ string, repo string) *Renderer { return &Renderer{ changelog: c, fs: fs, dest: dest, templ: templ, + repo: repo, } } @@ -47,6 +49,7 @@ func (r Renderer) Render() error { type TemplateData struct { Component string Version string + Repo string Changelog Changelog Kinds map[Kind]bool @@ -62,15 +65,20 @@ func (r Renderer) Render() error { } td := TemplateData{ - buildTitleByComponents(r.changelog.Entries), r.changelog.Version, r.changelog, + buildTitleByComponents(r.changelog.Entries), r.changelog.Version, r.repo, r.changelog, collectKinds(r.changelog.Entries), - collectByKindMap(r.changelog.Entries, BreakingChange), - collectByKindMap(r.changelog.Entries, Deprecation), - collectByKindMap(r.changelog.Entries, BugFix), + // In Markdown, this goes to release notes collectByKindMap(r.changelog.Entries, Enhancement), collectByKindMap(r.changelog.Entries, Feature), - collectByKindMap(r.changelog.Entries, KnownIssue), collectByKindMap(r.changelog.Entries, Security), + collectByKindMap(r.changelog.Entries, BugFix), + // In Markdown, this goes to breaking changes + collectByKindMap(r.changelog.Entries, BreakingChange), + // In Markdown, this goes to deprecations + collectByKindMap(r.changelog.Entries, Deprecation), + // In Markdown, this goes to known issues + collectByKindMap(r.changelog.Entries, KnownIssue), + // In Markdown... TBD collectByKindMap(r.changelog.Entries, Upgrade), collectByKindMap(r.changelog.Entries, Other), } @@ -81,42 +89,35 @@ func (r Renderer) Render() error { return strings.Join(ids, "-") }, // nolint:staticcheck // ignoring for now, supports for multiple component is not implemented - "linkPRSource": func(component string, ids []string) string { + "linkPRSource": func(repo string, ids []string) string { res := make([]string, len(ids)) - for i, id := range ids { - res[i] = fmt.Sprintf("{%s-pull}%v[#%v]", component, id, id) + res[i] = getLink(id, r.repo, "pull", r.templ) } - return strings.Join(res, " ") }, // nolint:staticcheck // ignoring for now, supports for multiple component is not implemented - "linkIssueSource": func(component string, ids []string) string { + "linkIssueSource": func(repo string, ids []string) string { res := make([]string, len(ids)) - for i, id := range ids { - res[i] = fmt.Sprintf("{%s-issue}%v[#%v]", component, id, id) + res[i] = getLink(id, r.repo, "issues", r.templ) } - return strings.Join(res, " ") }, // Capitalize sentence and ensure ends with . - "beautify": func(s1 string) string { - s2 := strings.Builder{} - s2.WriteString(cases.Title(language.English).String(s1)) - if !strings.HasSuffix(s1, ".") { - s2.WriteString(".") + "beautify": func(s string) string { + if s == "" { + return "" } - return s2.String() + s = strings.ToUpper(string(s[0])) + s[1:] + if !strings.HasSuffix(s, ".") { + s += "." + } + return s }, // Ensure components have section styling "header2": func(s1 string) string { - s2 := strings.Builder{} - s2.WriteString(s1) - if !strings.HasSuffix(s1, "::") && s1 != "" { - s2.WriteString("::") - } - return s2.String() + return fmt.Sprintf("**%s**", s1) }, }). Parse(string(tpl)) @@ -131,10 +132,23 @@ func (r Renderer) Render() error { panic(err) } - outFile := path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version)) - log.Printf("saving changelog in %s\n", outFile) - - return afero.WriteFile(r.fs, outFile, data.Bytes(), changelogFilePerm) + outFile := func(template string) string { + 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") + } else if template == "markdown-deprecations" { + return path.Join(r.dest, r.changelog.Version, "deprecations.md") + } else if template == "markdown-known-issues" { + return path.Join(r.dest, r.changelog.Version, "known-issues.md") + } else { + return path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version)) + } + } + if r.templ != "asciidoc-embedded" { + os.MkdirAll(path.Join(r.dest, r.changelog.Version), os.ModePerm) + } + return afero.WriteFile(r.fs, outFile(r.templ), data.Bytes(), changelogFilePerm) } func (r Renderer) Template() ([]byte, error) { @@ -142,11 +156,20 @@ func (r Renderer) Template() ([]byte, error) { var err error if embeddedFileName, ok := assets.GetEmbeddedTemplates()[r.templ]; ok { - data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName) + 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 == "markdown-known-issues" { + data, err = assets.MarkdownKnownIssuesTemplate.ReadFile(embeddedFileName) + } else if r.templ == "asciidoc-embedded" { + data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName) + } if err != nil { return []byte{}, fmt.Errorf("cannot read embedded template: %s %w", embeddedFileName, err) } - return data, nil } @@ -158,6 +181,21 @@ func (r Renderer) Template() ([]byte, error) { return data, nil } +func getLink(id string, repo string, ghType string, templ string) string { + re := regexp.MustCompile(`\d+$`) + number := re.FindString(id) + if id == number { + id = fmt.Sprintf("https://github.com/elastic/%s/%s/%s", repo, ghType, id) + } + if templ == "asciidoc-embedded" { + // Format as AsciiDoc links + return fmt.Sprintf("%s[#%s]", id, number) + } else { + // Format as Markdown links + return fmt.Sprintf("[#%s](%s)", number, id) + } +} + func collectKinds(items []Entry) map[Kind]bool { // NOTE: collect kinds in a set-like map to avoid duplicates kinds := map[Kind]bool{} diff --git a/internal/settings/settings.go b/internal/settings/settings.go index be6b5e6..5f4ee5a 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -25,7 +25,7 @@ func Init() { setConstants() viper.AddConfigPath(viper.GetString("config_file")) - viper.SetConfigName("config") + viper.SetConfigName("config.changelog.yaml") viper.SetConfigType("yaml") // TODO: better error handling (skip missing file error) From 450e447cbeb7564b699e52357e34a18aa8cb025d Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Fri, 15 Aug 2025 18:44:20 -0500 Subject: [PATCH 02/11] refine formatting --- internal/assets/markdown-breaking-template.md | 5 +-- .../assets/markdown-deprecations-template.md | 3 +- internal/assets/markdown-index-template.md | 22 +++++++++---- internal/changelog/fragment/fragment.go | 3 ++ internal/changelog/renderer.go | 31 +++++++++++++------ internal/settings/settings.go | 2 +- 6 files changed, 47 insertions(+), 19 deletions(-) diff --git a/internal/assets/markdown-breaking-template.md b/internal/assets/markdown-breaking-template.md index 1c3c302..5a2db49 100644 --- a/internal/assets/markdown-breaking-template.md +++ b/internal/assets/markdown-breaking-template.md @@ -1,5 +1,4 @@ ## {{.Version}} [{{.Repo}}-{{.Version}}-breaking-changes] - {{ if .BreakingChange -}}{{ range $k, $v := .BreakingChange }}{{ range $item := $v }} ::::{dropdown} {{ $item.Summary | beautify }} {{ if $item.Description }}{{ $item.Description }}{{ end }} @@ -10,4 +9,6 @@ For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ {{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} :::: -{{- end }}{{- end }}{{- end }} +{{- end }}{{- end }}{{ else }} +_No breaking changes._ +{{- end }} diff --git a/internal/assets/markdown-deprecations-template.md b/internal/assets/markdown-deprecations-template.md index 73186e3..cf5d066 100644 --- a/internal/assets/markdown-deprecations-template.md +++ b/internal/assets/markdown-deprecations-template.md @@ -11,5 +11,6 @@ For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ {{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} :::: {{- end }}{{- end }} -{{ else }}_No deprecations._ +{{ else }} +_No deprecations._ {{- end }} diff --git a/internal/assets/markdown-index-template.md b/internal/assets/markdown-index-template.md index ca4cb5b..c5999f1 100644 --- a/internal/assets/markdown-index-template.md +++ b/internal/assets/markdown-index-template.md @@ -1,19 +1,29 @@ ## {{.Version}} [{{.Repo}}-release-notes-{{.Version}}] - +{{ if or .Feature .Enhancement .Security .BugFix }} {{ if or .Feature .Enhancement }} ### Features and enhancements [{{.Repo}}-{{.Version}}-features-enhancements] {{ if .Feature }}{{ range $k, $v := .Feature }}{{ range $item := $v }} -* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }} +{{ $item.Description | indent }} +{{- end }} {{- end }}{{- end }}{{- end }}{{ if .Enhancement }}{{ range $k, $v := .Enhancement }}{{ range $item := $v }} -* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }} +{{ $item.Description | indent }} +{{- end }} {{- end }}{{- end }}{{- end }} {{- end }} {{ if or .Security .BugFix }} ### Fixes [{{.Repo}}-{{.Version}}-fixes] {{ if .Security }}{{ range $k, $v := .Security }}{{ range $item := $v }} -* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }} +{{ $item.Description | indent }} +{{- end }} {{- end }}{{- end }}{{- end }}{{ if .BugFix }}{{ range $k, $v := .BugFix }}{{ range $item := $v }} -* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }} -{{- end }}{{- end }}{{- end }} +* {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }} +{{ $item.Description | indent }} +{{- end }} +{{- end }}{{- end }}{{- end }}{{- end }} +{{ else }} +_No new features, enhancements, or fixes._ {{- end }} diff --git a/internal/changelog/fragment/fragment.go b/internal/changelog/fragment/fragment.go index 6e717f7..e0e7890 100644 --- a/internal/changelog/fragment/fragment.go +++ b/internal/changelog/fragment/fragment.go @@ -11,4 +11,7 @@ type Fragment struct { Component string `yaml:"component"` Pr string `yaml:"pr"` Issue string `yaml:"issue"` + Impact string `yaml:"impact"` + Action string `yaml:"action"` + Workaround string `yaml:"workaround"` } diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index ff2c0c6..0c051c4 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -53,19 +53,27 @@ func (r Renderer) Render() error { Changelog Changelog Kinds map[Kind]bool + // In Markdown, this goes to release notes + Enhancement map[string][]Entry + Feature map[string][]Entry + Security map[string][]Entry + BugFix map[string][]Entry + // In Markdown, this goes to breaking changes BreakingChange map[string][]Entry - Deprecation map[string][]Entry - BugFix map[string][]Entry - Enhancement map[string][]Entry - Feature map[string][]Entry - KnownIssue map[string][]Entry - Security map[string][]Entry - Upgrade map[string][]Entry - Other map[string][]Entry + // In Markdown, this goes to deprecations + Deprecation map[string][]Entry + // In Markdown, this goes to known issues + KnownIssue map[string][]Entry + // In Markdown... TBD + Upgrade map[string][]Entry + Other map[string][]Entry } td := TemplateData{ - buildTitleByComponents(r.changelog.Entries), r.changelog.Version, r.repo, r.changelog, + buildTitleByComponents(r.changelog.Entries), + r.changelog.Version, + r.repo, + r.changelog, collectKinds(r.changelog.Entries), // In Markdown, this goes to release notes collectByKindMap(r.changelog.Entries, Enhancement), @@ -115,6 +123,11 @@ func (r Renderer) Render() error { } return s }, + // Indent lines + "indent": func(s string) string { + re := regexp.MustCompile(`\n|\r|^`) + return re.ReplaceAllString(s, "\n ") + }, // Ensure components have section styling "header2": func(s1 string) string { return fmt.Sprintf("**%s**", s1) diff --git a/internal/settings/settings.go b/internal/settings/settings.go index 5f4ee5a..e7aa5a1 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -59,7 +59,7 @@ func setDefaults() { viper.GetString("fragment_path"))) viper.SetDefault("changelog_destination", "changelog") - viper.SetDefault("rendered_changelog_destination", "changelog") + // viper.SetDefault("rendered_changelog_destination", "changelog") viper.SetDefault("template", "asciidoc-embedded") } From 3286c50369c85b44e32e4fba1edb5c7cfe20cea6 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Sun, 17 Aug 2025 20:57:15 -0500 Subject: [PATCH 03/11] add other links to release notes page --- internal/assets/markdown-index-template.md | 5 +-- internal/changelog/renderer.go | 41 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/internal/assets/markdown-index-template.md b/internal/assets/markdown-index-template.md index c5999f1..3ea38be 100644 --- a/internal/assets/markdown-index-template.md +++ b/internal/assets/markdown-index-template.md @@ -1,6 +1,7 @@ ## {{.Version}} [{{.Repo}}-release-notes-{{.Version}}] -{{ if or .Feature .Enhancement .Security .BugFix }} -{{ if or .Feature .Enhancement }} +{{ if or .KnownIssue .BreakingChange .Deprecation }} +{{ other_links }}{{- end }} +{{ if or .Feature .Enhancement .Security .BugFix }}{{ if or .Feature .Enhancement }} ### Features and enhancements [{{.Repo}}-{{.Version}}-features-enhancements] {{ if .Feature }}{{ range $k, $v := .Feature }}{{ range $item := $v }} * {{ $item.Summary | beautify }} {{ linkPRSource $item.Component $item.LinkedPR }} {{ linkIssueSource $item.Component $item.LinkedIssue }}{{ if $item.Description }} diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index 0c051c4..15dccea 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -128,6 +128,47 @@ func (r Renderer) Render() error { re := regexp.MustCompile(`\n|\r|^`) return re.ReplaceAllString(s, "\n ") }, + "other_links": func() string { + var links []string + if len(td.KnownIssue) > 0 { + links = append( + links, + fmt.Sprintf( + "[Known issues](/release-notes/known-issues.md#%s-%s-known-issues)", + r.repo, + r.changelog.Version, + ), + ) + } + if len(td.BreakingChange) > 0 { + links = append( + links, + fmt.Sprintf( + "[Breaking changes](/release-notes/breaking-changes.md#%s-%s-breaking-changes)", + r.repo, + r.changelog.Version, + ), + ) + } + if len(td.Deprecation) > 0 { + links = append( + links, + fmt.Sprintf( + "[Deprecations](/release-notes/deprecations.md#%s-%s-deprecations)", + r.repo, + r.changelog.Version, + ), + ) + } + if len(links) > 0 { + return fmt.Sprintf( + "_This release also includes: %s._", + strings.Join(links, " and"), + ) + } else { + return "" + } + }, // Ensure components have section styling "header2": func(s1 string) string { return fmt.Sprintf("**%s**", s1) From 3071c62f0d7d28bff80b8c950e946adb1e5befce Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Mon, 18 Aug 2025 10:39:37 -0500 Subject: [PATCH 04/11] package all markdown templates in one command --- cmd/render.go | 22 ++++++++++++++--- docs/configuration.md | 18 ++++++++++---- docs/getting-started.md | 45 ++++++++++++++++++++++++++++------ internal/changelog/renderer.go | 8 ++---- internal/settings/settings.go | 2 +- 5 files changed, 71 insertions(+), 24 deletions(-) diff --git a/cmd/render.go b/cmd/render.go index 0f917f1..2894a78 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -52,10 +52,24 @@ func RenderCmd(fs afero.Fs) *cobra.Command { return fmt.Errorf("error loading changelog from file: %w", err) } - r := changelog.NewRenderer(fs, c, renderedDest, template, repo) - - if err := r.Render(); err != nil { - return fmt.Errorf("cannot build asciidoc file: %w", err) + if template == "asciidoc-embedded" { + r := changelog.NewRenderer(fs, c, renderedDest, template, repo) + if err := r.Render(); err != nil { + return fmt.Errorf("cannot build asciidoc file: %w", err) + } + } else if template == "markdown" { + r_index := changelog.NewRenderer(fs, c, renderedDest, "markdown-index", repo) + if err := r_index.Render(); err != nil { + return fmt.Errorf("cannot build asciidoc file: %w", err) + } + r_breaking := changelog.NewRenderer(fs, c, renderedDest, "markdown-breaking", repo) + if err := r_breaking.Render(); err != nil { + return fmt.Errorf("cannot build asciidoc file: %w", err) + } + r_deprecations := changelog.NewRenderer(fs, c, renderedDest, "markdown-deprecations", repo) + if err := r_deprecations.Render(); err != nil { + return fmt.Errorf("cannot build asciidoc file: %w", err) + } } return nil diff --git a/docs/configuration.md b/docs/configuration.md index 15a099e..f108bf7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,21 +1,29 @@ # Configuration options -`elastic-agent-changelog-tool` has configuration options available to change it's behaviour. +`elastic-agent-changelog-tool` has configuration options available to change its behaviour. -All settings are managed via the [`settings`][settings] package, using [`spf13/viper`][viper]. +All settings are managed via the [`settings`][settings] package, using [`spf13/viper`][viper]. Configurations are bound to environment variables with same name and `ELASTIC_AGENT_CHANGELOG` prefix using [`viper.BindEnv`][bindenv]. This CLI supports and adhere to cross platform XDG Standard provided by [`OpenPeeDeeP/xdg`][xdg]. -|Settings key|Default value|Note| +| Settings | Default value | Description | |---|---|---| -|`fragment_location`|`$GIT_REPO_ROOT/changelog/fragments`|The location of changelog fragments used by the CLI. By default `fragment_root` + `fragment_path`.| +|`fragment_location`|`$GIT_REPO_ROOT/changelog/fragments`|The location of changelog fragments used by the CLI. By default `fragment_root` + `fragment_path`.| |`fragment_path`|`changelog/fragments`|The path in `fragment_root` where to locate changelog fragments.| |`fragment_root`|`$GIT_REPO_ROOT`|The root folder for `fragment_location`.| ## Configuration file -Not supported yet. +Add a `config.changelog.yaml` file to the repo where you're generating release notes. + +When generating Markdown files, at a minimum you should set the following settings: + +| Setting | Default value | Description | +|---|---|---| +| `owner` (required) | `elastic` | The owner of the GitHub repo. | +| `repo` (required) | ‒ | The name of the GitHub repo. | +| `rendered_changelog_destination` | `changelog` | The directory where you want to put the generated files.

When generating Markdown files, this should probably be `docs/release-notes/_snippets`. | ## Supported Environment Variables diff --git a/docs/getting-started.md b/docs/getting-started.md index da0039f..3dc3660 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -25,6 +25,7 @@ $ elastic-agent-changelog-tool new "my test fragment" This will create `./changelog/fragments/-my-test-fragment.yaml` with this content: ```yaml +# REQUIRED # Kind can be one of: # - breaking-change: a change to previously-documented behavior # - deprecation: functionality that is being removed in a later release @@ -37,25 +38,39 @@ This will create `./changelog/fragments/-my-test-fragment.yaml` with # - other: does not fit into any of the other categories kind: feature +# REQUIRED for all kinds # Change summary; a 80ish characters long description of the change. -summary: +summary: {{.Summary}} +# 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: +# description: +# 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: -# PR URL; optional; the PR number that added the changeset. +# 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/owner/repo/1234 +# pr: https://github.com/owner/repo/1234 +# 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 +# issue: https://github.com/owner/repo/1234 + ``` Ensure `kind` is correct and fill the `summary` field with a brief description. @@ -80,7 +95,7 @@ entries: kind: feature pr: - https://github.com/elastic/elastic-agent-changelog-tool/pull/13 - issue: + issue: - https://github.com/elastic/elastic-agent-changelog-tool/issues/21 timestamp: 1649924282 file: @@ -93,12 +108,26 @@ There will be multiple entries, one for each files in `changelog/fragments`. ## 5. Render the consolidated changelog -_Note_: at the moment there is only one renderer implemented: Asciidoc. +### Markdown + +From the root folder of the repository run: + +``` +$ elastic-agent-changelog-tool render --version 0.1.0 --template markdown +``` + +This will create three files: + +* `./changelog/0.1.0/index.md` +* `./changelog/0.1.0/breaking.md` +* `./changelog/0.1.0/deprecations.md` + +### AsciiDoc From the root folder of the repository run: ``` -$ elastic-agent-changelog-tool render --version 0.1.0 +$ elastic-agent-changelog-tool render --version 0.1.0 --template asciidoc-embedded ``` This will create `./changelog/0.1.0.asciidoc`. diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index 15dccea..d8fe07f 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -39,7 +39,7 @@ func NewRenderer(fs afero.Fs, c Changelog, dest string, templ string, repo strin } func (r Renderer) Render() error { - log.Printf("render changelog for version: %s\n", r.changelog.Version) + log.Printf("render %s for version: %s\n", r.templ, r.changelog.Version) tpl, err := r.Template() if err != nil { @@ -133,11 +133,7 @@ func (r Renderer) Render() error { if len(td.KnownIssue) > 0 { links = append( links, - fmt.Sprintf( - "[Known issues](/release-notes/known-issues.md#%s-%s-known-issues)", - r.repo, - r.changelog.Version, - ), + "[Known issues](/release-notes/known-issues.md)", ) } if len(td.BreakingChange) > 0 { diff --git a/internal/settings/settings.go b/internal/settings/settings.go index e7aa5a1..5f4ee5a 100644 --- a/internal/settings/settings.go +++ b/internal/settings/settings.go @@ -59,7 +59,7 @@ func setDefaults() { viper.GetString("fragment_path"))) viper.SetDefault("changelog_destination", "changelog") - // viper.SetDefault("rendered_changelog_destination", "changelog") + viper.SetDefault("rendered_changelog_destination", "changelog") viper.SetDefault("template", "asciidoc-embedded") } From b6b455c5b04c21370315459ff33887a5a1bf716f Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Mon, 18 Aug 2025 10:40:52 -0500 Subject: [PATCH 05/11] remove known issues template --- internal/assets/assets.go | 4 ---- internal/assets/markdown-known-issues-template.md | 14 -------------- internal/changelog/renderer.go | 4 ---- 3 files changed, 22 deletions(-) delete mode 100644 internal/assets/markdown-known-issues-template.md diff --git a/internal/assets/assets.go b/internal/assets/assets.go index bcd8b54..dd25626 100644 --- a/internal/assets/assets.go +++ b/internal/assets/assets.go @@ -18,7 +18,6 @@ func GetEmbeddedTemplates() embeddedTemplates { "markdown-index": "markdown-index-template.md", "markdown-breaking": "markdown-breaking-template.md", "markdown-deprecations": "markdown-deprecations-template.md", - "markdown-known-issues": "markdown-known-issues-template.md", } } @@ -34,9 +33,6 @@ var MarkdownBreakingTemplate embed.FS //go:embed markdown-deprecations-template.md var MarkdownDeprecationsTemplate embed.FS -//go:embed markdown-known-issues-template.md -var MarkdownKnownIssuesTemplate embed.FS - type embeddedTemplates map[string]string func (t embeddedTemplates) String() string { diff --git a/internal/assets/markdown-known-issues-template.md b/internal/assets/markdown-known-issues-template.md deleted file mode 100644 index 8dc3073..0000000 --- a/internal/assets/markdown-known-issues-template.md +++ /dev/null @@ -1,14 +0,0 @@ -{{ if .KnownIssue -}}{{ range $k, $v := .KnownIssue }}{{ range $item := $v }} - -::::{dropdown} {{ $item.Summary | beautify }} -**Applies to**: {{.Version}} - -{{ if $item.Description }}{{ $item.Description }}{{ end }} - -For more information, check {{ linkPRSource $item.Component $item.LinkedPR }}{{ linkIssueSource $item.Component $item.LinkedIssue }}. - -{{ if not $item.Impact }}% {{ end }}**Impact**
{{ if $item.Impact }}{{ $item.Impact }}{{ else }}_Add a description of the impact_{{ end }} - -{{ if not $item.Action }}% {{ end }}**Action**
{{ if $item.Action }}{{ $item.Action }}{{ else }}_Add a description of the what action to take_{{ end }} -:::: -{{- end }}{{- end }}{{- end }} \ No newline at end of file diff --git a/internal/changelog/renderer.go b/internal/changelog/renderer.go index d8fe07f..fbbc9b2 100644 --- a/internal/changelog/renderer.go +++ b/internal/changelog/renderer.go @@ -189,8 +189,6 @@ func (r Renderer) Render() error { return path.Join(r.dest, r.changelog.Version, "breaking.md") } else if template == "markdown-deprecations" { return path.Join(r.dest, r.changelog.Version, "deprecations.md") - } else if template == "markdown-known-issues" { - return path.Join(r.dest, r.changelog.Version, "known-issues.md") } else { return path.Join(r.dest, fmt.Sprintf("%s.asciidoc", r.changelog.Version)) } @@ -212,8 +210,6 @@ func (r Renderer) Template() ([]byte, error) { data, err = assets.MarkdownBreakingTemplate.ReadFile(embeddedFileName) } else if r.templ == "markdown-deprecations" { data, err = assets.MarkdownDeprecationsTemplate.ReadFile(embeddedFileName) - } else if r.templ == "markdown-known-issues" { - data, err = assets.MarkdownKnownIssuesTemplate.ReadFile(embeddedFileName) } else if r.templ == "asciidoc-embedded" { data, err = assets.AsciidocTemplate.ReadFile(embeddedFileName) } From 0a22b3d799554675229d652ff5c67f1d0e3458f9 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Wed, 20 Aug 2025 11:38:14 -0500 Subject: [PATCH 06/11] rename template to file_type --- cmd/render.go | 21 +++++------- docs/getting-started.md | 4 +-- docs/usage.md | 74 ++++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 46 deletions(-) diff --git a/cmd/render.go b/cmd/render.go index 2894a78..b03b8bd 100644 --- a/cmd/render.go +++ b/cmd/render.go @@ -8,21 +8,16 @@ import ( "fmt" "log" - "github.com/elastic/elastic-agent-changelog-tool/internal/assets" "github.com/elastic/elastic-agent-changelog-tool/internal/changelog" "github.com/spf13/afero" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var RenderLongDescription = fmt.Sprintf(`Use this command to render the consolidated changelog. +var RenderLongDescription = `Use this command to render the consolidated changelog. --version is required. Consolidated changelog version (x.y.z) in 'changelogs' folder ---template is optional. Specify full path to your template file or use predefined templates. Default: asciidoc-embedded - -Predefined templates: -%s -`, assets.GetEmbeddedTemplates().String()) +--file_type is required. Specify the file_type: 'asciidoc' or 'markdown'` func RenderCmd(fs afero.Fs) *cobra.Command { renderCmd := &cobra.Command{ @@ -42,9 +37,9 @@ func RenderCmd(fs afero.Fs) *cobra.Command { return fmt.Errorf("error parsing flag 'version': %w", err) } - template, err := cmd.Flags().GetString("template") + file_type, err := cmd.Flags().GetString("file_type") if err != nil { - return fmt.Errorf("error parsing flag 'template': %w", err) + return fmt.Errorf("error parsing flag 'file_type': %w", err) } c, err := changelog.FromFile(fs, fmt.Sprintf("./%s/%s.yaml", dest, version)) @@ -52,12 +47,12 @@ func RenderCmd(fs afero.Fs) *cobra.Command { return fmt.Errorf("error loading changelog from file: %w", err) } - if template == "asciidoc-embedded" { - r := changelog.NewRenderer(fs, c, renderedDest, template, repo) + if file_type == "asciidoc" { + r := changelog.NewRenderer(fs, c, renderedDest, "asciidoc-embedded", repo) if err := r.Render(); err != nil { return fmt.Errorf("cannot build asciidoc file: %w", err) } - } else if template == "markdown" { + } else if file_type == "markdown" { r_index := changelog.NewRenderer(fs, c, renderedDest, "markdown-index", repo) if err := r_index.Render(); err != nil { return fmt.Errorf("cannot build asciidoc file: %w", err) @@ -76,7 +71,7 @@ func RenderCmd(fs afero.Fs) *cobra.Command { }, } - renderCmd.Flags().String("template", viper.GetString("template"), "The template used to generate the changelog") + renderCmd.Flags().String("file_type", viper.GetString("file_type"), "The file type used to generate the changelog: `asciidoc` or `markdown`") renderCmd.Flags().String("version", "", "The version of the consolidated changelog being created") err := renderCmd.MarkFlagRequired("version") if err != nil { diff --git a/docs/getting-started.md b/docs/getting-started.md index 3dc3660..476d040 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -113,7 +113,7 @@ There will be multiple entries, one for each files in `changelog/fragments`. From the root folder of the repository run: ``` -$ elastic-agent-changelog-tool render --version 0.1.0 --template markdown +$ elastic-agent-changelog-tool render --version 0.1.0 --file_type markdown ``` This will create three files: @@ -127,7 +127,7 @@ This will create three files: From the root folder of the repository run: ``` -$ elastic-agent-changelog-tool render --version 0.1.0 --template asciidoc-embedded +$ elastic-agent-changelog-tool render --version 0.1.0 --file_type asciidoc ``` This will create `./changelog/0.1.0.asciidoc`. diff --git a/docs/usage.md b/docs/usage.md index 790d18b..a76b15a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -43,11 +43,9 @@ $ elastic-agent-changelog-tool build --version=next --owner --repo +$ elastic-agent-changelog-tool render --version=next --file_type ``` -The template value can be chosen from a predefined internal list of templates (`render --help`) or use a full path to your template file. - An example is [`../changelog/0.1.0.yaml`](../changelog/0.1.0.yaml). ### My PR does not need a changelog @@ -82,9 +80,8 @@ $ elastic-agent-changelog-tool build --version=next --owner --repo +$ elastic-agent-changelog-tool render --version=next --file_type ``` -The template value can be chosen from a predefined internal list of templates (`render --help`) or use a full path to your template file. An example is [`../changelog/0.1.0.yaml`](../changelog/0.1.0.yaml). @@ -97,8 +94,14 @@ The side effect is that the changelog will include all entries from latest stabl 1. Create consolidated changelog with `$ elastic-agent-changelog-tool build --version --owner --repo `; * This will create `./changelog/x.y.z.yaml`; -2. Create rendered changelog with `$ elastic-agent-changelog-tool render --version --template