@@ -19,6 +19,7 @@ const (
1919 BumpMajor BumpType = "major"
2020 BumpRelease BumpType = "release"
2121 BumpAuto BumpType = "auto"
22+ BumpPre BumpType = "pre"
2223)
2324
2425// BumpOperation performs a version bump on a module.
@@ -59,69 +60,174 @@ func (op *BumpOperation) Execute(ctx context.Context, mod *workspace.Module) err
5960 return fmt .Errorf ("failed to read version from %s: %w" , mod .Path , err )
6061 }
6162
62- // Perform the bump based on type
63- var newVer semver.SemVersion
63+ // Calculate the new version
64+ newVer , err := op .calculateNewVersion (currentVer )
65+ if err != nil {
66+ return err
67+ }
68+
69+ // BumpPre handles its own metadata, others use common logic
70+ if op .bumpType != BumpPre {
71+ op .applyPreReleaseAndMetadata (& newVer , currentVer )
72+ }
73+
74+ // Write the new version
75+ if err := vm .Save (ctx , mod .Path , newVer ); err != nil {
76+ return fmt .Errorf ("failed to write version to %s: %w" , mod .Path , err )
77+ }
78+
79+ // Update module's current version for display
80+ mod .CurrentVersion = newVer .String ()
81+
82+ return nil
83+ }
84+
85+ // calculateNewVersion computes the new version based on bump type.
86+ func (op * BumpOperation ) calculateNewVersion (currentVer semver.SemVersion ) (semver.SemVersion , error ) {
6487 switch op .bumpType {
6588 case BumpPatch :
66- newVer = semver.SemVersion {
67- Major : currentVer .Major ,
68- Minor : currentVer .Minor ,
69- Patch : currentVer .Patch + 1 ,
70- }
89+ return op .bumpPatch (currentVer ), nil
7190 case BumpMinor :
72- newVer = semver.SemVersion {
73- Major : currentVer .Major ,
74- Minor : currentVer .Minor + 1 ,
75- Patch : 0 ,
76- }
91+ return op .bumpMinor (currentVer ), nil
7792 case BumpMajor :
78- newVer = semver.SemVersion {
79- Major : currentVer .Major + 1 ,
80- Minor : 0 ,
81- Patch : 0 ,
82- }
93+ return op .bumpMajor (currentVer ), nil
8394 case BumpRelease :
84- // Release removes pre-release and build metadata
85- newVer = semver.SemVersion {
86- Major : currentVer .Major ,
87- Minor : currentVer .Minor ,
88- Patch : currentVer .Patch ,
89- }
95+ return op .bumpRelease (currentVer ), nil
9096 case BumpAuto :
91- // Auto bump uses heuristic-based logic
92- autoVer , autoErr := semver .BumpNextFunc (currentVer )
93- if autoErr != nil {
94- return fmt .Errorf ("auto bump failed: %w" , autoErr )
95- }
96- newVer = autoVer
97+ return op .bumpAuto (currentVer )
98+ case BumpPre :
99+ return op .bumpPre (currentVer )
97100 default :
98- return fmt .Errorf ("unknown bump type: %s" , op .bumpType )
101+ return semver.SemVersion {}, fmt .Errorf ("unknown bump type: %s" , op .bumpType )
102+ }
103+ }
104+
105+ func (op * BumpOperation ) bumpPatch (current semver.SemVersion ) semver.SemVersion {
106+ return semver.SemVersion {
107+ Major : current .Major ,
108+ Minor : current .Minor ,
109+ Patch : current .Patch + 1 ,
110+ }
111+ }
112+
113+ func (op * BumpOperation ) bumpMinor (current semver.SemVersion ) semver.SemVersion {
114+ return semver.SemVersion {
115+ Major : current .Major ,
116+ Minor : current .Minor + 1 ,
117+ Patch : 0 ,
99118 }
119+ }
100120
101- // Apply pre-release label if provided
121+ func (op * BumpOperation ) bumpMajor (current semver.SemVersion ) semver.SemVersion {
122+ return semver.SemVersion {
123+ Major : current .Major + 1 ,
124+ Minor : 0 ,
125+ Patch : 0 ,
126+ }
127+ }
128+
129+ func (op * BumpOperation ) bumpRelease (current semver.SemVersion ) semver.SemVersion {
130+ return semver.SemVersion {
131+ Major : current .Major ,
132+ Minor : current .Minor ,
133+ Patch : current .Patch ,
134+ }
135+ }
136+
137+ func (op * BumpOperation ) bumpAuto (current semver.SemVersion ) (semver.SemVersion , error ) {
138+ newVer , err := semver .BumpNextFunc (current )
139+ if err != nil {
140+ return semver.SemVersion {}, fmt .Errorf ("auto bump failed: %w" , err )
141+ }
142+ return newVer , nil
143+ }
144+
145+ func (op * BumpOperation ) bumpPre (current semver.SemVersion ) (semver.SemVersion , error ) {
146+ newVer := semver.SemVersion {
147+ Major : current .Major ,
148+ Minor : current .Minor ,
149+ Patch : current .Patch ,
150+ }
151+
152+ // Determine the pre-release value
153+ preRelease , err := op .calculatePreRelease (current )
154+ if err != nil {
155+ return semver.SemVersion {}, err
156+ }
157+ newVer .PreRelease = preRelease
158+
159+ // Apply metadata for pre-release bump
160+ op .applyMetadata (& newVer , current )
161+
162+ return newVer , nil
163+ }
164+
165+ // calculatePreRelease determines the pre-release string for BumpPre.
166+ func (op * BumpOperation ) calculatePreRelease (current semver.SemVersion ) (string , error ) {
167+ if op .preRelease != "" {
168+ return semver .IncrementPreRelease (current .PreRelease , op .preRelease ), nil
169+ }
170+ if current .PreRelease != "" {
171+ base := extractPreReleaseBase (current .PreRelease )
172+ return semver .IncrementPreRelease (current .PreRelease , base ), nil
173+ }
174+ return "" , fmt .Errorf ("current version has no pre-release; use --label to specify one" )
175+ }
176+
177+ // applyPreReleaseAndMetadata applies pre-release and metadata to the version.
178+ func (op * BumpOperation ) applyPreReleaseAndMetadata (newVer * semver.SemVersion , currentVer semver.SemVersion ) {
102179 if op .preRelease != "" {
103180 newVer .PreRelease = op .preRelease
104181 }
182+ op .applyMetadata (newVer , currentVer )
183+ }
105184
106- // Apply metadata
185+ // applyMetadata applies build metadata to the version.
186+ func (op * BumpOperation ) applyMetadata (newVer * semver.SemVersion , currentVer semver.SemVersion ) {
107187 if op .metadata != "" {
108188 newVer .Build = op .metadata
109189 } else if op .preserveMetadata && currentVer .Build != "" {
110190 newVer .Build = currentVer .Build
111191 }
112-
113- // Write the new version
114- if err := vm .Save (ctx , mod .Path , newVer ); err != nil {
115- return fmt .Errorf ("failed to write version to %s: %w" , mod .Path , err )
116- }
117-
118- // Update module's current version for display
119- mod .CurrentVersion = newVer .String ()
120-
121- return nil
122192}
123193
124194// Name returns the name of this operation.
125195func (op * BumpOperation ) Name () string {
126196 return fmt .Sprintf ("bump %s" , op .bumpType )
127197}
198+
199+ // extractPreReleaseBase extracts the base label from a pre-release string.
200+ // e.g., "rc.1" -> "rc", "beta.2" -> "beta", "alpha" -> "alpha", "rc1" -> "rc"
201+ func extractPreReleaseBase (pre string ) string {
202+ // First, check for dot followed by a number
203+ for i := len (pre ) - 1 ; i >= 0 ; i -- {
204+ if pre [i ] == '.' {
205+ // Check if everything after the dot is numeric
206+ suffix := pre [i + 1 :]
207+ isNumeric := true
208+ for _ , c := range suffix {
209+ if c < '0' || c > '9' {
210+ isNumeric = false
211+ break
212+ }
213+ }
214+ if isNumeric && len (suffix ) > 0 {
215+ return pre [:i ]
216+ }
217+ }
218+ }
219+
220+ // Check for trailing digits without dot (e.g., "rc1" -> "rc")
221+ lastNonDigit := - 1
222+ for i := len (pre ) - 1 ; i >= 0 ; i -- {
223+ if pre [i ] < '0' || pre [i ] > '9' {
224+ lastNonDigit = i
225+ break
226+ }
227+ }
228+ if lastNonDigit >= 0 && lastNonDigit < len (pre )- 1 {
229+ return pre [:lastNonDigit + 1 ]
230+ }
231+
232+ return pre
233+ }
0 commit comments