@@ -6,7 +6,11 @@ import (
66 "fmt"
77 "log/slog"
88 "os"
9+ "sort"
10+ "strconv"
11+ "strings"
912
13+ "github.com/Masterminds/semver"
1014 "github.com/go-git/go-billy/v5"
1115 "github.com/rancher/charts-build-scripts/pkg/filesystem"
1216 "github.com/rancher/charts-build-scripts/pkg/logger"
@@ -41,8 +45,8 @@ func CreateOrUpdateHelmIndex(ctx context.Context, rootFs billy.Filesystem) error
4145 }
4246
4347 // Sort entries to ensure consistent ordering
44- helmIndexFile . SortEntries ( )
45- newHelmIndexFile . SortEntries ( )
48+ SortVersions ( helmIndexFile )
49+ SortVersions ( newHelmIndexFile )
4650
4751 // Update index
4852 helmIndexFile , upToDate := UpdateIndex (ctx , helmIndexFile , newHelmIndexFile )
@@ -128,3 +132,85 @@ func OpenIndexYaml(ctx context.Context, rootFs billy.Filesystem) (*helmRepo.Inde
128132
129133 return helmRepo .LoadIndexFile (helmIndexFilePath )
130134}
135+
136+ // SortVersions sorts chart versions with custom RC handling
137+ func SortVersions (index * helmRepo.IndexFile ) {
138+ for _ , versions := range index .Entries {
139+ sort .Slice (versions , func (i , j int ) bool {
140+ return compareVersions (versions [i ].Version , versions [j ].Version )
141+ })
142+ }
143+ }
144+
145+ // compareVersions compares two version strings for sorting
146+ // Returns true if versionA should come before versionB (descending order)
147+ func compareVersions (versionA , versionB string ) bool {
148+ // Parse both versions
149+ baseA , rcA , isRCA := parseVersionWithRC (versionA )
150+ baseB , rcB , isRCB := parseVersionWithRC (versionB )
151+
152+ // Parse base versions using semver
153+ semverA , errA := semver .NewVersion (baseA )
154+ semverB , errB := semver .NewVersion (baseB )
155+
156+ if errA != nil {
157+ return false // push invalid to end
158+ }
159+ if errB != nil {
160+ return true // push invalid to end
161+ }
162+
163+ // If base versions are different, use semver comparison (descending)
164+ if ! semverA .Equal (semverB ) {
165+ return semverA .GreaterThan (semverB )
166+ }
167+
168+ // Same base version - handle RC logic
169+ // Stable (non-RC) should come first
170+ if ! isRCA && isRCB {
171+ return true // A is stable, B is RC - A comes first
172+ }
173+ if isRCA && ! isRCB {
174+ return false // A is RC, B is stable - B comes first
175+ }
176+
177+ // Both are RCs - higher RC number comes first (descending)
178+ if isRCA && isRCB {
179+ return rcA > rcB
180+ }
181+
182+ // Both are stable with same base version - they're equal
183+ return false
184+ }
185+
186+ // parseVersionWithRC extracts the base version and RC number from a version string
187+ // Example: "108.0.0+up0.9.0-rc.1" returns ("108.0.0+up0.9.0", 1, true)
188+ func parseVersionWithRC (version string ) (baseVersion string , rcNumber int , isRC bool ) {
189+ // Split by '+' to separate version from build metadata
190+ parts := strings .Split (version , "+" )
191+ if len (parts ) != 2 {
192+ return version , 0 , false
193+ }
194+
195+ baseVersionNum := parts [0 ]
196+ buildMetadata := parts [1 ]
197+
198+ // Check if build metadata contains RC
199+ if ! strings .Contains (buildMetadata , "-rc." ) {
200+ return version , 0 , false
201+ }
202+
203+ // Extract RC number
204+ rcParts := strings .Split (buildMetadata , "-rc." )
205+ if len (rcParts ) != 2 {
206+ return version , 0 , false
207+ }
208+
209+ rcNum , err := strconv .Atoi (rcParts [1 ])
210+ if err != nil {
211+ return version , 0 , false
212+ }
213+
214+ // Return base version with the non-RC part of build metadata
215+ return baseVersionNum + "+" + rcParts [0 ], rcNum , true
216+ }
0 commit comments