From f1180974bbbd88d0ce5da0553873249c3471b37f Mon Sep 17 00:00:00 2001 From: Marc Szanto <11840265+Xemdo@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:30:59 -0700 Subject: [PATCH] Added notice for when a new version is available to upgrade to --- README.md | 55 ++++++++++++- cmd/root.go | 4 + go.mod | 1 + go.sum | 3 +- internal/util/version_checker.go | 132 +++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 internal/util/version_checker.go diff --git a/README.md b/README.md index d4ed8118..4f394b29 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,39 @@ To install via Winget, run: winget install Twitch.TwitchCLI ``` -To update, run: +### Manual Download + +To download, go to the [Releases tab of GitHub](https://github.com/twitchdev/twitch-cli/releases). The examples in the documentation assume you have put this into your PATH and renamed to `twitch` (or symlinked as such). + +**Note**: If using MacOS and downloading manually, you may need to adjust the permissions of the file to allow for execution. + +To do so, please run: `chmod 755 ` where the filename is the name of the downloaded binary. + +## Updating + +To update the Twitch CLI, run the command relevant to your installation method. + +**NOTE:** Once a day the program will make an HTTP call to Github to check if the application is of the latest version. For information on disabling this, see *Disabling release version checks and notices* below. + +### Homebrew + +To update using Homebrew, run: + +```sh +brew upgrade twitchdev/twitch/twitch-cli +``` + +### Scoop + +To update using Scoop, run: + +```sh +scoop update twitch-cli +``` + +### WinGet + +To update using WinGet, run: ```sh winget update Twitch.TwitchCLI @@ -56,7 +88,26 @@ To download, go to the [Releases tab of GitHub](https://github.com/twitchdev/twi **Note**: If using MacOS and downloading manually, you may need to adjust the permissions of the file to allow for execution. -To do so, please run: `chmod 755 ` where the filename is the name of the downloaded binary. +To do so, please run: `chmod 755 ` where the filename is the name of the downloaded binary. + +## Disabling release version checks and notices + +When the Twitch CLI exits successfully, the application will automatically check the Twitch CLI's Github releases at the following URL: + +``` +https://api.github.com/repos/twitchdev/twitch-cli/releases/latest +``` + +If the version of the Twitch CLI you are running is older than the latest released version, a notice will be printed to the console. + +To prevent this from happening, make one of the following changes: + +- Set the environment variable `CI` to `true` +- Set the environment variable `TWITCH_DISABLE_UPDATE_CHECKS` to `true` +- Add `DISABLE_UPDATE_CHECKS=true` to your **.twitch-cli.env** configuration file +- SET `LAST_UPDATE_CHECK` to `3000-01-01` in your **.twitch-cli.env** configuration file, which will prevent it from running until the year 3000 + +If you're running the Twitch CLI in a CI/CD environment, most environments will have already set the `CI` environment variable to `true`. ## Usage diff --git a/cmd/root.go b/cmd/root.go index c1fc30a3..00659d97 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,10 +23,14 @@ var rootCmd = &cobra.Command{ // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { + // Execute normally; Exit without checking for updates if there's an error. if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } + + // Check Github's Releases API to see if we're running the latest version. + util.CheckForUpdatesAndPrintNotice() } func init() { diff --git a/go.mod b/go.mod index cd6f963e..6830ae07 100755 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hokaccha/go-prettyjson v0.0.0-20201222001619-a42f9ac2ec8e // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index d869dd4b..f8502a95 100644 --- a/go.sum +++ b/go.sum @@ -98,6 +98,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -149,7 +151,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= diff --git a/internal/util/version_checker.go b/internal/util/version_checker.go new file mode 100644 index 00000000..9bbf9775 --- /dev/null +++ b/internal/util/version_checker.go @@ -0,0 +1,132 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package util + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "regexp" + "strings" + "time" + + "github.com/fatih/color" + "github.com/spf13/viper" + + vrs "github.com/hashicorp/go-version" +) + +type updateCheckerReleasesResponse struct { + // Only thing we really care about here is the tag name + TagName string `json:"tag_name"` +} + +// Check Github's Releases API to see if we're running the latest version. +// If there's any errors, quietly allow it to fail. +func CheckForUpdatesAndPrintNotice() { + // Don't bother running this if current application is built from source + if strings.EqualFold(GetVersion(), "source") { + return + } + + // Don't run if program is running in CI/CD (CI env variable will be set), or if TWITCH_DISABLE_UPDATE_CHECKS is set to true. + if viper.GetBool("disable_update_checks") || strings.EqualFold(os.Getenv("CI"), "true") { + return + } + + // Don't run if this already ran successfully today + today, _ := time.Parse(time.DateOnly, time.Now().Format(time.DateOnly)) + lastRunDate, _ := time.Parse(time.DateOnly, viper.GetString("last_update_check")) + if !today.After(lastRunDate) { + return + } + + runningLatestVersion, latestVersionTag, err := areWeRunningLatestVersion() + if err != nil { + return // Drop errors without notifying + } + + if !runningLatestVersion { + // Messages to be displayed + messages := []string{ + " A new Twitch CLI release is available: " + latestVersionTag + " ", + "", + " See upgrade instructions at: ", + " https://github.com/twitchdev/twitch-cli/blob/main/README.md#update ", + "", + " Check out the release notes at: ", + " https://github.com/twitchdev/twitch-cli/releases/latest ", + } + + // Find longest message + longestMessageLength := 0 + for _, str := range messages { + if len(str) > longestMessageLength { + longestMessageLength = len(str) + } + } + + // Print messages + shaded := color.New(color.BgWhite, color.FgBlack).SprintfFunc() + + fmt.Println() + for _, str := range messages { + fmt.Printf(" %v\n", shaded(str+strings.Repeat(" ", longestMessageLength-len(str)))) + } + fmt.Println() + + // Update config so this isn't repeated until tomorrow + viper.Set("last_update_check", GetTimestamp().Format(time.DateOnly)) + configPath, err := GetConfigPath() + if err != nil { + return + } + _ = viper.WriteConfigAs(configPath) + } +} + +// Makes the call to Github's Releases API to check for the latest release version, and compares it to the current version. +func areWeRunningLatestVersion() (bool, string, error) { + REGEX_TAG_NAME_VERSION := regexp.MustCompile("^(?:v)(.+)") // Removes the v from the start of the tag, if it exists + + client := &http.Client{ + Timeout: time.Second * 2, + } + + req, err := http.NewRequest("GET", "https://api.github.com/repos/twitchdev/twitch-cli/releases/latest", nil) + if err != nil { + return false, "", err + } + req.Header.Set("User-Agent", "twitch-cli/"+GetVersion()) + + response, err := client.Do(req) + if err != nil { + return false, "", err + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + return false, "", err + } + + var obj updateCheckerReleasesResponse + err = json.Unmarshal(body, &obj) + if err != nil { + return false, "", err + } + + latestReleaseVersion, err := vrs.NewVersion(REGEX_TAG_NAME_VERSION.FindAllStringSubmatch(obj.TagName, -1)[0][1]) + if err != nil { + return false, "", err + } + + currentVersion, err := vrs.NewVersion(GetVersion()) + if err != nil { + return false, "", err + } + + return currentVersion.GreaterThanOrEqual(latestReleaseVersion), obj.TagName, nil +}