diff --git a/cli/client.go b/cli/client.go index 2f509734382..96ba75c18bb 100644 --- a/cli/client.go +++ b/cli/client.go @@ -19,6 +19,7 @@ import ( "path/filepath" "runtime" "runtime/debug" + "slices" "strconv" "strings" "sync" @@ -3334,7 +3335,15 @@ func RobotsPartRemoveFragmentAction(ctx context.Context, cmd *cli.Command, args } } } else { - // No fragment provided, prompt user to select + if !isInteractive() { + names := make([]string, 0, len(fragmentNamesToIDs)) + for name, id := range fragmentNamesToIDs { + names = append(names, fmt.Sprintf(" %s (%s)", name, id)) + } + slices.Sort(names) + return fmt.Errorf("--fragment flag required in non-interactive mode; available fragments:\n%s", + strings.Join(names, "\n")) + } whichFragment, whichID, err = client.selectFragment(fragmentNamesToIDs) if err != nil { return err diff --git a/cli/module_generate.go b/cli/module_generate.go index 52c5ac90b89..8b1d6002235 100644 --- a/cli/module_generate.go +++ b/cli/module_generate.go @@ -79,6 +79,9 @@ type generateModuleArgs struct { func GenerateModuleAction(ctx context.Context, cmd *cli.Command, args generateModuleArgs) error { c, err := newViamClient(ctx, cmd) if err != nil { + if !isInteractive() { + return errors.New("authentication required; run `viam login` before using module generate non-interactively") + } shouldContinueGeneration := promptUnauthenticated() if !shouldContinueGeneration { return err @@ -133,6 +136,10 @@ func (c *viamClient) generateModuleAction(ctx context.Context, cmd *cli.Command, } if newModule.HasEmptyInput() { + if !isInteractive() { + return errors.New("missing required flags for non-interactive mode; " + + "provide --name, --language, --public-namespace, --resource-subtype, and --model-name") + } err = promptUser(newModule) if err != nil { return err @@ -148,7 +155,7 @@ func (c *viamClient) generateModuleAction(ctx context.Context, cmd *cli.Command, } populateAdditionalInfo(newModule) - s := spinner.New() + var s *spinner.Spinner var fatalError error var registryURL string nonFatalError := false @@ -157,8 +164,20 @@ func (c *viamClient) generateModuleAction(ctx context.Context, cmd *cli.Command, return err } globalArgs := *gArgs + + // logTitle updates the spinner in interactive mode, prints a status line + // in non-interactive mode, and is a no-op in debug mode. + logTitle := func(string) {} + interactive := isInteractive() + if interactive && !globalArgs.Debug { + s = spinner.New() + logTitle = func(msg string) { s.Title(msg) } + } else if !globalArgs.Debug { + logTitle = func(msg string) { printf(cmd.Root().Writer, "%s", msg) } + } + action := func() { - s.Title("Getting latest release...") + logTitle("Getting latest release...") version, err := getLatestSDKTag(ctx, cmd, newModule.Language, globalArgs) if err != nil { fatalError = err @@ -170,52 +189,52 @@ func (c *viamClient) generateModuleAction(ctx context.Context, cmd *cli.Command, newModule.SDKVersion = strings.TrimPrefix(newModule.SDKVersion[idx+1:], "v") } - s.Title("Setting up module directory...") + logTitle("Setting up module directory...") if err = setupDirectories(cmd, newModule.ModuleName, globalArgs); err != nil { fatalError = err return } - s.Title("Creating module and generating manifest...") + logTitle("Creating module and generating manifest...") registryURL, err = createModuleAndManifest(ctx, cmd, c, *newModule, globalArgs) if err != nil { fatalError = err return } - s.Title("Rendering common files...") + logTitle("Rendering common files...") if err = renderCommonFiles(cmd, *newModule, globalArgs); err != nil { fatalError = err return } - s.Title(fmt.Sprintf("Copying %s files...", newModule.Language)) + logTitle(fmt.Sprintf("Copying %s files...", newModule.Language)) if err = copyLanguageTemplate(cmd, newModule.Language, newModule.ModuleName, globalArgs); err != nil { fatalError = err return } - s.Title("Rendering template...") + logTitle("Rendering template...") if err = renderTemplate(cmd, *newModule, globalArgs); err != nil { fatalError = err return } - s.Title(fmt.Sprintf("Generating %s stubs...", newModule.Language)) + logTitle(fmt.Sprintf("Generating %s stubs...", newModule.Language)) if err = generateStubs(cmd, *newModule, globalArgs); err != nil { warningf(cmd.Root().ErrWriter, err.Error()) nonFatalError = true } } - if globalArgs.Debug { - action() - } else { + if interactive && !globalArgs.Debug { s.Action(action) err := s.Run() if err != nil { return err } + } else { + action() } if fatalError != nil { diff --git a/cli/tty.go b/cli/tty.go new file mode 100644 index 00000000000..a4460993375 --- /dev/null +++ b/cli/tty.go @@ -0,0 +1,12 @@ +package cli + +import ( + "os" + + "golang.org/x/term" +) + +// isInteractive reports whether stdin is connected to a terminal. +func isInteractive() bool { + return term.IsTerminal(int(os.Stdin.Fd())) +}