hookdeck-cli 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/.github/workflows/publish-npm.yml +19 -0
  2. package/.github/workflows/release.yml +85 -0
  3. package/.goreleaser/linux.yml +68 -0
  4. package/.goreleaser/mac.yml +69 -0
  5. package/.goreleaser/windows.yml +52 -0
  6. package/.tool-versions +1 -0
  7. package/Dockerfile +5 -0
  8. package/LICENSE +202 -0
  9. package/README.md +223 -0
  10. package/docs/cli-demo.gif +0 -0
  11. package/go.mod +58 -0
  12. package/go.sum +444 -0
  13. package/main.go +22 -0
  14. package/package.json +30 -0
  15. package/pkg/ansi/ansi.go +208 -0
  16. package/pkg/ansi/init_windows.go +23 -0
  17. package/pkg/cmd/completion.go +128 -0
  18. package/pkg/cmd/listen.go +114 -0
  19. package/pkg/cmd/login.go +37 -0
  20. package/pkg/cmd/logout.go +35 -0
  21. package/pkg/cmd/root.go +112 -0
  22. package/pkg/cmd/version.go +25 -0
  23. package/pkg/cmd/whoami.go +50 -0
  24. package/pkg/config/config.go +326 -0
  25. package/pkg/config/config_test.go +20 -0
  26. package/pkg/config/profile.go +296 -0
  27. package/pkg/config/profile_test.go +109 -0
  28. package/pkg/hookdeck/client.go +210 -0
  29. package/pkg/hookdeck/client_test.go +203 -0
  30. package/pkg/hookdeck/connections.go +61 -0
  31. package/pkg/hookdeck/destinations.go +14 -0
  32. package/pkg/hookdeck/guest.go +37 -0
  33. package/pkg/hookdeck/session.go +37 -0
  34. package/pkg/hookdeck/sources.go +73 -0
  35. package/pkg/hookdeck/telemetry.go +84 -0
  36. package/pkg/hookdeck/telemetry_test.go +35 -0
  37. package/pkg/hookdeck/verbosetransport.go +82 -0
  38. package/pkg/hookdeck/verbosetransport_test.go +47 -0
  39. package/pkg/listen/connection.go +91 -0
  40. package/pkg/listen/listen.go +140 -0
  41. package/pkg/listen/source.go +84 -0
  42. package/pkg/login/client_login.go +209 -0
  43. package/pkg/login/interactive_login.go +180 -0
  44. package/pkg/login/login_message.go +24 -0
  45. package/pkg/login/poll.go +78 -0
  46. package/pkg/login/validate.go +52 -0
  47. package/pkg/logout/logout.go +48 -0
  48. package/pkg/open/open.go +50 -0
  49. package/pkg/proxy/proxy.go +433 -0
  50. package/pkg/useragent/uname_unix.go +25 -0
  51. package/pkg/useragent/uname_unix_test.go +24 -0
  52. package/pkg/useragent/uname_windows.go +9 -0
  53. package/pkg/useragent/useragent.go +74 -0
  54. package/pkg/validators/cmds.go +71 -0
  55. package/pkg/validators/validate.go +144 -0
  56. package/pkg/version/version.go +58 -0
  57. package/pkg/version/version_test.go +15 -0
  58. package/pkg/websocket/attempt_messages.go +47 -0
  59. package/pkg/websocket/client.go +525 -0
  60. package/pkg/websocket/connection_messages.go +11 -0
  61. package/pkg/websocket/messages.go +64 -0
