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