feat: initial stamp implementation
- stamp add: create .stamp/*.md changeset files (interactive + --no-interactive) - stamp status: show pending stamps and projected next versions (plain + --json) - stamp version: consume stamps, bump semver, update CHANGELOGs, commit - stamp publish: create git tags, GitHub/Gitea releases, upload artifacts - stamp comment: post/update idempotent PR comments via GitHub/Gitea API - Supports monorepos via stamp.toml [[projects]] blocks - Changeset files support YAML (---) and TOML (+++) frontmatter - Semver + pre-release support (alpha, beta, rc) - Examples and workflow documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
123
internal/semver/semver.go
Normal file
123
internal/semver/semver.go
Normal file
@@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user