mirror of
https://github.com/go-task/task.git
synced 2026-08-29 10:08:27 +02:00
feat(release): generate the GitHub release body from the changelog (#2987)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
5
.github/workflows/release.yml
vendored
5
.github/workflows/release.yml
vendored
@@ -46,12 +46,15 @@ jobs:
|
|||||||
run: pnpm install
|
run: pnpm install
|
||||||
working-directory: website
|
working-directory: website
|
||||||
|
|
||||||
|
- name: Generate release notes
|
||||||
|
run: go run ./cmd/release --notes > "${RUNNER_TEMP}/release-notes.md"
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
|
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: latest
|
version: latest
|
||||||
args: release --clean --draft
|
args: release --clean --release-notes=${{ runner.temp }}/release-notes.md
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
|
GH_GORELEASER_TOKEN: ${{secrets.GH_GORELEASER_TOKEN}}
|
||||||
|
|||||||
@@ -217,7 +217,6 @@ tasks:
|
|||||||
|
|
||||||
Please wait for the CI to finish and then do the following:
|
Please wait for the CI to finish and then do the following:
|
||||||
|
|
||||||
- Copy the changelog for v{{.VERSION}} to the GitHub release
|
|
||||||
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
- Update and push the snapcraft manifest in https://github.com/go-task/snap/blob/main/snap/snapcraft.yaml
|
||||||
preconditions:
|
preconditions:
|
||||||
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
- sh: test $(git rev-parse --abbrev-ref HEAD) = "main"
|
||||||
|
|||||||
@@ -43,20 +43,29 @@ var changelogReleaseRegex = regexp.MustCompile(`## Unreleased`)
|
|||||||
// Flags
|
// Flags
|
||||||
var (
|
var (
|
||||||
versionFlag bool
|
versionFlag bool
|
||||||
|
notesFlag bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
|
pflag.BoolVarP(&versionFlag, "version", "v", false, "resolved version number")
|
||||||
|
pflag.BoolVar(¬esFlag, "notes", false, "changelog section of the current version, for the GitHub release body")
|
||||||
pflag.Parse()
|
pflag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := release(); err != nil {
|
if err := run(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
if notesFlag {
|
||||||
|
return notes(os.Stdout)
|
||||||
|
}
|
||||||
|
return release()
|
||||||
|
}
|
||||||
|
|
||||||
func release() error {
|
func release() error {
|
||||||
if len(pflag.Args()) != 1 {
|
if len(pflag.Args()) != 1 {
|
||||||
return errors.New("error: expected version number")
|
return errors.New("error: expected version number")
|
||||||
|
|||||||
48
cmd/release/notes.go
Normal file
48
cmd/release/notes.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notes(w io.Writer) error {
|
||||||
|
version, err := getVersion(versionFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := os.ReadFile(changelogSource)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
section := changelogSection(string(b), version)
|
||||||
|
if section == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintln(w, section)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func changelogSection(changelog string, version *semver.Version) string {
|
||||||
|
heading := fmt.Sprintf("## v%s ", version)
|
||||||
|
var section []string
|
||||||
|
var found bool
|
||||||
|
for line := range strings.SplitSeq(changelog, "\n") {
|
||||||
|
if strings.HasPrefix(line, "## ") {
|
||||||
|
if found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
found = strings.HasPrefix(line, heading)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
section = append(section, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(strings.Join(section, "\n"))
|
||||||
|
}
|
||||||
75
cmd/release/notes_test.go
Normal file
75
cmd/release/notes_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChangelogSection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const changelog = `# Changelog
|
||||||
|
|
||||||
|
## v3.53.1 - 2026-08-18
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- Something new (#1 by @someone).
|
||||||
|
|
||||||
|
### 🐛 Fixes
|
||||||
|
|
||||||
|
- Something fixed (#2 by @someone).
|
||||||
|
|
||||||
|
## v3.53.10 - 2026-08-17
|
||||||
|
|
||||||
|
- An entry of a version whose heading starts with "## v3.53.1".
|
||||||
|
|
||||||
|
## v3.5.0 - 2026-01-01
|
||||||
|
|
||||||
|
- An older entry.
|
||||||
|
`
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "section with sub-headings",
|
||||||
|
version: "3.53.1",
|
||||||
|
expected: `### 🚀 Features
|
||||||
|
|
||||||
|
- Something new (#1 by @someone).
|
||||||
|
|
||||||
|
### 🐛 Fixes
|
||||||
|
|
||||||
|
- Something fixed (#2 by @someone).`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "last section of the file",
|
||||||
|
version: "3.5.0",
|
||||||
|
expected: "- An older entry.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// A patch release may ship without changelog entries.
|
||||||
|
name: "missing section",
|
||||||
|
version: "3.53.2",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "heading of another version starts with this one",
|
||||||
|
version: "3.53.10",
|
||||||
|
expected: `- An entry of a version whose heading starts with "## v3.53.1".`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
version := semver.MustParse(test.version)
|
||||||
|
assert.Equal(t, test.expected, changelogSection(changelog, version))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ of the Taskfile.
|
|||||||
artifacts automatically when a new Git tag is pushed to `main` branch (raw
|
artifacts automatically when a new Git tag is pushed to `main` branch (raw
|
||||||
executables and DEB and RPM packages).
|
executables and DEB and RPM packages).
|
||||||
|
|
||||||
|
The body of the GitHub release is the section of the `CHANGELOG.md` matching the
|
||||||
|
version being released, extracted by `go run ./cmd/release --notes`. A version
|
||||||
|
without changelog entries is released with an empty body.
|
||||||
|
|
||||||
Raw executables can also be reproduced and verified locally by
|
Raw executables can also be reproduced and verified locally by
|
||||||
checking out a specific tag and calling `goreleaser build`, using the Go version
|
checking out a specific tag and calling `goreleaser build`, using the Go version
|
||||||
defined in the above GitHub Actions.
|
defined in the above GitHub Actions.
|
||||||
|
|||||||
Reference in New Issue
Block a user