Skip to content

Commit 924d5cc

Browse files
committed
fix: filter prereleases by semver tag instead of trusting GitHub's pinky promise
Replace the GitHub API prerelease boolean check with semver.Version.Prerelease() filtering — because the version string is the source of truth, not metadata. Also: - Extract highestRelease() to eliminate 3x copy-pasted loops - Extract formatVersion() for the repeated Sprintf pattern - Fix pre-existing bug: missing continue after semver parse errors caused stale version from prior iteration to be used - Add table-driven tests for both helpers (there were none before)
1 parent 416eac4 commit 924d5cc

2 files changed

Lines changed: 140 additions & 65 deletions

File tree

scripts/coderversion/main.go

Lines changed: 31 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,90 +12,57 @@ import (
1212
func main() {
1313
releases := fetchReleases()
1414

15-
mainlineVer := semver.MustParse("v0.0.0")
16-
for _, rel := range releases {
17-
if rel == "" {
18-
debug("ignoring untagged version %s\n", rel)
19-
continue
20-
}
21-
22-
ver, err := semver.NewVersion(rel)
23-
if err != nil {
24-
debug("skipping invalid version %s\n", rel)
25-
}
15+
mainlineVer := highestRelease(releases, nil)
16+
_, _ = fmt.Fprintf(os.Stdout, "CODER_MAINLINE_VERSION=%q\n", formatVersion(mainlineVer))
2617

27-
if ver.Compare(mainlineVer) > 0 {
28-
mainlineVer = ver
29-
continue
30-
}
18+
stableMinor := mainlineVer.Minor() - 1
19+
if stableMinor < 0 {
20+
stableMinor = 0
3121
}
22+
debug("expected stable minor: %d\n", stableMinor)
23+
stableVer := highestRelease(releases, &stableMinor)
24+
_, _ = fmt.Fprintf(os.Stdout, "CODER_STABLE_VERSION=%q\n", formatVersion(stableVer))
3225

33-
mainline := fmt.Sprintf("v%d.%d.%d", mainlineVer.Major(), mainlineVer.Minor(), mainlineVer.Patch())
34-
_, _ = fmt.Fprintf(os.Stdout, "CODER_MAINLINE_VERSION=%q\n", mainline)
35-
36-
expectedStableMinor := mainlineVer.Minor() - 1
37-
if expectedStableMinor < 0 {
38-
expectedStableMinor = 0
26+
oldStableMinor := mainlineVer.Minor() - 2
27+
if oldStableMinor < 0 {
28+
oldStableMinor = 0
3929
}
40-
debug("expected stable minor: %d\n", expectedStableMinor)
41-
stableVer := semver.MustParse("v0.0.0")
30+
debug("expected old stable minor: %d\n", oldStableMinor)
31+
oldStableVer := highestRelease(releases, &oldStableMinor)
32+
_, _ = fmt.Fprintf(os.Stdout, "CODER_OLDSTABLE_VERSION=%q\n", formatVersion(oldStableVer))
33+
}
34+
35+
// highestRelease returns the highest non-prerelease semver from releases,
36+
// optionally filtered to a specific minor version.
37+
func highestRelease(releases []string, filterMinor *int64) *semver.Version {
38+
best := semver.MustParse("v0.0.0")
4239
for _, rel := range releases {
43-
debug("check version %s\n", rel)
4440
if rel == "" {
45-
debug("ignoring untagged version %s\n", rel)
4641
continue
4742
}
48-
4943
ver, err := semver.NewVersion(rel)
5044
if err != nil {
5145
debug("skipping invalid version %s\n", rel)
52-
}
53-
54-
if ver.Minor() != expectedStableMinor {
55-
debug("skipping version %s\n", rel)
56-
continue
57-
}
58-
59-
if ver.Compare(stableVer) > 0 {
60-
stableVer = ver
6146
continue
6247
}
63-
}
64-
65-
stable := fmt.Sprintf("v%d.%d.%d", stableVer.Major(), stableVer.Minor(), stableVer.Patch())
66-
_, _ = fmt.Fprintf(os.Stdout, "CODER_STABLE_VERSION=%q\n", stable)
67-
68-
expectedOldStableMinor := mainlineVer.Minor() - 2
69-
if expectedOldStableMinor < 0 {
70-
expectedOldStableMinor = 0
71-
}
72-
debug("expected old stable minor: %d\n", expectedStableMinor)
73-
oldStableVer := semver.MustParse("v0.0.0")
74-
for _, rel := range releases {
75-
debug("check version %s\n", rel)
76-
if rel == "" {
77-
debug("ignoring untagged version %s\n", rel)
48+
if ver.Prerelease() != "" {
49+
debug("skipping prerelease version %s\n", rel)
7850
continue
7951
}
80-
81-
ver, err := semver.NewVersion(rel)
82-
if err != nil {
83-
debug("skipping invalid version %s\n", rel)
84-
}
85-
86-
if ver.Minor() != expectedOldStableMinor {
87-
debug("skipping version %s\n", rel)
52+
if filterMinor != nil && ver.Minor() != *filterMinor {
8853
continue
8954
}
90-
91-
if ver.Compare(oldStableVer) > 0 {
92-
oldStableVer = ver
93-
continue
55+
if ver.Compare(best) > 0 {
56+
best = ver
9457
}
9558
}
59+
return best
60+
}
9661

97-
oldStable := fmt.Sprintf("v%d.%d.%d", oldStableVer.Major(), oldStableVer.Minor(), oldStableVer.Patch())
98-
_, _ = fmt.Fprintf(os.Stdout, "CODER_OLDSTABLE_VERSION=%q\n", oldStable)
62+
// formatVersion formats a semver version as "vMAJOR.MINOR.PATCH",
63+
// stripping any prerelease or build metadata.
64+
func formatVersion(v *semver.Version) string {
65+
return fmt.Sprintf("v%d.%d.%d", v.Major(), v.Minor(), v.Patch())
9966
}
10067

10168
type release struct {
@@ -123,7 +90,6 @@ func fetchReleases() []string {
12390
for _, rel := range releases {
12491
if rel.TagName != "" {
12592
ss = append(ss, rel.TagName)
126-
12793
}
12894
}
12995
return ss

scripts/coderversion/main_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/masterminds/semver"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func int64Ptr(v int64) *int64 { return &v }
12+
13+
func TestHighestRelease(t *testing.T) {
14+
t.Parallel()
15+
16+
tests := []struct {
17+
name string
18+
releases []string
19+
filterMinor *int64
20+
expected string
21+
}{
22+
{
23+
name: "skips RC versions",
24+
releases: []string{"v2.32.0-rc.0", "v2.31.2", "v2.31.1"},
25+
expected: "v2.31.2",
26+
},
27+
{
28+
name: "skips all prerelease variants",
29+
releases: []string{"v2.32.0-rc.0", "v2.32.0-beta.1", "v2.31.0"},
30+
expected: "v2.31.0",
31+
},
32+
{
33+
name: "returns highest when no prereleases",
34+
releases: []string{"v2.31.2", "v2.31.1", "v2.30.0"},
35+
expected: "v2.31.2",
36+
},
37+
{
38+
name: "filters by minor version",
39+
releases: []string{"v2.32.1", "v2.31.3", "v2.31.2"},
40+
filterMinor: int64Ptr(31),
41+
expected: "v2.31.3",
42+
},
43+
{
44+
name: "filters by minor AND skips prerelease",
45+
releases: []string{"v2.31.0-rc.1", "v2.31.2", "v2.30.0"},
46+
filterMinor: int64Ptr(31),
47+
expected: "v2.31.2",
48+
},
49+
{
50+
name: "skips invalid version strings",
51+
releases: []string{"v2.31.0", "not-a-version", "v2.30.0"},
52+
expected: "v2.31.0",
53+
},
54+
{
55+
name: "handles empty strings",
56+
releases: []string{"", "v2.31.0"},
57+
expected: "v2.31.0",
58+
},
59+
{
60+
name: "all prereleases returns v0.0.0",
61+
releases: []string{"v2.32.0-rc.0", "v2.31.0-beta.1"},
62+
expected: "v0.0.0",
63+
},
64+
{
65+
name: "empty input returns v0.0.0",
66+
releases: []string{},
67+
expected: "v0.0.0",
68+
},
69+
}
70+
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
t.Parallel()
74+
got := highestRelease(tt.releases, tt.filterMinor)
75+
require.NotNil(t, got)
76+
expected := semver.MustParse(tt.expected)
77+
assert.Equal(t, 0, got.Compare(expected), "expected %s but got %s", tt.expected, got.Original())
78+
})
79+
}
80+
}
81+
82+
func TestFormatVersion(t *testing.T) {
83+
t.Parallel()
84+
85+
tests := []struct {
86+
name string
87+
input string
88+
expected string
89+
}{
90+
{
91+
name: "standard version",
92+
input: "v2.31.2",
93+
expected: "v2.31.2",
94+
},
95+
{
96+
name: "zero version",
97+
input: "v0.0.0",
98+
expected: "v0.0.0",
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
t.Parallel()
105+
v := semver.MustParse(tt.input)
106+
assert.Equal(t, tt.expected, formatVersion(v))
107+
})
108+
}
109+
}

0 commit comments

Comments
 (0)