Skip to content

Commit bf62b1b

Browse files
committed
Added support for ZDOTDIR handling in Zsh shell initialization.
Fixes #1715 #2297 This commit adds a patch to correctly handle ZDOTDIR env var for zsh. Instead of copying .z* files directly, we now source them inside devbox zsh config after setting ZDOTDIR to user's config dir. This makes sure any user config referencing ZDOTDIR doesn't break.
1 parent 1a8e44f commit bf62b1b

File tree

6 files changed

+451
-11
lines changed

6 files changed

+451
-11
lines changed

internal/devbox/shell.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929

3030
//go:embed shellrc.tmpl
3131
var shellrcText string
32-
var shellrcTmpl = template.Must(template.New("shellrc").Parse(shellrcText))
32+
var shellrcTmpl = template.Must(template.New("shellrc").Funcs(template.FuncMap{"dirPath": filepath.Dir}).Parse(shellrcText))
3333

3434
//go:embed shellrc_fish.tmpl
3535
var fishrcText string
@@ -228,8 +228,8 @@ func (s *DevboxShell) Run() error {
228228
return errors.WithStack(err)
229229
}
230230

231-
// Link other files that affect the shell settings and environments.
232-
s.linkShellStartupFiles(filepath.Dir(shellrc))
231+
// Setup other files that affect the shell settings and environments.
232+
s.setupShellStartupFiles(filepath.Dir(shellrc))
233233
extraEnv, extraArgs := s.shellRCOverrides(shellrc)
234234
env := s.env
235235
for k, v := range extraEnv {
@@ -323,6 +323,7 @@ func (s *DevboxShell) writeDevboxShellrc() (path string, err error) {
323323
ShellStartTime string
324324
HistoryFile string
325325
ExportEnv string
326+
ShellName string
326327

327328
RefreshAliasName string
328329
RefreshCmd string
@@ -335,6 +336,7 @@ func (s *DevboxShell) writeDevboxShellrc() (path string, err error) {
335336
ShellStartTime: telemetry.FormatShellStart(s.shellStartTime),
336337
HistoryFile: strings.TrimSpace(s.historyFile),
337338
ExportEnv: exportify(s.env),
339+
ShellName: string(s.name),
338340
RefreshAliasName: s.devbox.refreshAliasName(),
339341
RefreshCmd: s.devbox.refreshCmd(),
340342
RefreshAliasEnvVar: s.devbox.refreshAliasEnvVar(),
@@ -347,12 +349,13 @@ func (s *DevboxShell) writeDevboxShellrc() (path string, err error) {
347349
return path, nil
348350
}
349351

350-
// linkShellStartupFiles will link files used by the shell for initialization.
351-
// We choose to link instead of copy so that changes made outside can be reflected
352-
// within the devbox shell.
352+
// setupShellStartupFiles creates initialization files for the shell by sourcing the user's originals.
353+
// We do this instead of linking or copying, so that we can set correct ZDOTDIR when sourcing
354+
// user's config files which may use the ZDOTDIR env var inside them.
355+
// This also allows us to make sure any devbox config is run after correctly sourcing the user's config.
353356
//
354357
// We do not link the .{shell}rc files, since devbox modifies them. See writeDevboxShellrc
355-
func (s *DevboxShell) linkShellStartupFiles(shellSettingsDir string) {
358+
func (s *DevboxShell) setupShellStartupFiles(shellSettingsDir string) {
356359
// For now, we only need to do this for zsh shell
357360
if s.name == shZsh {
358361
// List of zsh startup files: https://zsh.sourceforge.io/Intro/intro_3.html
@@ -375,10 +378,41 @@ func (s *DevboxShell) linkShellStartupFiles(shellSettingsDir string) {
375378
}
376379

377380
fileNew := filepath.Join(shellSettingsDir, filename)
378-
cmd := exec.Command("cp", fileOld, fileNew)
379-
if err := cmd.Run(); err != nil {
380-
// This is a best-effort operation. If there's an error then log it for visibility but continue.
381-
slog.Error("error copying zsh setting file", "from", fileOld, "to", fileNew, "err", err)
381+
382+
// Create template content that sources the original file
383+
templateContent := `if [[ -f "{{.FileOld}}" ]]; then
384+
local OLD_ZDOTDIR="$ZDOTDIR"
385+
export ZDOTDIR="{{.ZDOTDIR}}"
386+
. "{{.FileOld}}"
387+
export ZDOTDIR="$OLD_ZDOTDIR"
388+
fi`
389+
390+
// Parse and execute the template
391+
tmpl, err := template.New("shellrc").Parse(templateContent)
392+
if err != nil {
393+
slog.Error("error parsing template for zsh setting file", "filename", filename, "err", err)
394+
continue
395+
}
396+
397+
// Create the new file with template content
398+
file, err := os.Create(fileNew)
399+
if err != nil {
400+
slog.Error("error creating zsh setting file", "filename", filename, "err", err)
401+
continue
402+
}
403+
defer file.Close()
404+
405+
// Execute template with data
406+
data := struct {
407+
FileOld string
408+
ZDOTDIR string
409+
}{
410+
FileOld: fileOld,
411+
ZDOTDIR: filepath.Dir(s.userShellrcPath),
412+
}
413+
414+
if err := tmpl.Execute(file, data); err != nil {
415+
slog.Error("error executing template for zsh setting file", "filename", filename, "err", err)
382416
continue
383417
}
384418
}

0 commit comments

Comments
 (0)