From bf198ffc278f8682a6e06ff520a3399dcafa8427 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 23 Oct 2025 13:47:05 -0300 Subject: [PATCH] fix: handle pre releases Signed-off-by: Carlos Alexandro Becker --- internal/app/app.go | 7 +++--- internal/update/update.go | 41 ++++++++++++++++++++++++---------- internal/update/update_test.go | 41 +++++++++++++++++++++------------- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index df57400dfe8e67a63a4e73df017820da367c0e33..5312751cff72b5538683dea3294c9d13d776b1c9 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -23,6 +23,7 @@ import ( "github.com/charmbracelet/crush/internal/pubsub" "github.com/charmbracelet/crush/internal/session" "github.com/charmbracelet/crush/internal/update" + "github.com/charmbracelet/crush/internal/version" "github.com/charmbracelet/x/ansi" ) @@ -352,12 +353,12 @@ func (app *App) Shutdown() { func (app *App) checkForUpdates(ctx context.Context) { checkCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - info, err := update.Check(checkCtx, update.Default) + info, err := update.Check(checkCtx, version.Version, update.Default) if err != nil || !info.Available() { return } app.events <- pubsub.UpdateAvailableMsg{ - CurrentVersion: info.CurrentVersion, - LatestVersion: info.LatestVersion, + CurrentVersion: info.Current, + LatestVersion: info.Latest, } } diff --git a/internal/update/update.go b/internal/update/update.go index 47377778838045b53b9917c13692f46c77819dc3..061766c3a4eadfe5226dbc95a1afd2dfd677a957 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -8,8 +8,6 @@ import ( "net/http" "strings" "time" - - "github.com/charmbracelet/crush/internal/version" ) const ( @@ -22,22 +20,41 @@ var Default Client = &github{} // Info contains information about an available update. type Info struct { - CurrentVersion string - LatestVersion string - ReleaseURL string + Current string + Latest string + URL string } // Available returns true if there's an update available. -func (i Info) Available() bool { return i.CurrentVersion != i.LatestVersion } +// +// If both current and latest are stable versions, returns true if versions are +// different. +// If current is a pre-release and latest isn't, returns true. +// If latest is a pre-release and current isn't, returns false. +func (i Info) Available() bool { + cpr := strings.Contains(i.Current, "-") + lpr := strings.Contains(i.Latest, "-") + // current is pre release + if cpr { + // latest isn't a prerelease + if !lpr { + return true + } + } + if lpr && !cpr { + return false + } + return i.Current != i.Latest +} // Check checks if a new version is available. -func Check(ctx context.Context, client Client) (Info, error) { +func Check(ctx context.Context, current string, client Client) (Info, error) { info := Info{ - CurrentVersion: version.Version, - LatestVersion: version.Version, + Current: current, + Latest: current, } - if info.CurrentVersion == "devel" || info.CurrentVersion == "unknown" { + if info.Current == "devel" || info.Current == "unknown" { return info, nil } @@ -46,8 +63,8 @@ func Check(ctx context.Context, client Client) (Info, error) { return info, fmt.Errorf("failed to fetch latest release: %w", err) } - info.LatestVersion = strings.TrimPrefix(release.TagName, "v") - info.ReleaseURL = release.HTMLURL + info.Latest = strings.TrimPrefix(release.TagName, "v") + info.URL = release.HTMLURL return info, nil } diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 9ac23d43257289811b5dd4e917fa46745c75f998..e833ad220705837a0f18cbc4b6abf6338987644c 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -4,41 +4,50 @@ import ( "context" "testing" - "github.com/charmbracelet/crush/internal/version" "github.com/stretchr/testify/require" ) func TestCheckForUpdate_DevelopmentVersion(t *testing.T) { - originalVersion := version.Version - version.Version = "unknown" - t.Cleanup(func() { - version.Version = originalVersion - }) - - info, err := Check(t.Context(), testClient{}) + info, err := Check(t.Context(), "unknown", testClient{"v0.11.0"}) require.NoError(t, err) require.NotNil(t, info) require.False(t, info.Available()) } func TestCheckForUpdate_Old(t *testing.T) { - originalVersion := version.Version - version.Version = "0.10.0" - t.Cleanup(func() { - version.Version = originalVersion - }) - info, err := Check(t.Context(), testClient{}) + info, err := Check(t.Context(), "v0.10.0", testClient{"v0.11.0"}) require.NoError(t, err) require.NotNil(t, info) require.True(t, info.Available()) } -type testClient struct{} +func TestCheckForUpdate_Beta(t *testing.T) { + t.Run("current is stable", func(t *testing.T) { + info, err := Check(t.Context(), "v0.10.0", testClient{"v0.11.0-beta.1"}) + require.NoError(t, err) + require.NotNil(t, info) + require.False(t, info.Available()) + }) + t.Run("current is also beta", func(t *testing.T) { + info, err := Check(t.Context(), "v0.11.0-beta.1", testClient{"v0.11.0-beta.2"}) + require.NoError(t, err) + require.NotNil(t, info) + require.True(t, info.Available()) + }) + t.Run("current is beta, latest isn't", func(t *testing.T) { + info, err := Check(t.Context(), "v0.11.0-beta.1", testClient{"v0.11.0"}) + require.NoError(t, err) + require.NotNil(t, info) + require.True(t, info.Available()) + }) +} + +type testClient struct{ tag string } // Latest implements Client. func (t testClient) Latest(ctx context.Context) (*Release, error) { return &Release{ - TagName: "v0.11.0", + TagName: t.tag, HTMLURL: "https://example.org", }, nil }