255 lines
6.7 KiB
Go
255 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"strings"
|
|
|
|
surveyCore "github.com/AlecAivazis/survey/v2/core"
|
|
"github.com/cli/cli/api"
|
|
"github.com/cli/cli/internal/build"
|
|
"github.com/cli/cli/internal/config"
|
|
"github.com/cli/cli/internal/ghinstance"
|
|
"github.com/cli/cli/internal/run"
|
|
"github.com/cli/cli/pkg/cmd/alias/expand"
|
|
"github.com/cli/cli/pkg/cmd/factory"
|
|
"github.com/cli/cli/pkg/cmd/root"
|
|
"github.com/cli/cli/pkg/cmdutil"
|
|
"github.com/cli/cli/update"
|
|
"github.com/cli/cli/utils"
|
|
"github.com/mgutz/ansi"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var updaterEnabled = ""
|
|
|
|
func main() {
|
|
buildDate := build.Date
|
|
buildVersion := build.Version
|
|
|
|
updateMessageChan := make(chan *update.ReleaseInfo)
|
|
go func() {
|
|
rel, _ := checkForUpdate(buildVersion)
|
|
updateMessageChan <- rel
|
|
}()
|
|
|
|
hasDebug := os.Getenv("DEBUG") != ""
|
|
|
|
if hostFromEnv := os.Getenv("GH_HOST"); hostFromEnv != "" {
|
|
ghinstance.OverrideDefault(hostFromEnv)
|
|
}
|
|
|
|
cmdFactory := factory.New(buildVersion)
|
|
stderr := cmdFactory.IOStreams.ErrOut
|
|
if !cmdFactory.IOStreams.ColorEnabled() {
|
|
surveyCore.DisableColor = true
|
|
} else {
|
|
// override survey's poor choice of color
|
|
surveyCore.TemplateFuncsWithColor["color"] = func(style string) string {
|
|
switch style {
|
|
case "white":
|
|
if cmdFactory.IOStreams.ColorSupport256() {
|
|
return fmt.Sprintf("\x1b[%d;5;%dm", 38, 242)
|
|
}
|
|
return ansi.ColorCode("default")
|
|
default:
|
|
return ansi.ColorCode(style)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Enable running gh from explorer.exe. Without this, the user is told to stop and run from a
|
|
// terminal. With this, a user can clone a repo (or take other actions) directly from explorer.
|
|
cobra.MousetrapHelpText = ""
|
|
|
|
rootCmd := root.NewCmdRoot(cmdFactory, buildVersion, buildDate)
|
|
|
|
cfg, err := cmdFactory.Config()
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "failed to read configuration: %s\n", err)
|
|
os.Exit(2)
|
|
}
|
|
|
|
if prompt, _ := cfg.Get("", "prompt"); prompt == "disabled" {
|
|
cmdFactory.IOStreams.SetNeverPrompt(true)
|
|
}
|
|
|
|
if pager, _ := cfg.Get("", "pager"); pager != "" {
|
|
cmdFactory.IOStreams.SetPager(pager)
|
|
}
|
|
|
|
expandedArgs := []string{}
|
|
if len(os.Args) > 0 {
|
|
expandedArgs = os.Args[1:]
|
|
}
|
|
|
|
cmd, _, err := rootCmd.Traverse(expandedArgs)
|
|
if err != nil || cmd == rootCmd {
|
|
originalArgs := expandedArgs
|
|
isShell := false
|
|
|
|
expandedArgs, isShell, err = expand.ExpandAlias(cfg, os.Args, nil)
|
|
if err != nil {
|
|
fmt.Fprintf(stderr, "failed to process aliases: %s\n", err)
|
|
os.Exit(2)
|
|
}
|
|
|
|
if hasDebug {
|
|
fmt.Fprintf(stderr, "%v -> %v\n", originalArgs, expandedArgs)
|
|
}
|
|
|
|
if isShell {
|
|
externalCmd := exec.Command(expandedArgs[0], expandedArgs[1:]...)
|
|
externalCmd.Stderr = os.Stderr
|
|
externalCmd.Stdout = os.Stdout
|
|
externalCmd.Stdin = os.Stdin
|
|
preparedCmd := run.PrepareCmd(externalCmd)
|
|
|
|
err = preparedCmd.Run()
|
|
if err != nil {
|
|
if ee, ok := err.(*exec.ExitError); ok {
|
|
os.Exit(ee.ExitCode())
|
|
}
|
|
|
|
fmt.Fprintf(stderr, "failed to run external command: %s", err)
|
|
os.Exit(3)
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
authCheckEnabled := os.Getenv("GITHUB_TOKEN") == "" &&
|
|
os.Getenv("GITHUB_ENTERPRISE_TOKEN") == "" &&
|
|
cmd != nil && cmdutil.IsAuthCheckEnabled(cmd)
|
|
if authCheckEnabled {
|
|
if !cmdutil.CheckAuth(cfg) {
|
|
fmt.Fprintln(stderr, utils.Bold("Welcome to GitHub CLI!"))
|
|
fmt.Fprintln(stderr)
|
|
fmt.Fprintln(stderr, "To authenticate, please run `gh auth login`.")
|
|
fmt.Fprintln(stderr, "You can also set the GITHUB_TOKEN environment variable, if preferred.")
|
|
os.Exit(4)
|
|
}
|
|
}
|
|
|
|
rootCmd.SetArgs(expandedArgs)
|
|
|
|
if cmd, err := rootCmd.ExecuteC(); err != nil {
|
|
printError(stderr, err, cmd, hasDebug)
|
|
|
|
var httpErr api.HTTPError
|
|
if errors.As(err, &httpErr) && httpErr.StatusCode == 401 {
|
|
fmt.Println("hint: try authenticating with `gh auth login`")
|
|
}
|
|
|
|
os.Exit(1)
|
|
}
|
|
if root.HasFailed() {
|
|
os.Exit(1)
|
|
}
|
|
|
|
newRelease := <-updateMessageChan
|
|
if newRelease != nil {
|
|
msg := fmt.Sprintf("%s %s → %s\n%s",
|
|
ansi.Color("A new release of gh is available:", "yellow"),
|
|
ansi.Color(buildVersion, "cyan"),
|
|
ansi.Color(newRelease.Version, "cyan"),
|
|
ansi.Color(newRelease.URL, "yellow"))
|
|
|
|
fmt.Fprintf(stderr, "\n\n%s\n\n", msg)
|
|
}
|
|
}
|
|
|
|
func printError(out io.Writer, err error, cmd *cobra.Command, debug bool) {
|
|
if err == cmdutil.SilentError {
|
|
return
|
|
}
|
|
|
|
var dnsError *net.DNSError
|
|
if errors.As(err, &dnsError) {
|
|
fmt.Fprintf(out, "error connecting to %s\n", dnsError.Name)
|
|
if debug {
|
|
fmt.Fprintln(out, dnsError)
|
|
}
|
|
fmt.Fprintln(out, "check your internet connection or githubstatus.com")
|
|
return
|
|
}
|
|
|
|
fmt.Fprintln(out, err)
|
|
|
|
var flagError *cmdutil.FlagError
|
|
if errors.As(err, &flagError) || strings.HasPrefix(err.Error(), "unknown command ") {
|
|
if !strings.HasSuffix(err.Error(), "\n") {
|
|
fmt.Fprintln(out)
|
|
}
|
|
fmt.Fprintln(out, cmd.UsageString())
|
|
}
|
|
}
|
|
|
|
func shouldCheckForUpdate() bool {
|
|
if os.Getenv("GH_NO_UPDATE_NOTIFIER") != "" {
|
|
return false
|
|
}
|
|
if os.Getenv("CODESPACES") != "" {
|
|
return false
|
|
}
|
|
return updaterEnabled != "" && !isCI() && !isCompletionCommand() && utils.IsTerminal(os.Stderr)
|
|
}
|
|
|
|
// based on https://github.com/watson/ci-info/blob/HEAD/index.js
|
|
func isCI() bool {
|
|
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
|
|
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
|
|
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
|
|
}
|
|
|
|
func isCompletionCommand() bool {
|
|
return len(os.Args) > 1 && os.Args[1] == "completion"
|
|
}
|
|
|
|
func checkForUpdate(currentVersion string) (*update.ReleaseInfo, error) {
|
|
if !shouldCheckForUpdate() {
|
|
return nil, nil
|
|
}
|
|
|
|
client, err := basicClient(currentVersion)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repo := updaterEnabled
|
|
stateFilePath := path.Join(config.ConfigDir(), "state.yml")
|
|
return update.CheckForUpdate(client, stateFilePath, repo, currentVersion)
|
|
}
|
|
|
|
// BasicClient returns an API client for github.com only that borrows from but
|
|
// does not depend on user configuration
|
|
func basicClient(currentVersion string) (*api.Client, error) {
|
|
var opts []api.ClientOption
|
|
if verbose := os.Getenv("DEBUG"); verbose != "" {
|
|
opts = append(opts, apiVerboseLog())
|
|
}
|
|
opts = append(opts, api.AddHeader("User-Agent", fmt.Sprintf("GitHub CLI %s", currentVersion)))
|
|
|
|
token := os.Getenv("GITHUB_TOKEN")
|
|
if token == "" {
|
|
if c, err := config.ParseDefaultConfig(); err == nil {
|
|
token, _ = c.Get(ghinstance.Default(), "oauth_token")
|
|
}
|
|
}
|
|
if token != "" {
|
|
opts = append(opts, api.AddHeader("Authorization", fmt.Sprintf("token %s", token)))
|
|
}
|
|
return api.NewClient(opts...), nil
|
|
}
|
|
|
|
func apiVerboseLog() api.ClientOption {
|
|
logTraffic := strings.Contains(os.Getenv("DEBUG"), "api")
|
|
colorize := utils.IsTerminal(os.Stderr)
|
|
return api.VerboseLog(utils.NewColorable(os.Stderr), logTraffic, colorize)
|
|
}
|