X Tutup
Skip to content

Commit aef1a4b

Browse files
committed
Extract root command and factory logic into separate packages
1 parent 5e24e0d commit aef1a4b

File tree

14 files changed

+341
-303
lines changed

14 files changed

+341
-303
lines changed

cmd/gh/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/cli/cli/command"
1414
"github.com/cli/cli/internal/config"
15+
"github.com/cli/cli/pkg/cmd/root"
1516
"github.com/cli/cli/pkg/cmdutil"
1617
"github.com/cli/cli/update"
1718
"github.com/cli/cli/utils"
@@ -73,7 +74,7 @@ func main() {
7374
printError(os.Stderr, err, cmd, hasDebug)
7475
os.Exit(1)
7576
}
76-
if command.HasFailed() {
77+
if root.HasFailed() {
7778
os.Exit(1)
7879
}
7980

command/alias.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
)
1414

1515
func init() {
16-
RootCmd.AddCommand(aliasCmd)
1716
aliasCmd.AddCommand(aliasSetCmd)
1817
aliasCmd.AddCommand(aliasListCmd)
1918
aliasCmd.AddCommand(aliasDeleteCmd)

command/completion.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
)
1111

1212
func init() {
13-
RootCmd.AddCommand(completionCmd)
1413
completionCmd.Flags().StringP("shell", "s", "", "Shell type: {bash|zsh|fish|powershell}")
1514
}
1615

command/config.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
)
99

