-
Notifications
You must be signed in to change notification settings - Fork 152
/
root.go
229 lines (197 loc) · 7.43 KB
/
root.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package commands
import (
"errors"
"io/fs"
"os"
"path/filepath"
"time"
"github.com/fatih/color"
"github.com/joho/godotenv"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
"github.com/wundergraph/wundergraph/cli/helpers"
"github.com/wundergraph/wundergraph/pkg/config"
"github.com/wundergraph/wundergraph/pkg/files"
"github.com/wundergraph/wundergraph/pkg/logging"
"github.com/wundergraph/wundergraph/pkg/node"
"github.com/wundergraph/wundergraph/pkg/telemetry"
)
const (
configJsonFilename = "wundergraph.config.json"
configEntryPointFilename = "wundergraph.config.ts"
serverEntryPointFilename = "wundergraph.server.ts"
wunderctlBinaryPathEnvKey = "WUNDERCTL_BINARY_PATH"
defaultNodeGracefulTimeoutSeconds = 10
)
var (
BuildInfo node.BuildInfo
GitHubAuthDemo node.GitHubAuthDemo
TelemetryClient telemetry.Client
DotEnvFile string
log *zap.Logger
serviceToken string
cmdDurationMetric telemetry.DurationMetric
_wunderGraphDirConfig string
disableCache bool
clearCache bool
rootFlags helpers.RootFlags
red = color.New(color.FgHiRed)
green = color.New(color.FgHiGreen)
blue = color.New(color.FgHiBlue)
yellow = color.New(color.FgHiYellow)
cyan = color.New(color.FgHiCyan)
white = color.New(color.FgHiWhite)
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "wunderctl",
Short: "wunderctl is the cli to manage, build and debug your WunderGraph applications",
Long: `wunderctl is the cli to manage, build and debug your WunderGraph applications.`,
// Don't show usage on error
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
switch cmd.Name() {
// skip any setup to avoid logging anything
// because the command output data on stdout
case LoadOperationsCmdName:
return nil
// up command has a different default for global pretty logging
// we can't overwrite the default value in the init function because
// it would overwrite the default value for all other commands
case UpCmdName:
rootFlags.PrettyLogs = upCmdPrettyLogging
}
if rootFlags.DebugMode {
// override log level to debug
rootFlags.CliLogLevel = "debug"
}
logLevel, err := logging.FindLogLevel(rootFlags.CliLogLevel)
if err != nil {
return err
}
log = logging.
New(rootFlags.PrettyLogs, rootFlags.DebugMode, logLevel).
With(zap.String("component", "@wundergraph/wunderctl"))
err = godotenv.Load(DotEnvFile)
if err != nil {
if _, ok := err.(*fs.PathError); ok {
log.Debug("starting without env file")
} else {
log.Error("error loading env file",
zap.Error(err))
return err
}
} else {
log.Debug("env file successfully loaded",
zap.String("file", DotEnvFile),
)
}
if clearCache {
wunderGraphDir, err := files.FindWunderGraphDir(_wunderGraphDirConfig)
if err != nil {
return err
}
cacheDir := filepath.Join(wunderGraphDir, "cache")
if err := os.RemoveAll(cacheDir); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
}
// Check if we want to track telemetry for this command
if rootFlags.Telemetry && cmd.Annotations["telemetry"] == "true" {
TelemetryClient = telemetry.NewClient(
viper.GetString("API_URL"),
telemetry.MetricClientInfo{
WunderctlVersion: BuildInfo.Version,
IsCI: os.Getenv("CI") != "" || os.Getenv("ci") != "",
AnonymousID: viper.GetString("anonymousid"),
},
telemetry.WithTimeout(3*time.Second),
telemetry.WithLogger(log),
telemetry.WithDebug(rootFlags.TelemetryDebugMode),
)
cmdMetricName := telemetry.CobraFullCommandPathMetricName(cmd)
metricDurationName := telemetry.DurationMetricSuffix(cmdMetricName)
cmdDurationMetric = telemetry.NewDurationMetric(metricDurationName)
metricUsageName := telemetry.UsageMetricSuffix(cmdMetricName)
cmdUsageMetric := telemetry.NewUsageMetric(metricUsageName)
// Send telemetry in a goroutine to not block the command
go func() {
err := TelemetryClient.Send([]telemetry.Metric{cmdUsageMetric})
// AddMetric the usage of the command immediately
if rootFlags.TelemetryDebugMode {
if err != nil {
log.Error("Could not send telemetry data", zap.Error(err))
} else {
log.Info("Telemetry data sent")
}
}
}()
}
return nil
},
}
type BuildTimeConfig struct {
DefaultApiEndpoint string
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute(buildInfo node.BuildInfo, githubAuthDemo node.GitHubAuthDemo) {
var err error
defer func() {
// In case of a panic or error we want to flush the telemetry data
if r := recover(); r != nil || err != nil {
FlushTelemetry()
os.Exit(1)
} else {
FlushTelemetry()
os.Exit(0)
}
}()
BuildInfo = buildInfo
GitHubAuthDemo = githubAuthDemo
err = rootCmd.Execute()
}
func FlushTelemetry() {
if TelemetryClient != nil && rootFlags.Telemetry && cmdDurationMetric != nil {
err := TelemetryClient.Send([]telemetry.Metric{cmdDurationMetric()})
if rootFlags.TelemetryDebugMode {
if err != nil {
log.Error("Could not send telemetry data", zap.Error(err))
} else {
log.Info("Telemetry data sent")
}
}
}
}
// wunderctlBinaryPath() returns the path to the currently executing parent wunderctl
// command which is then passed via wunderctlBinaryPathEnvKey to subprocesses. This
// ensures than when the SDK calls back into wunderctl, the same copy is always used.
func wunderctlBinaryPath() string {
// Check if a parent wunderctl set this for us
path, isSet := os.LookupEnv(wunderctlBinaryPathEnvKey)
if !isSet {
// Variable is not set, find out our path and set it
exe, err := os.Executable()
if err == nil {
path = exe
}
}
return path
}
func init() {
_, isTelemetryDisabled := os.LookupEnv("WG_TELEMETRY_DISABLED")
_, isTelemetryDebugEnabled := os.LookupEnv("WG_TELEMETRY_DEBUG")
config.InitConfig(!isTelemetryDisabled)
// Can be overwritten by WG_API_URL=<url> env variable
viper.SetDefault("API_URL", "https://gateway.wundergraph.com")
rootCmd.PersistentFlags().StringVarP(&rootFlags.CliLogLevel, "cli-log-level", "l", "info", "sets the CLI log level")
rootCmd.PersistentFlags().StringVarP(&DotEnvFile, "env", "e", ".env", "allows you to set environment variables from an env file")
rootCmd.PersistentFlags().BoolVar(&rootFlags.DebugMode, "debug", false, "enables the debug mode so that all requests and responses will be logged")
rootCmd.PersistentFlags().BoolVar(&rootFlags.Telemetry, "telemetry", !isTelemetryDisabled, "enables telemetry. Telemetry allows us to accurately gauge WunderGraph feature usage, pain points, and customization across all users.")
rootCmd.PersistentFlags().BoolVar(&rootFlags.TelemetryDebugMode, "telemetry-debug", isTelemetryDebugEnabled, "enables the debug mode for telemetry. Understand what telemetry is being sent to us.")
rootCmd.PersistentFlags().BoolVar(&rootFlags.PrettyLogs, "pretty-logging", false, "switches to human readable format")
rootCmd.PersistentFlags().StringVar(&_wunderGraphDirConfig, "wundergraph-dir", ".", "directory of your wundergraph.config.ts")
rootCmd.PersistentFlags().BoolVar(&disableCache, "no-cache", false, "disables local caches")
rootCmd.PersistentFlags().BoolVar(&clearCache, "clear-cache", false, "clears local caches during startup")
}