| package version |
|
|
| import ( |
| "os" |
| "path/filepath" |
| "runtime" |
| "strconv" |
| "strings" |
| "sync" |
| ) |
|
|
| |
| |
| var BuildVersion = "" |
|
|
| var ( |
| currentOnce sync.Once |
| currentVal string |
| sourceVal string |
| ) |
|
|
| func Current() (value string, source string) { |
| currentOnce.Do(func() { |
| if build := strings.TrimSpace(BuildVersion); build != "" { |
| currentVal = normalize(build) |
| sourceVal = "build-ldflags" |
| return |
| } |
| if fv := readVersionFile(); fv != "" { |
| currentVal = normalize(fv) |
| sourceVal = "file:VERSION" |
| return |
| } |
|
|
| if vv := versionFromVercelEnv(); vv != "" { |
| currentVal = vv |
| sourceVal = "env:vercel" |
| return |
| } |
| currentVal = "dev" |
| sourceVal = "default" |
| }) |
| return currentVal, sourceVal |
| } |
|
|
| func readVersionFile() string { |
| candidates := []string{"VERSION"} |
| if wd, err := os.Getwd(); err == nil { |
| candidates = append(candidates, filepath.Join(wd, "VERSION")) |
| } |
| if _, file, _, ok := runtime.Caller(0); ok { |
| repoRoot := filepath.Clean(filepath.Join(filepath.Dir(file), "../..")) |
| candidates = append(candidates, filepath.Join(repoRoot, "VERSION")) |
| } |
| seen := map[string]struct{}{} |
| for _, c := range candidates { |
| c = filepath.Clean(strings.TrimSpace(c)) |
| if c == "" { |
| continue |
| } |
| if _, ok := seen[c]; ok { |
| continue |
| } |
| seen[c] = struct{}{} |
| b, err := os.ReadFile(c) |
| if err != nil { |
| continue |
| } |
| if v := strings.TrimSpace(string(b)); v != "" { |
| return v |
| } |
| } |
| return "" |
| } |
|
|
| func normalize(v string) string { |
| v = strings.TrimSpace(v) |
| if v == "" { |
| return "" |
| } |
| return strings.TrimPrefix(v, "v") |
| } |
|
|
| func Tag(v string) string { |
| v = normalize(v) |
| if v == "" || v == "dev" { |
| return v |
| } |
| if v[0] < '0' || v[0] > '9' { |
| return v |
| } |
| return "v" + v |
| } |
|
|
| func versionFromVercelEnv() string { |
| if tag := normalize(strings.TrimSpace(os.Getenv("VERCEL_GIT_COMMIT_TAG"))); tag != "" { |
| return tag |
| } |
| ref := strings.TrimSpace(os.Getenv("VERCEL_GIT_COMMIT_REF")) |
| sha := strings.TrimSpace(os.Getenv("VERCEL_GIT_COMMIT_SHA")) |
| if len(sha) > 7 { |
| sha = sha[:7] |
| } |
| ref = sanitizeVersionLabel(ref) |
| sha = sanitizeVersionLabel(sha) |
| if ref == "" && sha == "" { |
| return "" |
| } |
| if ref != "" && sha != "" { |
| return "preview-" + ref + "." + sha |
| } |
| if ref != "" { |
| return "preview-" + ref |
| } |
| return "preview-" + sha |
| } |
|
|
| func sanitizeVersionLabel(in string) string { |
| in = strings.TrimSpace(strings.ToLower(in)) |
| if in == "" { |
| return "" |
| } |
| var b strings.Builder |
| b.Grow(len(in)) |
| prevDash := false |
| for i := 0; i < len(in); i++ { |
| c := in[i] |
| if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { |
| b.WriteByte(c) |
| prevDash = false |
| continue |
| } |
| if !prevDash { |
| b.WriteByte('-') |
| prevDash = true |
| } |
| } |
| out := strings.Trim(b.String(), "-") |
| return out |
| } |
|
|
| func Compare(a, b string) int { |
| pa := parse(normalize(a)) |
| pb := parse(normalize(b)) |
| for i := 0; i < 3; i++ { |
| if pa[i] < pb[i] { |
| return -1 |
| } |
| if pa[i] > pb[i] { |
| return 1 |
| } |
| } |
| return 0 |
| } |
|
|
| func parse(v string) [3]int { |
| var out [3]int |
| parts := strings.SplitN(v, ".", 4) |
| for i := 0; i < 3 && i < len(parts); i++ { |
| n := readLeadingInt(parts[i]) |
| out[i] = n |
| } |
| return out |
| } |
|
|
| func readLeadingInt(s string) int { |
| s = strings.TrimSpace(s) |
| if s == "" { |
| return 0 |
| } |
| i := 0 |
| for ; i < len(s); i++ { |
| if s[i] < '0' || s[i] > '9' { |
| break |
| } |
| } |
| if i == 0 { |
| return 0 |
| } |
| n, err := strconv.Atoi(s[:i]) |
| if err != nil { |
| return 0 |
| } |
| return n |
| } |
|
|