Skip to content

Commit

Permalink
CLI: Update version management (#204)
Browse files Browse the repository at this point in the history
* Add semver package

* Use semver package to manage the version argument

* Remove utils/utils

* Move yq to it's own package

* Move changelog funcs to the release utils
  • Loading branch information
jhnstn authored Oct 27, 2023
1 parent 55d68e5 commit 44990ca
Show file tree
Hide file tree
Showing 20 changed files with 237 additions and 246 deletions.
3 changes: 2 additions & 1 deletion cli/cmd/release/integrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ var IntegrateCmd = &cobra.Command{
Short: "integrate a release",
Long: `Use this command to integrate a release. If the android or ios flags are set, only that platform will be integrated. Otherwise, both will be integrated.`,
Run: func(cmd *cobra.Command, args []string) {
version, err := utils.GetVersionArg(args)
semver, err := utils.GetVersionArg(args)
exitIfError(err, 1)
version := semver.String()

gbmPr, err := gbm.FindGbmReleasePr(version)
exitIfError(err, 1)
Expand Down
4 changes: 3 additions & 1 deletion cli/cmd/release/prepare/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ func Execute() {
func preflight(args []string) {
var err error
tempDir = workspace.Dir()
version, err = utils.GetVersionArg(args)

semver, err := utils.GetVersionArg(args)
exitIfError(err, 1)
version = semver.String()

// Validate Aztec version
if valid := gbm.ValidateAztecVersions(); !valid {
Expand Down
4 changes: 3 additions & 1 deletion cli/cmd/release/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ var StatusCmd = &cobra.Command{
Short: "get the status of a release",
Long: `Use this command to get the status of a release.`,
Run: func(cmd *cobra.Command, args []string) {
version, err := utils.GetVersionArg(args)
semver, err := utils.GetVersionArg(args)
exitIfError(err, 1)

version := semver.String()

// Print styles
heading := console.Heading
headingRow := console.HeadingRow
Expand Down
13 changes: 7 additions & 6 deletions cli/cmd/render/checklist.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/wordpress-mobile/gbm-cli/pkg/console"
"github.com/wordpress-mobile/gbm-cli/pkg/gbm"
"github.com/wordpress-mobile/gbm-cli/pkg/render"
"github.com/wordpress-mobile/gbm-cli/pkg/utils"
"github.com/wordpress-mobile/gbm-cli/pkg/semver"
)

var version string
Expand Down Expand Up @@ -37,10 +37,11 @@ var ChecklistCmd = &cobra.Command{
`,
Run: func(cmd *cobra.Command, args []string) {

vv := utils.ValidateVersion(version)
if !vv {
exitIfError(fmt.Errorf("%v is not a valid version. Versions must have a `Major.Minor.Patch` form", version), 1)
semver, err := semver.NewSemVer(version)
if err != nil {
exitIfError(fmt.Errorf("invalid version %s. Versions must have a `Major.Minor.Patch` form", version), 1)
}
version = semver.String()

// For now let's assume we should include the Aztec steps unless explicitly checking if the versions are valid.
// We'll render the aztec steps with the optional
Expand All @@ -53,10 +54,10 @@ var ChecklistCmd = &cobra.Command{
}
}

scheduled := utils.IsScheduledRelease(version)
scheduled := semver.IsScheduledRelease()

if releaseDate == "" {
releaseDate = utils.NextReleaseDate()
releaseDate = nextReleaseDate()
}

releaseUrl := fmt.Sprintf("https://github.com/wordpress-mobile/gutenberg-mobile/releases/new?tag=v%s&target=release/%s&title=Release+%s", version, version, version)
Expand Down
10 changes: 10 additions & 0 deletions cli/cmd/render/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package render

import (
"fmt"
"time"

"github.com/wordpress-mobile/gbm-cli/pkg/render"
)
Expand All @@ -12,3 +13,12 @@ func renderAztecSteps(conditional bool) (string, error) {
Json: fmt.Sprintf(`{"conditional": %v}`, conditional),
})
}

func nextReleaseDate() string {
weekday := time.Now().Weekday()
daysUntilThursday := 4 - weekday

nextThursday := time.Now().AddDate(0, 0, int(daysUntilThursday))

return nextThursday.Format("Monday January 2, 2006")
}
14 changes: 8 additions & 6 deletions cli/cmd/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ import (
"os"

"github.com/wordpress-mobile/gbm-cli/pkg/console"
"github.com/wordpress-mobile/gbm-cli/pkg/utils"
"github.com/wordpress-mobile/gbm-cli/pkg/semver"
)

func GetVersionArg(args []string) (string, error) {
func GetVersionArg(args []string) (semver.SemVer, error) {
if len(args) == 0 {
return "", fmt.Errorf("missing version")
return nil, fmt.Errorf("missing version")
}
if !utils.ValidateVersion(args[0]) {
return "", fmt.Errorf("invalid version %s. Versions must have a `Major.Minor.Patch` form", args[0])
version, err := semver.NewSemVer(args[0])
if err != nil {
return nil, fmt.Errorf("invalid version %s. Versions must have a `Major.Minor.Patch` form", args[0])
}
return utils.NormalizeVersion(args[0])

return version, nil
}

func ExitIfError(err error, code int) {
Expand Down
3 changes: 1 addition & 2 deletions cli/pkg/release/gb.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/wordpress-mobile/gbm-cli/pkg/render"
"github.com/wordpress-mobile/gbm-cli/pkg/repo"
"github.com/wordpress-mobile/gbm-cli/pkg/shell"
"github.com/wordpress-mobile/gbm-cli/pkg/utils"
)

func CreateGbPR(version, dir string, noTag bool) (gh.PullRequest, error) {
Expand Down Expand Up @@ -72,7 +71,7 @@ func CreateGbPR(version, dir string, noTag bool) (gh.PullRequest, error) {

console.Info("Update the CHANGELOG in the react-native-editor package")
chnPath := filepath.Join(dir, "packages", "react-native-editor", "CHANGELOG.md")
if err := utils.UpdateChangeLog(version, chnPath); err != nil {
if err := UpdateChangeLog(version, chnPath); err != nil {
return pr, fmt.Errorf("error updating the CHANGELOG: %v", err)
}
if err := git.CommitAll("Release script: Update CHANGELOG for version %s", version); err != nil {
Expand Down
3 changes: 1 addition & 2 deletions cli/pkg/release/gbm.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (

"github.com/wordpress-mobile/gbm-cli/pkg/render"
"github.com/wordpress-mobile/gbm-cli/pkg/repo"
"github.com/wordpress-mobile/gbm-cli/pkg/utils"
)

func CreateGbmPR(version, dir string) (gh.PullRequest, error) {
Expand Down Expand Up @@ -107,7 +106,7 @@ func CreateGbmPR(version, dir string) (gh.PullRequest, error) {
// Update the RELEASE-NOTES.txt and commit output
console.Info("Update the release-notes in the mobile package")
chnPath := filepath.Join(dir, "RELEASE-NOTES.txt")
if err := utils.UpdateReleaseNotes(version, chnPath); err != nil {
if err := UpdateReleaseNotes(version, chnPath); err != nil {
return pr, fmt.Errorf("error updating the release notes: %v", err)
}

Expand Down
4 changes: 2 additions & 2 deletions cli/pkg/release/integrate/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/wordpress-mobile/gbm-cli/pkg/gh"
"github.com/wordpress-mobile/gbm-cli/pkg/repo"
"github.com/wordpress-mobile/gbm-cli/pkg/shell"
"github.com/wordpress-mobile/gbm-cli/pkg/utils"
"github.com/wordpress-mobile/gbm-cli/pkg/yq"
)

type IosIntegration struct {
Expand Down Expand Up @@ -43,7 +43,7 @@ func (ii IosIntegration) UpdateGutenbergConfig(dir string, gbmPr gh.PullRequest)
}

// perform updates using the yq syntax
config, err := utils.YqEvalAll(updates, string(buf))
config, err := yq.YqEvalAll(updates, string(buf))
if err != nil {
return err
}
Expand Down
64 changes: 64 additions & 0 deletions cli/pkg/release/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package release

import (
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -128,3 +131,64 @@ func checkPRforIssues(pr gh.PullRequest, rc *ReleaseChanges) {
rc.Issues = append(rc.Issues, m[1])
}
}

// Updates the release notes by replacing "Unreleased" with
// the new version and adding a new "Unreleased" section
func UpdateReleaseNotes(version, path string) error {
return readWriteNotes(version, path, releaseNotesUpdater)
}

// See UpdateReleaseNotes
// This handles the string replacement
func releaseNotesUpdater(version string, notes []byte) []byte {
re := regexp.MustCompile(`(^Unreleased\s*\n)`)

repl := fmt.Sprintf("$1---\n\n%s\n", version)

return re.ReplaceAll(notes, []byte(repl))
}

// Updates the change log by replacing "Unreleased" with
// the new version and adding a new "Unreleased" section
func UpdateChangeLog(version, path string) error {
return readWriteNotes(version, path, changeLogUpdater)
}

// See UpdateChangeLog
// This handles the string replacement
func changeLogUpdater(version string, notes []byte) []byte {

re := regexp.MustCompile(`(##\s*Unreleased\s*\n)`)

repl := fmt.Sprintf("$1\n## %s\n", version)

return re.ReplaceAll(notes, []byte(repl))
}

func readWriteNotes(version, path string, updater func(string, []byte) []byte) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()

changeNotes, err := io.ReadAll(f)
if err != nil {
return err
}

update := updater(version, changeNotes)
if err != nil {
return err
}

w, err := os.Create(path)
if err != nil {
return err
}
defer w.Close()
if _, err := w.Write(update); err != nil {
return err
}
return nil
}
55 changes: 55 additions & 0 deletions cli/pkg/semver/semver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package semver

import "fmt"

type semver struct {
Major int
Minor int
Patch int
}

type SemVer interface {
String() string
Vstring() string
PriorVersion() string
IsScheduledRelease() bool
IsPatchRelease() bool
Parse(version string) error
}

func NewSemVer(version string) (SemVer, error) {
s := &semver{}
err := s.Parse(version)
return s, err
}

func (s *semver) String() string {
return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor, s.Patch)
}

func (s *semver) Vstring() string {
return fmt.Sprintf("v%d.%d.%d", s.Major, s.Minor, s.Patch)
}

func (s *semver) PriorVersion() string {
if s.IsPatchRelease() {
return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor, s.Patch-1)
}
return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor-1, 0)
}

func (s *semver) IsScheduledRelease() bool {
return s.Patch == 0
}

func (s *semver) IsPatchRelease() bool {
return s.Patch > 0
}

func (s *semver) Parse(version string) error {
if version[0] == 'v' {
version = version[1:]
}
_, err := fmt.Sscanf(version, "%d.%d.%d", &s.Major, &s.Minor, &s.Patch)
return err
}
79 changes: 79 additions & 0 deletions cli/pkg/semver/semver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package semver

import "testing"

func TestSemVer(t *testing.T) {

t.Run("It returns an error if the version is invalid", func(t *testing.T) {
_, err := NewSemVer("1.0")
assertError(t, err)
})

t.Run("It returns the version without the 'v' prefix", func(t *testing.T) {
semver, err := NewSemVer("v1.0.0")
assertNotError(t, err)
assertEqual(t, semver.String(), "1.0.0")
})

t.Run("It returns the version with the 'v' prefix", func(t *testing.T) {
semver, err := NewSemVer("1.0.0")
assertNotError(t, err)
assertEqual(t, semver.Vstring(), "v1.0.0")
})

t.Run("It returns the prior version of patch release", func(t *testing.T) {
semver, err := NewSemVer("1.0.1")
assertNotError(t, err)
assertEqual(t, semver.PriorVersion(), "1.0.0")
})

t.Run("It can determine a scheduled release", func(t *testing.T) {
semver, err := NewSemVer("1.0.0")
assertNotError(t, err)
if !semver.IsScheduledRelease() {
t.Fatal("Expected 1.0.0 to be a scheduled release")
}

semver, err = NewSemVer("1.0.1")
assertNotError(t, err)
if semver.IsScheduledRelease() {
t.Fatal("Expected 1.0.1 to not be a scheduled release")
}
})

t.Run("It can determine a patch release", func(t *testing.T) {
semver, err := NewSemVer("1.0.1")
assertNotError(t, err)
if !semver.IsPatchRelease() {
t.Fatal("Expected 1.0.1 to be a patch release")
}

semver, err = NewSemVer("1.0.0")
assertNotError(t, err)
if semver.IsPatchRelease() {
t.Fatal("Expected 1.0.0 to not be a patch release")
}
})

}

func assertError(t *testing.T, err error) {
t.Helper()
if err == nil {
t.Fatal("Expected an error, got nil")
}
}

func assertNotError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
}

func assertEqual(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Fatalf("Expected %s, got %s", want, got)
}
}
Loading

0 comments on commit 44990ca

Please sign in to comment.