From 7f5cf5fdad294d80a3fa715437c7169da940e000 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 14:15:46 +0000 Subject: [PATCH 1/3] fix(generate): use relative paths for env vars in generated README devbox generate readme rendered plugin-provided environment variables (e.g. PGDATA, PGHOST from the postgresql plugin) with their fully expanded absolute paths, leaking machine-specific paths such as the user's home directory into the committed README. Replace the absolute project directory with "." in env var values, mirroring the existing Scripts.WithRelativePaths behavior already applied to scripts in the generated README. Fixes #2178 --- internal/devbox/docgen/docgen.go | 20 +++++++++++- internal/devbox/docgen/docgen_test.go | 44 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 internal/devbox/docgen/docgen_test.go diff --git a/internal/devbox/docgen/docgen.go b/internal/devbox/docgen/docgen.go index 957a22f7821..e0d715ba7b7 100644 --- a/internal/devbox/docgen/docgen.go +++ b/internal/devbox/docgen/docgen.go @@ -3,6 +3,7 @@ package docgen import ( _ "embed" "os" + "strings" "text/template" "go.jetify.com/devbox/internal/devbox" @@ -55,13 +56,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 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..3c5abff11de --- /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/carloratm/ooo/oha" + + 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) + } + }) +} From ee7a0101ea11d538bf2b7e77a0a1e859f42d31e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 14:20:47 +0000 Subject: [PATCH 2/3] docgen: always return a copy from envWithRelativePaths; genericize test data Address review feedback: - envWithRelativePaths now returns an independent copy even when projectDir is empty (via maps.Clone), matching its doc comment so callers can always mutate the result safely. - Use a generic project path in the test fixture instead of a real-looking username. --- internal/devbox/docgen/docgen.go | 3 ++- internal/devbox/docgen/docgen_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/devbox/docgen/docgen.go b/internal/devbox/docgen/docgen.go index e0d715ba7b7..7e338c4c38a 100644 --- a/internal/devbox/docgen/docgen.go +++ b/internal/devbox/docgen/docgen.go @@ -2,6 +2,7 @@ package docgen import ( _ "embed" + "maps" "os" "strings" "text/template" @@ -71,7 +72,7 @@ func GenerateReadme( // scripts in the generated README. func envWithRelativePaths(env map[string]string, projectDir string) map[string]string { if projectDir == "" { - return env + return maps.Clone(env) } result := make(map[string]string, len(env)) for key, value := range env { diff --git a/internal/devbox/docgen/docgen_test.go b/internal/devbox/docgen/docgen_test.go index 3c5abff11de..9c312aed988 100644 --- a/internal/devbox/docgen/docgen_test.go +++ b/internal/devbox/docgen/docgen_test.go @@ -6,7 +6,7 @@ import ( ) func TestEnvWithRelativePaths(t *testing.T) { - projectDir := "/home/carloratm/ooo/oha" + projectDir := "/home/user/myproject" t.Run("replaces project dir with relative path", func(t *testing.T) { env := map[string]string{ From 1b76ee240d7b5049ed317237d670d6dfa129f4e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 14:36:10 +0000 Subject: [PATCH 3/3] ci: re-trigger checks (flaky unrelated elixir example test)