mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-09 23:59:45 +00:00
96ae1d45e0
- [x] Add golangci-lint configuration with gocritic ifElseChain disabled
- [x] Rename error variables to avoid shadowing (createErr, watcherErr, watchErr, etc.)
- [x] Replace `interface{}` with `any` type alias throughout codebase
- [x] Add package-level documentation comments to all internal packages
- [x] Reorder struct fields alphabetically for consistency
- [x] Extract UI constants (terminal dimensions, column widths, colors) to constants.go
- [x] Refactor BubbleTeaUI main view rendering into smaller helper functions
- [x] Simplify nested conditionals and improve code clarity
- [x] Add `isForwardDisabled()` helper method to BubbleTeaUI
- [x] Update file permissions from 0644 to 0600 in config tests
- [x] Add `#nosec` comments and error suppression where appropriate
- [x] Improve test table struct field ordering for readability
- [x] Fix resource parsing in AddForward using strings.SplitN
- [x] Add comprehensive tests for new UI helper functions and constants
165 lines
4.1 KiB
Go
165 lines
4.1 KiB
Go
// Package version provides version checking against GitHub releases.
|
|
// It queries the GitHub API to check for newer versions of kportal
|
|
// and provides update notifications.
|
|
//
|
|
// Basic usage:
|
|
//
|
|
// info, err := version.CheckForUpdate(ctx, "owner", "repo", "v1.0.0")
|
|
// if err != nil {
|
|
// log.Printf("Version check failed: %v", err)
|
|
// } else if info.UpdateAvailable {
|
|
// fmt.Printf("Update available: %s -> %s\n", info.CurrentVersion, info.LatestVersion)
|
|
// }
|
|
package version
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// GitHubAPIURL is the GitHub API endpoint for releases
|
|
githubReleasesURL = "https://api.github.com/repos/%s/%s/releases/latest"
|
|
// requestTimeout is the timeout for HTTP requests
|
|
requestTimeout = 5 * time.Second
|
|
)
|
|
|
|
// ReleaseInfo contains information about a GitHub release
|
|
type ReleaseInfo struct {
|
|
TagName string `json:"tag_name"`
|
|
HTMLURL string `json:"html_url"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// UpdateInfo contains information about an available update
|
|
type UpdateInfo struct {
|
|
CurrentVersion string
|
|
LatestVersion string
|
|
ReleaseURL string
|
|
ReleaseName string
|
|
}
|
|
|
|
// Checker checks for new versions on GitHub
|
|
type Checker struct {
|
|
client *http.Client
|
|
owner string
|
|
repo string
|
|
current string
|
|
}
|
|
|
|
// NewChecker creates a new version checker
|
|
func NewChecker(owner, repo, currentVersion string) *Checker {
|
|
return &Checker{
|
|
owner: owner,
|
|
repo: repo,
|
|
current: normalizeVersion(currentVersion),
|
|
client: &http.Client{
|
|
Timeout: requestTimeout,
|
|
},
|
|
}
|
|
}
|
|
|
|
// CheckForUpdate checks if a newer version is available.
|
|
// Returns nil if current version is up to date or if check fails.
|
|
// This is designed to fail silently - network errors should not impact the user.
|
|
func (c *Checker) CheckForUpdate(ctx context.Context) *UpdateInfo {
|
|
release, err := c.fetchLatestRelease(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
latestVersion := normalizeVersion(release.TagName)
|
|
if isNewerVersion(latestVersion, c.current) {
|
|
return &UpdateInfo{
|
|
CurrentVersion: c.current,
|
|
LatestVersion: latestVersion,
|
|
ReleaseURL: release.HTMLURL,
|
|
ReleaseName: release.Name,
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// fetchLatestRelease fetches the latest release info from GitHub API
|
|
func (c *Checker) fetchLatestRelease(ctx context.Context) (*ReleaseInfo, error) {
|
|
url := fmt.Sprintf(githubReleasesURL, c.owner, c.repo)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
|
req.Header.Set("User-Agent", "kportal-version-checker")
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("GitHub API returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
var release ReleaseInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &release, nil
|
|
}
|
|
|
|
// normalizeVersion removes 'v' or 'V' prefix and trims whitespace
|
|
func normalizeVersion(v string) string {
|
|
v = strings.TrimSpace(v)
|
|
v = strings.TrimPrefix(v, "v")
|
|
v = strings.TrimPrefix(v, "V")
|
|
return v
|
|
}
|
|
|
|
// isNewerVersion compares two semver-like versions.
|
|
// Returns true if latest is newer than current.
|
|
func isNewerVersion(latest, current string) bool {
|
|
latestParts := parseVersion(latest)
|
|
currentParts := parseVersion(current)
|
|
|
|
// Compare each part
|
|
for i := 0; i < len(latestParts) && i < len(currentParts); i++ {
|
|
if latestParts[i] > currentParts[i] {
|
|
return true
|
|
}
|
|
if latestParts[i] < currentParts[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// If all compared parts are equal, longer version is newer
|
|
// e.g., 1.0.1 > 1.0
|
|
return len(latestParts) > len(currentParts)
|
|
}
|
|
|
|
// parseVersion splits a version string into numeric parts
|
|
func parseVersion(v string) []int {
|
|
// Remove any suffix like -beta, -rc1, etc.
|
|
if idx := strings.IndexAny(v, "-+"); idx != -1 {
|
|
v = v[:idx]
|
|
}
|
|
|
|
parts := strings.Split(v, ".")
|
|
result := make([]int, 0, len(parts))
|
|
|
|
for _, p := range parts {
|
|
var num int
|
|
_, _ = fmt.Sscanf(p, "%d", &num)
|
|
result = append(result, num)
|
|
}
|
|
|
|
return result
|
|
}
|