mirror of
https://github.com/fosrl/newt.git
synced 2026-02-08 05:56:40 +00:00
Move updates
This commit is contained in:
173
updates/updates.go
Normal file
173
updates/updates.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package updates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GitHubRelease represents the GitHub API response for a release
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Name string `json:"name"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
}
|
||||
|
||||
// Version represents a semantic version
|
||||
type Version struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
}
|
||||
|
||||
// parseVersion parses a semantic version string (e.g., "v1.2.3" or "1.2.3")
|
||||
func parseVersion(versionStr string) (Version, error) {
|
||||
// Remove 'v' prefix if present
|
||||
versionStr = strings.TrimPrefix(versionStr, "v")
|
||||
|
||||
parts := strings.Split(versionStr, ".")
|
||||
if len(parts) != 3 {
|
||||
return Version{}, fmt.Errorf("invalid version format: %s", versionStr)
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("invalid major version: %s", parts[0])
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("invalid minor version: %s", parts[1])
|
||||
}
|
||||
|
||||
patch, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("invalid patch version: %s", parts[2])
|
||||
}
|
||||
|
||||
return Version{Major: major, Minor: minor, Patch: patch}, nil
|
||||
}
|
||||
|
||||
// isNewer returns true if v2 is newer than v1
|
||||
func (v1 Version) isNewer(v2 Version) bool {
|
||||
if v2.Major > v1.Major {
|
||||
return true
|
||||
}
|
||||
if v2.Major < v1.Major {
|
||||
return false
|
||||
}
|
||||
|
||||
if v2.Minor > v1.Minor {
|
||||
return true
|
||||
}
|
||||
if v2.Minor < v1.Minor {
|
||||
return false
|
||||
}
|
||||
|
||||
return v2.Patch > v1.Patch
|
||||
}
|
||||
|
||||
// String returns the version as a string
|
||||
func (v Version) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||
}
|
||||
|
||||
// CheckForUpdate checks GitHub for a newer version and prints an update banner if found
|
||||
func CheckForUpdate(owner, repo, currentVersion string) error {
|
||||
if currentVersion == "version_replaceme" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GitHub API URL for latest release
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
|
||||
|
||||
// Create HTTP client with timeout
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// Make the request
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch release info: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("GitHub API returned status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
var release GitHubRelease
|
||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||
return fmt.Errorf("failed to parse release info: %w", err)
|
||||
}
|
||||
|
||||
// Parse current and latest versions
|
||||
currentVer, err := parseVersion(currentVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid current version: %w", err)
|
||||
}
|
||||
|
||||
latestVer, err := parseVersion(release.TagName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid latest version: %w", err)
|
||||
}
|
||||
|
||||
// Check if update is available
|
||||
if currentVer.isNewer(latestVer) {
|
||||
printUpdateBanner(currentVer.String(), latestVer.String(), release.HTMLURL)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// printUpdateBanner prints a colorful update notification banner
|
||||
func printUpdateBanner(currentVersion, latestVersion, releaseURL string) {
|
||||
const contentWidth = 70 // width between the border lines
|
||||
|
||||
borderTop := "╔" + strings.Repeat("═", contentWidth) + "╗"
|
||||
borderMid := "╠" + strings.Repeat("═", contentWidth) + "╣"
|
||||
borderBot := "╚" + strings.Repeat("═", contentWidth) + "╝"
|
||||
emptyLine := "║" + strings.Repeat(" ", contentWidth) + "║"
|
||||
|
||||
lines := []string{
|
||||
borderTop,
|
||||
"║" + centerText("UPDATE AVAILABLE", contentWidth) + "║",
|
||||
borderMid,
|
||||
emptyLine,
|
||||
"║ Current Version: " + padRight(currentVersion, contentWidth-19) + "║",
|
||||
"║ Latest Version: " + padRight(latestVersion, contentWidth-19) + "║",
|
||||
emptyLine,
|
||||
"║ A newer version is available! Please update to get the" + padRight("", contentWidth-56) + "║",
|
||||
"║ latest features, bug fixes, and security improvements." + padRight("", contentWidth-56) + "║",
|
||||
emptyLine,
|
||||
"║ Release URL: " + padRight(releaseURL, contentWidth-15) + "║",
|
||||
emptyLine,
|
||||
borderBot,
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
|
||||
// padRight pads s with spaces on the right to the given width
|
||||
func padRight(s string, width int) string {
|
||||
if len(s) > width {
|
||||
return s[:width]
|
||||
}
|
||||
return s + strings.Repeat(" ", width-len(s))
|
||||
}
|
||||
|
||||
// centerText centers s in a field of width w
|
||||
func centerText(s string, w int) string {
|
||||
if len(s) >= w {
|
||||
return s[:w]
|
||||
}
|
||||
padding := (w - len(s)) / 2
|
||||
return strings.Repeat(" ", padding) + s + strings.Repeat(" ", w-len(s)-padding)
|
||||
}
|
||||
Reference in New Issue
Block a user