aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/cmdutil/cmdutil.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/cmdutil/cmdutil.go')
-rw-r--r--cli/internal/cmdutil/cmdutil.go245
1 files changed, 245 insertions, 0 deletions
diff --git a/cli/internal/cmdutil/cmdutil.go b/cli/internal/cmdutil/cmdutil.go
new file mode 100644
index 0000000..0b02392
--- /dev/null
+++ b/cli/internal/cmdutil/cmdutil.go
@@ -0,0 +1,245 @@
+// Package cmdutil holds functionality to run turbo via cobra. That includes flag parsing and configuration
+// of components common to all subcommands
+package cmdutil
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "strconv"
+ "sync"
+
+ "github.com/hashicorp/go-hclog"
+
+ "github.com/fatih/color"
+ "github.com/mitchellh/cli"
+ "github.com/vercel/turbo/cli/internal/client"
+ "github.com/vercel/turbo/cli/internal/config"
+ "github.com/vercel/turbo/cli/internal/fs"
+ "github.com/vercel/turbo/cli/internal/turbopath"
+ "github.com/vercel/turbo/cli/internal/turbostate"
+ "github.com/vercel/turbo/cli/internal/ui"
+)
+
+const (
+ // _envLogLevel is the environment log level
+ _envLogLevel = "TURBO_LOG_LEVEL"
+)
+
+// Helper is a struct used to hold configuration values passed via flag, env vars,
+// config files, etc. It is not intended for direct use by turbo commands, it drives
+// the creation of CmdBase, which is then used by the commands themselves.
+type Helper struct {
+ // TurboVersion is the version of turbo that is currently executing
+ TurboVersion string
+
+ // for logging
+ verbosity int
+
+ rawRepoRoot string
+
+ clientOpts client.Opts
+
+ // UserConfigPath is the path to where we expect to find
+ // a user-specific config file, if one is present. Public
+ // to allow overrides in tests
+ UserConfigPath turbopath.AbsoluteSystemPath
+
+ cleanupsMu sync.Mutex
+ cleanups []io.Closer
+}
+
+// RegisterCleanup saves a function to be run after turbo execution,
+// even if the command that runs returns an error
+func (h *Helper) RegisterCleanup(cleanup io.Closer) {
+ h.cleanupsMu.Lock()
+ defer h.cleanupsMu.Unlock()
+ h.cleanups = append(h.cleanups, cleanup)
+}
+
+// Cleanup runs the register cleanup handlers. It requires the flags
+// to the root command so that it can construct a UI if necessary
+func (h *Helper) Cleanup(cliConfig *turbostate.ParsedArgsFromRust) {
+ h.cleanupsMu.Lock()
+ defer h.cleanupsMu.Unlock()
+ var ui cli.Ui
+ for _, cleanup := range h.cleanups {
+ if err := cleanup.Close(); err != nil {
+ if ui == nil {
+ ui = h.getUI(cliConfig)
+ }
+ ui.Warn(fmt.Sprintf("failed cleanup: %v", err))
+ }
+ }
+}
+
+func (h *Helper) getUI(cliConfig *turbostate.ParsedArgsFromRust) cli.Ui {
+ colorMode := ui.GetColorModeFromEnv()
+ if cliConfig.GetNoColor() {
+ colorMode = ui.ColorModeSuppressed
+ }
+ if cliConfig.GetColor() {
+ colorMode = ui.ColorModeForced
+ }
+ return ui.BuildColoredUi(colorMode)
+}
+
+func (h *Helper) getLogger() (hclog.Logger, error) {
+ var level hclog.Level
+ switch h.verbosity {
+ case 0:
+ if v := os.Getenv(_envLogLevel); v != "" {
+ level = hclog.LevelFromString(v)
+ if level == hclog.NoLevel {
+ return nil, fmt.Errorf("%s value %q is not a valid log level", _envLogLevel, v)
+ }
+ } else {
+ level = hclog.NoLevel
+ }
+ case 1:
+ level = hclog.Info
+ case 2:
+ level = hclog.Debug
+ case 3:
+ level = hclog.Trace
+ default:
+ level = hclog.Trace
+ }
+ // Default output is nowhere unless we enable logging.
+ output := ioutil.Discard
+ color := hclog.ColorOff
+ if level != hclog.NoLevel {
+ output = os.Stderr
+ color = hclog.AutoColor
+ }
+
+ return hclog.New(&hclog.LoggerOptions{
+ Name: "turbo",
+ Level: level,
+ Color: color,
+ Output: output,
+ }), nil
+}
+
+// NewHelper returns a new helper instance to hold configuration values for the root
+// turbo command.
+func NewHelper(turboVersion string, args *turbostate.ParsedArgsFromRust) *Helper {
+ return &Helper{
+ TurboVersion: turboVersion,
+ UserConfigPath: config.DefaultUserConfigPath(),
+ verbosity: args.Verbosity,
+ }
+}
+
+// GetCmdBase returns a CmdBase instance configured with values from this helper.
+// It additionally returns a mechanism to set an error, so
+func (h *Helper) GetCmdBase(cliConfig *turbostate.ParsedArgsFromRust) (*CmdBase, error) {
+ // terminal is for color/no-color output
+ terminal := h.getUI(cliConfig)
+ // logger is configured with verbosity level using --verbosity flag from end users
+ logger, err := h.getLogger()
+ if err != nil {
+ return nil, err
+ }
+ cwdRaw, err := cliConfig.GetCwd()
+ if err != nil {
+ return nil, err
+ }
+ cwd, err := fs.GetCwd(cwdRaw)
+ if err != nil {
+ return nil, err
+ }
+ repoRoot := fs.ResolveUnknownPath(cwd, h.rawRepoRoot)
+ repoRoot, err = repoRoot.EvalSymlinks()
+ if err != nil {
+ return nil, err
+ }
+ repoConfig, err := config.ReadRepoConfigFile(config.GetRepoConfigPath(repoRoot), cliConfig)
+ if err != nil {
+ return nil, err
+ }
+ userConfig, err := config.ReadUserConfigFile(h.UserConfigPath, cliConfig)
+ if err != nil {
+ return nil, err
+ }
+ remoteConfig := repoConfig.GetRemoteConfig(userConfig.Token())
+ if remoteConfig.Token == "" && ui.IsCI {
+ vercelArtifactsToken := os.Getenv("VERCEL_ARTIFACTS_TOKEN")
+ vercelArtifactsOwner := os.Getenv("VERCEL_ARTIFACTS_OWNER")
+ if vercelArtifactsToken != "" {
+ remoteConfig.Token = vercelArtifactsToken
+ }
+ if vercelArtifactsOwner != "" {
+ remoteConfig.TeamID = vercelArtifactsOwner
+ }
+ }
+
+ // Primacy: Arg > Env
+ timeout, err := cliConfig.GetRemoteCacheTimeout()
+ if err == nil {
+ h.clientOpts.Timeout = timeout
+ } else {
+ val, ok := os.LookupEnv("TURBO_REMOTE_CACHE_TIMEOUT")
+ if ok {
+ number, err := strconv.ParseUint(val, 10, 64)
+ if err == nil {
+ h.clientOpts.Timeout = number
+ }
+ }
+ }
+
+ apiClient := client.NewClient(
+ remoteConfig,
+ logger,
+ h.TurboVersion,
+ h.clientOpts,
+ )
+
+ return &CmdBase{
+ UI: terminal,
+ Logger: logger,
+ RepoRoot: repoRoot,
+ APIClient: apiClient,
+ RepoConfig: repoConfig,
+ UserConfig: userConfig,
+ RemoteConfig: remoteConfig,
+ TurboVersion: h.TurboVersion,
+ }, nil
+}
+
+// CmdBase encompasses configured components common to all turbo commands.
+type CmdBase struct {
+ UI cli.Ui
+ Logger hclog.Logger
+ RepoRoot turbopath.AbsoluteSystemPath
+ APIClient *client.APIClient
+ RepoConfig *config.RepoConfig
+ UserConfig *config.UserConfig
+ RemoteConfig client.RemoteConfig
+ TurboVersion string
+}
+
+// LogError prints an error to the UI
+func (b *CmdBase) LogError(format string, args ...interface{}) {
+ err := fmt.Errorf(format, args...)
+ b.Logger.Error("error", err)
+ b.UI.Error(fmt.Sprintf("%s%s", ui.ERROR_PREFIX, color.RedString(" %v", err)))
+}
+
+// LogWarning logs an error and outputs it to the UI.
+func (b *CmdBase) LogWarning(prefix string, err error) {
+ b.Logger.Warn(prefix, "warning", err)
+
+ if prefix != "" {
+ prefix = " " + prefix + ": "
+ }
+
+ b.UI.Warn(fmt.Sprintf("%s%s%s", ui.WARNING_PREFIX, prefix, color.YellowString(" %v", err)))
+}
+
+// LogInfo logs an message and outputs it to the UI.
+func (b *CmdBase) LogInfo(msg string) {
+ b.Logger.Info(msg)
+ b.UI.Info(fmt.Sprintf("%s%s", ui.InfoPrefix, color.WhiteString(" %v", msg)))
+}