mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-01-13 21:21:03 +00:00
refactor: move things to packages (#39)
This commit is contained in:
parent
44184a77f9
commit
a0a064d387
32 changed files with 923 additions and 892 deletions
1
internal/releasepr/label.go
Normal file
1
internal/releasepr/label.go
Normal file
|
|
@ -0,0 +1 @@
|
|||
package releasepr
|
||||
258
internal/releasepr/releasepr.go
Normal file
258
internal/releasepr/releasepr.go
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
package releasepr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/text"
|
||||
|
||||
"github.com/apricote/releaser-pleaser/internal/git"
|
||||
"github.com/apricote/releaser-pleaser/internal/markdown"
|
||||
ast2 "github.com/apricote/releaser-pleaser/internal/markdown/extensions/ast"
|
||||
"github.com/apricote/releaser-pleaser/internal/versioning"
|
||||
)
|
||||
|
||||
var (
|
||||
releasePRTemplate *template.Template
|
||||
)
|
||||
|
||||
//go:embed releasepr.md.tpl
|
||||
var rawReleasePRTemplate string
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
releasePRTemplate, err = template.New("releasepr").Parse(rawReleasePRTemplate)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse release pr template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReleasePullRequest
|
||||
//
|
||||
// TODO: Reuse [git.PullRequest]
|
||||
type ReleasePullRequest struct {
|
||||
ID int
|
||||
Title string
|
||||
Description string
|
||||
Labels []Label
|
||||
|
||||
Head string
|
||||
ReleaseCommit *git.Commit
|
||||
}
|
||||
|
||||
// Label is the string identifier of a pull/merge request label on the forge.
|
||||
type Label string
|
||||
|
||||
func NewReleasePullRequest(head, branch, version, changelogEntry string) (*ReleasePullRequest, error) {
|
||||
rp := &ReleasePullRequest{
|
||||
Head: head,
|
||||
Labels: []Label{LabelReleasePending},
|
||||
}
|
||||
|
||||
rp.SetTitle(branch, version)
|
||||
if err := rp.SetDescription(changelogEntry, ReleaseOverrides{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rp, nil
|
||||
}
|
||||
|
||||
type ReleaseOverrides struct {
|
||||
Prefix string
|
||||
Suffix string
|
||||
NextVersionType versioning.NextVersionType
|
||||
}
|
||||
|
||||
const (
|
||||
LabelNextVersionTypeNormal Label = "rp-next-version::normal"
|
||||
LabelNextVersionTypeRC Label = "rp-next-version::rc"
|
||||
LabelNextVersionTypeBeta Label = "rp-next-version::beta"
|
||||
LabelNextVersionTypeAlpha Label = "rp-next-version::alpha"
|
||||
|
||||
LabelReleasePending Label = "rp-release::pending"
|
||||
LabelReleaseTagged Label = "rp-release::tagged"
|
||||
)
|
||||
|
||||
var KnownLabels = []Label{
|
||||
LabelNextVersionTypeNormal,
|
||||
LabelNextVersionTypeRC,
|
||||
LabelNextVersionTypeBeta,
|
||||
LabelNextVersionTypeAlpha,
|
||||
|
||||
LabelReleasePending,
|
||||
LabelReleaseTagged,
|
||||
}
|
||||
|
||||
const (
|
||||
DescriptionLanguagePrefix = "rp-prefix"
|
||||
DescriptionLanguageSuffix = "rp-suffix"
|
||||
)
|
||||
|
||||
const (
|
||||
MarkdownSectionChangelog = "changelog"
|
||||
)
|
||||
|
||||
const (
|
||||
TitleFormat = "chore(%s): release %s"
|
||||
)
|
||||
|
||||
var (
|
||||
TitleRegex = regexp.MustCompile("chore(.*): release (.*)")
|
||||
)
|
||||
|
||||
func (pr *ReleasePullRequest) GetOverrides() (ReleaseOverrides, error) {
|
||||
overrides := ReleaseOverrides{}
|
||||
overrides = pr.parseVersioningFlags(overrides)
|
||||
overrides, err := pr.parseDescription(overrides)
|
||||
if err != nil {
|
||||
return ReleaseOverrides{}, err
|
||||
}
|
||||
|
||||
return overrides, nil
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) parseVersioningFlags(overrides ReleaseOverrides) ReleaseOverrides {
|
||||
for _, label := range pr.Labels {
|
||||
switch label {
|
||||
// Versioning
|
||||
case LabelNextVersionTypeNormal:
|
||||
overrides.NextVersionType = versioning.NextVersionTypeNormal
|
||||
case LabelNextVersionTypeRC:
|
||||
overrides.NextVersionType = versioning.NextVersionTypeRC
|
||||
case LabelNextVersionTypeBeta:
|
||||
overrides.NextVersionType = versioning.NextVersionTypeBeta
|
||||
case LabelNextVersionTypeAlpha:
|
||||
overrides.NextVersionType = versioning.NextVersionTypeAlpha
|
||||
case LabelReleasePending, LabelReleaseTagged:
|
||||
// These labels have no effect on the versioning.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return overrides
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) parseDescription(overrides ReleaseOverrides) (ReleaseOverrides, error) {
|
||||
source := []byte(pr.Description)
|
||||
descriptionAST := markdown.New().Parser().Parse(text.NewReader(source))
|
||||
|
||||
err := ast.Walk(descriptionAST, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
if n.Type() != ast.TypeBlock || n.Kind() != ast.KindFencedCodeBlock {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
codeBlock, ok := n.(*ast.FencedCodeBlock)
|
||||
if !ok {
|
||||
return ast.WalkStop, fmt.Errorf("node has unexpected type: %T", n)
|
||||
}
|
||||
|
||||
switch string(codeBlock.Language(source)) {
|
||||
case DescriptionLanguagePrefix:
|
||||
overrides.Prefix = textFromLines(source, codeBlock)
|
||||
case DescriptionLanguageSuffix:
|
||||
overrides.Suffix = textFromLines(source, codeBlock)
|
||||
}
|
||||
|
||||
return ast.WalkContinue, nil
|
||||
})
|
||||
if err != nil {
|
||||
return ReleaseOverrides{}, err
|
||||
}
|
||||
|
||||
return overrides, nil
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) ChangelogText() (string, error) {
|
||||
source := []byte(pr.Description)
|
||||
gm := markdown.New()
|
||||
descriptionAST := gm.Parser().Parse(text.NewReader(source))
|
||||
|
||||
var section *ast2.Section
|
||||
|
||||
err := ast.Walk(descriptionAST, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
if n.Type() != ast.TypeBlock || n.Kind() != ast2.KindSection {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
anySection, ok := n.(*ast2.Section)
|
||||
if !ok {
|
||||
return ast.WalkStop, fmt.Errorf("node has unexpected type: %T", n)
|
||||
}
|
||||
|
||||
if anySection.Name != MarkdownSectionChangelog {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
section = anySection
|
||||
return ast.WalkStop, nil
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if section == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
outputBuffer := new(bytes.Buffer)
|
||||
err = gm.Renderer().Render(outputBuffer, source, section)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outputBuffer.String(), nil
|
||||
}
|
||||
|
||||
func textFromLines(source []byte, n ast.Node) string {
|
||||
content := make([]byte, 0)
|
||||
|
||||
l := n.Lines().Len()
|
||||
for i := 0; i < l; i++ {
|
||||
line := n.Lines().At(i)
|
||||
content = append(content, line.Value(source)...)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) SetTitle(branch, version string) {
|
||||
pr.Title = fmt.Sprintf(TitleFormat, branch, version)
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) Version() (string, error) {
|
||||
matches := TitleRegex.FindStringSubmatch(pr.Title)
|
||||
if len(matches) != 3 {
|
||||
return "", fmt.Errorf("title has unexpected format")
|
||||
}
|
||||
|
||||
return matches[2], nil
|
||||
}
|
||||
|
||||
func (pr *ReleasePullRequest) SetDescription(changelogEntry string, overrides ReleaseOverrides) error {
|
||||
var description bytes.Buffer
|
||||
err := releasePRTemplate.Execute(&description, map[string]any{
|
||||
"Changelog": changelogEntry,
|
||||
"Overrides": overrides,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.Description = description.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
32
internal/releasepr/releasepr.md.tpl
Normal file
32
internal/releasepr/releasepr.md.tpl
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<!-- section-start changelog -->
|
||||
{{ .Changelog }}
|
||||
<!-- section-end changelog -->
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||
|
||||
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||
|
||||
## Release Notes
|
||||
|
||||
### Prefix / Start
|
||||
|
||||
This will be added to the start of the release notes.
|
||||
|
||||
```rp-prefix
|
||||
{{- if .Overrides.Prefix }}
|
||||
{{ .Overrides.Prefix }}{{ end }}
|
||||
```
|
||||
|
||||
### Suffix / End
|
||||
|
||||
This will be added to the end of the release notes.
|
||||
|
||||
```rp-suffix
|
||||
{{- if .Overrides.Suffix }}
|
||||
{{ .Overrides.Suffix }}{{ end }}
|
||||
```
|
||||
|
||||
</details>
|
||||
144
internal/releasepr/releasepr_test.go
Normal file
144
internal/releasepr/releasepr_test.go
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package releasepr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestReleasePullRequest_SetTitle(t *testing.T) {
|
||||
type args struct {
|
||||
branch string
|
||||
version string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
pr *ReleasePullRequest
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "simple update",
|
||||
pr: &ReleasePullRequest{Title: "foo: bar"},
|
||||
args: args{
|
||||
branch: "main",
|
||||
version: "v1.0.0",
|
||||
},
|
||||
want: "chore(main): release v1.0.0",
|
||||
},
|
||||
{
|
||||
name: "no previous title",
|
||||
pr: &ReleasePullRequest{},
|
||||
args: args{
|
||||
branch: "release-1.x",
|
||||
version: "v1.1.1-rc.0",
|
||||
},
|
||||
want: "chore(release-1.x): release v1.1.1-rc.0",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.pr.SetTitle(tt.args.branch, tt.args.version)
|
||||
|
||||
assert.Equal(t, tt.want, tt.pr.Title)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleasePullRequest_SetDescription(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
changelogEntry string
|
||||
overrides ReleaseOverrides
|
||||
want string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "no overrides",
|
||||
changelogEntry: `## v1.0.0`,
|
||||
overrides: ReleaseOverrides{},
|
||||
want: `<!-- section-start changelog -->
|
||||
## v1.0.0
|
||||
<!-- section-end changelog -->
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||
|
||||
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||
|
||||
## Release Notes
|
||||
|
||||
### Prefix / Start
|
||||
|
||||
This will be added to the start of the release notes.
|
||||
|
||||
` + "```" + `rp-prefix
|
||||
` + "```" + `
|
||||
|
||||
### Suffix / End
|
||||
|
||||
This will be added to the end of the release notes.
|
||||
|
||||
` + "```" + `rp-suffix
|
||||
` + "```" + `
|
||||
|
||||
</details>
|
||||
`,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "existing overrides",
|
||||
changelogEntry: `## v1.0.0`,
|
||||
overrides: ReleaseOverrides{
|
||||
Prefix: "This release is awesome!",
|
||||
Suffix: "Fooo",
|
||||
},
|
||||
want: `<!-- section-start changelog -->
|
||||
## v1.0.0
|
||||
<!-- section-end changelog -->
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><h4>PR by <a href="https://github.com/apricote/releaser-pleaser">releaser-pleaser</a> 🤖</h4></summary>
|
||||
|
||||
If you want to modify the proposed release, add you overrides here. You can learn more about the options in the docs.
|
||||
|
||||
## Release Notes
|
||||
|
||||
### Prefix / Start
|
||||
|
||||
This will be added to the start of the release notes.
|
||||
|
||||
` + "```" + `rp-prefix
|
||||
This release is awesome!
|
||||
` + "```" + `
|
||||
|
||||
### Suffix / End
|
||||
|
||||
This will be added to the end of the release notes.
|
||||
|
||||
` + "```" + `rp-suffix
|
||||
Fooo
|
||||
` + "```" + `
|
||||
|
||||
</details>
|
||||
`,
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pr := &ReleasePullRequest{}
|
||||
err := pr.SetDescription(tt.changelogEntry, tt.overrides)
|
||||
if !tt.wantErr(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.want, pr.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue