fix(version): resolve invalid semantic version handling
The change improves version checking reliability by: - Adding proper handling for empty and dev versions - Ensuring version strings are properly prefixed with 'v' - Moving version checking to use proxy URL instead of direct GitHub API - Restructuring version info to include proxy URL configuration
- commit
- 1358ff2755c9a2042a4cc944e5e72d00d84f8c5f
- author
- Brice · · 2024-12-08 10:30 UTC
- parent
- 3c49eb9
- signature
- unsigned
3 files · +91 -74
M cmd/gordon.go +3 -2
| @@ -10,7 +10,6 @@ import ( | |||
| 10 | 10 | "github.com/bnema/gordon/internal/server" | |
| 11 | 11 | "github.com/spf13/cobra" | |
| 12 | 12 | ||
| 13 | - | // env auto load | |
| 14 | 13 | _ "github.com/joho/godotenv/autoload" | |
| 15 | 14 | ) | |
| 16 | 15 | ||
| @@ -35,6 +34,8 @@ func Execute(client *cli.App, server *server.App) { | |||
| 35 | 34 | } | |
| 36 | 35 | ||
| 37 | 36 | func ExecuteCLI(build, commit, date string) { | |
| 37 | + | versionInfo := common.GetVersionInfo(build, commit, date, proxyURL) | |
| 38 | + | ||
| 38 | 39 | buildInfo := &common.BuildConfig{ | |
| 39 | 40 | BuildVersion: build, | |
| 40 | 41 | BuildCommit: commit, | |
| @@ -54,7 +55,7 @@ func ExecuteCLI(build, commit, date string) { | |||
| 54 | 55 | common.DockerInit(&s.Config.ContainerEngine) | |
| 55 | 56 | ||
| 56 | 57 | // Start periodic version checking in the background | |
| 57 | - | go common.CheckVersionPeriodically(buildInfo.BuildVersion, 3*time.Hour) | |
| 58 | + | go common.CheckVersionPeriodically(versionInfo, 3*time.Hour) | |
| 58 | 59 | ||
| 59 | 60 | Execute(a, s) | |
| 60 | 61 | } | |
| @@ -10,7 +10,6 @@ import ( | |||||
| 10 | "github.com/bnema/gordon/internal/server" | 10 | "github.com/bnema/gordon/internal/server" | ||
| 11 | "github.com/spf13/cobra" | 11 | "github.com/spf13/cobra" | ||
| 12 | 12 | ||||
| 13 | - | // env auto load | |||
| 14 | _ "github.com/joho/godotenv/autoload" | 13 | _ "github.com/joho/godotenv/autoload" | ||
| 15 | ) | 14 | ) | ||
| 16 | 15 | ||||
| @@ -35,6 +34,8 @@ func Execute(client *cli.App, server *server.App) { | |||||
| 35 | } | 34 | } | ||
| 36 | 35 | ||||
| 37 | func ExecuteCLI(build, commit, date string) { | 36 | func ExecuteCLI(build, commit, date string) { | ||
| 37 | + | versionInfo := common.GetVersionInfo(build, commit, date, proxyURL) | |||
| 38 | + | ||||
| 38 | buildInfo := &common.BuildConfig{ | 39 | buildInfo := &common.BuildConfig{ | ||
| 39 | BuildVersion: build, | 40 | BuildVersion: build, | ||
| 40 | BuildCommit: commit, | 41 | BuildCommit: commit, | ||
| @@ -54,7 +55,7 @@ func ExecuteCLI(build, commit, date string) { | |||||
| 54 | common.DockerInit(&s.Config.ContainerEngine) | 55 | common.DockerInit(&s.Config.ContainerEngine) | ||
| 55 | 56 | ||||
| 56 | // Start periodic version checking in the background | 57 | // Start periodic version checking in the background | ||
| 57 | - | go common.CheckVersionPeriodically(buildInfo.BuildVersion, 3*time.Hour) | 58 | + | go common.CheckVersionPeriodically(versionInfo, 3*time.Hour) |
| 58 | 59 | ||||
| 59 | Execute(a, s) | 60 | Execute(a, s) | ||
| 60 | } | 61 | } | ||
M internal/cli/cmd/version.go +14 -9
| @@ -8,28 +8,33 @@ import ( | |||
| 8 | 8 | "github.com/spf13/cobra" | |
| 9 | 9 | ) | |
| 10 | 10 | ||
| 11 | - | func NewVersionCommand(a *cli.App) *cobra.Command { | |
| 12 | - | return &cobra.Command{ | |
| 11 | + | func NewVersionCommand(app *cli.App) *cobra.Command { | |
| 12 | + | cmd := &cobra.Command{ | |
| 13 | 13 | Use: "version", | |
| 14 | - | Short: "Display the version of Gordon", | |
| 14 | + | Short: "Print version information", | |
| 15 | 15 | Run: func(cmd *cobra.Command, args []string) { | |
| 16 | 16 | info := common.GetVersionInfo( | |
| 17 | - | a.Config.Build.BuildVersion, | |
| 18 | - | a.Config.Build.BuildCommit, | |
| 19 | - | a.Config.Build.BuildDate, | |
| 17 | + | app.Config.Build.BuildVersion, | |
| 18 | + | app.Config.Build.BuildCommit, | |
| 19 | + | app.Config.Build.BuildDate, | |
| 20 | + | app.Config.Build.ProxyURL, | |
| 20 | 21 | ) | |
| 21 | 22 | fmt.Println(info.String()) | |
| 22 | 23 | ||
| 23 | - | // Check for updates | |
| 24 | - | hasUpdate, latestVersion, err := common.CheckForNewVersion(info.Version) | |
| 24 | + | hasUpdate, latestVersion, err := common.CheckForNewVersion( | |
| 25 | + | info.Version, | |
| 26 | + | info.ProxyURL, | |
| 27 | + | ) | |
| 25 | 28 | if err != nil { | |
| 26 | 29 | fmt.Printf("Error checking for updates: %v\n", err) | |
| 27 | 30 | return | |
| 28 | 31 | } | |
| 29 | 32 | ||
| 30 | 33 | if hasUpdate { | |
| 31 | - | fmt.Printf("\nNew version %s available! Visit: https://github.com/bnema/gordon/releases/latest\n", latestVersion) | |
| 34 | + | fmt.Printf("\nA new version is available: %s\n", latestVersion) | |
| 35 | + | fmt.Println("You can update using: gordon update") | |
| 32 | 36 | } | |
| 33 | 37 | }, | |
| 34 | 38 | } | |
| 39 | + | return cmd | |
| 35 | 40 | } | |
| @@ -8,28 +8,33 @@ import ( | |||||
| 8 | "github.com/spf13/cobra" | 8 | "github.com/spf13/cobra" | ||
| 9 | ) | 9 | ) | ||
| 10 | 10 | ||||
| 11 | - | func NewVersionCommand(a *cli.App) *cobra.Command { | 11 | + | func NewVersionCommand(app *cli.App) *cobra.Command { |
| 12 | - | return &cobra.Command{ | 12 | + | cmd := &cobra.Command{ |
| 13 | Use: "version", | 13 | Use: "version", | ||
| 14 | - | Short: "Display the version of Gordon", | 14 | + | Short: "Print version information", |
| 15 | Run: func(cmd *cobra.Command, args []string) { | 15 | Run: func(cmd *cobra.Command, args []string) { | ||
| 16 | info := common.GetVersionInfo( | 16 | info := common.GetVersionInfo( | ||
| 17 | - | a.Config.Build.BuildVersion, | 17 | + | app.Config.Build.BuildVersion, |
| 18 | - | a.Config.Build.BuildCommit, | 18 | + | app.Config.Build.BuildCommit, |
| 19 | - | a.Config.Build.BuildDate, | 19 | + | app.Config.Build.BuildDate, |
| 20 | + | app.Config.Build.ProxyURL, | |||
| 20 | ) | 21 | ) | ||
| 21 | fmt.Println(info.String()) | 22 | fmt.Println(info.String()) | ||
| 22 | 23 | ||||
| 23 | - | // Check for updates | 24 | + | hasUpdate, latestVersion, err := common.CheckForNewVersion( |
| 24 | - | hasUpdate, latestVersion, err := common.CheckForNewVersion(info.Version) | 25 | + | info.Version, |
| 26 | + | info.ProxyURL, | |||
| 27 | + | ) | |||
| 25 | if err != nil { | 28 | if err != nil { | ||
| 26 | fmt.Printf("Error checking for updates: %v\n", err) | 29 | fmt.Printf("Error checking for updates: %v\n", err) | ||
| 27 | return | 30 | return | ||
| 28 | } | 31 | } | ||
| 29 | 32 | ||||
| 30 | if hasUpdate { | 33 | if hasUpdate { | ||
| 31 | - | fmt.Printf("\nNew version %s available! Visit: https://github.com/bnema/gordon/releases/latest\n", latestVersion) | 34 | + | fmt.Printf("\nA new version is available: %s\n", latestVersion) |
| 35 | + | fmt.Println("You can update using: gordon update") | |||
| 32 | } | 36 | } | ||
| 33 | }, | 37 | }, | ||
| 34 | } | 38 | } | ||
| 39 | + | return cmd | |||
| 35 | } | 40 | } | ||
M internal/common/version.go +74 -63
| @@ -1,4 +1,5 @@ | |||
| 1 | 1 | // internal/common/version.go | |
| 2 | + | ||
| 2 | 3 | package common | |
| 3 | 4 | ||
| 4 | 5 | import ( | |
| @@ -14,89 +15,66 @@ import ( | |||
| 14 | 15 | "github.com/charmbracelet/log" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 17 | - | const ( | |
| 18 | - | githubAPI = "https://api.github.com/repos/bnema/gordon/releases/latest" | |
| 19 | - | ) | |
| 20 | - | ||
| 21 | 18 | // Info represents version information | |
| 22 | 19 | type Info struct { | |
| 23 | - | Version string | |
| 24 | - | Commit string | |
| 25 | - | Date string | |
| 20 | + | Version string | |
| 21 | + | Commit string | |
| 22 | + | Date string | |
| 23 | + | ProxyURL string | |
| 26 | 24 | } | |
| 27 | 25 | ||
| 28 | - | // GitHubRelease represents the GitHub API response | |
| 29 | - | type GitHubRelease struct { | |
| 30 | - | TagName string `json:"tag_name"` | |
| 31 | - | Name string `json:"name"` | |
| 32 | - | PublishedAt time.Time `json:"published_at"` | |
| 26 | + | type ProxyVersion struct { | |
| 27 | + | Version string `json:"version"` | |
| 33 | 28 | } | |
| 34 | 29 | ||
| 35 | 30 | // GetVersionInfo returns the current version information based on BuildConfig | |
| 36 | - | func GetVersionInfo(buildVersion, buildCommit, buildDate string) Info { | |
| 31 | + | func GetVersionInfo(buildVersion, buildCommit, buildDate, proxyURL string) Info { | |
| 37 | 32 | // If version was set in BuildConfig, use that | |
| 38 | 33 | if buildVersion != "" && buildVersion != "dev" { | |
| 39 | 34 | return Info{ | |
| 40 | - | Version: buildVersion, | |
| 41 | - | Commit: buildCommit, | |
| 42 | - | Date: buildDate, | |
| 35 | + | Version: buildVersion, | |
| 36 | + | Commit: buildCommit, | |
| 37 | + | Date: buildDate, | |
| 38 | + | ProxyURL: proxyURL, | |
| 43 | 39 | } | |
| 44 | 40 | } | |
| 45 | 41 | ||
| 46 | 42 | // Try to get version from runtime build info (for go install) | |
| 47 | 43 | if buildInfo, ok := debug.ReadBuildInfo(); ok { | |
| 48 | 44 | return Info{ | |
| 49 | - | Version: buildInfo.Main.Version, | |
| 50 | - | Commit: "unknown", | |
| 51 | - | Date: "unknown", | |
| 45 | + | Version: buildInfo.Main.Version, | |
| 46 | + | Commit: "unknown", | |
| 47 | + | Date: "unknown", | |
| 48 | + | ProxyURL: proxyURL, | |
| 52 | 49 | } | |
| 53 | 50 | } | |
| 54 | 51 | ||
| 55 | 52 | // Fallback for development builds | |
| 56 | 53 | return Info{ | |
| 57 | - | Version: "dev", | |
| 58 | - | Commit: "unknown", | |
| 59 | - | Date: "unknown", | |
| 60 | - | } | |
| 61 | - | } | |
| 62 | - | ||
| 63 | - | // String returns a formatted version string | |
| 64 | - | func (i Info) String() string { | |
| 65 | - | if i.Version == "dev" { | |
| 66 | - | return "Gordon version dev (development build)" | |
| 67 | - | } | |
| 68 | - | ||
| 69 | - | // Check if it's a dirty version | |
| 70 | - | if strings.Contains(i.Version, "-dirty") { | |
| 71 | - | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |
| 72 | - | i.Version, i.Commit, i.Date) | |
| 54 | + | Version: "dev", | |
| 55 | + | Commit: "unknown", | |
| 56 | + | Date: "unknown", | |
| 57 | + | ProxyURL: proxyURL, | |
| 73 | 58 | } | |
| 74 | - | ||
| 75 | - | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |
| 76 | - | i.Version, i.Commit, i.Date) | |
| 77 | 59 | } | |
| 78 | 60 | ||
| 79 | - | // CheckForNewVersion checks if a newer version is available on GitHub | |
| 80 | - | func CheckForNewVersion(currentVersion string) (bool, string, error) { | |
| 61 | + | // CheckForNewVersion checks if a newer version is available via proxy | |
| 62 | + | func CheckForNewVersion(currentVersion, proxyURL string) (bool, string, error) { | |
| 81 | 63 | // Don't check for new versions in development builds or dirty builds | |
| 82 | 64 | if currentVersion == "dev" || strings.Contains(currentVersion, "-dirty") { | |
| 83 | 65 | return false, "", nil | |
| 84 | 66 | } | |
| 85 | 67 | ||
| 86 | - | // Create an HTTP client with timeout | |
| 87 | - | client := &http.Client{ | |
| 88 | - | Timeout: 10 * time.Second, | |
| 68 | + | // If currentVersion is empty, treat it as v0.0.0 | |
| 69 | + | if currentVersion == "" { | |
| 70 | + | currentVersion = "v0.0.0" | |
| 89 | 71 | } | |
| 90 | 72 | ||
| 91 | - | // Create request with User-Agent header (GitHub API requires this) | |
| 92 | - | req, err := http.NewRequest("GET", githubAPI, nil) | |
| 93 | - | if err != nil { | |
| 94 | - | return false, "", fmt.Errorf("error creating request: %w", err) | |
| 73 | + | client := &http.Client{ | |
| 74 | + | Timeout: 10 * time.Second, | |
| 95 | 75 | } | |
| 96 | - | req.Header.Set("User-Agent", "Gordon-Version-Checker") | |
| 97 | 76 | ||
| 98 | - | // Make the request | |
| 99 | - | resp, err := client.Do(req) | |
| 77 | + | resp, err := client.Get(proxyURL + "/version") | |
| 100 | 78 | if err != nil { | |
| 101 | 79 | return false, "", fmt.Errorf("error making request: %w", err) | |
| 102 | 80 | } | |
| @@ -111,50 +89,62 @@ func CheckForNewVersion(currentVersion string) (bool, string, error) { | |||
| 111 | 89 | return false, "", fmt.Errorf("error reading response: %w", err) | |
| 112 | 90 | } | |
| 113 | 91 | ||
| 114 | - | var release GitHubRelease | |
| 115 | - | if err := json.Unmarshal(body, &release); err != nil { | |
| 92 | + | var proxyVer ProxyVersion | |
| 93 | + | if err := json.Unmarshal(body, &proxyVer); err != nil { | |
| 116 | 94 | return false, "", fmt.Errorf("error parsing JSON: %w", err) | |
| 117 | 95 | } | |
| 118 | 96 | ||
| 97 | + | // Ensure versions start with 'v' | |
| 98 | + | if !strings.HasPrefix(currentVersion, "v") { | |
| 99 | + | currentVersion = "v" + currentVersion | |
| 100 | + | } | |
| 101 | + | if !strings.HasPrefix(proxyVer.Version, "v") { | |
| 102 | + | proxyVer.Version = "v" + proxyVer.Version | |
| 103 | + | } | |
| 104 | + | ||
| 119 | 105 | // Clean up version strings | |
| 120 | - | latestVersion := strings.TrimPrefix(release.TagName, "v") | |
| 106 | + | latestVersion := strings.TrimPrefix(proxyVer.Version, "v") | |
| 121 | 107 | currentBase := extractBaseVersion(currentVersion) | |
| 122 | 108 | ||
| 109 | + | // Handle development versions | |
| 110 | + | if currentBase == "" || currentBase == "dev" { | |
| 111 | + | currentBase = "0.0.0" | |
| 112 | + | } | |
| 113 | + | ||
| 123 | 114 | // Parse versions using semver | |
| 124 | 115 | current, err := semver.NewVersion(currentBase) | |
| 125 | 116 | if err != nil { | |
| 126 | - | return false, "", fmt.Errorf("error parsing current version: %w", err) | |
| 117 | + | return false, "", fmt.Errorf("error parsing current version (%s): %w", currentBase, err) | |
| 127 | 118 | } | |
| 128 | 119 | ||
| 129 | 120 | latest, err := semver.NewVersion(latestVersion) | |
| 130 | 121 | if err != nil { | |
| 131 | - | return false, "", fmt.Errorf("error parsing latest version: %w", err) | |
| 122 | + | return false, "", fmt.Errorf("error parsing latest version (%s): %w", latestVersion, err) | |
| 132 | 123 | } | |
| 133 | 124 | ||
| 134 | - | // Compare versions | |
| 135 | 125 | if latest.GreaterThan(current) { | |
| 136 | - | return true, release.TagName, nil | |
| 126 | + | return true, proxyVer.Version, nil | |
| 137 | 127 | } | |
| 138 | 128 | ||
| 139 | 129 | return false, "", nil | |
| 140 | 130 | } | |
| 141 | 131 | ||
| 142 | 132 | // CheckVersionPeriodically checks for new versions periodically | |
| 143 | - | func CheckVersionPeriodically(currentVersion string, checkInterval time.Duration) { | |
| 133 | + | func CheckVersionPeriodically(info Info, checkInterval time.Duration) { | |
| 144 | 134 | ticker := time.NewTicker(checkInterval) | |
| 145 | 135 | defer ticker.Stop() | |
| 146 | 136 | ||
| 147 | 137 | // Initial check | |
| 148 | - | checkVersion(currentVersion) | |
| 138 | + | checkVersion(info) | |
| 149 | 139 | ||
| 150 | 140 | // Periodic checks | |
| 151 | 141 | for range ticker.C { | |
| 152 | - | checkVersion(currentVersion) | |
| 142 | + | checkVersion(info) | |
| 153 | 143 | } | |
| 154 | 144 | } | |
| 155 | 145 | ||
| 156 | - | func checkVersion(currentVersion string) { | |
| 157 | - | hasUpdate, latestVersion, err := CheckForNewVersion(currentVersion) | |
| 146 | + | func checkVersion(info Info) { | |
| 147 | + | hasUpdate, latestVersion, err := CheckForNewVersion(info.Version, info.ProxyURL) | |
| 158 | 148 | if err != nil { | |
| 159 | 149 | log.Error("Failed to check for updates", "error", err) | |
| 160 | 150 | return | |
| @@ -162,7 +152,7 @@ func checkVersion(currentVersion string) { | |||
| 162 | 152 | ||
| 163 | 153 | if hasUpdate { | |
| 164 | 154 | log.Info("New version available!", | |
| 165 | - | "current", currentVersion, | |
| 155 | + | "current", info.Version, | |
| 166 | 156 | "latest", latestVersion, | |
| 167 | 157 | "update_url", "https://github.com/bnema/gordon/releases/latest") | |
| 168 | 158 | } | |
| @@ -172,7 +162,28 @@ func extractBaseVersion(version string) string { | |||
| 172 | 162 | // Remove 'v' prefix if present | |
| 173 | 163 | version = strings.TrimPrefix(version, "v") | |
| 174 | 164 | ||
| 165 | + | // Handle empty or invalid versions | |
| 166 | + | if version == "" || version == "dev" { | |
| 167 | + | return "0.0.0" | |
| 168 | + | } | |
| 169 | + | ||
| 175 | 170 | // Split on first hyphen to remove dirty/commit info | |
| 176 | 171 | parts := strings.SplitN(version, "-", 2) | |
| 177 | 172 | return parts[0] | |
| 178 | 173 | } | |
| 174 | + | ||
| 175 | + | // String returns a formatted version string | |
| 176 | + | func (i Info) String() string { | |
| 177 | + | if i.Version == "dev" { | |
| 178 | + | return "Gordon version dev (development build)" | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | // Check if it's a dirty version | |
| 182 | + | if strings.Contains(i.Version, "-dirty") { | |
| 183 | + | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |
| 184 | + | i.Version, i.Commit, i.Date) | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |
| 188 | + | i.Version, i.Commit, i.Date) | |
| 189 | + | } | |
| @@ -1,4 +1,5 @@ | |||||
| 1 | // internal/common/version.go | 1 | // internal/common/version.go | ||
| 2 | + | ||||
| 2 | package common | 3 | package common | ||
| 3 | 4 | ||||
| 4 | import ( | 5 | import ( | ||
| @@ -14,89 +15,66 @@ import ( | |||||
| 14 | "github.com/charmbracelet/log" | 15 | "github.com/charmbracelet/log" | ||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 17 | - | const ( | |||
| 18 | - | githubAPI = "https://api.github.com/repos/bnema/gordon/releases/latest" | |||
| 19 | - | ) | |||
| 20 | - | ||||
| 21 | // Info represents version information | 18 | // Info represents version information | ||
| 22 | type Info struct { | 19 | type Info struct { | ||
| 23 | - | Version string | 20 | + | Version string |
| 24 | - | Commit string | 21 | + | Commit string |
| 25 | - | Date string | 22 | + | Date string |
| 23 | + | ProxyURL string | |||
| 26 | } | 24 | } | ||
| 27 | 25 | ||||
| 28 | - | // GitHubRelease represents the GitHub API response | 26 | + | type ProxyVersion struct { |
| 29 | - | type GitHubRelease struct { | 27 | + | Version string `json:"version"` |
| 30 | - | TagName string `json:"tag_name"` | |||
| 31 | - | Name string `json:"name"` | |||
| 32 | - | PublishedAt time.Time `json:"published_at"` | |||
| 33 | } | 28 | } | ||
| 34 | 29 | ||||
| 35 | // GetVersionInfo returns the current version information based on BuildConfig | 30 | // GetVersionInfo returns the current version information based on BuildConfig | ||
| 36 | - | func GetVersionInfo(buildVersion, buildCommit, buildDate string) Info { | 31 | + | func GetVersionInfo(buildVersion, buildCommit, buildDate, proxyURL string) Info { |
| 37 | // If version was set in BuildConfig, use that | 32 | // If version was set in BuildConfig, use that | ||
| 38 | if buildVersion != "" && buildVersion != "dev" { | 33 | if buildVersion != "" && buildVersion != "dev" { | ||
| 39 | return Info{ | 34 | return Info{ | ||
| 40 | - | Version: buildVersion, | 35 | + | Version: buildVersion, |
| 41 | - | Commit: buildCommit, | 36 | + | Commit: buildCommit, |
| 42 | - | Date: buildDate, | 37 | + | Date: buildDate, |
| 38 | + | ProxyURL: proxyURL, | |||
| 43 | } | 39 | } | ||
| 44 | } | 40 | } | ||
| 45 | 41 | ||||
| 46 | // Try to get version from runtime build info (for go install) | 42 | // Try to get version from runtime build info (for go install) | ||
| 47 | if buildInfo, ok := debug.ReadBuildInfo(); ok { | 43 | if buildInfo, ok := debug.ReadBuildInfo(); ok { | ||
| 48 | return Info{ | 44 | return Info{ | ||
| 49 | - | Version: buildInfo.Main.Version, | 45 | + | Version: buildInfo.Main.Version, |
| 50 | - | Commit: "unknown", | 46 | + | Commit: "unknown", |
| 51 | - | Date: "unknown", | 47 | + | Date: "unknown", |
| 48 | + | ProxyURL: proxyURL, | |||
| 52 | } | 49 | } | ||
| 53 | } | 50 | } | ||
| 54 | 51 | ||||
| 55 | // Fallback for development builds | 52 | // Fallback for development builds | ||
| 56 | return Info{ | 53 | return Info{ | ||
| 57 | - | Version: "dev", | 54 | + | Version: "dev", |
| 58 | - | Commit: "unknown", | 55 | + | Commit: "unknown", |
| 59 | - | Date: "unknown", | 56 | + | Date: "unknown", |
| 60 | - | } | 57 | + | ProxyURL: proxyURL, |
| 61 | - | } | |||
| 62 | - | ||||
| 63 | - | // String returns a formatted version string | |||
| 64 | - | func (i Info) String() string { | |||
| 65 | - | if i.Version == "dev" { | |||
| 66 | - | return "Gordon version dev (development build)" | |||
| 67 | - | } | |||
| 68 | - | ||||
| 69 | - | // Check if it's a dirty version | |||
| 70 | - | if strings.Contains(i.Version, "-dirty") { | |||
| 71 | - | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |||
| 72 | - | i.Version, i.Commit, i.Date) | |||
| 73 | } | 58 | } | ||
| 74 | - | ||||
| 75 | - | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |||
| 76 | - | i.Version, i.Commit, i.Date) | |||
| 77 | } | 59 | } | ||
| 78 | 60 | ||||
| 79 | - | // CheckForNewVersion checks if a newer version is available on GitHub | 61 | + | // CheckForNewVersion checks if a newer version is available via proxy |
| 80 | - | func CheckForNewVersion(currentVersion string) (bool, string, error) { | 62 | + | func CheckForNewVersion(currentVersion, proxyURL string) (bool, string, error) { |
| 81 | // Don't check for new versions in development builds or dirty builds | 63 | // Don't check for new versions in development builds or dirty builds | ||
| 82 | if currentVersion == "dev" || strings.Contains(currentVersion, "-dirty") { | 64 | if currentVersion == "dev" || strings.Contains(currentVersion, "-dirty") { | ||
| 83 | return false, "", nil | 65 | return false, "", nil | ||
| 84 | } | 66 | } | ||
| 85 | 67 | ||||
| 86 | - | // Create an HTTP client with timeout | 68 | + | // If currentVersion is empty, treat it as v0.0.0 |
| 87 | - | client := &http.Client{ | 69 | + | if currentVersion == "" { |
| 88 | - | Timeout: 10 * time.Second, | 70 | + | currentVersion = "v0.0.0" |
| 89 | } | 71 | } | ||
| 90 | 72 | ||||
| 91 | - | // Create request with User-Agent header (GitHub API requires this) | 73 | + | client := &http.Client{ |
| 92 | - | req, err := http.NewRequest("GET", githubAPI, nil) | 74 | + | Timeout: 10 * time.Second, |
| 93 | - | if err != nil { | |||
| 94 | - | return false, "", fmt.Errorf("error creating request: %w", err) | |||
| 95 | } | 75 | } | ||
| 96 | - | req.Header.Set("User-Agent", "Gordon-Version-Checker") | |||
| 97 | 76 | ||||
| 98 | - | // Make the request | 77 | + | resp, err := client.Get(proxyURL + "/version") |
| 99 | - | resp, err := client.Do(req) | |||
| 100 | if err != nil { | 78 | if err != nil { | ||
| 101 | return false, "", fmt.Errorf("error making request: %w", err) | 79 | return false, "", fmt.Errorf("error making request: %w", err) | ||
| 102 | } | 80 | } | ||
| @@ -111,50 +89,62 @@ func CheckForNewVersion(currentVersion string) (bool, string, error) { | |||||
| 111 | return false, "", fmt.Errorf("error reading response: %w", err) | 89 | return false, "", fmt.Errorf("error reading response: %w", err) | ||
| 112 | } | 90 | } | ||
| 113 | 91 | ||||
| 114 | - | var release GitHubRelease | 92 | + | var proxyVer ProxyVersion |
| 115 | - | if err := json.Unmarshal(body, &release); err != nil { | 93 | + | if err := json.Unmarshal(body, &proxyVer); err != nil { |
| 116 | return false, "", fmt.Errorf("error parsing JSON: %w", err) | 94 | return false, "", fmt.Errorf("error parsing JSON: %w", err) | ||
| 117 | } | 95 | } | ||
| 118 | 96 | ||||
| 97 | + | // Ensure versions start with 'v' | |||
| 98 | + | if !strings.HasPrefix(currentVersion, "v") { | |||
| 99 | + | currentVersion = "v" + currentVersion | |||
| 100 | + | } | |||
| 101 | + | if !strings.HasPrefix(proxyVer.Version, "v") { | |||
| 102 | + | proxyVer.Version = "v" + proxyVer.Version | |||
| 103 | + | } | |||
| 104 | + | ||||
| 119 | // Clean up version strings | 105 | // Clean up version strings | ||
| 120 | - | latestVersion := strings.TrimPrefix(release.TagName, "v") | 106 | + | latestVersion := strings.TrimPrefix(proxyVer.Version, "v") |
| 121 | currentBase := extractBaseVersion(currentVersion) | 107 | currentBase := extractBaseVersion(currentVersion) | ||
| 122 | 108 | ||||
| 109 | + | // Handle development versions | |||
| 110 | + | if currentBase == "" || currentBase == "dev" { | |||
| 111 | + | currentBase = "0.0.0" | |||
| 112 | + | } | |||
| 113 | + | ||||
| 123 | // Parse versions using semver | 114 | // Parse versions using semver | ||
| 124 | current, err := semver.NewVersion(currentBase) | 115 | current, err := semver.NewVersion(currentBase) | ||
| 125 | if err != nil { | 116 | if err != nil { | ||
| 126 | - | return false, "", fmt.Errorf("error parsing current version: %w", err) | 117 | + | return false, "", fmt.Errorf("error parsing current version (%s): %w", currentBase, err) |
| 127 | } | 118 | } | ||
| 128 | 119 | ||||
| 129 | latest, err := semver.NewVersion(latestVersion) | 120 | latest, err := semver.NewVersion(latestVersion) | ||
| 130 | if err != nil { | 121 | if err != nil { | ||
| 131 | - | return false, "", fmt.Errorf("error parsing latest version: %w", err) | 122 | + | return false, "", fmt.Errorf("error parsing latest version (%s): %w", latestVersion, err) |
| 132 | } | 123 | } | ||
| 133 | 124 | ||||
| 134 | - | // Compare versions | |||
| 135 | if latest.GreaterThan(current) { | 125 | if latest.GreaterThan(current) { | ||
| 136 | - | return true, release.TagName, nil | 126 | + | return true, proxyVer.Version, nil |
| 137 | } | 127 | } | ||
| 138 | 128 | ||||
| 139 | return false, "", nil | 129 | return false, "", nil | ||
| 140 | } | 130 | } | ||
| 141 | 131 | ||||
| 142 | // CheckVersionPeriodically checks for new versions periodically | 132 | // CheckVersionPeriodically checks for new versions periodically | ||
| 143 | - | func CheckVersionPeriodically(currentVersion string, checkInterval time.Duration) { | 133 | + | func CheckVersionPeriodically(info Info, checkInterval time.Duration) { |
| 144 | ticker := time.NewTicker(checkInterval) | 134 | ticker := time.NewTicker(checkInterval) | ||
| 145 | defer ticker.Stop() | 135 | defer ticker.Stop() | ||
| 146 | 136 | ||||
| 147 | // Initial check | 137 | // Initial check | ||
| 148 | - | checkVersion(currentVersion) | 138 | + | checkVersion(info) |
| 149 | 139 | ||||
| 150 | // Periodic checks | 140 | // Periodic checks | ||
| 151 | for range ticker.C { | 141 | for range ticker.C { | ||
| 152 | - | checkVersion(currentVersion) | 142 | + | checkVersion(info) |
| 153 | } | 143 | } | ||
| 154 | } | 144 | } | ||
| 155 | 145 | ||||
| 156 | - | func checkVersion(currentVersion string) { | 146 | + | func checkVersion(info Info) { |
| 157 | - | hasUpdate, latestVersion, err := CheckForNewVersion(currentVersion) | 147 | + | hasUpdate, latestVersion, err := CheckForNewVersion(info.Version, info.ProxyURL) |
| 158 | if err != nil { | 148 | if err != nil { | ||
| 159 | log.Error("Failed to check for updates", "error", err) | 149 | log.Error("Failed to check for updates", "error", err) | ||
| 160 | return | 150 | return | ||
| @@ -162,7 +152,7 @@ func checkVersion(currentVersion string) { | |||||
| 162 | 152 | ||||
| 163 | if hasUpdate { | 153 | if hasUpdate { | ||
| 164 | log.Info("New version available!", | 154 | log.Info("New version available!", | ||
| 165 | - | "current", currentVersion, | 155 | + | "current", info.Version, |
| 166 | "latest", latestVersion, | 156 | "latest", latestVersion, | ||
| 167 | "update_url", "https://github.com/bnema/gordon/releases/latest") | 157 | "update_url", "https://github.com/bnema/gordon/releases/latest") | ||
| 168 | } | 158 | } | ||
| @@ -172,7 +162,28 @@ func extractBaseVersion(version string) string { | |||||
| 172 | // Remove 'v' prefix if present | 162 | // Remove 'v' prefix if present | ||
| 173 | version = strings.TrimPrefix(version, "v") | 163 | version = strings.TrimPrefix(version, "v") | ||
| 174 | 164 | ||||
| 165 | + | // Handle empty or invalid versions | |||
| 166 | + | if version == "" || version == "dev" { | |||
| 167 | + | return "0.0.0" | |||
| 168 | + | } | |||
| 169 | + | ||||
| 175 | // Split on first hyphen to remove dirty/commit info | 170 | // Split on first hyphen to remove dirty/commit info | ||
| 176 | parts := strings.SplitN(version, "-", 2) | 171 | parts := strings.SplitN(version, "-", 2) | ||
| 177 | return parts[0] | 172 | return parts[0] | ||
| 178 | } | 173 | } | ||
| 174 | + | ||||
| 175 | + | // String returns a formatted version string | |||
| 176 | + | func (i Info) String() string { | |||
| 177 | + | if i.Version == "dev" { | |||
| 178 | + | return "Gordon version dev (development build)" | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | // Check if it's a dirty version | |||
| 182 | + | if strings.Contains(i.Version, "-dirty") { | |||
| 183 | + | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |||
| 184 | + | i.Version, i.Commit, i.Date) | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | return fmt.Sprintf("Gordon version %s (commit: %s, built at: %s)", | |||
| 188 | + | i.Version, i.Commit, i.Date) | |||
| 189 | + | } | |||