From ef3de2415e8417d6ebfc2cf9b1fb816bb25f82cc Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 15:49:42 -0700 Subject: [PATCH 1/9] Replace auto-update with notify + manual update command - Remove silent auto-update mechanism, replace with update check that notifies users when a new version is available - Add `deepsource update` subcommand for explicit manual updates - Remove AutoUpdate config option since updates are now user-initiated - Rename ShouldAutoUpdate to ShouldCheckForUpdate to reflect new behavior --- cmd/deepsource/main.go | 25 +++++--------- command/root.go | 5 +++ command/update/update.go | 58 +++++++++++++++++++++++++++++++++ config/config.go | 3 +- internal/update/updater.go | 12 ++----- internal/update/updater_test.go | 16 ++++----- 6 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 command/update/update.go diff --git a/cmd/deepsource/main.go b/cmd/deepsource/main.go index 00be64b5..a8949bd7 100644 --- a/cmd/deepsource/main.go +++ b/cmd/deepsource/main.go @@ -71,28 +71,19 @@ func mainRun() (exitCode int) { func run() int { v.SetBuildInfo(version, Date, buildMode) - // Two-phase auto-update: apply pending update or check for new one - if update.ShouldAutoUpdate() { + // Check for available updates and notify + if update.ShouldCheckForUpdate() { + client := &http.Client{Timeout: 3 * time.Second} + if err := update.CheckForUpdate(client); err != nil { + debug.Log("update: %v", err) + } + state, err := update.ReadUpdateState() if err != nil { debug.Log("update: %v", err) } - if state != nil { - // Phase 2: a previous run found a newer version — apply it now - client := &http.Client{Timeout: 30 * time.Second} - newVer, err := update.ApplyUpdate(client) - if err != nil { - debug.Log("update: %v", err) - } else if newVer != "" { - fmt.Fprintf(os.Stderr, "%s\n", style.Yellow("Updated DeepSource CLI to v%s", newVer)) - } - } else { - // Phase 1: check manifest and write state file for next run - client := &http.Client{Timeout: 3 * time.Second} - if err := update.CheckForUpdate(client); err != nil { - debug.Log("update: %v", err) - } + fmt.Fprintf(os.Stderr, "Update available: v%s — run 'deepsource update' to install.\n", state.Version) } } diff --git a/command/root.go b/command/root.go index ed15790a..e70b86a5 100644 --- a/command/root.go +++ b/command/root.go @@ -8,6 +8,7 @@ import ( "github.com/deepsourcelabs/cli/buildinfo" "github.com/deepsourcelabs/cli/command/auth" completionCmd "github.com/deepsourcelabs/cli/command/completion" + updateCmd "github.com/deepsourcelabs/cli/command/update" "github.com/deepsourcelabs/cli/command/issues" "github.com/deepsourcelabs/cli/command/metrics" "github.com/deepsourcelabs/cli/command/report" @@ -89,6 +90,10 @@ func NewCmdRoot() *cobra.Command { completionC.GroupID = "setup" cmd.AddCommand(completionC) + updateC := updateCmd.NewCmdUpdate() + updateC.GroupID = "setup" + cmd.AddCommand(updateC) + cmd.PersistentFlags().Bool("skip-tls-verify", false, "Skip TLS certificate verification (for self-signed certs)") cmd.InitDefaultHelpFlag() diff --git a/command/update/update.go b/command/update/update.go new file mode 100644 index 00000000..ec08b45d --- /dev/null +++ b/command/update/update.go @@ -0,0 +1,58 @@ +package update + +import ( + "fmt" + "net/http" + "os" + "time" + + "github.com/deepsourcelabs/cli/buildinfo" + "github.com/deepsourcelabs/cli/internal/cli/style" + "github.com/deepsourcelabs/cli/internal/update" + "github.com/spf13/cobra" +) + +func NewCmdUpdate() *cobra.Command { + return &cobra.Command{ + Use: "update", + Short: "Update DeepSource CLI to the latest version", + RunE: func(cmd *cobra.Command, _ []string) error { + return runUpdate(cmd) + }, + } +} + +func runUpdate(cmd *cobra.Command) error { + w := cmd.ErrOrStderr() + + // Check for the latest version + checkClient := &http.Client{Timeout: 10 * time.Second} + if err := update.CheckForUpdate(checkClient); err != nil { + return fmt.Errorf("checking for updates: %w", err) + } + + state, err := update.ReadUpdateState() + if err != nil { + return fmt.Errorf("reading update state: %w", err) + } + + if state == nil { + bi := buildinfo.GetBuildInfo() + style.Successf(w, "Already up to date (v%s)", bi.Version) + return nil + } + + fmt.Fprintf(w, "Updating to v%s...\n", state.Version) + + applyClient := &http.Client{Timeout: 30 * time.Second} + newVer, err := update.ApplyUpdate(applyClient) + if err != nil { + return fmt.Errorf("applying update: %w", err) + } + + if newVer != "" { + style.Successf(os.Stderr, "Updated DeepSource CLI to v%s", newVer) + } + + return nil +} diff --git a/config/config.go b/config/config.go index f628b10c..2ee8a499 100644 --- a/config/config.go +++ b/config/config.go @@ -16,8 +16,7 @@ type CLIConfig struct { User string `toml:"user"` Token string `toml:"token"` TokenExpiresIn time.Time `toml:"token_expires_in,omitempty"` - AutoUpdate *bool `toml:"auto_update,omitempty"` - SkipTLSVerify bool `toml:"skip_tls_verify,omitempty"` +SkipTLSVerify bool `toml:"skip_tls_verify,omitempty"` TokenFromEnv bool `toml:"-"` } diff --git a/internal/update/updater.go b/internal/update/updater.go index 40208d0d..d5efc8ba 100644 --- a/internal/update/updater.go +++ b/internal/update/updater.go @@ -19,7 +19,6 @@ import ( "time" "github.com/deepsourcelabs/cli/buildinfo" - "github.com/deepsourcelabs/cli/config" "github.com/deepsourcelabs/cli/internal/debug" ) @@ -179,8 +178,8 @@ func ApplyUpdate(client *http.Client) (string, error) { return state.Version, nil } -// ShouldAutoUpdate reports whether the auto-updater should run. -func ShouldAutoUpdate() bool { +// ShouldCheckForUpdate reports whether the update check should run. +func ShouldCheckForUpdate() bool { bi := buildinfo.GetBuildInfo() if bi == nil { return false @@ -204,13 +203,6 @@ func ShouldAutoUpdate() bool { } } - // Check config - cfg, err := config.GetConfig() - if err == nil && cfg.AutoUpdate != nil && !*cfg.AutoUpdate { - debug.Log("update: skipping (disabled in config)") - return false - } - return true } diff --git a/internal/update/updater_test.go b/internal/update/updater_test.go index 6247d8b6..38832553 100644 --- a/internal/update/updater_test.go +++ b/internal/update/updater_test.go @@ -100,7 +100,7 @@ func TestReplaceBinary(t *testing.T) { } } -func TestShouldAutoUpdate_DevBuild(t *testing.T) { +func TestShouldCheckForUpdate_DevBuild(t *testing.T) { buildinfo.SetBuildInfo("2.0.3", "", "dev") // Clear CI vars so they don't interfere @@ -109,27 +109,27 @@ func TestShouldAutoUpdate_DevBuild(t *testing.T) { t.Setenv(v, "") } - if !ShouldAutoUpdate() { + if !ShouldCheckForUpdate() { t.Error("expected true for dev build with real version") } } -func TestShouldAutoUpdate_DevelopmentVersion(t *testing.T) { +func TestShouldCheckForUpdate_DevelopmentVersion(t *testing.T) { buildinfo.SetBuildInfo("development", "", "") - if ShouldAutoUpdate() { + if ShouldCheckForUpdate() { t.Error("expected false for development version") } } -func TestShouldAutoUpdate_CI(t *testing.T) { +func TestShouldCheckForUpdate_CI(t *testing.T) { buildinfo.SetBuildInfo("2.0.3", "", "prod") t.Setenv("CI", "true") - if ShouldAutoUpdate() { + if ShouldCheckForUpdate() { t.Error("expected false in CI") } } -func TestShouldAutoUpdate_Prod(t *testing.T) { +func TestShouldCheckForUpdate_Prod(t *testing.T) { buildinfo.SetBuildInfo("2.0.3", "", "prod") // Clear CI vars @@ -138,7 +138,7 @@ func TestShouldAutoUpdate_Prod(t *testing.T) { t.Setenv(v, "") } - if !ShouldAutoUpdate() { + if !ShouldCheckForUpdate() { t.Error("expected true for prod build outside CI") } } From 37de39eeea03cf4036cf8cccde45bb4a095ead43 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 15:50:02 -0700 Subject: [PATCH 2/9] Bump version to 2.0.47 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dd1266b3..668d0aa8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.46 +2.0.47 From b482d44b59a3e98f496872552b276a2039fd28f7 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:19:48 -0700 Subject: [PATCH 3/9] Use plain output for up-to-date message in update command - Replace style.Successf with fmt.Fprintf for the "already up to date" message - Keeps output simple and consistent with non-styled update flow --- command/update/update.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/update/update.go b/command/update/update.go index ec08b45d..980844cf 100644 --- a/command/update/update.go +++ b/command/update/update.go @@ -38,7 +38,7 @@ func runUpdate(cmd *cobra.Command) error { if state == nil { bi := buildinfo.GetBuildInfo() - style.Successf(w, "Already up to date (v%s)", bi.Version) + fmt.Fprintf(w, "Already up to date (v%s)\n", bi.Version) return nil } From 991480e67287dbebda20e7d39e092b895974500e Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:19:56 -0700 Subject: [PATCH 4/9] Bump version to 2.0.48 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 668d0aa8..77a45ae9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.47 +2.0.48 From 647a71fa95231584391c24ddd3403e8faf06d6c3 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:35:40 -0700 Subject: [PATCH 5/9] Polish update and help UX - Skip update check when running update command - Colorize update notification - Show examples only with --help -v - Simplify update success message --- cmd/deepsource/main.go | 8 +++++--- command/root.go | 19 +++++++++++++------ command/update/update.go | 4 +--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cmd/deepsource/main.go b/cmd/deepsource/main.go index a8949bd7..068e6939 100644 --- a/cmd/deepsource/main.go +++ b/cmd/deepsource/main.go @@ -16,6 +16,7 @@ import ( clierrors "github.com/deepsourcelabs/cli/internal/errors" "github.com/deepsourcelabs/cli/internal/update" "github.com/getsentry/sentry-go" + "github.com/pterm/pterm" ) var ( @@ -71,8 +72,9 @@ func mainRun() (exitCode int) { func run() int { v.SetBuildInfo(version, Date, buildMode) - // Check for available updates and notify - if update.ShouldCheckForUpdate() { + // Check for available updates and notify (skip when running "update" itself) + isUpdateCmd := len(os.Args) >= 2 && os.Args[1] == "update" + if !isUpdateCmd && update.ShouldCheckForUpdate() { client := &http.Client{Timeout: 3 * time.Second} if err := update.CheckForUpdate(client); err != nil { debug.Log("update: %v", err) @@ -83,7 +85,7 @@ func run() int { debug.Log("update: %v", err) } if state != nil { - fmt.Fprintf(os.Stderr, "Update available: v%s — run 'deepsource update' to install.\n", state.Version) + fmt.Fprintln(os.Stderr, pterm.Yellow(fmt.Sprintf("Update available: v%s — run 'deepsource update' to install.", state.Version))) } } diff --git a/command/root.go b/command/root.go index e70b86a5..f293b6df 100644 --- a/command/root.go +++ b/command/root.go @@ -95,6 +95,7 @@ func NewCmdRoot() *cobra.Command { cmd.AddCommand(updateC) cmd.PersistentFlags().Bool("skip-tls-verify", false, "Skip TLS certificate verification (for self-signed certs)") + cmd.Flags().BoolP("verbose", "v", false, "Show detailed output including examples") cmd.InitDefaultHelpFlag() cmd.InitDefaultVersionFlag() @@ -172,13 +173,19 @@ func rootHelpFunc(cmd *cobra.Command, _ []string) { } } - // Examples - examples := buildExampleText() - if examples != "" { - fmt.Fprintf(out, "%s\n", style.BoldCyan("Examples:")) - for _, line := range strings.Split(examples, "\n") { - fmt.Fprintf(out, " %s\n", line) + // Examples (shown only with --verbose / -v) + verbose, _ := cmd.Flags().GetBool("verbose") + if verbose { + examples := buildExampleText() + if examples != "" { + fmt.Fprintf(out, "%s\n", style.BoldCyan("Examples:")) + for _, line := range strings.Split(examples, "\n") { + fmt.Fprintf(out, " %s\n", line) + } + fmt.Fprintln(out) } + } else { + fmt.Fprintln(out, pterm.Gray("Use --help -v to see usage examples.")) fmt.Fprintln(out) } diff --git a/command/update/update.go b/command/update/update.go index 980844cf..91ba0175 100644 --- a/command/update/update.go +++ b/command/update/update.go @@ -3,11 +3,9 @@ package update import ( "fmt" "net/http" - "os" "time" "github.com/deepsourcelabs/cli/buildinfo" - "github.com/deepsourcelabs/cli/internal/cli/style" "github.com/deepsourcelabs/cli/internal/update" "github.com/spf13/cobra" ) @@ -51,7 +49,7 @@ func runUpdate(cmd *cobra.Command) error { } if newVer != "" { - style.Successf(os.Stderr, "Updated DeepSource CLI to v%s", newVer) + fmt.Fprintf(w, "Updated to v%s\n", newVer) } return nil From db2b1186e9f1ca7a415bf6c6648e29f7cafab95d Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:37:03 -0700 Subject: [PATCH 6/9] Bump version to 2.0.49 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 77a45ae9..aecb98f0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.48 +2.0.49 From a0b7e0f3cd1e7849500f4787892090d03c5876c6 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:40:19 -0700 Subject: [PATCH 7/9] Bump version to 2.0.50 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index aecb98f0..52be4db4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.49 +2.0.50 From 827ae1645661baca36316229f4fef8d4e0d21a95 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:58:15 -0700 Subject: [PATCH 8/9] Use actual binary name in update notification - Derive binary name from os.Args[0] instead of hardcoding 'deepsource' - Replace em dash with comma in the message --- cmd/deepsource/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/deepsource/main.go b/cmd/deepsource/main.go index 068e6939..4d535ab0 100644 --- a/cmd/deepsource/main.go +++ b/cmd/deepsource/main.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "os" + "path/filepath" "strings" "time" @@ -85,7 +86,7 @@ func run() int { debug.Log("update: %v", err) } if state != nil { - fmt.Fprintln(os.Stderr, pterm.Yellow(fmt.Sprintf("Update available: v%s — run 'deepsource update' to install.", state.Version))) + fmt.Fprintln(os.Stderr, pterm.Yellow(fmt.Sprintf("Update available: v%s, run '%s update' to install.", state.Version, filepath.Base(os.Args[0])))) } } From 23b4adbd5cc3349f975f5879def8f75a995ed572 Mon Sep 17 00:00:00 2001 From: Jai Pradeesh Date: Fri, 20 Mar 2026 16:58:35 -0700 Subject: [PATCH 9/9] Bump version to 2.0.51 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 52be4db4..b74a3424 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.50 +2.0.51