package semver import ( "fmt" "strings" goSemver "github.com/Masterminds/semver/v3" "github.com/thokra/stamp/internal/changeset" ) // Bump computes the next version given the current version string and a bump type. // preID is the pre-release identifier (e.g. "alpha", "beta", "rc") used for pre-* bumps. func Bump(current string, bump changeset.BumpType, preID string) (string, error) { v, err := goSemver.NewVersion(current) if err != nil { return "", fmt.Errorf("invalid version %q: %w", current, err) } if preID == "" { preID = "0" } switch bump { case changeset.BumpMajor: next := v.IncMajor() return next.Original(), nil case changeset.BumpMinor: next := v.IncMinor() return next.Original(), nil case changeset.BumpPatch: next := v.IncPatch() return next.Original(), nil case changeset.BumpPreMajor: next := v.IncMajor() pre, err := goSemver.NewVersion(fmt.Sprintf("%d.%d.%d-%s.0", next.Major(), next.Minor(), next.Patch(), preID)) if err != nil { return "", err } return pre.Original(), nil case changeset.BumpPreMinor: next := v.IncMinor() pre, err := goSemver.NewVersion(fmt.Sprintf("%d.%d.%d-%s.0", next.Major(), next.Minor(), next.Patch(), preID)) if err != nil { return "", err } return pre.Original(), nil case changeset.BumpPrePatch: next := v.IncPatch() pre, err := goSemver.NewVersion(fmt.Sprintf("%d.%d.%d-%s.0", next.Major(), next.Minor(), next.Patch(), preID)) if err != nil { return "", err } return pre.Original(), nil case changeset.BumpPreRelease: // If already a pre-release with matching identifier, increment the pre-release number. // Otherwise start a fresh pre-release on current patch. preStr := v.Prerelease() if preStr != "" && strings.HasPrefix(preStr, preID+".") { parts := strings.SplitN(preStr, ".", 2) var num int fmt.Sscanf(parts[1], "%d", &num) next, err := goSemver.NewVersion(fmt.Sprintf("%d.%d.%d-%s.%d", v.Major(), v.Minor(), v.Patch(), preID, num+1)) if err != nil { return "", err } return next.Original(), nil } // Start a new pre-release on next patch. patch := v.IncPatch() next, err := goSemver.NewVersion(fmt.Sprintf("%d.%d.%d-%s.0", patch.Major(), patch.Minor(), patch.Patch(), preID)) if err != nil { return "", err } return next.Original(), nil default: return "", fmt.Errorf("unknown bump type %q", bump) } } // HighestBump returns the highest-priority bump type from a set of bumps. // Priority: major > minor > patch > premajor > preminor > prepatch > prerelease. func HighestBump(bumps []changeset.BumpType) changeset.BumpType { priority := map[changeset.BumpType]int{ changeset.BumpMajor: 6, changeset.BumpMinor: 5, changeset.BumpPatch: 4, changeset.BumpPreMajor: 3, changeset.BumpPreMinor: 2, changeset.BumpPrePatch: 1, changeset.BumpPreRelease: 0, } best := changeset.BumpPreRelease bestP := -1 for _, b := range bumps { if p, ok := priority[b]; ok && p > bestP { best = b bestP = p } } return best } // ProjectBumps aggregates bump types per project from a list of changesets. func ProjectBumps(changesets []*changeset.Changeset) map[string][]changeset.BumpType { result := map[string][]changeset.BumpType{} for _, cs := range changesets { for project, bump := range cs.Bumps { result[project] = append(result[project], bump) } } return result }