From 87c27f3098475743d2887ca127e7abf2445cabd5 Mon Sep 17 00:00:00 2001 From: Vladimir Urushev Date: Wed, 1 Oct 2025 02:34:14 +0200 Subject: [PATCH] feat: add --no-tty flag to run and shell commands --- .github/{workflows => }/dependabot.yml | 0 cmd/devbox/run.go | 15 ++++++++++---- cmd/devbox/shell.go | 27 +++++++++++++++++++++++--- internal/project/config.go | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) rename .github/{workflows => }/dependabot.yml (100%) diff --git a/.github/workflows/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .github/workflows/dependabot.yml rename to .github/dependabot.yml diff --git a/cmd/devbox/run.go b/cmd/devbox/run.go index adadcd4..81686c6 100644 --- a/cmd/devbox/run.go +++ b/cmd/devbox/run.go @@ -11,6 +11,8 @@ import ( ) func init() { + var noTty bool + cmd := &cobra.Command{ Use: "run ", Short: "Run scenario defined in devbox project", @@ -42,7 +44,7 @@ func init() { args = []string{} } - if err := runRun(ctx, p, command, args); err != nil { + if err := runRun(ctx, p, command, args, noTty); err != nil { return fmt.Errorf("failed to run scenario: %w", err) } @@ -50,12 +52,13 @@ func init() { }), } + cmd.Flags().BoolVarP(&noTty, "no-tty", "t", false, "Do not allocate a pseudo-TTY") cmd.Flags().SetInterspersed(false) root.AddCommand(cmd) } -func runRun(ctx context.Context, p *project.Project, command string, args []string) error { +func runRun(ctx context.Context, p *project.Project, command string, args []string, noTtyFlag bool) error { isRunning, err := isRunning(ctx, apiService, p) if err != nil { return fmt.Errorf("failed to check if services are running: %w", err) @@ -79,9 +82,13 @@ func runRun(ctx context.Context, p *project.Project, command string, args []stri interactive = *scenario.Interactive } - tty := true - if scenario.Tty != nil { + var tty bool + if noTtyFlag { + tty = false + } else if scenario.Tty != nil { tty = *scenario.Tty + } else { + tty = isTTYAvailable() } opts := project.RunOptions{ diff --git a/cmd/devbox/shell.go b/cmd/devbox/shell.go index 2d13f4d..17e7e8a 100644 --- a/cmd/devbox/shell.go +++ b/cmd/devbox/shell.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "os" "strings" "github.com/docker/docker/api/types/container" @@ -15,6 +16,8 @@ import ( ) func init() { + var noTty bool + cmd := &cobra.Command{ Use: "shell ", Short: "Run interactive shell in one of the services", @@ -32,7 +35,7 @@ func init() { return err } - if err := runShell(ctx, p, args[0]); err != nil { + if err := runShell(ctx, p, args[0], noTty); err != nil { return fmt.Errorf("failed to run shell: %w", err) } @@ -40,10 +43,12 @@ func init() { }), } + cmd.Flags().BoolVarP(&noTty, "no-tty", "t", false, "Do not allocate a pseudo-TTY") + root.AddCommand(cmd) } -func runShell(ctx context.Context, p *project.Project, serviceName string) error { +func runShell(ctx context.Context, p *project.Project, serviceName string, noTtyFlag bool) error { _, ok := p.Services[serviceName] if !ok { return fmt.Errorf("service %q not found", serviceName) @@ -73,10 +78,17 @@ func runShell(ctx context.Context, p *project.Project, serviceName string) error return fmt.Errorf("failed to find a shell") } + var tty bool + if noTtyFlag { + tty = false + } else { + tty = isTTYAvailable() + } + opts := project.RunOptions{ Service: serviceName, Interactive: true, - Tty: true, + Tty: tty, Command: []string{lastShell}, } @@ -157,3 +169,12 @@ func filterLabels(projectName, serviceName string) filters.Args { return filters.NewArgs(pairs...) } + +func isTTYAvailable() bool { + stat, err := os.Stdin.Stat() + if err != nil { + return false + } + + return (stat.Mode() & os.ModeCharDevice) != 0 +} diff --git a/internal/project/config.go b/internal/project/config.go index b0b8518..eefe770 100644 --- a/internal/project/config.go +++ b/internal/project/config.go @@ -14,7 +14,7 @@ type ScenarioConfig struct { Description string `yaml:"description"` Command []string `yaml:"command"` Entrypoint []string `yaml:"entrypoint"` - Tty *bool `yaml:"tty"` // default: true + Tty *bool `yaml:"tty"` // default: auto-detect Interactive *bool `yaml:"stdin_open"` // default: true WorkingDir string `yaml:"working_dir"` User string `yaml:"user"`