mirror of
https://github.com/apricote/releaser-pleaser.git
synced 2026-01-13 21:21:03 +00:00
feat: update version references in any files (#14)
This commit is contained in:
parent
589fdde401
commit
47de2f97bc
5 changed files with 200 additions and 18 deletions
|
|
@ -12,14 +12,19 @@ inputs:
|
||||||
description: 'GitHub token for creating and grooming release PRs, defaults to using secrets.GITHUB_TOKEN'
|
description: 'GitHub token for creating and grooming release PRs, defaults to using secrets.GITHUB_TOKEN'
|
||||||
required: false
|
required: false
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
|
extra-files:
|
||||||
|
description: 'List of files that are scanned for version references.'
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
outputs: {}
|
outputs: {}
|
||||||
runs:
|
runs:
|
||||||
using: 'docker'
|
using: 'docker'
|
||||||
image: ghcr.io/apricote/releaser-pleaser:v0.1.0
|
image: ghcr.io/apricote/releaser-pleaser:v0.1.0 # x-releaser-pleaser-version
|
||||||
args:
|
args:
|
||||||
- run
|
- run
|
||||||
- --forge=github
|
- --forge=github
|
||||||
- --branch=${{ inputs.branch }}
|
- --branch=${{ inputs.branch }}
|
||||||
|
- --extra-files="${{ inputs.extra-files }}"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ inputs.token }}
|
GITHUB_TOKEN: ${{ inputs.token }}
|
||||||
GITHUB_USER: "oauth2"
|
GITHUB_USER: "oauth2"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
rp "github.com/apricote/releaser-pleaser"
|
rp "github.com/apricote/releaser-pleaser"
|
||||||
|
|
@ -13,10 +15,11 @@ var runCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagForge string
|
flagForge string
|
||||||
flagBranch string
|
flagBranch string
|
||||||
flagOwner string
|
flagOwner string
|
||||||
flagRepo string
|
flagRepo string
|
||||||
|
flagExtraFiles string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -28,6 +31,7 @@ func init() {
|
||||||
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
|
runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "")
|
||||||
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
|
runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
|
||||||
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
|
runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
|
||||||
|
runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(cmd *cobra.Command, _ []string) error {
|
func run(cmd *cobra.Command, _ []string) error {
|
||||||
|
|
@ -59,7 +63,31 @@ func run(cmd *cobra.Command, _ []string) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
releaserPleaser := rp.New(forge, logger, flagBranch, rp.NewConventionalCommitsParser(), rp.SemVerNextVersion)
|
extraFiles := parseExtraFiles(flagExtraFiles)
|
||||||
|
|
||||||
|
releaserPleaser := rp.New(
|
||||||
|
forge,
|
||||||
|
logger,
|
||||||
|
flagBranch,
|
||||||
|
rp.NewConventionalCommitsParser(),
|
||||||
|
rp.SemVerNextVersion,
|
||||||
|
extraFiles,
|
||||||
|
[]rp.Updater{&rp.GenericUpdater{}},
|
||||||
|
)
|
||||||
|
|
||||||
return releaserPleaser.Run(ctx)
|
return releaserPleaser.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseExtraFiles(input string) []string {
|
||||||
|
lines := strings.Split(input, "\n")
|
||||||
|
|
||||||
|
extraFiles := make([]string, 0, len(lines))
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if len(line) > 0 {
|
||||||
|
extraFiles = append(extraFiles, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extraFiles
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package rp
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
|
|
@ -20,15 +22,19 @@ type ReleaserPleaser struct {
|
||||||
targetBranch string
|
targetBranch string
|
||||||
commitParser CommitParser
|
commitParser CommitParser
|
||||||
nextVersion VersioningStrategy
|
nextVersion VersioningStrategy
|
||||||
|
extraFiles []string
|
||||||
|
updaters []Updater
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(forge Forge, logger *slog.Logger, targetBranch string, commitParser CommitParser, versioningStrategy VersioningStrategy) *ReleaserPleaser {
|
func New(forge Forge, logger *slog.Logger, targetBranch string, commitParser CommitParser, versioningStrategy VersioningStrategy, extraFiles []string, updaters []Updater) *ReleaserPleaser {
|
||||||
return &ReleaserPleaser{
|
return &ReleaserPleaser{
|
||||||
forge: forge,
|
forge: forge,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
targetBranch: targetBranch,
|
targetBranch: targetBranch,
|
||||||
commitParser: commitParser,
|
commitParser: commitParser,
|
||||||
nextVersion: versioningStrategy,
|
nextVersion: versioningStrategy,
|
||||||
|
extraFiles: extraFiles,
|
||||||
|
updaters: updaters,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,21 +242,74 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
|
||||||
return fmt.Errorf("failed to check out branch: %w", err)
|
return fmt.Errorf("failed to check out branch: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = RunUpdater(ctx, nextVersion, worktree)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update files with new version: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changelogEntry, err := NewChangelogEntry(analyzedCommits, nextVersion, rp.forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix)
|
changelogEntry, err := NewChangelogEntry(analyzedCommits, nextVersion, rp.forge.ReleaseURL(nextVersion), releaseOverrides.Prefix, releaseOverrides.Suffix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to build changelog entry: %w", err)
|
return fmt.Errorf("failed to build changelog entry: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Info for updaters
|
||||||
|
info := ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry}
|
||||||
|
|
||||||
err = UpdateChangelogFile(worktree, changelogEntry)
|
err = UpdateChangelogFile(worktree, changelogEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update changelog file: %w", err)
|
return fmt.Errorf("failed to update changelog file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFile := func(path string, updaters []Updater) error {
|
||||||
|
file, err := worktree.Filesystem.OpenFile(path, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
content, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedContent := string(content)
|
||||||
|
|
||||||
|
for _, updater := range updaters {
|
||||||
|
updatedContent, err = updater.UpdateContent(updatedContent, info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run updater %T on file %s", updater, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Truncate(0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
_, err = file.Write([]byte(updatedContent))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to replace file content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = worktree.Add(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add updated file to git worktree: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range rp.extraFiles {
|
||||||
|
_, err = worktree.Filesystem.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: Check for non existing file or dirs
|
||||||
|
return fmt.Errorf("failed to run file updater because the file %s does not exist: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateFile(path, rp.updaters)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run file updater: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
releaseCommitMessage := fmt.Sprintf("chore(%s): release %s", rp.targetBranch, nextVersion)
|
releaseCommitMessage := fmt.Sprintf("chore(%s): release %s", rp.targetBranch, nextVersion)
|
||||||
releaseCommitHash, err := worktree.Commit(releaseCommitMessage, &git.CommitOptions{
|
releaseCommitHash, err := worktree.Commit(releaseCommitMessage, &git.CommitOptions{
|
||||||
Author: GitSignature(),
|
Author: GitSignature(),
|
||||||
|
|
|
||||||
28
updater.go
28
updater.go
|
|
@ -1,12 +1,28 @@
|
||||||
package rp
|
package rp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunUpdater(ctx context.Context, version string, worktree *git.Worktree) error {
|
var (
|
||||||
// TODO: Implement updater for Go,Python,ExtraFilesMarkers
|
GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`)
|
||||||
return nil
|
)
|
||||||
|
|
||||||
|
type ReleaseInfo struct {
|
||||||
|
Version string
|
||||||
|
ChangelogEntry string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
UpdateContent(content string, info ReleaseInfo) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericUpdater struct{}
|
||||||
|
|
||||||
|
func (u *GenericUpdater) UpdateContent(content string, info ReleaseInfo) (string, error) {
|
||||||
|
// We strip the "v" prefix to avoid adding/removing it from the users input.
|
||||||
|
version := strings.TrimPrefix(info.Version, "v")
|
||||||
|
|
||||||
|
return GenericUpdaterSemVerRegex.ReplaceAllString(content, version+"${2}"), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
74
updater_test.go
Normal file
74
updater_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package rp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updaterTestCase struct {
|
||||||
|
name string
|
||||||
|
content string
|
||||||
|
info ReleaseInfo
|
||||||
|
want string
|
||||||
|
wantErr assert.ErrorAssertionFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUpdaterTest(t *testing.T, updater Updater, tt updaterTestCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
got, err := updater.UpdateContent(tt.content, tt.info)
|
||||||
|
if !tt.wantErr(t, err, fmt.Sprintf("UpdateContent(%v, %v)", tt.content, tt.info)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equalf(t, tt.want, got, "UpdateContent(%v, %v)", tt.content, tt.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericUpdater_UpdateContent(t *testing.T) {
|
||||||
|
updater := &GenericUpdater{}
|
||||||
|
|
||||||
|
tests := []updaterTestCase{
|
||||||
|
{
|
||||||
|
name: "single line",
|
||||||
|
content: "v1.0.0 // x-releaser-pleaser-version",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "v1.2.0 // x-releaser-pleaser-version",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiline line",
|
||||||
|
content: "Foooo\n\v1.2.0\nv1.0.0 // x-releaser-pleaser-version\n",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "Foooo\n\v1.2.0\nv1.2.0 // x-releaser-pleaser-version\n",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid existing version",
|
||||||
|
content: "1.0 // x-releaser-pleaser-version",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "1.0 // x-releaser-pleaser-version",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "complicated line",
|
||||||
|
content: "version: v1.2.0-alpha.1 => Awesome, isnt it? x-releaser-pleaser-version foobar",
|
||||||
|
info: ReleaseInfo{
|
||||||
|
Version: "v1.2.0",
|
||||||
|
},
|
||||||
|
want: "version: v1.2.0 => Awesome, isnt it? x-releaser-pleaser-version foobar",
|
||||||
|
wantErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
runUpdaterTest(t, updater, tt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue