diff --git a/internal/impl/devbox.go b/internal/impl/devbox.go index 94e91942095..e169cfa33fe 100644 --- a/internal/impl/devbox.go +++ b/internal/impl/devbox.go @@ -210,6 +210,17 @@ func (d *Devbox) Shell(ctx context.Context) error { return shell.Run() } +// IsUserShellFish returns true if the user's shell is fish. +// This wrapper function over DevboxShell enables querying from other packages that +// make a devboxer interface. +func (d *Devbox) IsUserShellFish() (bool, error) { + sh, err := NewDevboxShell(d) + if err != nil { + return false, err + } + return sh.IsFish(), nil +} + func (d *Devbox) RunScript(ctx context.Context, cmdName string, cmdArgs []string) error { ctx, task := trace.NewTask(ctx, "devboxRun") defer task.End() diff --git a/internal/impl/shell.go b/internal/impl/shell.go index 40fb73f410d..547d45e7d1a 100644 --- a/internal/impl/shell.go +++ b/internal/impl/shell.go @@ -269,6 +269,12 @@ func (s *DevboxShell) Run() error { return errors.WithStack(err) } +// IsFish returns whether this DevboxShell wraps a fish shell. Fish shells are non-posix compatible, +// and so sometimes we may need to switch logic based on this function's result. +func (s *DevboxShell) IsFish() bool { + return s.name == shFish +} + func (s *DevboxShell) shellRCOverrides(shellrc string) (extraEnv map[string]string, extraArgs []string) { // Shells have different ways of overriding the shellrc, so we need to // look at the name to know which env vars or args to set when launching the shell. @@ -319,7 +325,7 @@ func (s *DevboxShell) writeDevboxShellrc() (path string, err error) { }() tmpl := shellrcTmpl - if s.name == shFish { + if s.IsFish() { tmpl = fishrcTmpl } diff --git a/internal/shellgen/scripts.go b/internal/shellgen/scripts.go index 9078ccdcbe6..b15bbea9820 100644 --- a/internal/shellgen/scripts.go +++ b/internal/shellgen/scripts.go @@ -25,6 +25,7 @@ type devboxer interface { Lockfile() *lock.File AllInstallablePackages() ([]*devpkg.Package, error) InstallablePackages() []*devpkg.Package + IsUserShellFish() (bool, error) PluginManager() *plugin.Manager ProjectDir() string } @@ -80,8 +81,8 @@ func WriteScriptsToFiles(devbox devboxer) error { return nil } -func WriteScriptFile(d devboxer, name string, body string) (err error) { - script, err := os.Create(ScriptPath(d.ProjectDir(), name)) +func WriteScriptFile(devbox devboxer, name string, body string) (err error) { + script, err := os.Create(ScriptPath(devbox.ProjectDir(), name)) if err != nil { return errors.WithStack(err) } @@ -97,7 +98,17 @@ func WriteScriptFile(d devboxer, name string, body string) (err error) { } if featureflag.ScriptExitOnError.Enabled() { - body = fmt.Sprintf("set -e\n\n%s", body) + // Fish cannot run scripts with `set -e`. + // NOTE: Devbox scripts will run using `sh` for consistency. However, + // init_hooks in a fish shell will run using `fish` shell, and need this + // check. + isFish, err := devbox.IsUserShellFish() + if err != nil { + return errors.WithStack(err) + } + if !isFish { + body = fmt.Sprintf("set -e\n\n%s", body) + } } _, err = script.WriteString(body) return errors.WithStack(err) diff --git a/testscripts/run/script_exit_on_error.test.txt b/testscripts/run/script_exit_on_error.test.txt new file mode 100644 index 00000000000..19e873c75d8 --- /dev/null +++ b/testscripts/run/script_exit_on_error.test.txt @@ -0,0 +1,20 @@ +# Testscript to ensure that the script exits on error. + +! exec devbox run multi_line +stdout 'first line' +! stdout 'second line' + +-- devbox.json -- +{ + "packages": [ + ], + "shell": { + "scripts": { + "multi_line": [ + "echo \"first line\"", + "exit 1", + "echo \"second line\"" + ] + } + } +}