Skip to content

Commit 18330d5

Browse files
authored
refactor(operations/bump): add BumpPre and refactor bump logic into helpers (#190)
1 parent b521fbc commit 18330d5

2 files changed

Lines changed: 372 additions & 42 deletions

File tree

internal/operations/bump.go

Lines changed: 148 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
125195
func (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

Comments
 (0)