cli/pkg/cmd/root/root.go
Mislav Marohnić 2139e763fb Write Cobra deprecation messages to stderr
We used to do the equivalent of `rootCmd.SetOut(os.Stdout)` because we
thought that Cobra's "Out" stream represents standard output. However,
upon closer inspection it turns out that this is Cobra's stream for
usage errors and deprecation warnings, and those we want written to
stderr as well. It is not clear to me why Cobra maintains a distinction
between "Out" and "Err" streams since both seem to go to sdterr by
default.

This change also ceases our usage of `command.Print()` functions in
favor of explicitly writing to `IOStreams.Out/ErrOut`.
2022-05-23 20:23:42 +02:00

182 lines
5.8 KiB
Go

package root
import (
"net/http"
"os"
"sync"
"github.com/MakeNowJust/heredoc"
codespacesAPI "github.com/cli/cli/v2/internal/codespaces/api"
actionsCmd "github.com/cli/cli/v2/pkg/cmd/actions"
aliasCmd "github.com/cli/cli/v2/pkg/cmd/alias"
apiCmd "github.com/cli/cli/v2/pkg/cmd/api"
authCmd "github.com/cli/cli/v2/pkg/cmd/auth"
browseCmd "github.com/cli/cli/v2/pkg/cmd/browse"
codespaceCmd "github.com/cli/cli/v2/pkg/cmd/codespace"
completionCmd "github.com/cli/cli/v2/pkg/cmd/completion"
configCmd "github.com/cli/cli/v2/pkg/cmd/config"
extensionCmd "github.com/cli/cli/v2/pkg/cmd/extension"
"github.com/cli/cli/v2/pkg/cmd/factory"
gistCmd "github.com/cli/cli/v2/pkg/cmd/gist"
gpgKeyCmd "github.com/cli/cli/v2/pkg/cmd/gpg-key"
issueCmd "github.com/cli/cli/v2/pkg/cmd/issue"
labelCmd "github.com/cli/cli/v2/pkg/cmd/label"
prCmd "github.com/cli/cli/v2/pkg/cmd/pr"
releaseCmd "github.com/cli/cli/v2/pkg/cmd/release"
repoCmd "github.com/cli/cli/v2/pkg/cmd/repo"
creditsCmd "github.com/cli/cli/v2/pkg/cmd/repo/credits"
runCmd "github.com/cli/cli/v2/pkg/cmd/run"
searchCmd "github.com/cli/cli/v2/pkg/cmd/search"
secretCmd "github.com/cli/cli/v2/pkg/cmd/secret"
sshKeyCmd "github.com/cli/cli/v2/pkg/cmd/ssh-key"
statusCmd "github.com/cli/cli/v2/pkg/cmd/status"
versionCmd "github.com/cli/cli/v2/pkg/cmd/version"
workflowCmd "github.com/cli/cli/v2/pkg/cmd/workflow"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/spf13/cobra"
)
func NewCmdRoot(f *cmdutil.Factory, version, buildDate string) *cobra.Command {
cmd := &cobra.Command{
Use: "gh <command> <subcommand> [flags]",
Short: "GitHub CLI",
Long: `Work seamlessly with GitHub from the command line.`,
SilenceErrors: true,
SilenceUsage: true,
Example: heredoc.Doc(`
$ gh issue create
$ gh repo clone cli/cli
$ gh pr checkout 321
`),
Annotations: map[string]string{
"help:feedback": heredoc.Doc(`
Open an issue using 'gh issue create -R github.com/cli/cli'
`),
},
}
cmd.SetOut(f.IOStreams.ErrOut) // command usage summary and deprecation warnings
cmd.SetErr(f.IOStreams.ErrOut) // error messages
cmd.PersistentFlags().Bool("help", false, "Show help for command")
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
rootHelpFunc(f, c, args)
})
cmd.SetUsageFunc(func(c *cobra.Command) error {
return rootUsageFunc(f.IOStreams.ErrOut, c)
})
cmd.SetFlagErrorFunc(rootFlagErrorFunc)
formattedVersion := versionCmd.Format(version, buildDate)
cmd.SetVersionTemplate(formattedVersion)
cmd.Version = formattedVersion
cmd.Flags().Bool("version", false, "Show gh version")
// Child commands
cmd.AddCommand(versionCmd.NewCmdVersion(f, version, buildDate))
cmd.AddCommand(actionsCmd.NewCmdActions(f))
cmd.AddCommand(aliasCmd.NewCmdAlias(f))
cmd.AddCommand(authCmd.NewCmdAuth(f))
cmd.AddCommand(configCmd.NewCmdConfig(f))
cmd.AddCommand(creditsCmd.NewCmdCredits(f, nil))
cmd.AddCommand(gistCmd.NewCmdGist(f))
cmd.AddCommand(gpgKeyCmd.NewCmdGPGKey(f))
cmd.AddCommand(completionCmd.NewCmdCompletion(f.IOStreams))
cmd.AddCommand(extensionCmd.NewCmdExtension(f))
cmd.AddCommand(searchCmd.NewCmdSearch(f))
cmd.AddCommand(secretCmd.NewCmdSecret(f))
cmd.AddCommand(sshKeyCmd.NewCmdSSHKey(f))
cmd.AddCommand(statusCmd.NewCmdStatus(f, nil))
cmd.AddCommand(newCodespaceCmd(f))
// the `api` command should not inherit any extra HTTP headers
bareHTTPCmdFactory := *f
bareHTTPCmdFactory.HttpClient = bareHTTPClient(f, version)
cmd.AddCommand(apiCmd.NewCmdApi(&bareHTTPCmdFactory, nil))
// below here at the commands that require the "intelligent" BaseRepo resolver
repoResolvingCmdFactory := *f
repoResolvingCmdFactory.BaseRepo = factory.SmartBaseRepoFunc(f)
cmd.AddCommand(browseCmd.NewCmdBrowse(&repoResolvingCmdFactory, nil))
cmd.AddCommand(prCmd.NewCmdPR(&repoResolvingCmdFactory))
cmd.AddCommand(issueCmd.NewCmdIssue(&repoResolvingCmdFactory))
cmd.AddCommand(releaseCmd.NewCmdRelease(&repoResolvingCmdFactory))
cmd.AddCommand(repoCmd.NewCmdRepo(&repoResolvingCmdFactory))
cmd.AddCommand(runCmd.NewCmdRun(&repoResolvingCmdFactory))
cmd.AddCommand(workflowCmd.NewCmdWorkflow(&repoResolvingCmdFactory))
cmd.AddCommand(labelCmd.NewCmdLabel(&repoResolvingCmdFactory))
// Help topics
cmd.AddCommand(NewHelpTopic(f.IOStreams, "environment"))
cmd.AddCommand(NewHelpTopic(f.IOStreams, "formatting"))
cmd.AddCommand(NewHelpTopic(f.IOStreams, "mintty"))
referenceCmd := NewHelpTopic(f.IOStreams, "reference")
referenceCmd.SetHelpFunc(referenceHelpFn(f.IOStreams))
cmd.AddCommand(referenceCmd)
cmdutil.DisableAuthCheck(cmd)
// this needs to appear last:
referenceCmd.Long = referenceLong(cmd)
return cmd
}
func bareHTTPClient(f *cmdutil.Factory, version string) func() (*http.Client, error) {
return func() (*http.Client, error) {
cfg, err := f.Config()
if err != nil {
return nil, err
}
return factory.NewHTTPClient(f.IOStreams, cfg, version, false)
}
}
func newCodespaceCmd(f *cmdutil.Factory) *cobra.Command {
serverURL := os.Getenv("GITHUB_SERVER_URL")
apiURL := os.Getenv("GITHUB_API_URL")
vscsURL := os.Getenv("INTERNAL_VSCS_TARGET_URL")
app := codespaceCmd.NewApp(
f.IOStreams,
f,
codespacesAPI.New(
serverURL,
apiURL,
vscsURL,
&lazyLoadedHTTPClient{factory: f},
),
f.Browser,
)
cmd := codespaceCmd.NewRootCmd(app)
cmd.Use = "codespace"
cmd.Aliases = []string{"cs"}
cmd.Annotations = map[string]string{"IsCore": "true"}
return cmd
}
type lazyLoadedHTTPClient struct {
factory *cmdutil.Factory
httpClientMu sync.RWMutex // guards httpClient
httpClient *http.Client
}
func (l *lazyLoadedHTTPClient) Do(req *http.Request) (*http.Response, error) {
l.httpClientMu.RLock()
httpClient := l.httpClient
l.httpClientMu.RUnlock()
if httpClient == nil {
var err error
l.httpClientMu.Lock()
l.httpClient, err = l.factory.HttpClient()
l.httpClientMu.Unlock()
if err != nil {
return nil, err
}
}
return l.httpClient.Do(req)
}