Feat v0.5.4 package updates (#34)
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-sqlite (push) Successful in 2m41s
CI/CD / test-go-pg (push) Successful in 2m42s
CI/CD / build-and-deploy (push) Successful in 29s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #34.
This commit is contained in:
2026-03-30 19:31:45 +00:00
committed by xcaliber
parent 8092f00fbe
commit 4da25350ac
15 changed files with 1294 additions and 32 deletions

97
server/handlers/semver.go Normal file
View File

@@ -0,0 +1,97 @@
package handlers
import (
"fmt"
"strconv"
"strings"
)
// Semver represents a parsed semantic version (major.minor.patch[-prerelease]).
type Semver struct {
Major int
Minor int
Patch int
Prerelease string
}
// ParseSemver parses a version string like "1.2.3" or "v1.2.3-beta.1".
// Leading "v" is stripped. Prerelease suffix after "-" is preserved.
func ParseSemver(s string) (Semver, error) {
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "v")
if s == "" {
return Semver{}, fmt.Errorf("empty version string")
}
// Split off prerelease suffix
var pre string
if idx := strings.IndexByte(s, '-'); idx >= 0 {
pre = s[idx+1:]
s = s[:idx]
}
parts := strings.Split(s, ".")
if len(parts) != 3 {
return Semver{}, fmt.Errorf("version %q must have exactly 3 parts (major.minor.patch)", s)
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return Semver{}, fmt.Errorf("invalid major version %q: %w", parts[0], err)
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return Semver{}, fmt.Errorf("invalid minor version %q: %w", parts[1], err)
}
patch, err := strconv.Atoi(parts[2])
if err != nil {
return Semver{}, fmt.Errorf("invalid patch version %q: %w", parts[2], err)
}
return Semver{Major: major, Minor: minor, Patch: patch, Prerelease: pre}, nil
}
// Compare returns -1 if a < b, 0 if a == b, +1 if a > b.
// Prerelease versions sort lower than the same release version (per semver spec).
func (a Semver) Compare(b Semver) int {
if a.Major != b.Major {
return cmpInt(a.Major, b.Major)
}
if a.Minor != b.Minor {
return cmpInt(a.Minor, b.Minor)
}
if a.Patch != b.Patch {
return cmpInt(a.Patch, b.Patch)
}
// Same numeric version — prerelease < release
if a.Prerelease == b.Prerelease {
return 0
}
if a.Prerelease == "" {
return 1 // a is release, b has prerelease → a > b
}
if b.Prerelease == "" {
return -1 // a has prerelease, b is release → a < b
}
// Both have prerelease — compare lexicographically
if a.Prerelease < b.Prerelease {
return -1
}
return 1
}
// String returns the version in "major.minor.patch[-prerelease]" format.
func (v Semver) String() string {
s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.Prerelease != "" {
s += "-" + v.Prerelease
}
return s
}
func cmpInt(a, b int) int {
if a < b {
return -1
}
return 1
}