diff --git a/internal/devbox/docgen/docgen.go b/internal/devbox/docgen/docgen.go index 957a22f7821..7e338c4c38a 100644 --- a/internal/devbox/docgen/docgen.go +++ b/internal/devbox/docgen/docgen.go @@ -2,7 +2,9 @@ package docgen import ( _ "embed" + "maps" "os" + "strings" "text/template" "go.jetify.com/devbox/internal/devbox" @@ -55,13 +57,30 @@ func GenerateReadme( "Description": devbox.Config().Root.Description, "Scripts": devbox.Config().Scripts(). WithRelativePaths(devbox.ProjectDir()), - "EnvVars": devbox.Config().Env(), + "EnvVars": envWithRelativePaths(devbox.Config().Env(), devbox.ProjectDir()), "InitHook": devbox.Config().InitHook(), "Packages": devbox.TopLevelPackages(), // TODO add includes }) } +// envWithRelativePaths returns a copy of env where occurrences of the absolute +// project directory are replaced with ".". This keeps generated READMEs free of +// machine-specific absolute paths (such as a user's home directory) that plugins +// expand into environment variables like PGDATA and PGHOST. It mirrors the +// behavior of configfile.Scripts.WithRelativePaths, which is already applied to +// scripts in the generated README. +func envWithRelativePaths(env map[string]string, projectDir string) map[string]string { + if projectDir == "" { + return maps.Clone(env) + } + result := make(map[string]string, len(env)) + for key, value := range env { + result[key] = strings.ReplaceAll(value, projectDir, ".") + } + return result +} + func SaveDefaultReadmeTemplate(outputPath string) error { if outputPath == "" { outputPath = defaultTemplateName diff --git a/internal/devbox/docgen/docgen_test.go b/internal/devbox/docgen/docgen_test.go new file mode 100644 index 00000000000..9c312aed988 --- /dev/null +++ b/internal/devbox/docgen/docgen_test.go @@ -0,0 +1,44 @@ +package docgen + +import ( + "maps" + "testing" +) + +func TestEnvWithRelativePaths(t *testing.T) { + projectDir := "/home/user/myproject" + + t.Run("replaces project dir with relative path", func(t *testing.T) { + env := map[string]string{ + "PGDATA": projectDir + "/.devbox/virtenv/postgresql/data", + "PGHOST": projectDir + "/.devbox/virtenv/postgresql", + "PGPORT": "5432", + } + got := envWithRelativePaths(env, projectDir) + want := map[string]string{ + "PGDATA": "./.devbox/virtenv/postgresql/data", + "PGHOST": "./.devbox/virtenv/postgresql", + "PGPORT": "5432", + } + if !maps.Equal(got, want) { + t.Errorf("envWithRelativePaths() = %v, want %v", got, want) + } + }) + + t.Run("does not mutate the input map", func(t *testing.T) { + original := projectDir + "/.devbox/virtenv/postgresql" + env := map[string]string{"PGHOST": original} + envWithRelativePaths(env, projectDir) + if env["PGHOST"] != original { + t.Errorf("input map was mutated: PGHOST = %q, want %q", env["PGHOST"], original) + } + }) + + t.Run("empty project dir returns env unchanged", func(t *testing.T) { + env := map[string]string{"PGHOST": "/some/abs/path"} + got := envWithRelativePaths(env, "") + if !maps.Equal(got, env) { + t.Errorf("envWithRelativePaths() = %v, want %v", got, env) + } + }) +}