From 840c8eb8d02a1831399d93a8f67a971f1997d8c2 Mon Sep 17 00:00:00 2001 From: Greg Curtis Date: Tue, 13 Sep 2022 17:16:44 -0400 Subject: [PATCH] boxcli,devbox: move default prompt into devbox.json We currently set the devbox shell prompt in the shellrc template with: export PS1="(devbox) $PS1" This breaks the prompt for users that have a theme or use a shell that sets the prompt in some other way. To allow user's to opt out of the prompt, move it into devbox.json where they can modify or delete it. Running `devbox init` will now generate a config that looks like: { "packages": [], "shell": { "init_hook": [ "# Here you can remove or customize the prompt for your project's devbox shell.", "# By convention, individual users can set DEVBOX_NO_PROMPT=1 in their shell's", "# rc file to opt out of prompt customization.", "if [ -z \"$DEVBOX_NO_PROMPT\" ]; then", "\tPS1=\"(devbox:tmp.wGAYMHRO) $PS1\"", "fi" ] } } This also means that users with an existing devbox config will no longer get a prompt. If they want to get the default prompt back, they can run: devbox init -fix prompt This fix will automatically append the default devbox prompt to the shell init hook unless there's already a command that contains `PS1=`. A future change will print a hint when launching a shell to tell the user about this change. Note that the `-fix` flag accepts a comma-separated list of fixes in case we need to add more as the config evolves. For simplicity, the current implementation only allows "prompt", but can be redone later to support more. --- boxcli/init.go | 34 +++++++++--- devbox.go | 54 ++++++++++++++++++- go.mod | 2 +- nix/shellrc.tmpl | 3 -- nix/testdata/shellrc/basic/shellrc.golden | 3 -- nix/testdata/shellrc/nohook/shellrc.golden | 3 -- nix/testdata/shellrc/noshellrc/shellrc.golden | 3 -- 7 files changed, 81 insertions(+), 21 deletions(-) diff --git a/boxcli/init.go b/boxcli/init.go index 806b05f19e4..1de14112fa7 100644 --- a/boxcli/init.go +++ b/boxcli/init.go @@ -7,24 +7,44 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "go.jetpack.io/devbox" + "go.jetpack.io/devbox/boxcli/usererr" ) func InitCmd() *cobra.Command { + fixes := &[]string{} + command := &cobra.Command{ Use: "init []", Short: "Initialize a directory as a devbox project", Args: cobra.MaximumNArgs(1), - RunE: runInitCmd, + RunE: initCmdFunc(fixes), } + command.Flags().StringSliceVar(fixes, "fix", nil, "Run a comma-separated list of fixes on a devbox config: 'prompt'") return command } -func runInitCmd(cmd *cobra.Command, args []string) error { - path := pathArg(args) +func initCmdFunc(fixes *[]string) runFunc { + return func(cmd *cobra.Command, args []string) error { + path := pathArg(args) + + created, err := devbox.InitConfig(path) + if err != nil { + return errors.WithStack(err) + } + + // New configs come with the latest and greatest fixes already, + // so no need to apply any. + if created || len(*fixes) == 0 { + return nil + } - _, err := devbox.InitConfig(path) - if err != nil { - return errors.WithStack(err) + if len(*fixes) > 1 || (*fixes)[0] != "prompt" { + return usererr.New("The only currently available fix is 'prompt'.") + } + box, err := devbox.Open(path) + if err != nil { + return err + } + return box.Fix() } - return nil } diff --git a/devbox.go b/devbox.go index 9bb3a3f53bd..11fa3163582 100644 --- a/devbox.go +++ b/devbox.go @@ -5,7 +5,9 @@ package devbox import ( + "fmt" "path/filepath" + "strings" "github.com/pkg/errors" "go.jetpack.io/devbox/cuecfg" @@ -24,7 +26,19 @@ const configFilename = "devbox.json" // exist. func InitConfig(dir string) (created bool, err error) { cfgPath := filepath.Join(dir, configFilename) - return cuecfg.InitFile(cfgPath, &Config{}) + created, err = cuecfg.InitFile(cfgPath, &Config{}) + if err != nil { + return created, err + } + if created { + // Apply any available fixes to new configs. + box, err := Open(dir) + if err != nil { + return created, err + } + return created, box.Fix() + } + return created, err } // Devbox provides an isolated development environment that contains a set of @@ -109,6 +123,44 @@ func (d *Devbox) Plan() (*plansdk.Plan, error) { return plansdk.MergeUserPlan(userPlan, automatedPlan) } +// Fix applies one or more automated fixes to the config. +func (d *Devbox) Fix() error { + // This method currently only applies the "prompt" fix, but if we ever + // want to support more fixes we can add a variadic argument for a list + // of fixes. + + needsFix := true + for _, cmd := range d.cfg.Shell.InitHook.Cmds { + // Don't do anything if we or the user have already added a + // command that looks like it sets a prompt. This obviously + // isn't bulletproof, but it's good enough to make the fix + // itself idempotent. + if strings.Contains(cmd, "PS1=") { + needsFix = false + break + } + } + if !needsFix { + return nil + } + + // Include the config's directory name in the prompt if we can get it. + // Otherwise just use a generic devbox prompt. + prompt := ` PS1="(devbox) $PS1"` + if abs, err := filepath.Abs(d.srcDir); err == nil { + prompt = fmt.Sprintf(` PS1="(devbox:%s) $PS1"`, filepath.Base(abs)) + } + d.cfg.Shell.InitHook.Cmds = append(d.cfg.Shell.InitHook.Cmds, []string{ + `# Here you can remove or customize the prompt for your project's devbox shell.`, + `# By convention, individual users can set DEVBOX_NO_PROMPT=1 in their shell's`, + `# rc file to opt out of prompt customization.`, + `if [ -z "$DEVBOX_NO_PROMPT" ]; then`, + prompt, + `fi`, + }...) + return errors.Wrap(d.saveCfg(), "fix: save config") +} + // Generate creates the directory of Nix files and the Dockerfile that define // the devbox environment. func (d *Devbox) Generate() error { diff --git a/go.mod b/go.mod index 080bec5e223..8bd006e8add 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( cuelang.org/go v0.4.3 github.com/bmatcuk/doublestar/v4 v4.2.0 + github.com/creekorful/mvnparser v1.5.0 github.com/denisbrodbeck/machineid v1.0.1 github.com/fatih/color v1.13.0 github.com/google/go-cmp v0.4.0 @@ -25,7 +26,6 @@ require golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cockroachdb/apd/v2 v2.0.1 // indirect - github.com/creekorful/mvnparser v1.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.2.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/nix/shellrc.tmpl b/nix/shellrc.tmpl index 926d1ccb660..605267551c0 100644 --- a/nix/shellrc.tmpl +++ b/nix/shellrc.tmpl @@ -60,9 +60,6 @@ PATH="$( echo "${nix_path:+$nix_path:}${non_nix_path}" )" -# Prepend to the prompt to make it clear we're in a devbox shell. -export PS1="(devbox) $PS1" - # End Devbox Post-init Hook {{- if .UserHook }} diff --git a/nix/testdata/shellrc/basic/shellrc.golden b/nix/testdata/shellrc/basic/shellrc.golden index 121a2fc004c..89900cc5f58 100644 --- a/nix/testdata/shellrc/basic/shellrc.golden +++ b/nix/testdata/shellrc/basic/shellrc.golden @@ -74,9 +74,6 @@ PATH="$( echo "${nix_path:+$nix_path:}${non_nix_path}" )" -# Prepend to the prompt to make it clear we're in a devbox shell. -export PS1="(devbox) $PS1" - # End Devbox Post-init Hook # Begin Devbox User Hook diff --git a/nix/testdata/shellrc/nohook/shellrc.golden b/nix/testdata/shellrc/nohook/shellrc.golden index ccdb232d709..ce0b177640c 100644 --- a/nix/testdata/shellrc/nohook/shellrc.golden +++ b/nix/testdata/shellrc/nohook/shellrc.golden @@ -74,7 +74,4 @@ PATH="$( echo "${nix_path:+$nix_path:}${non_nix_path}" )" -# Prepend to the prompt to make it clear we're in a devbox shell. -export PS1="(devbox) $PS1" - # End Devbox Post-init Hook diff --git a/nix/testdata/shellrc/noshellrc/shellrc.golden b/nix/testdata/shellrc/noshellrc/shellrc.golden index 6c787fafbb1..7c87aeb08b4 100644 --- a/nix/testdata/shellrc/noshellrc/shellrc.golden +++ b/nix/testdata/shellrc/noshellrc/shellrc.golden @@ -32,9 +32,6 @@ PATH="$( echo "${nix_path:+$nix_path:}${non_nix_path}" )" -# Prepend to the prompt to make it clear we're in a devbox shell. -export PS1="(devbox) $PS1" - # End Devbox Post-init Hook # Begin Devbox User Hook