/
main.go
183 lines (165 loc) · 4.69 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package main
import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"sync"
"time"
"emperror.dev/errors"
"github.com/aviator-co/av/internal/actions"
"github.com/aviator-co/av/internal/config"
"github.com/aviator-co/av/internal/gh"
"github.com/fatih/color"
"github.com/kr/text"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)
var rootFlags struct {
Debug bool
Directory string
}
var rootCmd = &cobra.Command{
Use: "av",
// Don't automatically print errors or usage information (we handle that ourselves).
// Cobra still prints usage if you return cmd.Usage() from RunE.
SilenceErrors: true,
SilenceUsage: true,
// Don't show "completion" command in help menu
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: true,
},
// Run setup before invoking any child commands.
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if rootFlags.Debug {
logrus.SetLevel(logrus.DebugLevel)
logrus.WithField("av_version", config.Version).Debug("enabled debug logging")
}
var configDirs []string
repo, err := getRepo()
// If we weren't able to load the Git repo, that probably just means the
// command isn't being run from inside a repo. That's fine, we just
// don't need to bother reading repo-local config.
if err != nil {
logrus.WithError(err).Debug("unable to load Git repo (probably not inside a repo)")
} else {
gitDir, err := repo.Git("rev-parse", "--git-dir")
if err != nil {
logrus.WithError(err).Warning("failed to determine git root directory")
} else {
configDirs = append(configDirs, gitDir)
}
logrus.WithField("git_dir", gitDir).Debug("loaded Git repo")
}
// Note: this only returns an error if config exists and it can't be
// read/parsed. It doesn't return an error if no config file exists.
didLoadConfig, err := config.Load(configDirs)
if err != nil {
return errors.Wrap(err, "failed to load configuration")
}
if didLoadConfig {
logrus.Debug("loaded configuration")
} else {
logrus.Debug("no configuration found")
}
return nil
},
}
func init() {
rootCmd.PersistentFlags().BoolVar(
&rootFlags.Debug, "debug", false,
"enable verbose debug logging",
)
rootCmd.PersistentFlags().StringVarP(
&rootFlags.Directory, "repo", "C", "",
"directory to use for git repository",
)
rootCmd.AddCommand(
branchMetaCmd,
commitCmd,
fetchCmd,
initCmd,
prCmd,
stackCmd,
versionCmd,
authCmd,
)
}
func main() {
// Note: this doesn't include whatever time is spent in initializing the
// runtime and various packages (e.g., package init functions).
startTime := time.Now()
err := rootCmd.Execute()
logrus.WithField("duration", time.Since(startTime)).Debug("command exited")
checkCliVersion()
var exitSilently actions.ErrExitSilently
if errors.As(err, &exitSilently) {
os.Exit(exitSilently.ExitCode)
}
if err != nil {
// In debug mode, show more detailed information about the error
// (including the stack trace if using pkg/errors).
if rootFlags.Debug {
stackTrace := fmt.Sprintf("%+v", err)
_, _ = fmt.Fprintf(os.Stderr, "error: %s\n%s\n", err, text.Indent(stackTrace, "\t"))
} else {
_, _ = fmt.Fprintf(os.Stderr, "error: %s\n", err)
}
os.Exit(1)
}
}
func checkCliVersion() {
if config.Version == config.VersionDev {
logrus.Debug("skipping CLI version check (development version)")
return
}
latest, err := config.FetchLatestVersion()
if err != nil {
logrus.WithError(err).Warning("failed to determine latest released version of av")
return
}
logrus.WithField("latest", latest).Debug("fetched latest released version")
if semver.Compare(config.Version, latest) < 0 {
c := color.New(color.Faint, color.Bold)
_, _ = fmt.Fprint(
os.Stderr,
c.Sprint(">> A new version of av is available: "),
color.RedString(config.Version),
c.Sprint(" => "),
color.GreenString(latest),
"\n",
c.Sprint(">> https://docs.aviator.co/reference/aviator-cli/installation#upgrade\n"),
)
}
}
var once sync.Once
var lazyGithubClient *gh.Client
func discoverGitHubAPIToken() string {
if config.Av.GitHub.Token != "" {
return config.Av.GitHub.Token
}
if ghCli, err := exec.LookPath("gh"); err == nil {
var stdout bytes.Buffer
cmd := exec.Command(ghCli, "auth", "token")
cmd.Stdout = &stdout
cmd.Stderr = nil
if err := cmd.Run(); err == nil {
return strings.TrimSpace(stdout.String())
}
}
return ""
}
var errNoGitHubToken = errors.New("No GitHub token is set (do you need to configure one?).")
func getGitHubClient() (*gh.Client, error) {
token := discoverGitHubAPIToken()
if token == "" {
return nil, errNoGitHubToken
}
var err error
once.Do(func() {
lazyGithubClient, err = gh.NewClient(token)
})
return lazyGithubClient, err
}