Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 14 additions & 39 deletions pkg/cli/compile_update_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import (
"net/http"
"os"
"path"
"strconv"
"strings"
"time"

"github.com/github/gh-aw/pkg/constants"
"golang.org/x/mod/semver"
"github.com/github/gh-aw/pkg/semverutil"

"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/logger"
Expand Down Expand Up @@ -276,55 +275,31 @@ func updateCompileUpdateCheckTime() {
}

func isMinorVersionBehind(currentVersion string, latestVersion string) bool {
currentSV := ensureSemverPrefix(currentVersion)
latestSV := ensureSemverPrefix(latestVersion)
currentSV := semverutil.EnsureVPrefix(currentVersion)
latestSV := semverutil.EnsureVPrefix(latestVersion)

if !semver.IsValid(currentSV) || !semver.IsValid(latestSV) {
if !semverutil.IsValid(currentSV) || !semverutil.IsValid(latestSV) {
return false
}
if semver.Compare(currentSV, latestSV) >= 0 {
if semverutil.Compare(currentSV, latestSV) >= 0 {
return false
}

currentMajor, currentMinor, ok := semverMajorMinorParts(currentSV)
if !ok {
return false
}
latestMajor, latestMinor, ok := semverMajorMinorParts(latestSV)
if !ok {
if !hasExplicitMinorComponent(currentSV) || !hasExplicitMinorComponent(latestSV) {
return false
}

return currentMajor == latestMajor && latestMinor > currentMinor
}

func semverMajorMinorParts(version string) (int, int, bool) {
trimmed := strings.TrimPrefix(version, "v")
trimmed = strings.SplitN(trimmed, "-", 2)[0]
trimmed = strings.SplitN(trimmed, "+", 2)[0]

parts := strings.Split(trimmed, ".")
if len(parts) < 2 {
return 0, 0, false
}

major, err := strconv.Atoi(parts[0])
if err != nil {
return 0, 0, false
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return 0, 0, false
}
currentParsed := semverutil.ParseVersion(currentSV)
latestParsed := semverutil.ParseVersion(latestSV)

return major, minor, true
return currentParsed.Major == latestParsed.Major && latestParsed.Minor > currentParsed.Minor
Comment on lines +291 to +294
}

func ensureSemverPrefix(version string) string {
if strings.HasPrefix(version, "v") {
return version
func hasExplicitMinorComponent(version string) bool {
core := strings.TrimPrefix(version, "v")
if idx := strings.IndexAny(core, "-+"); idx >= 0 {
core = core[:idx]
}
return "v" + version
return strings.Count(core, ".") >= 1
}

func printCompileUpdateNotification(notification *compileUpdateNotification) {
Expand Down
40 changes: 40 additions & 0 deletions pkg/cli/compile_update_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,46 @@ func TestPrintCompileUpdateNotification(t *testing.T) {
}
}

func TestIsMinorVersionBehind(t *testing.T) {
tests := []struct {
name string
current string
latest string
want bool
}{
{
name: "explicit minor behind",
current: "v1.2.3",
latest: "v1.3.0",
want: true,
},
{
name: "current missing minor returns false",
current: "v1",
latest: "v1.1.0",
want: false,
},
{
name: "latest missing minor returns false",
current: "v1.0.0",
latest: "v1",
want: false,
},
{
name: "prerelease still counts explicit minor",
current: "v1.0.0-rc.1",
latest: "v1.1.0",
want: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, isMinorVersionBehind(tt.current, tt.latest))
})
}
}

func TestRunCompileUpdateCheckUsesHEADRequests(t *testing.T) {
originalVersion := GetVersion()
originalRelease := workflow.IsRelease()
Expand Down
11 changes: 5 additions & 6 deletions pkg/cli/update_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import (
"strings"
"time"

"golang.org/x/mod/semver"

"github.com/cli/go-gh/v2/pkg/api"
"github.com/github/gh-aw/pkg/console"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/semverutil"
"github.com/github/gh-aw/pkg/workflow"
)

Expand Down Expand Up @@ -172,10 +171,10 @@ func isCurrentVersionAtLeastLatest(currentVersion, latestVersion string) bool {
return true
}

currentSV := ensureSemverPrefix(currentVersion)
latestSV := ensureSemverPrefix(latestVersion)
if semver.IsValid(currentSV) && semver.IsValid(latestSV) {
return semver.Compare(currentSV, latestSV) >= 0
currentSV := semverutil.EnsureVPrefix(currentVersion)
latestSV := semverutil.EnsureVPrefix(latestVersion)
if semverutil.IsValid(currentSV) && semverutil.IsValid(latestSV) {
return semverutil.Compare(currentSV, latestSV) >= 0
}

return currentVersionNormalized > latestVersionNormalized
Expand Down
15 changes: 15 additions & 0 deletions pkg/semverutil/semverutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ func (v *SemanticVersion) IsNewer(other *SemanticVersion) bool {
return Compare(v.Raw, other.Raw) > 0
}

// IsMorePreciseVersion reports whether v1 should sort ahead of v2 in the
// action-version specificity ordering. Versions with more dot-separated
// components sort first (for example "v4.3.0" ahead of "v4"), and ties use
// lexicographic ordering. This is an ordering predicate, not a strict
// "more-precise-only" check. No validation is performed; callers should ensure
// both inputs are well-formed version tags.
func IsMorePreciseVersion(v1, v2 string) bool {
dots1 := strings.Count(v1, ".")
dots2 := strings.Count(v2, ".")
if dots1 != dots2 {
return dots1 > dots2
}
return v1 > v2
}

// IsCompatible reports whether pinVersion is semver-compatible with requestedVersion.
// Semver compatibility is defined as both versions sharing the same major version.
//
Expand Down
22 changes: 3 additions & 19 deletions pkg/workflow/action_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
"github.com/github/gh-aw/pkg/semverutil"
"github.com/github/gh-aw/pkg/setutil"
"github.com/github/gh-aw/pkg/sliceutil"
"github.com/github/gh-aw/pkg/stringutil"
Expand Down Expand Up @@ -640,9 +641,9 @@ func buildDedupKeyInfos(keys []string) []cacheKeyInfo {
}
slices.SortFunc(keyInfos, func(a, b cacheKeyInfo) int {
switch {
case isMorePreciseVersion(a.versionRef, b.versionRef):
case semverutil.IsMorePreciseVersion(a.versionRef, b.versionRef):
return -1
case isMorePreciseVersion(b.versionRef, a.versionRef):
case semverutil.IsMorePreciseVersion(b.versionRef, a.versionRef):
return 1
default:
return 0
Expand Down Expand Up @@ -716,20 +717,3 @@ func (c *ActionCache) PruneStaleGHAWEntries(currentVersion string, actionsRepoPr
actionCacheLog.Printf("Pruned %d stale gh-aw-actions entries, %d entries remaining", len(toDelete), len(c.Entries))
}
}

// isMorePreciseVersion returns true if v1 is more precise than v2
// For example: "v4.3.0" is more precise than "v4"
func isMorePreciseVersion(v1, v2 string) bool {
// Count the number of dots in each version string
// More dots means more precision
dots1 := strings.Count(v1, ".")
dots2 := strings.Count(v2, ".")

if dots1 != dots2 {
return dots1 > dots2
}

// If same number of dots, compare lexicographically
// This handles cases like "v1.2.3" vs "v1.2.10" correctly
return v1 > v2
}
5 changes: 3 additions & 2 deletions pkg/workflow/action_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"
"time"

"github.com/github/gh-aw/pkg/semverutil"
"github.com/github/gh-aw/pkg/testutil"
)

Expand Down Expand Up @@ -539,9 +540,9 @@ func TestIsMorePreciseVersion(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isMorePreciseVersion(tt.v1, tt.v2)
result := semverutil.IsMorePreciseVersion(tt.v1, tt.v2)
if result != tt.expected {
t.Errorf("isMorePreciseVersion(%q, %q) = %v, want %v", tt.v1, tt.v2, result, tt.expected)
t.Errorf("semverutil.IsMorePreciseVersion(%q, %q) = %v, want %v", tt.v1, tt.v2, result, tt.expected)
}
})
}
Expand Down
16 changes: 8 additions & 8 deletions pkg/workflow/testdata/TestWasmGolden_AllEngines/claude.golden

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions pkg/workflow/testdata/TestWasmGolden_AllEngines/codex.golden

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions pkg/workflow/testdata/TestWasmGolden_AllEngines/copilot.golden

Large diffs are not rendered by default.

Loading
Loading