@@ -0,0 +1,208 @@
1
+ package ansi
2
+
3
+ import (
4
+ "fmt"
5
+ "io"
6
+ "os"
7
+ "runtime"
8
+ "time"
9
+
10
+ "github.com/briandowns/spinner"
11
+ "github.com/logrusorgru/aurora"
12
+ "github.com/tidwall/pretty"
13
+ "golang.org/x/term"
14
+ )
15
+
16
+ var darkTerminalStyle = &pretty.Style{
17
+ Key: [2]string{"\x1B[34m", "\x1B[0m"},
18
+ String: [2]string{"\x1B[30m", "\x1B[0m"},
19
+ Number: [2]string{"\x1B[94m", "\x1B[0m"},
20
+ True: [2]string{"\x1B[35m", "\x1B[0m"},
21
+ False: [2]string{"\x1B[35m", "\x1B[0m"},
22
+ Null: [2]string{"\x1B[31m", "\x1B[0m"},
23
+ }
24
+
25
+ //
26
+ // Public variables
27
+ //
28
+
29
+ // ForceColors forces the use of colors and other ANSI sequences.
30
+ var ForceColors = false
31
+
32
+ // DisableColors disables all colors and other ANSI sequences.
33
+ var DisableColors = false
34
+
35
+ // EnvironmentOverrideColors overs coloring based on `CLICOLOR` and
36
+ // `CLICOLOR_FORCE`. Cf. https://bixense.com/clicolors/
37
+ var EnvironmentOverrideColors = true
38
+
39
+ //
40
+ // Public functions
41
+ //
42
+
43
+ // Bold returns bolded text if the writer supports colors
44
+ func Bold(text string) string {
45
+ color := Color(os.Stdout)
46
+ return color.Sprintf(color.Bold(text))
47
+ }
48
+
49
+ // Color returns an aurora.Aurora instance with colors enabled or disabled
50
+ // depending on whether the writer supports colors.
51
+ func Color(w io.Writer) aurora.Aurora {
52
+ return aurora.NewAurora(shouldUseColors(w))
53
+ }
54
+
55
+ // ColorizeJSON returns a colorized version of the input JSON, if the writer
56
+ // supports colors.
57
+ func ColorizeJSON(json string, darkStyle bool, w io.Writer) string {
58
+ if !shouldUseColors(w) {
59
+ return json
60
+ }
61
+
62
+ style := (*pretty.Style)(nil)
63
+ if darkStyle {
64
+ style = darkTerminalStyle
65
+ }
66
+
67
+ return string(pretty.Color([]byte(json), style))
68
+ }
69
+
70
+ // ColorizeStatus returns a colorized number for HTTP status code
71
+ func ColorizeStatus(status int) aurora.Value {
72
+ color := Color(os.Stdout)
73
+
74
+ switch {
75
+ case status >= 500:
76
+ return color.Red(status).Bold()
77
+ case status >= 300:
78
+ return color.Yellow(status).Bold()
79
+ default:
80
+ return color.Green(status).Bold()
81
+ }
82
+ }
83
+
84
+ // Faint returns slightly offset color text if the writer supports it
85
+ func Faint(text string) string {
86
+ color := Color(os.Stdout)
87
+ return color.Sprintf(color.Faint(text))
88
+ }
89
+
90
+ // Italic returns italicized text if the writer supports it.
91
+ func Italic(text string) string {
92
+ color := Color(os.Stdout)
93
+ return color.Sprintf(color.Italic(text))
94
+ }
95
+
96
+ // Linkify returns an ANSI escape sequence with an hyperlink, if the writer
97
+ // supports colors.
98
+ func Linkify(text, url string, w io.Writer) string {
99
+ if !shouldUseColors(w) {
100
+ return text
101
+ }
102
+
103
+ // See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
104
+ // for more information about this escape sequence.
105
+ return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text)
106
+ }
107
+
108
+ type charset = []string
109
+
110
+ func getCharset() charset {
111
+ // See https://github.com/briandowns/spinner#available-character-sets for
112
+ // list of available charsets
113
+ if runtime.GOOS == "windows" {
114
+ // Less fancy, but uses ASCII characters so works with Windows default
115
+ // console.
116
+ return spinner.CharSets[8]
117
+ }
118
+ return spinner.CharSets[11]
119
+ }
120
+
121
+ const duration = time.Duration(100) * time.Millisecond
122
+
123
+ // StartNewSpinner starts a new spinner with the given message. If the writer is not
124
+ // a terminal or doesn't support colors, it simply prints the message.
125
+ func StartNewSpinner(msg string, w io.Writer) *spinner.Spinner {
126
+ if !isTerminal(w) || !shouldUseColors(w) {
127
+ fmt.Fprintln(w, msg)
128
+ return nil
129
+ }
130
+
131
+ s := spinner.New(getCharset(), duration)
132
+ s.Writer = w
133
+
134
+ if msg != "" {
135
+ s.Suffix = " " + msg
136
+ }
137
+
138
+ s.Start()
139
+
140
+ return s
141
+ }
142
+
143
+ // StartSpinner updates an existing spinner's message, and starts it if it was stopped
144
+ func StartSpinner(s *spinner.Spinner, msg string, w io.Writer) {
145
+ if s == nil {
146
+ fmt.Fprintln(w, msg)
147
+ return
148
+ }
149
+ if msg != "" {
150
+ s.Suffix = " " + msg
151
+ }
152
+ if !s.Active() {
153
+ s.Start()
154
+ }
155
+ }
156
+
157
+ // StopSpinner stops a spinner with the given message. If the writer is not
158
+ // a terminal or doesn't support colors, it simply prints the message.
159
+ func StopSpinner(s *spinner.Spinner, msg string, w io.Writer) {
160
+ if !isTerminal(w) || !shouldUseColors(w) {
161
+ fmt.Fprintln(w, msg)
162
+ return
163
+ }
164
+
165
+ if msg != "" {
166
+ s.FinalMSG = "> " + msg + "\n"
167
+ }
168
+
169
+ s.Stop()
170
+ }
171
+
172
+ // StrikeThrough returns struck though text if the writer supports colors
173
+ func StrikeThrough(text string) string {
174
+ color := Color(os.Stdout)
175
+ return color.Sprintf(color.StrikeThrough(text))
176
+ }
177
+
178
+ //
179
+ // Private functions
180
+ //
181
+
182
+ func isTerminal(w io.Writer) bool {
183
+ switch v := w.(type) {
184
+ case *os.File:
185
+ return term.IsTerminal(int(v.Fd()))
186
+ default:
187
+ return false
188
+ }
189
+ }
190
+
191
+ func shouldUseColors(w io.Writer) bool {
192
+ useColors := ForceColors || isTerminal(w)
193
+
194
+ if EnvironmentOverrideColors {
195
+ force, ok := os.LookupEnv("CLICOLOR_FORCE")
196
+
197
+ switch {
198
+ case ok && force != "0":
199
+ useColors = true
200
+ case ok && force == "0":
201
+ useColors = false
202
+ case os.Getenv("CLICOLOR") == "0":
203
+ useColors = false
204
+ }
205
+ }
206
+
207
+ return useColors && !DisableColors
208
+ }
@@ -0,0 +1,23 @@
1
+ // +build windows
2
+
3
+ package ansi
4
+
5
+ import (
6
+ "os"
7
+
8
+ "golang.org/x/sys/windows"
9
+ )
10
+
11
+ // enableAnsiColors enables support for ANSI color sequences in Windows
12
+ // default console. Note that this only works with Windows 10.
13
+ func enableAnsiColors() {
14
+ stdout := windows.Handle(os.Stdout.Fd())
15
+ var originalMode uint32
16
+
17
+ windows.GetConsoleMode(stdout, &originalMode)
18
+ windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
19
+ }
20
+
21
+ func init() {
22
+ enableAnsiColors()
23
+ }
@@ -0,0 +1,128 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "strings"
7
+
8
+ "github.com/spf13/cobra"
9
+
10
+ "runtime"
11
+
12
+ "github.com/hookdeck/hookdeck-cli/pkg/validators"
13
+ )
14
+
15
+ type completionCmd struct {
16
+ cmd *cobra.Command
17
+
18
+ shell string
19
+ }
20
+
21
+ func newCompletionCmd() *completionCmd {
22
+ cc := &completionCmd{}
23
+
24
+ cc.cmd = &cobra.Command{
25
+ Use: "completion",
26
+ Short: "Generate bash and zsh completion scripts",
27
+ Args: validators.NoArgs,
28
+ RunE: func(cmd *cobra.Command, args []string) error {
29
+ return selectShell(cc.shell)
30
+ },
31
+ }
32
+
33
+ cc.cmd.Flags().StringVar(&cc.shell, "shell", "", "The shell to generate completion commands for. Supports \"bash\" or \"zsh\"")
34
+
35
+ return cc
36
+ }
37
+
38
+ const (
39
+ instructionsHeader = `
40
+ Suggested next steps:
41
+ ---------------------`
42
+
43
+ zshCompletionInstructions = `
44
+ 1. Move ` + "`hookdeck-completion.zsh`" + ` to the correct location:
45
+ mkdir -p ~/.hookdeck
46
+ mv hookdeck-completion.zsh ~/.hookdeck
47
+
48
+ 2. Add the following lines to your ` + "`.zshrc`" + ` enabling shell completion for Hookdeck:
49
+ fpath=(~/.hookdeck $fpath)
50
+ autoload -Uz compinit && compinit -i
51
+
52
+ 3. Source your ` + "`.zshrc`" + ` or open a new terminal session:
53
+ source ~/.zshrc`
54
+
55
+ bashCompletionInstructionsMac = `
56
+ Set up bash autocompletion on your system:
57
+ 1. Install the bash autocompletion package:
58
+ brew install bash-completion
59
+ 2. Follow the post-install instructions displayed by Homebrew; add a line like the following to your bash profile:
60
+ [[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
61
+
62
+ Set up Hookdeck autocompletion:
63
+ 3. Move ` + "`hookdeck-completion.bash`" + ` to the correct location:
64
+ mkdir -p ~/.hookdeck
65
+ mv hookdeck-completion.bash ~/.hookdeck
66
+
67
+ 4. Add the following line to your bash profile, so that Hookdeck autocompletion will be enabled every time you start a new terminal session:
68
+ source ~/.hookdeck/hookdeck-completion.bash
69
+
70
+ 5. Either restart your terminal, or run the following command in your current session to enable immediately:
71
+ source ~/.hookdeck/hookdeck-completion.bash`
72
+
73
+ bashCompletionInstructionsLinux = `
74
+ 1. Ensure bash autocompletion is installed on your system. Often, this means verifying that ` + "`/etc/profile.d/bash_completion.sh`" + ` exists, and is sourced by your bash profile; the location of this file varies across distributions of Linux.
75
+
76
+ 2. Move ` + "`hookdeck-completion.bash`" + ` to the correct location:
77
+ mkdir -p ~/.hookdeck
78
+ mv hookdeck-completion.bash ~/.hookdeck
79
+
80
+ 3. Add the following line to your bash profile, so that Hookdeck autocompletion will be enabled every time you start a new terminal session:
81
+ source ~/.hookdeck/hookdeck-completion.bash
82
+
83
+ 4. Either restart your terminal, or run the following command in your current session to enable immediately:
84
+ source ~/.hookdeck/hookdeck-completion.bash`
85
+ )
86
+
87
+ func selectShell(shell string) error {
88
+ selected := shell
89
+ if selected == "" {
90
+ selected = detectShell()
91
+ }
92
+
93
+ switch {
94
+ case selected == "zsh":
95
+ fmt.Println("Detected `zsh`, generating zsh completion file: hookdeck-completion.zsh")
96
+ err := rootCmd.GenZshCompletionFile("hookdeck-completion.zsh")
97
+ if err == nil {
98
+ fmt.Printf("%s%s\n", instructionsHeader, zshCompletionInstructions)
99
+ }
100
+ return err
101
+ case selected == "bash":
102
+ fmt.Println("Detected `bash`, generating bash completion file: hookdeck-completion.bash")
103
+ err := rootCmd.GenBashCompletionFile("hookdeck-completion.bash")
104
+ if err == nil {
105
+ if runtime.GOOS == "darwin" {
106
+ fmt.Printf("%s%s\n", instructionsHeader, bashCompletionInstructionsMac)
107
+ } else if runtime.GOOS == "linux" {
108
+ fmt.Printf("%s%s\n", instructionsHeader, bashCompletionInstructionsLinux)
109
+ }
110
+ }
111
+ return err
112
+ default:
113
+ return fmt.Errorf("Could not automatically detect your shell. Please run the command with the `--shell` flag for either bash or zsh")
114
+ }
115
+ }
116
+
117
+ func detectShell() string {
118
+ shell := os.Getenv("SHELL")
119
+
120
+ switch {
121
+ case strings.Contains(shell, "zsh"):
122
+ return "zsh"
123
+ case strings.Contains(shell, "bash"):
124
+ return "bash"
125
+ default:
126
+ return ""
127
+ }
128
+ }
@@ -0,0 +1,114 @@
1
+ /*
2
+ Copyright © 2020 NAME HERE <EMAIL ADDRESS>
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ package cmd
17
+
18
+ import (
19
+ "errors"
20
+ "net/url"
21
+ "strconv"
22
+ "strings"
23
+
24
+ "github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
25
+ "github.com/hookdeck/hookdeck-cli/pkg/listen"
26
+ "github.com/spf13/cobra"
27
+ )
28
+
29
+ type listenCmd struct {
30
+ cmd *cobra.Command
31
+ wsBaseURL string
32
+ noWSS bool
33
+ }
34
+
35
+ func newListenCmd() *listenCmd {
36
+ lc := &listenCmd{}
37
+
38
+ lc.cmd = &cobra.Command{
39
+ Use: "listen",
40
+ Short: "Forward webhooks for a source to your local server",
41
+ Args: func(cmd *cobra.Command, args []string) error {
42
+ if len(args) < 1 {
43
+ return errors.New("Requires a port or forwarding URL to foward the webhooks to")
44
+ }
45
+
46
+ _, err_port := strconv.ParseInt(args[0], 10, 64)
47
+
48
+ var parsed_url *url.URL
49
+ var err_url error
50
+ if strings.HasPrefix(args[0], "http") {
51
+ parsed_url, err_url = url.Parse(args[0])
52
+ } else {
53
+ parsed_url, err_url = url.Parse("http://" + args[0])
54
+ }
55
+
56
+ if err_port != nil && err_url != nil {
57
+ return errors.New("Argument is not a valid port or forwading URL")
58
+ }
59
+
60
+ if err_port != nil {
61
+ if parsed_url.Host == "" {
62
+ return errors.New("Forwarding URL must contain a host.")
63
+ }
64
+
65
+ if parsed_url.RawQuery != "" {
66
+ return errors.New("Forwarding URL cannot contain query params.")
67
+ }
68
+ }
69
+
70
+ if len(args) > 3 {
71
+ return errors.New("Invalid extra argument provided")
72
+ }
73
+
74
+ return nil
75
+ },
76
+ RunE: lc.runListenCmd,
77
+ }
78
+ lc.cmd.Flags().StringVar(&lc.wsBaseURL, "ws-base", hookdeck.DefaultWebsocektURL, "Sets the Websocket base URL")
79
+ lc.cmd.Flags().BoolVar(&lc.noWSS, "no-wss", false, "Force unencrypted ws:// protocol instead of wss://")
80
+
81
+ return lc
82
+ }
83
+
84
+ // listenCmd represents the listen command
85
+ func (lc *listenCmd) runListenCmd(cmd *cobra.Command, args []string) error {
86
+ var source_alias, connection_query string
87
+ if len(args) > 1 {
88
+ source_alias = args[1]
89
+ }
90
+ if len(args) > 2 {
91
+ connection_query = args[2]
92
+ }
93
+
94
+ _, err_port := strconv.ParseInt(args[0], 10, 64)
95
+ var url *url.URL
96
+ if err_port != nil {
97
+ if strings.HasPrefix(args[0], "http") {
98
+ url, _ = url.Parse(args[0])
99
+ } else {
100
+ url, _ = url.Parse("http://" + args[0])
101
+ }
102
+ } else {
103
+ url, _ = url.Parse("http://localhost:" + args[0])
104
+ }
105
+
106
+ if url.Scheme == "" {
107
+ url.Scheme = "http"
108
+ }
109
+
110
+ return listen.Listen(url, source_alias, connection_query, listen.Flags{
111
+ WSBaseURL: lc.wsBaseURL,
112
+ NoWSS: lc.noWSS,
113
+ }, &Config)
114
+ }
@@ -0,0 +1,37 @@
1
+ package cmd
2
+
3
+ import (
4
+ "os"
5
+
6
+ "github.com/spf13/cobra"
7
+
8
+ "github.com/hookdeck/hookdeck-cli/pkg/login"
9
+ "github.com/hookdeck/hookdeck-cli/pkg/validators"
10
+ )
11
+
12
+ type loginCmd struct {
13
+ cmd *cobra.Command
14
+ interactive bool
15
+ }
16
+
17
+ func newLoginCmd() *loginCmd {
18
+ lc := &loginCmd{}
19
+
20
+ lc.cmd = &cobra.Command{
21
+ Use: "login",
22
+ Args: validators.NoArgs,
23
+ Short: "Login to your Hookdeck account",
24
+ Long: `Login to your Hookdeck account to setup the CLI`,
25
+ RunE: lc.runLoginCmd,
26
+ }
27
+ lc.cmd.Flags().BoolVarP(&lc.interactive, "interactive", "i", false, "Run interactive configuration mode if you cannot open a browser")
28
+
29
+ return lc
30
+ }
31
+
32
+ func (lc *loginCmd) runLoginCmd(cmd *cobra.Command, args []string) error {
33
+ if lc.interactive {
34
+ return login.InteractiveLogin(&Config)
35
+ }
36
+ return login.Login(&Config, os.Stdin)
37
+ }
@@ -0,0 +1,35 @@
1
+ package cmd
2
+
3
+ import (
4
+ "github.com/spf13/cobra"
5
+
6
+ "github.com/hookdeck/hookdeck-cli/pkg/logout"
7
+ "github.com/hookdeck/hookdeck-cli/pkg/validators"
8
+ )
9
+
10
+ type logoutCmd struct {
11
+ cmd *cobra.Command
12
+ all bool
13
+ }
14
+
15
+ func newLogoutCmd() *logoutCmd {
16
+ lc := &logoutCmd{}
17
+
18
+ lc.cmd = &cobra.Command{
19
+ Use: "logout",
20
+ Args: validators.NoArgs,
21
+ Short: "Logout of your Hookdeck account",
22
+ Long: `Logout of your Hookdeck account to setup the CLI`,
23
+ RunE: lc.runLogoutCmd,
24
+ }
25
+ lc.cmd.Flags().BoolVarP(&lc.all, "all", "a", false, "Clear credentials for all projects you are currently logged into.")
26
+
27
+ return lc
28
+ }
29
+
30
+ func (lc *logoutCmd) runLogoutCmd(cmd *cobra.Command, args []string) error {
31
+ if lc.all {
32
+ return logout.All(&Config)
33
+ }
34
+ return logout.Logout(&Config)
35
+ }
@@ -0,0 +1,112 @@
1
+ /*
2
+ Copyright © 2020 NAME HERE <EMAIL ADDRESS>
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+ package cmd
17
+
18
+ import (
19
+ "fmt"
20
+ "os"
21
+ "strings"
22
+ "unicode"
23
+
24
+ "github.com/hookdeck/hookdeck-cli/pkg/config"
25
+ "github.com/hookdeck/hookdeck-cli/pkg/hookdeck"
26
+ "github.com/hookdeck/hookdeck-cli/pkg/validators"
27
+ "github.com/hookdeck/hookdeck-cli/pkg/version"
28
+ "github.com/spf13/cobra"
29
+ )
30
+
31
+ var cfgFile string
32
+
33
+ var Config config.Config
34
+
35
+ var rootCmd = &cobra.Command{
36
+ Use: "hookdeck",
37
+ SilenceUsage: true,
38
+ SilenceErrors: true,
39
+ Version: version.Version,
40
+ Short: "A CLI to forward webhooks received on Hookdeck to your local server.",
41
+ }
42
+
43
+ // Execute adds all child commands to the root command and sets flags appropriately.
44
+ // This is called by main.main(). It only needs to happen once to the rootCmd.
45
+ func Execute() {
46
+ if err := rootCmd.Execute(); err != nil {
47
+ errString := err.Error()
48
+ isLoginRequiredError := errString == validators.ErrAPIKeyNotConfigured.Error() || errString == validators.ErrDeviceNameNotConfigured.Error()
49
+
50
+ switch {
51
+ case isLoginRequiredError:
52
+ // capitalize first letter of error because linter
53
+ errRunes := []rune(errString)
54
+ errRunes[0] = unicode.ToUpper(errRunes[0])
55
+
56
+ fmt.Printf("%s. Running `hookdeck login`...\n", string(errRunes))
57
+ loginCommand, _, err := rootCmd.Find([]string{"login"})
58
+
59
+ if err != nil {
60
+ fmt.Println(err)
61
+ }
62
+
63
+ err = loginCommand.RunE(&cobra.Command{}, []string{})
64
+
65
+ if err != nil {
66
+ fmt.Println(err)
67
+ }
68
+
69
+ case strings.Contains(errString, "unknown command"):
70
+ suggStr := "\nS"
71
+
72
+ suggestions := rootCmd.SuggestionsFor(os.Args[1])
73
+ if len(suggestions) > 0 {
74
+ suggStr = fmt.Sprintf(" Did you mean \"%s\"?\nIf not, s", suggestions[0])
75
+ }
76
+
77
+ fmt.Println(fmt.Sprintf("Unknown command \"%s\" for \"%s\".%s"+
78
+ "ee \"hookdeck --help\" for a list of available commands.",
79
+ os.Args[1], rootCmd.CommandPath(), suggStr))
80
+
81
+ default:
82
+ fmt.Println(err)
83
+ }
84
+
85
+ os.Exit(1)
86
+ }
87
+ }
88
+
89
+ func init() {
90
+ cobra.OnInitialize(Config.InitConfig)
91
+
92
+ rootCmd.PersistentFlags().StringVar(&Config.Profile.APIKey, "cli-key", "", "Your CLI key to use for the command")
93
+ rootCmd.PersistentFlags().StringVar(&Config.Color, "color", "", "turn on/off color output (on, off, auto)")
94
+ rootCmd.PersistentFlags().StringVar(&Config.ProfilesFile, "config", "", "config file (default is $HOME/.config/hookdeck/config.toml)")
95
+ rootCmd.PersistentFlags().StringVar(&Config.Profile.DeviceName, "device-name", "", "device name")
96
+ rootCmd.PersistentFlags().StringVar(&Config.LogLevel, "log-level", "info", "log level (debug, info, warn, error)")
97
+ rootCmd.PersistentFlags().BoolVar(&Config.Insecure, "insecure", false, "Allow invalid TLS certificates")
98
+ rootCmd.PersistentFlags().StringVarP(&Config.Profile.ProfileName, "project-name", "p", "default", "the project name to read from for config")
99
+
100
+ // Hidden configuration flags, useful for dev/debugging
101
+ rootCmd.PersistentFlags().StringVar(&Config.APIBaseURL, "api-base", hookdeck.DefaultAPIBaseURL, "Sets the API base URL")
102
+ rootCmd.PersistentFlags().StringVar(&Config.DashboardBaseURL, "dashboard-base", hookdeck.DefaultDashboardBaseURL, "Sets the web dashboard base URL")
103
+ rootCmd.PersistentFlags().StringVar(&Config.ConsoleBaseURL, "console-base", hookdeck.DefaultConsoleBaseURL, "Sets the web console base URL")
104
+
105
+ rootCmd.Flags().BoolP("version", "v", false, "Get the version of the Hookdeck CLI")
106
+
107
+ rootCmd.AddCommand(newLoginCmd().cmd)
108
+ rootCmd.AddCommand(newLogoutCmd().cmd)
109
+ rootCmd.AddCommand(newListenCmd().cmd)
110
+ rootCmd.AddCommand(newCompletionCmd().cmd)
111
+ rootCmd.AddCommand(newWhoamiCmd().cmd)
112
+ }
@@ -0,0 +1,25 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+
6
+ "github.com/spf13/cobra"
7
+
8
+ "github.com/hookdeck/hookdeck-cli/pkg/validators"
9
+ "github.com/hookdeck/hookdeck-cli/pkg/version"
10
+ )
11
+
12
+ var versionCmd = &cobra.Command{
13
+ Use: "version",
14
+ Args: validators.NoArgs,
15
+ Short: "Get the version of the Hookdeck CLI",
16
+ Run: func(cmd *cobra.Command, args []string) {
17
+ fmt.Print(version.Template)
18
+
19
+ version.CheckLatestVersion()
20
+ },
21
+ }
22
+
23
+ func init() {
24
+ rootCmd.AddCommand(versionCmd)
25
+ }