1010
func init() {
11-
RootCmd.AddCommand(configCmd)
1211
configCmd.AddCommand(configGetCmd)
1312
configCmd.AddCommand(configSetCmd)
1413

command/root.go

Lines changed: 8 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"errors"
55
"fmt"
66
"io"
7-
"net/http"
87
"os"
98
"os/exec"
109
"path/filepath"
@@ -13,27 +12,17 @@ import (
1312
"runtime/debug"
1413
"strings"
1514

16-
"github.com/MakeNowJust/heredoc"
1715
"github.com/cli/cli/api"
1816
"github.com/cli/cli/context"
19-
"github.com/cli/cli/git"
2017
"github.com/cli/cli/internal/config"
2118
"github.com/cli/cli/internal/ghinstance"
22-
"github.com/cli/cli/internal/ghrepo"
2319
"github.com/cli/cli/internal/run"
24-
apiCmd "github.com/cli/cli/pkg/cmd/api"
25-
gistCmd "github.com/cli/cli/pkg/cmd/gist"
26-
issueCmd "github.com/cli/cli/pkg/cmd/issue"
27-
prCmd "github.com/cli/cli/pkg/cmd/pr"
28-
repoCmd "github.com/cli/cli/pkg/cmd/repo"
29-
creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits"
30-
"github.com/cli/cli/pkg/cmdutil"
31-
"github.com/cli/cli/pkg/iostreams"
20+
"github.com/cli/cli/pkg/cmd/factory"
21+
"github.com/cli/cli/pkg/cmd/root"
3222
"github.com/cli/cli/utils"
3323
"github.com/google/shlex"
3424

3525
"github.com/spf13/cobra"
36-
"github.com/spf13/pflag"
3726
)
3827

3928
// Version is dynamically set by the toolchain or overridden by the Makefile.
@@ -42,169 +31,20 @@ var Version = "DEV"
4231
// BuildDate is dynamically set at build time in the Makefile.
4332
var BuildDate = "" // YYYY-MM-DD
4433

45-
var versionOutput = ""
46-
47-
var defaultStreams *iostreams.IOStreams
34+
var RootCmd *cobra.Command
4835

4936
func init() {
5037
if Version == "DEV" {
5138
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "(devel)" {
5239
Version = info.Main.Version
5340
}
5441
}
55-
Version = strings.TrimPrefix(Version, "v")
56-
if BuildDate == "" {
57-
RootCmd.Version = Version
58-
} else {
59-
RootCmd.Version = fmt.Sprintf("%s (%s)", Version, BuildDate)
60-
}
61-
versionOutput = fmt.Sprintf("gh version %s\n%s\n", RootCmd.Version, changelogURL(Version))
62-
RootCmd.AddCommand(versionCmd)
63-
RootCmd.SetVersionTemplate(versionOutput)
64-
65-
RootCmd.PersistentFlags().Bool("help", false, "Show help for command")
66-
RootCmd.Flags().Bool("version", false, "Show gh version")
67-
// TODO:
68-
// RootCmd.PersistentFlags().BoolP("verbose", "V", false, "enable verbose output")
69-
70-
RootCmd.SetHelpFunc(rootHelpFunc)
71-
RootCmd.SetUsageFunc(rootUsageFunc)
72-
73-
RootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
74-
if err == pflag.ErrHelp {
75-
return err
76-
}
77-
return &cmdutil.FlagError{Err: err}
78-
})
79-
80-
defaultStreams = iostreams.System()
81-
82-
// TODO: iron out how a factory incorporates context
83-
cmdFactory := &cmdutil.Factory{
84-
IOStreams: defaultStreams,
85-
HttpClient: func() (*http.Client, error) {
86-
// TODO: decouple from `context`
87-
ctx := context.New()
88-
cfg, err := ctx.Config()
89-
if err != nil {
90-
return nil, err
91-
}
92-
93-
// TODO: avoid setting Accept header for `api` command
94-
return httpClient(defaultStreams, cfg, true), nil
95-
},
96-
BaseRepo: func() (ghrepo.Interface, error) {
97-
// TODO: decouple from `context`
98-
ctx := context.New()
99-
return ctx.BaseRepo()
100-
},
101-
Remotes: func() (context.Remotes, error) {
102-
ctx := context.New()
103-
return ctx.Remotes()
104-
},
105-
Config: func() (config.Config, error) {
106-
cfg, err := config.ParseDefaultConfig()
107-
if errors.Is(err, os.ErrNotExist) {
108-
cfg = config.NewBlankConfig()
109-
} else if err != nil {
110-
return nil, err
111-
}
112-
return cfg, nil
113-
},
114-
Branch: func() (string, error) {
115-
currentBranch, err := git.CurrentBranch()
116-
if err != nil {
117-
return "", fmt.Errorf("could not determine current branch: %w", err)
118-
}
119-
return currentBranch, nil
120-
},
121-
}
122-
123-
RootCmd.AddCommand(apiCmd.NewCmdApi(cmdFactory, nil))
124-
RootCmd.AddCommand(gistCmd.NewCmdGist(cmdFactory))
125-
126-
resolvedBaseRepo := func() (ghrepo.Interface, error) {
127-
httpClient, err := cmdFactory.HttpClient()
128-
if err != nil {
129-
return nil, err
130-
}
131-
132-
apiClient := api.NewClientFromHTTP(httpClient)
133-
134-
ctx := context.New()
135-
remotes, err := ctx.Remotes()
136-
if err != nil {
137-
return nil, err
138-
}
139-
repoContext, err := context.ResolveRemotesToRepos(remotes, apiClient, "")
140-
if err != nil {
141-
return nil, err
142-
}
143-
baseRepo, err := repoContext.BaseRepo()
144-
if err != nil {
145-
return nil, err
146-
}
147-
148-
return baseRepo, nil
149-
}
150-
151-
repoResolvingCmdFactory := *cmdFactory
152-
153-
repoResolvingCmdFactory.BaseRepo = resolvedBaseRepo
154-
155-
RootCmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
156-
RootCmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
157-
RootCmd.AddCommand(repoCmd.NewCmdRepo(&repoResolvingCmdFactory))
158-
RootCmd.AddCommand(creditsCmd.NewCmdCredits(cmdFactory, nil))
159-
}
160-
161-
// RootCmd is the entry point of command-line execution
162-
var RootCmd = &cobra.Command{
163-
Use: "gh <command> <subcommand> [flags]",
164-
Short: "GitHub CLI",
165-
Long: `Work seamlessly with GitHub from the command line.`,
166-
167-
SilenceErrors: true,
168-
SilenceUsage: true,
169-
Example: heredoc.Doc(`
170-
$ gh issue create
171-
$ gh repo clone cli/cli
172-
$ gh pr checkout 321
173-
`),
174-
Annotations: map[string]string{
175-
"help:feedback": heredoc.Doc(`
176-
Fill out our feedback form https://forms.gle/umxd3h31c7aMQFKG7
177-
Open an issue using “gh issue create -R cli/cli”
178-
`),
179-
"help:environment": heredoc.Doc(`
180-
GITHUB_TOKEN: an authentication token for API requests. Setting this avoids being
181-
prompted to authenticate and overrides any previously stored credentials.
182-
183-
GH_REPO: specify the GitHub repository in "OWNER/REPO" format for commands that
184-
otherwise operate on a local repository.
185-
186-
GH_EDITOR, GIT_EDITOR, VISUAL, EDITOR (in order of precedence): the editor tool to use
187-
for authoring text.
188-
189-
BROWSER: the web browser to use for opening links.
190-
191-
DEBUG: set to any value to enable verbose output to standard error. Include values "api"
192-
or "oauth" to print detailed information about HTTP requests or authentication flow.
193-
194-
GLAMOUR_STYLE: the style to use for rendering Markdown. See
195-
https://github.com/charmbracelet/glamour#styles
196-
197-
NO_COLOR: avoid printing ANSI escape sequences for color output.
198-
`),
199-
},
200-
}
20142

