- Remove `os` import from internal/ax/ax.go; replace os.Getwd() with
syscall.Getwd(), os.MkdirAll() with coreio.Local.EnsureDir(), and
os.Chmod() with syscall.Chmod()
- Remove `os` import from pkg/sdk/generators/typescript_test.go;
replace os.PathListSeparator and os.Getenv() with core.Env("PS")
and core.Env("PATH")
- Replace all "Usage example: call/declare ... from integrating code"
placeholder comments with concrete code examples across 45 files
covering build, release, sdk, signing, publishers, builders, and cmd
Co-Authored-By: Virgil <virgil@lethean.io>
222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
// Package release provides release automation with changelog generation and publishing.
|
|
package release
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"dappco.re/go/core"
|
|
"dappco.re/go/core/build/internal/ax"
|
|
coreerr "dappco.re/go/core/log"
|
|
)
|
|
|
|
// semverRegex matches semantic version strings with or without 'v' prefix.
|
|
var semverRegex = regexp.MustCompile(`^v?(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$`)
|
|
|
|
// DetermineVersion determines the version for a release.
|
|
// It checks in order:
|
|
// 1. Git tag on HEAD
|
|
// 2. Most recent tag + increment patch
|
|
// 3. Default to v0.0.1 if no tags exist
|
|
//
|
|
// version, err := release.DetermineVersion(".") // → "v1.2.4"
|
|
func DetermineVersion(dir string) (string, error) {
|
|
return DetermineVersionWithContext(context.Background(), dir)
|
|
}
|
|
|
|
// DetermineVersionWithContext determines the version while honouring caller cancellation.
|
|
// It checks in order:
|
|
// 1. Git tag on HEAD
|
|
// 2. Most recent tag + increment patch
|
|
// 3. Default to v0.0.1 if no tags exist
|
|
//
|
|
// version, err := release.DetermineVersionWithContext(ctx, ".") // → "v1.2.4"
|
|
func DetermineVersionWithContext(ctx context.Context, dir string) (string, error) {
|
|
// Check if HEAD has a tag
|
|
headTag, err := getTagOnHeadWithContext(ctx, dir)
|
|
if err == nil && headTag != "" {
|
|
return normalizeVersion(headTag), nil
|
|
}
|
|
if err != nil && ctx.Err() != nil {
|
|
return "", coreerr.E("release.DetermineVersionWithContext", "version lookup cancelled", ctx.Err())
|
|
}
|
|
|
|
// Get most recent tag
|
|
latestTag, err := getLatestTagWithContext(ctx, dir)
|
|
if err != nil && ctx.Err() != nil {
|
|
return "", coreerr.E("release.DetermineVersionWithContext", "version lookup cancelled", ctx.Err())
|
|
}
|
|
if err != nil || latestTag == "" {
|
|
// No tags exist, return default
|
|
return "v0.0.1", nil
|
|
}
|
|
|
|
// Increment patch version
|
|
return IncrementVersion(latestTag), nil
|
|
}
|
|
|
|
// IncrementVersion increments the patch version of a semver string.
|
|
// - "v1.2.3" → "v1.2.4"
|
|
// - "1.2.3" → "v1.2.4"
|
|
// - "v1.2.3-alpha" → "v1.2.4" (strips prerelease)
|
|
//
|
|
// next := release.IncrementVersion("v1.2.3") // → "v1.2.4"
|
|
func IncrementVersion(current string) string {
|
|
matches := semverRegex.FindStringSubmatch(current)
|
|
if matches == nil {
|
|
// Not a valid semver, return as-is with increment suffix
|
|
return current + ".1"
|
|
}
|
|
|
|
major, _ := strconv.Atoi(matches[1])
|
|
minor, _ := strconv.Atoi(matches[2])
|
|
patch, _ := strconv.Atoi(matches[3])
|
|
|
|
// Increment patch
|
|
patch++
|
|
|
|
return core.Sprintf("v%d.%d.%d", major, minor, patch)
|
|
}
|
|
|
|
// IncrementMinor increments the minor version of a semver string.
|
|
// - "v1.2.3" → "v1.3.0"
|
|
// - "1.2.3" → "v1.3.0"
|
|
//
|
|
// next := release.IncrementMinor("v1.2.3") // → "v1.3.0"
|
|
func IncrementMinor(current string) string {
|
|
matches := semverRegex.FindStringSubmatch(current)
|
|
if matches == nil {
|
|
return current + ".1"
|
|
}
|
|
|
|
major, _ := strconv.Atoi(matches[1])
|
|
minor, _ := strconv.Atoi(matches[2])
|
|
|
|
// Increment minor, reset patch
|
|
minor++
|
|
|
|
return core.Sprintf("v%d.%d.0", major, minor)
|
|
}
|
|
|
|
// IncrementMajor increments the major version of a semver string.
|
|
// - "v1.2.3" → "v2.0.0"
|
|
// - "1.2.3" → "v2.0.0"
|
|
//
|
|
// next := release.IncrementMajor("v1.2.3") // → "v2.0.0"
|
|
func IncrementMajor(current string) string {
|
|
matches := semverRegex.FindStringSubmatch(current)
|
|
if matches == nil {
|
|
return current + ".1"
|
|
}
|
|
|
|
major, _ := strconv.Atoi(matches[1])
|
|
|
|
// Increment major, reset minor and patch
|
|
major++
|
|
|
|
return core.Sprintf("v%d.0.0", major)
|
|
}
|
|
|
|
// ParseVersion parses a semver string into its components.
|
|
// Returns (major, minor, patch, prerelease, build, error).
|
|
//
|
|
// major, minor, patch, pre, build, err := release.ParseVersion("v1.2.3-alpha+001")
|
|
func ParseVersion(version string) (int, int, int, string, string, error) {
|
|
matches := semverRegex.FindStringSubmatch(version)
|
|
if matches == nil {
|
|
return 0, 0, 0, "", "", coreerr.E("release.ParseVersion", "invalid semver: "+version, nil)
|
|
}
|
|
|
|
major, _ := strconv.Atoi(matches[1])
|
|
minor, _ := strconv.Atoi(matches[2])
|
|
patch, _ := strconv.Atoi(matches[3])
|
|
prerelease := matches[4]
|
|
build := matches[5]
|
|
|
|
return major, minor, patch, prerelease, build, nil
|
|
}
|
|
|
|
// ValidateVersion checks if a string is a valid semver.
|
|
//
|
|
// if release.ValidateVersion("v1.2.3") { ... }
|
|
func ValidateVersion(version string) bool {
|
|
return semverRegex.MatchString(version)
|
|
}
|
|
|
|
// normalizeVersion ensures the version starts with 'v'.
|
|
func normalizeVersion(version string) string {
|
|
if !core.HasPrefix(version, "v") {
|
|
return "v" + version
|
|
}
|
|
return version
|
|
}
|
|
|
|
func getTagOnHeadWithContext(ctx context.Context, dir string) (string, error) {
|
|
output, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--exact-match", "HEAD")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return core.Trim(output), nil
|
|
}
|
|
|
|
func getLatestTagWithContext(ctx context.Context, dir string) (string, error) {
|
|
output, err := ax.RunDir(ctx, dir, "git", "describe", "--tags", "--abbrev=0")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return core.Trim(output), nil
|
|
}
|
|
|
|
// CompareVersions compares two semver strings.
|
|
// Returns -1 if a < b, 0 if a == b, 1 if a > b.
|
|
//
|
|
// result := release.CompareVersions("v1.2.3", "v1.2.4") // → -1
|
|
func CompareVersions(a, b string) int {
|
|
aMajor, aMinor, aPatch, _, _, errA := ParseVersion(a)
|
|
bMajor, bMinor, bPatch, _, _, errB := ParseVersion(b)
|
|
|
|
// Invalid versions are considered less than valid ones
|
|
if errA != nil && errB != nil {
|
|
switch {
|
|
case a < b:
|
|
return -1
|
|
case a > b:
|
|
return 1
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
if errA != nil {
|
|
return -1
|
|
}
|
|
if errB != nil {
|
|
return 1
|
|
}
|
|
|
|
// Compare major
|
|
if aMajor != bMajor {
|
|
if aMajor < bMajor {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// Compare minor
|
|
if aMinor != bMinor {
|
|
if aMinor < bMinor {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// Compare patch
|
|
if aPatch != bPatch {
|
|
if aPatch < bPatch {
|
|
return -1
|
|
}
|
|
return 1
|
|
}
|
|
|
|
return 0
|
|
}
|