1package root
2
3import (
4	"net/http"
5	"os"
6	"sync"
7
8	"github.com/MakeNowJust/heredoc"
9	codespacesAPI "github.com/cli/cli/v2/internal/codespaces/api"
10	actionsCmd "github.com/cli/cli/v2/pkg/cmd/actions"
11	aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias"
12	apiCmd "github.com/cli/cli/v2/pkg/cmd/api"
13	authCmd "github.com/cli/cli/v2/pkg/cmd/auth"
14	browseCmd "github.com/cli/cli/v2/pkg/cmd/browse"
15	codespaceCmd "github.com/cli/cli/v2/pkg/cmd/codespace"
16	completionCmd "github.com/cli/cli/v2/pkg/cmd/completion"
17	configCmd "github.com/cli/cli/v2/pkg/cmd/config"
18	extensionCmd "github.com/cli/cli/v2/pkg/cmd/extension"
19	"github.com/cli/cli/v2/pkg/cmd/factory"
20	gistCmd "github.com/cli/cli/v2/pkg/cmd/gist"
21	gpgKeyCmd "github.com/cli/cli/v2/pkg/cmd/gpg-key"
22	issueCmd "github.com/cli/cli/v2/pkg/cmd/issue"
23	prCmd "github.com/cli/cli/v2/pkg/cmd/pr"
24	releaseCmd "github.com/cli/cli/v2/pkg/cmd/release"
25	repoCmd "github.com/cli/cli/v2/pkg/cmd/repo"
26	creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
27	runCmd "github.com/cli/cli/v2/pkg/cmd/run"
28	secretCmd "github.com/cli/cli/v2/pkg/cmd/secret"
29	sshKeyCmd "github.com/cli/cli/v2/pkg/cmd/ssh-key"
30	versionCmd "github.com/cli/cli/v2/pkg/cmd/version"
31	workflowCmd "github.com/cli/cli/v2/pkg/cmd/workflow"
32	"github.com/cli/cli/v2/pkg/cmdutil"
33	"github.com/spf13/cobra"
34)
35
36func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
37	cmd := &cobra.Command{
38		Use:   "gh <command> <subcommand> [flags]",
39		Short: "GitHub CLI",
40		Long:  `Work seamlessly with GitHub from the command line.`,
41
42		SilenceErrors: true,
43		SilenceUsage:  true,
44		Example: heredoc.Doc(`
45			$ gh issue create
46			$ gh repo clone cli/cli
47			$ gh pr checkout 321
48		`),
49		Annotations: map[string]string{
50			"help:feedback": heredoc.Doc(`
51				Open an issue using 'gh issue create -R github.com/cli/cli'
52			`),
53			"help:environment": heredoc.Doc(`
54				See 'gh help environment' for the list of supported environment variables.
55			`),
56		},
57	}
58
59	cmd.SetOut(f.IOStreams.Out)
60	cmd.SetErr(f.IOStreams.ErrOut)
61
62	cmd.PersistentFlags().Bool("help", false, "Show help for command")
63	cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
64		rootHelpFunc(f, cmd, args)
65	})
66	cmd.SetUsageFunc(rootUsageFunc)
67	cmd.SetFlagErrorFunc(rootFlagErrorFunc)
68
69	formattedVersion := versionCmd.Format(version, buildDate)
70	cmd.SetVersionTemplate(formattedVersion)
71	cmd.Version = formattedVersion
72	cmd.Flags().Bool("version", false, "Show gh version")
73
74	// Child commands
75	cmd.AddCommand(versionCmd.NewCmdVersion(f, version, buildDate))
76	cmd.AddCommand(actionsCmd.NewCmdActions(f))
77	cmd.AddCommand(aliasCmd.NewCmdAlias(f))
78	cmd.AddCommand(authCmd.NewCmdAuth(f))
79	cmd.AddCommand(configCmd.NewCmdConfig(f))
80	cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil))
81	cmd.AddCommand(gistCmd.NewCmdGist(f))
82	cmd.AddCommand(gpgKeyCmd.NewCmdGPGKey(f))
83	cmd.AddCommand(completionCmd.NewCmdCompletion(f.IOStreams))
84	cmd.AddCommand(extensionCmd.NewCmdExtension(f))
85	cmd.AddCommand(secretCmd.NewCmdSecret(f))
86	cmd.AddCommand(sshKeyCmd.NewCmdSSHKey(f))
87	cmd.AddCommand(newCodespaceCmd(f))
88
89	// the `api` command should not inherit any extra HTTP headers
90	bareHTTPCmdFactory := *f
91	bareHTTPCmdFactory.HttpClient = bareHTTPClient(f, version)
92
93	cmd.AddCommand(apiCmd.NewCmdApi(&bareHTTPCmdFactory, nil))
94
95	// below here at the commands that require the "intelligent" BaseRepo resolver
96	repoResolvingCmdFactory := *f
97	repoResolvingCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(f)
98
99	cmd.AddCommand(browseCmd.NewCmdBrowse(&repoResolvingCmdFactory, nil))
100	cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
101	cmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
102	cmd.AddCommand(releaseCmd.NewCmdRelease(&repoResolvingCmdFactory))
103	cmd.AddCommand(repoCmd.NewCmdRepo(&repoResolvingCmdFactory))
104	cmd.AddCommand(runCmd.NewCmdRun(&repoResolvingCmdFactory))
105	cmd.AddCommand(workflowCmd.NewCmdWorkflow(&repoResolvingCmdFactory))
106
107	// Help topics
108	cmd.AddCommand(NewHelpTopic("environment"))
109	cmd.AddCommand(NewHelpTopic("formatting"))
110	cmd.AddCommand(NewHelpTopic("mintty"))
111	referenceCmd := NewHelpTopic("reference")
112	referenceCmd.SetHelpFunc(referenceHelpFn(f.IOStreams))
113	cmd.AddCommand(referenceCmd)
114
115	cmdutil.DisableAuthCheck(cmd)
116
117	// this needs to appear last:
118	referenceCmd.Long = referenceLong(cmd)
119	return cmd
120}
121
122func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, error) {
123	return func() (*http.Client, error) {
124		cfg, err := f.Config()
125		if err != nil {
126			return nil, err
127		}
128		return factory.NewHTTPClient(f.IOStreams, cfg, version, false)
129	}
130}
131
132func newCodespaceCmd(f *cmdutil.Factory) *cobra.Command {
133	serverURL := os.Getenv("GITHUB_SERVER_URL")
134	apiURL := os.Getenv("GITHUB_API_URL")
135	vscsURL := os.Getenv("INTERNAL_VSCS_TARGET_URL")
136	app := codespaceCmd.NewApp(
137		f.IOStreams,
138		codespacesAPI.New(
139			serverURL,
140			apiURL,
141			vscsURL,
142			&lazyLoadedHTTPClient{factory: f},
143		),
144	)
145	cmd := codespaceCmd.NewRootCmd(app)
146	cmd.Use = "codespace"
147	cmd.Aliases = []string{"cs"}
148	cmd.Annotations = map[string]string{"IsCore": "true"}
149	return cmd
150}
151
152type lazyLoadedHTTPClient struct {
153	factory *cmdutil.Factory
154
155	httpClientMu sync.RWMutex // guards httpClient
156	httpClient   *http.Client
157}
158
159func (l *lazyLoadedHTTPClient) Do(req *http.Request) (*http.Response, error) {
160	l.httpClientMu.RLock()
161	httpClient := l.httpClient
162	l.httpClientMu.RUnlock()
163
164	if httpClient == nil {
165		var err error
166		l.httpClientMu.Lock()
167		l.httpClient, err = l.factory.HttpClient()
168		l.httpClientMu.Unlock()
169		if err != nil {
170			return nil, err
171		}
172	}
173
174	return l.httpClient.Do(req)
175}
176