202-
var versionCmd = &cobra.Command{
203-
Use: "version",
204-
Hidden: true,
205-
Run: func(cmd *cobra.Command, args []string) {
206-
fmt.Print(versionOutput)
207-
},
43+
cmdFactory := factory.New(Version)
44+
RootCmd = root.NewCmdRoot(cmdFactory, Version, BuildDate)
45+
RootCmd.AddCommand(aliasCmd)
46+
RootCmd.AddCommand(completionCmd)
47+
RootCmd.AddCommand(configCmd)
20848
}
20949

21050
// overridden in tests
@@ -245,62 +85,6 @@ func contextForCommand(cmd *cobra.Command) context.Context {
24585
return ctx
24686
}
24787

248-
// generic authenticated HTTP client for commands
249-
func httpClient(io *iostreams.IOStreams, cfg config.Config, setAccept bool) *http.Client {
250-
var opts []api.ClientOption
251-
if verbose := os.Getenv("DEBUG"); verbose != "" {
252-
opts = append(opts, apiVerboseLog())
253-
}
254-
255-
opts = append(opts,
256-
api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", Version)),
257-
// antiope-preview: Checks
258-
// FIXME: avoid setting this header for `api` command
259-
api.AddHeader("Accept", "application/vnd.github.antiope-preview+json"),
260-
api.AddHeaderFunc("Authorization", func(req *http.Request) (string, error) {
261-
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
262-
return fmt.Sprintf("token %s", token), nil
263-
}
264-
265-
hostname := ghinstance.NormalizeHostname(req.URL.Hostname())
266-
token, err := cfg.Get(hostname, "oauth_token")
267-
if token == "" {
268-
var notFound *config.NotFoundError
269-
// TODO: check if stdout is TTY too
270-
if errors.As(err, &notFound) && io.IsStdinTTY() {
271-
// interactive OAuth flow
272-
token, err = config.AuthFlowWithConfig(cfg, hostname, "Notice: authentication required")
273-
}
274-
if err != nil {
275-
return "", err
276-
}
277-
if token == "" {
278-
// TODO: instruct user how to manually authenticate
279-
return "", fmt.Errorf("authentication required for %s", hostname)
280-
}
281-
}
282-
283-
return fmt.Sprintf("token %s", token), nil
284-
}),
285-
)
286-
287-
if setAccept {
288-
opts = append(opts,
289-
api.AddHeaderFunc("Accept", func(req *http.Request) (string, error) {
290-
// antiope-preview: Checks
291-
accept := "application/vnd.github.antiope-preview+json"
292-
if ghinstance.IsEnterprise(req.URL.Hostname()) {
293-
// shadow-cat-preview: Draft pull requests
294-
accept += ", application/vnd.github.shadow-cat-preview"
295-
}
296-
return accept, nil
297-
}),
298-
)
299-
}
300-
301-
return api.NewHTTPClient(opts...)
302-
}
303-
30488
func apiVerboseLog() api.ClientOption {
30589
logTraffic := strings.Contains(os.Getenv("DEBUG"), "api")
30690
colorize := utils.IsTerminal(os.Stderr)
@@ -323,17 +107,6 @@ func colorableErr(cmd *cobra.Command) io.Writer {
323107
return err
324108
}
325109

326-
func changelogURL(version string) string {
327-
path := "https://github.com/cli/cli"
328-
r := regexp.MustCompile(`^v?\d+\.\d+\.\d+(-[\w.]+)?$`)
329-
if !r.MatchString(version) {
330-
return fmt.Sprintf("%s/releases/latest", path)
331-
}
332-
333-
url := fmt.Sprintf("%s/releases/tag/v%s", path, strings.TrimPrefix(version, "v"))
334-
return url
335-
}
336-
337110
func ExecuteShellAlias(args []string) error {
338111
externalCmd := exec.Command(args[0], args[1:]...)
339112
externalCmd.Stderr = os.Stderr

0 commit comments

Comments
 (0)
X Tutup