diff --git a/internal/changelog/changelog.go b/internal/changelog/changelog.go index 1d0fd67..6004829 100644 --- a/internal/changelog/changelog.go +++ b/internal/changelog/changelog.go @@ -5,8 +5,10 @@ import ( _ "embed" "html/template" "log" + "log/slog" "github.com/apricote/releaser-pleaser/internal/commitparser" + "github.com/apricote/releaser-pleaser/internal/markdown" ) var ( @@ -24,7 +26,7 @@ func init() { } } -func NewChangelogEntry(commits []commitparser.AnalyzedCommit, version, link, prefix, suffix string) (string, error) { +func NewChangelogEntry(logger *slog.Logger, commits []commitparser.AnalyzedCommit, version, link, prefix, suffix string) (string, error) { features := make([]commitparser.AnalyzedCommit, 0) fixes := make([]commitparser.AnalyzedCommit, 0) @@ -50,5 +52,11 @@ func NewChangelogEntry(commits []commitparser.AnalyzedCommit, version, link, pre return "", err } - return changelog.String(), nil + formatted, err := markdown.Format(changelog.String()) + if err != nil { + logger.Warn("failed to format changelog entry, using unformatted", "error", err) + return changelog.String(), nil + } + + return formatted, nil } diff --git a/internal/changelog/changelog.md.tpl b/internal/changelog/changelog.md.tpl index 85482e1..1f7dd42 100644 --- a/internal/changelog/changelog.md.tpl +++ b/internal/changelog/changelog.md.tpl @@ -19,4 +19,4 @@ {{- if .Suffix }} {{ .Suffix }} -{{ end -}} +{{ end }} diff --git a/internal/changelog/changelog_test.go b/internal/changelog/changelog_test.go index 384e30f..e6582c7 100644 --- a/internal/changelog/changelog_test.go +++ b/internal/changelog/changelog_test.go @@ -1,6 +1,7 @@ package changelog import ( + "log/slog" "testing" "github.com/stretchr/testify/assert" @@ -34,7 +35,7 @@ func Test_NewChangelogEntry(t *testing.T) { version: "1.0.0", link: "https://example.com/1.0.0", }, - want: "## [1.0.0](https://example.com/1.0.0)", + want: "## [1.0.0](https://example.com/1.0.0)\n", wantErr: assert.NoError, }, { @@ -50,7 +51,7 @@ func Test_NewChangelogEntry(t *testing.T) { version: "1.0.0", link: "https://example.com/1.0.0", }, - want: "## [1.0.0](https://example.com/1.0.0)\n### Features\n\n- Foobar!\n", + want: "## [1.0.0](https://example.com/1.0.0)\n\n### Features\n\n- Foobar!\n", wantErr: assert.NoError, }, { @@ -66,7 +67,7 @@ func Test_NewChangelogEntry(t *testing.T) { version: "1.0.0", link: "https://example.com/1.0.0", }, - want: "## [1.0.0](https://example.com/1.0.0)\n### Bug Fixes\n\n- Foobar!\n", + want: "## [1.0.0](https://example.com/1.0.0)\n\n### Bug Fixes\n\n- Foobar!\n", wantErr: assert.NoError, }, { @@ -100,6 +101,7 @@ func Test_NewChangelogEntry(t *testing.T) { link: "https://example.com/1.0.0", }, want: `## [1.0.0](https://example.com/1.0.0) + ### Features - Blabla! @@ -127,6 +129,7 @@ func Test_NewChangelogEntry(t *testing.T) { prefix: "### Breaking Changes", }, want: `## [1.0.0](https://example.com/1.0.0) + ### Breaking Changes ### Bug Fixes @@ -150,6 +153,7 @@ func Test_NewChangelogEntry(t *testing.T) { suffix: "### Compatibility\n\nThis version is compatible with flux-compensator v2.2 - v2.9.", }, want: `## [1.0.0](https://example.com/1.0.0) + ### Bug Fixes - Foobar! @@ -164,7 +168,7 @@ This version is compatible with flux-compensator v2.2 - v2.9. for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := NewChangelogEntry(tt.args.analyzedCommits, tt.args.version, tt.args.link, tt.args.prefix, tt.args.suffix) + got, err := NewChangelogEntry(slog.Default(), tt.args.analyzedCommits, tt.args.version, tt.args.link, tt.args.prefix, tt.args.suffix) if !tt.wantErr(t, err) { return } diff --git a/internal/markdown/goldmark.go b/internal/markdown/goldmark.go index 252ef92..456fc55 100644 --- a/internal/markdown/goldmark.go +++ b/internal/markdown/goldmark.go @@ -1,8 +1,12 @@ package markdown import ( + "bytes" + markdown "github.com/teekennedy/goldmark-markdown" "github.com/yuin/goldmark" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/util" "github.com/apricote/releaser-pleaser/internal/markdown/extensions" ) @@ -10,6 +14,23 @@ import ( func New() goldmark.Markdown { return goldmark.New( goldmark.WithExtensions(extensions.Section), + goldmark.WithParserOptions(parser.WithASTTransformers( + util.Prioritized(&newLineTransformer{}, 1), + )), goldmark.WithRenderer(markdown.NewRenderer()), ) } + +// Format the Markdown document in a style mimicking Prettier. This is done for compatibility with other tools +// users might have installed in their IDE. This does not guarantee that the output matches Prettier exactly. +func Format(input string) (string, error) { + var buf bytes.Buffer + buf.Grow(len(input)) + + err := New().Convert([]byte(input), &buf) + if err != nil { + return "", err + } + + return buf.String(), nil +} diff --git a/internal/markdown/prettier.go b/internal/markdown/prettier.go new file mode 100644 index 0000000..914f1a9 --- /dev/null +++ b/internal/markdown/prettier.go @@ -0,0 +1,31 @@ +package markdown + +import ( + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +type newLineTransformer struct{} + +var _ parser.ASTTransformer = (*newLineTransformer)(nil) // interface compliance + +func (t *newLineTransformer) Transform(doc *ast.Document, _ text.Reader, _ parser.Context) { + // No error can happen as they can only come from the walker function + _ = ast.Walk(doc, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering || node.Type() != ast.TypeBlock { + return ast.WalkContinue, nil + } + + switch node.Kind() { + case ast.KindListItem: + // Do not add empty lines between every list item + break + default: + // Add empty lines between every other block + node.SetBlankPreviousLines(true) + } + + return ast.WalkContinue, nil + }) +} diff --git a/releaserpleaser.go b/releaserpleaser.go index 54064a4..6e500f6 100644 --- a/releaserpleaser.go +++ b/releaserpleaser.go @@ -239,7 +239,7 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error { return err } - changelogEntry, err := changelog.NewChangelogEntry(analyzedCommits, nextVersion, rp.forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix) + changelogEntry, err := changelog.NewChangelogEntry(logger, analyzedCommits, nextVersion, rp.forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix) if err != nil { return fmt.Errorf("failed to build changelog entry: %w", err) }