Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions internal/commands/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ type upgradeOptions struct {
currentVersion string
}

// IsUpgradeInvocation reports whether the given args resolve to the
// "upgrade" command. Used by main to suppress the update-available notice
// when the user is already upgrading.
func IsUpgradeInvocation(root *cobra.Command, args []string) bool {
cmd, _, err := root.Find(args)
if err != nil || cmd == nil {
return false
}
return cmd.Name() == "upgrade"
}

// AddUpgradeCommand registers the "upgrade" subcommand on the root command.
// It requires the current build version to display during the upgrade flow.
func AddUpgradeCommand(root *cobra.Command, currentVersion string) {
Expand Down
30 changes: 30 additions & 0 deletions internal/commands/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,38 @@ import (
"path/filepath"
"runtime"
"testing"

"github.com/spf13/cobra"
)

func TestIsUpgradeInvocation(t *testing.T) {
root := &cobra.Command{Use: "wherobots"}
other := &cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}}
root.AddCommand(other)
AddUpgradeCommand(root, "1.0.0")

tests := []struct {
name string
args []string
want bool
}{
{"upgrade alone", []string{"upgrade"}, true},
{"upgrade with flag", []string{"upgrade", "--tag", "v1.0.1"}, true},
{"other command", []string{"list"}, false},
{"no args", []string{}, false},
{"unknown command", []string{"nope"}, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsUpgradeInvocation(root, tt.args)
if got != tt.want {
t.Fatalf("IsUpgradeInvocation(%v) = %v, want %v", tt.args, got, tt.want)
}
})
}
}

func TestDetectPlatform(t *testing.T) {
osName, archName, err := detectPlatform()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/version/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func Collect(ch <-chan *Result) *Result {
// FormatNotice returns the human-readable update message to display.
func FormatNotice(r *Result) string {
return fmt.Sprintf(
"A newer version of the Wherobots CLI is available: %s (current: %s).\nRun `wherobots upgrade` to update.",
"[!] A newer version of the Wherobots CLI is available: %s (current: %s).\n Run `wherobots upgrade` to update.",
r.Latest, r.Current,
)
}
Expand Down
42 changes: 36 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"context"
"fmt"
"io"
"os"

"wherobots/cli/internal/commands"
Expand Down Expand Up @@ -50,16 +51,45 @@ func run(ctx context.Context) error {
root.Version = versionString
root.Short = fmt.Sprintf("Wherobots CLI %s", buildVersion)
commands.AddUpgradeCommand(root, buildVersion)

// Suppress the "update available" notice when the user is already running
// `upgrade` — the check races with the upgrade itself and would otherwise
// nag about the very version that just got installed.
suppressNotice := commands.IsUpgradeInvocation(root, os.Args[1:])

execErr := root.ExecuteContext(ctx)

// After the command finishes, print an update notice if one is available.
if result := version.Collect(updateCh); result != nil {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, version.FormatNotice(result))
if execErr != nil {
fmt.Fprintln(os.Stderr, "Note: your CLI is out of date. Run `wherobots upgrade` to update — it may resolve this issue.")
if !suppressNotice {
if result := version.Collect(updateCh); result != nil {
fmt.Fprintln(os.Stderr, "")
printUpdateNotice(os.Stderr, result)
if execErr != nil {
fmt.Fprintln(os.Stderr, "Note: your CLI is out of date. Run `wherobots upgrade` to update — it may resolve this issue.")
}
}
}

return execErr
}

func printUpdateNotice(w io.Writer, r *version.Result) {
notice := version.FormatNotice(r)
if isTTY(w) {
notice = "\033[1;33m" + notice + "\033[0m"
}
fmt.Fprintln(w, notice)
}

// isTTY reports whether w is an *os.File pointing at a terminal. Stdlib only;
// avoids pulling in a tty-detection dependency for a single notice.
func isTTY(w io.Writer) bool {
f, ok := w.(*os.File)
if !ok {
return false
}
fi, err := f.Stat()
if err != nil {
return false
}
return (fi.Mode() & os.ModeCharDevice) != 0
}
Loading