Skip to content

Commit ace64b7

Browse files
committed
reorganize
1 parent fd75c84 commit ace64b7

File tree

8 files changed

+119
-40
lines changed

8 files changed

+119
-40
lines changed

cmd/login.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"bufio"
5+
"errors"
56
"fmt"
67
"os"
78
"regexp"
@@ -13,10 +14,12 @@ import (
1314
)
1415

1516
var loginCmd = &cobra.Command{
16-
Use: "login",
17-
Aliases: []string{"auth", "authenticate", "signin"},
18-
Short: "Authenticate the CLI with your account",
19-
Run: func(cmd *cobra.Command, args []string) {
17+
Use: "login",
18+
Aliases: []string{"auth", "authenticate", "signin"},
19+
Short: "Authenticate the CLI with your account",
20+
SilenceUsage: true,
21+
PreRun: requireUpdated,
22+
RunE: func(cmd *cobra.Command, args []string) error {
2023
// TODO: check if the logo fits screen width
2124
// fmt.Print(logo)
2225
fmt.Print("Welcome to the boot.dev CLI!\n\n")
@@ -28,23 +31,33 @@ var loginCmd = &cobra.Command{
2831
reader := bufio.NewReader(os.Stdin)
2932
fmt.Print("\nPaste your login code: ")
3033
text, err := reader.ReadString('\n')
31-
cobra.CheckErr(err)
34+
35+
if err != nil {
36+
return err
37+
}
3238

3339
re := regexp.MustCompile(`[^A-Za-z0-9_-]`)
3440
text = re.ReplaceAllString(text, "")
3541
creds, err := api.LoginWithCode(text)
36-
cobra.CheckErr(err)
42+
if err != nil {
43+
return err
44+
}
45+
3746
if creds.AccessToken == "" || creds.RefreshToken == "" {
38-
cobra.CheckErr("invalid credentials received")
47+
return errors.New("invalid credentials received")
3948
}
4049

4150
viper.Set("access_token", creds.AccessToken)
4251
viper.Set("refresh_token", creds.RefreshToken)
4352
viper.Set("last_refresh", time.Now().Unix())
4453

4554
err = viper.WriteConfig()
46-
cobra.CheckErr(err)
55+
if err != nil {
56+
return err
57+
}
58+
4759
fmt.Println("Logged in successfully!")
60+
return nil
4861
},
4962
}
5063

cmd/logout.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ func logout() {
2626
}
2727

2828
var logoutCmd = &cobra.Command{
29-
Use: "logout",
30-
Aliases: []string{"signout"},
31-
Short: "Disconnect the CLI from your account",
32-
PreRun: requireAuth,
33-
Run: func(cmd *cobra.Command, args []string) {
29+
Use: "logout",
30+
Aliases: []string{"signout"},
31+
Short: "Disconnect the CLI from your account",
32+
PreRun: requireAuth,
33+
SilenceUsage: true,
34+
RunE: func(cmd *cobra.Command, args []string) error {
3435
logout()
36+
return nil
3537
},
3638
}
3739

cmd/root.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package cmd
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"path"
78
"time"
89

9-
"github.com/bootdotdev/bootdev/checks"
1010
api "github.com/bootdotdev/bootdev/client"
11+
"github.com/bootdotdev/bootdev/version"
1112
"github.com/spf13/cobra"
1213
"github.com/spf13/viper"
1314
)
@@ -42,13 +43,11 @@ const logo string = `
4243

4344
// Execute adds all child commands to the root command and sets flags appropriately.
4445
// This is called by main.main(). It only needs to happen once to the rootCmd.
45-
func Execute() {
46-
err := checks.PromptUpdateIfNecessary(rootCmd.Version)
47-
cobra.CheckErr(err)
48-
err = rootCmd.Execute()
49-
if err != nil {
50-
os.Exit(1)
51-
}
46+
func Execute() error {
47+
info := version.FetchUpdateInfo(rootCmd.Version)
48+
defer info.PromptUpdateIfAvailable()
49+
ctx := version.WithContext(context.Background(), &info)
50+
return rootCmd.ExecuteContext(ctx)
5251
}
5352

5453
func init() {
@@ -90,10 +89,25 @@ func initConfig() {
9089
viper.AutomaticEnv() // read in environment variables that match
9190
}
9291

93-
func promptLoginAndExitIf(condition bool) {
94-
if condition {
95-
fmt.Println("You must be logged in to use that command.")
96-
fmt.Println("Please run 'bootdev login' first.")
92+
// Chain multiple commands together.
93+
func compose(commands ...func(cmd *cobra.Command, args []string)) func(cmd *cobra.Command, args []string) {
94+
return func(cmd *cobra.Command, args []string) {
95+
for _, command := range commands {
96+
command(cmd, args)
97+
}
98+
}
99+
}
100+
101+
// Call this function at the beginning of a command handler
102+
// if you want to require the user to update their CLI first.
103+
func requireUpdated(cmd *cobra.Command, args []string) {
104+
info := version.FromContext(cmd.Context())
105+
if info == nil || info.FailedToFetch != nil {
106+
fmt.Fprintln(os.Stderr, "Failed to fetch update info. Are you online?")
107+
os.Exit(1)
108+
}
109+
if info.IsUpdateRequired {
110+
info.PromptUpdateIfAvailable()
97111
os.Exit(1)
98112
}
99113
}
@@ -103,6 +117,14 @@ func promptLoginAndExitIf(condition bool) {
103117
// automatically refresh the tokens, if necessary, and prompt
104118
// the user to re-login if anything goes wrong.
105119
func requireAuth(cmd *cobra.Command, args []string) {
120+
promptLoginAndExitIf := func(condition bool) {
121+
if condition {
122+
fmt.Fprintln(os.Stderr, "You must be logged in to use that command.")
123+
fmt.Fprintln(os.Stderr, "Please run 'bootdev login' first.")
124+
os.Exit(1)
125+
}
126+
}
127+
106128
access_token := viper.GetString("access_token")
107129
promptLoginAndExitIf(access_token == "")
108130

cmd/run.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,23 @@ func init() {
1616
// runCmd represents the run command
1717
var runCmd = &cobra.Command{
1818
Use: "run UUID",
19-
Args: cobra.MatchAll(cobra.ExactArgs(1)),
19+
Args: cobra.ExactArgs(1),
2020
Short: "Run an assignment without submitting",
21-
PreRun: requireAuth,
22-
Run: func(cmd *cobra.Command, args []string) {
21+
PreRun: compose(requireUpdated, requireAuth),
22+
RunE: func(cmd *cobra.Command, args []string) error {
23+
cmd.SilenceUsage = true
2324
assignmentUUID := args[0]
2425
assignment, err := api.FetchAssignment(assignmentUUID)
25-
cobra.CheckErr(err)
26+
if err != nil {
27+
return err
28+
}
2629
if assignment.Assignment.Type == "type_http_tests" {
2730
results, finalBaseURL := checks.HttpTest(*assignment, &runBaseURL)
2831
printResults(results, assignment, finalBaseURL)
2932
cobra.CheckErr(err)
3033
} else {
3134
cobra.CheckErr("unsupported assignment type")
3235
}
36+
return nil
3337
},
3438
}

cmd/submit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var submitCmd = &cobra.Command{
2121
Use: "submit UUID",
2222
Args: cobra.MatchAll(cobra.ExactArgs(1)),
2323
Short: "Submit an assignment",
24-
PreRun: requireAuth,
24+
PreRun: compose(requireUpdated, requireAuth),
2525
Run: func(cmd *cobra.Command, args []string) {
2626
assignmentUUID := args[0]
2727
assignment, err := api.FetchAssignment(assignmentUUID)

main.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package main
22

3-
import "github.com/bootdotdev/bootdev/cmd"
3+
import (
4+
"os"
5+
6+
"github.com/bootdotdev/bootdev/cmd"
7+
)
48

59
func main() {
6-
cmd.Execute()
10+
err := cmd.Execute()
11+
if err != nil {
12+
os.Exit(1)
13+
}
714
}

version/context.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package version
2+
3+
import "context"
4+
5+
var ContextKey = struct{ string }{"version"}
6+
7+
func WithContext(ctx context.Context, version *VersionInfo) context.Context {
8+
return context.WithValue(ctx, ContextKey, version)
9+
}
10+
11+
func FromContext(ctx context.Context) *VersionInfo {
12+
if c, ok := ctx.Value(ContextKey).(*VersionInfo); ok {
13+
return c
14+
}
15+
16+
return nil
17+
}

checks/version.go renamed to version/version.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package checks
1+
package version
22

33
import (
44
"encoding/json"
@@ -19,23 +19,37 @@ type GHTag struct {
1919
Name string `json:"name"`
2020
}
2121

22-
func PromptUpdateIfNecessary(currentVersion string) error {
22+
type VersionInfo struct {
23+
CurrentVersion string
24+
LatestVersion string
25+
IsOutdated bool
26+
IsUpdateRequired bool
27+
FailedToFetch error
28+
}
29+
30+
func FetchUpdateInfo(currentVersion string) VersionInfo {
2331
latest, err := getLatestVersion()
2432
if err != nil {
25-
return err
33+
return VersionInfo{
34+
FailedToFetch: err,
35+
}
2636
}
2737
isUpdateRequired := isUpdateRequired(currentVersion, latest)
2838
isOutdated := isOutdated(currentVersion, latest)
39+
return VersionInfo{
40+
IsUpdateRequired: isUpdateRequired,
41+
IsOutdated: isOutdated,
42+
CurrentVersion: currentVersion,
43+
LatestVersion: latest,
44+
}
45+
}
2946

30-
if isOutdated {
47+
func (v *VersionInfo) PromptUpdateIfAvailable() {
48+
if v.IsOutdated {
3149
fmt.Fprintln(os.Stderr, "A new version of the bootdev CLI is available!")
3250
fmt.Fprintln(os.Stderr, "Please run the following command to update:")
3351
fmt.Fprintf(os.Stderr, " go install github.com/%s/%s@latest\n\n", repoOwner, repoName)
34-
if isUpdateRequired {
35-
os.Exit(1)
36-
}
3752
}
38-
return nil
3953
}
4054

4155
// Returns true if the current version is older than the latest.

0 commit comments

Comments
 (0)