diff --git a/cmd/cc-connect/update.go b/cmd/cc-connect/update.go index ab47cb7..b315e3b 100644 --- a/cmd/cc-connect/update.go +++ b/cmd/cc-connect/update.go @@ -537,10 +537,55 @@ func isNewer(latest, current string) bool { if cPre == "" && lPre != "" { return false } - // Both have pre-release: compare lexicographically (beta.2 > beta.1, rc.1 > beta.9) + // Both have pre-release: split on "." and compare each segment + // numerically where possible so beta.10 > beta.2. if lPre != "" && cPre != "" { - return lPre > cPre + return comparePreRelease(lPre, cPre) > 0 } return false } + +// comparePreRelease compares two pre-release strings segment by segment. +// Numeric segments are compared as integers; non-numeric segments are +// compared lexicographically. Returns >0 if a is greater, <0 if b is +// greater, 0 if equal. +func comparePreRelease(a, b string) int { + aParts := strings.Split(a, ".") + bParts := strings.Split(b, ".") + + max := len(aParts) + if len(bParts) > max { + max = len(bParts) + } + for i := 0; i < max; i++ { + var ap, bp string + if i < len(aParts) { + ap = aParts[i] + } + if i < len(bParts) { + bp = bParts[i] + } + + var an, bn int + aN, _ := fmt.Sscanf(ap, "%d", &an) + bN, _ := fmt.Sscanf(bp, "%d", &bn) + aIsNum := aN == 1 && fmt.Sprintf("%d", an) == ap + bIsNum := bN == 1 && fmt.Sprintf("%d", bn) == bp + + if aIsNum && bIsNum { + if an != bn { + return an - bn + } + continue + } + // Non-numeric: lexicographic + if ap < bp { + return -1 + } + if ap > bp { + return 1 + } + } + return 0 +} diff --git a/cmd/cc-connect/update_test.go b/cmd/cc-connect/update_test.go new file mode 100644 index 0000000..713cb83 --- /dev/null +++ b/cmd/cc-connect/update_test.go @@ -0,0 +1,41 @@ +package main + +import "testing" + +func TestIsNewer(t *testing.T) { + tests := []struct { + latest, current string + want bool + }{ + // Basic semver + {"v1.2.3", "v1.2.2", true}, + {"v1.2.2", "v1.2.3", false}, + {"v1.2.3", "v1.2.3", false}, + {"v2.0.0", "v1.9.9", true}, + + // Pre-release vs stable + {"v1.2.3", "v1.2.3-beta.1", true}, + {"v1.2.3-beta.1", "v1.2.3", false}, + + // Pre-release numeric ordering + {"v1.2.3-beta.10", "v1.2.3-beta.2", true}, + {"v1.2.3-beta.2", "v1.2.3-beta.10", false}, + {"v1.2.3-beta.2", "v1.2.3-beta.2", false}, + + // rc > beta lexicographically + {"v1.2.3-rc.1", "v1.2.3-beta.9", true}, + + // Dev builds always upgradeable + {"v1.0.0", "dev", true}, + + // Empty + {"", "v1.0.0", false}, + {"v1.0.0", "", false}, + } + for _, tt := range tests { + got := isNewer(tt.latest, tt.current) + if got != tt.want { + t.Errorf("isNewer(%q, %q) = %v, want %v", tt.latest, tt.current, got, tt.want) + } + } +}