From d2f411431b495fc9ad222983c0bd454d224f22b6 Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Mon, 9 Feb 2026 11:01:31 -0500 Subject: [PATCH] fix(path): respect XDG_DATA_HOME on macOS and Windows ShimsDir(), install.sh, and install.ps1 were not checking XDG_DATA_HOME on macOS/Windows, causing dtvem init to add wrong paths to PATH when XDG_DATA_HOME was set. This brings all three locations in sync with the canonical getRootDir() logic in config/paths.go. Closes #202 --- install.ps1 | 5 +- install.sh | 10 ++- src/internal/path/path.go | 7 +- src/internal/path/path_test.go | 125 +++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/install.ps1 b/install.ps1 index 5295e5c..d2d7fde 100644 --- a/install.ps1 +++ b/install.ps1 @@ -12,11 +12,14 @@ $ErrorActionPreference = "Stop" $REPO = "CodingWithCalvin/dtvem.cli" # Get dtvem root directory -# Respects DTVEM_ROOT environment variable if set, otherwise uses default +# Respects DTVEM_ROOT environment variable if set, XDG_DATA_HOME if set, otherwise uses default function Get-DtvemRoot { if ($env:DTVEM_ROOT) { return $env:DTVEM_ROOT } + if ($env:XDG_DATA_HOME) { + return "$env:XDG_DATA_HOME\dtvem" + } return "$env:USERPROFILE\.dtvem" } diff --git a/install.sh b/install.sh index e2163c0..22a940a 100644 --- a/install.sh +++ b/install.sh @@ -53,7 +53,7 @@ warning() { # Get dtvem root directory # On Linux, respects XDG_DATA_HOME if set (defaults to ~/.local/share/dtvem) -# On macOS, uses ~/.dtvem +# On macOS, uses XDG_DATA_HOME if explicitly set (opt-in), otherwise ~/.dtvem get_dtvem_root() { # Check for DTVEM_ROOT environment variable first (overrides all) if [ -n "$DTVEM_ROOT" ]; then @@ -73,8 +73,12 @@ get_dtvem_root() { echo "$HOME/.local/share/dtvem" fi else - # macOS and others: use ~/.dtvem - echo "$HOME/.dtvem" + # macOS and others: use XDG_DATA_HOME if explicitly set (opt-in) + if [ -n "$XDG_DATA_HOME" ]; then + echo "$XDG_DATA_HOME/dtvem" + else + echo "$HOME/.dtvem" + fi fi } diff --git a/src/internal/path/path.go b/src/internal/path/path.go index 0b00775..6a5dc74 100644 --- a/src/internal/path/path.go +++ b/src/internal/path/path.go @@ -57,7 +57,12 @@ func ShimsDir() string { return filepath.Join(home, ".local", "share", "dtvem", "shims") } - // On macOS and Windows, use ~/.dtvem + // On macOS and Windows, use XDG_DATA_HOME if explicitly set (opt-in) + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return filepath.Join(xdgDataHome, "dtvem", "shims") + } + + // Default for macOS and Windows: ~/.dtvem return filepath.Join(home, ".dtvem", "shims") } diff --git a/src/internal/path/path_test.go b/src/internal/path/path_test.go index cfb570d..2a27ca1 100644 --- a/src/internal/path/path_test.go +++ b/src/internal/path/path_test.go @@ -109,6 +109,131 @@ func TestShimsDir(t *testing.T) { } } +func TestShimsDir_WithDTVEMROOT(t *testing.T) { + // Save original environment + originalRoot := os.Getenv("DTVEM_ROOT") + defer func() { + if originalRoot != "" { + _ = os.Setenv("DTVEM_ROOT", originalRoot) + } else { + _ = os.Unsetenv("DTVEM_ROOT") + } + }() + + // Set custom DTVEM_ROOT + customRoot := filepath.Join(os.TempDir(), "custom-dtvem-root") + _ = os.Setenv("DTVEM_ROOT", customRoot) + + result := ShimsDir() + expected := filepath.Join(customRoot, "shims") + if result != expected { + t.Errorf("ShimsDir() with DTVEM_ROOT=%q = %q, want %q", customRoot, result, expected) + } +} + +func TestShimsDir_NonLinux_WithXDG(t *testing.T) { + // On non-Linux platforms, verify that XDG_DATA_HOME is respected when set + if runtime.GOOS == constants.OSLinux { + t.Skip("This test only runs on non-Linux platforms") + } + + // Save original environment + originalRoot := os.Getenv("DTVEM_ROOT") + originalXDG := os.Getenv("XDG_DATA_HOME") + defer func() { + if originalRoot != "" { + _ = os.Setenv("DTVEM_ROOT", originalRoot) + } else { + _ = os.Unsetenv("DTVEM_ROOT") + } + if originalXDG != "" { + _ = os.Setenv("XDG_DATA_HOME", originalXDG) + } else { + _ = os.Unsetenv("XDG_DATA_HOME") + } + }() + + // Clear DTVEM_ROOT and set XDG_DATA_HOME + _ = os.Unsetenv("DTVEM_ROOT") + customXDG := filepath.Join(os.TempDir(), "custom-xdg-data") + _ = os.Setenv("XDG_DATA_HOME", customXDG) + + result := ShimsDir() + expected := filepath.Join(customXDG, "dtvem", "shims") + + if result != expected { + t.Errorf("ShimsDir() on %s should use XDG_DATA_HOME when set, got %q, want %q", + runtime.GOOS, result, expected) + } +} + +func TestShimsDir_NonLinux_WithoutXDG(t *testing.T) { + // On non-Linux platforms, verify that ~/.dtvem/shims is used when XDG_DATA_HOME is not set + if runtime.GOOS == constants.OSLinux { + t.Skip("This test only runs on non-Linux platforms") + } + + // Save original environment + originalRoot := os.Getenv("DTVEM_ROOT") + originalXDG := os.Getenv("XDG_DATA_HOME") + defer func() { + if originalRoot != "" { + _ = os.Setenv("DTVEM_ROOT", originalRoot) + } else { + _ = os.Unsetenv("DTVEM_ROOT") + } + if originalXDG != "" { + _ = os.Setenv("XDG_DATA_HOME", originalXDG) + } else { + _ = os.Unsetenv("XDG_DATA_HOME") + } + }() + + // Clear both DTVEM_ROOT and XDG_DATA_HOME + _ = os.Unsetenv("DTVEM_ROOT") + _ = os.Unsetenv("XDG_DATA_HOME") + + result := ShimsDir() + home, _ := os.UserHomeDir() + expected := filepath.Join(home, ".dtvem", "shims") + + if result != expected { + t.Errorf("ShimsDir() on %s without XDG_DATA_HOME should use ~/.dtvem/shims, got %q, want %q", + runtime.GOOS, result, expected) + } +} + +func TestShimsDir_DTVEMRootOverridesXDG(t *testing.T) { + // Verify that DTVEM_ROOT takes precedence over XDG_DATA_HOME + originalRoot := os.Getenv("DTVEM_ROOT") + originalXDG := os.Getenv("XDG_DATA_HOME") + defer func() { + if originalRoot != "" { + _ = os.Setenv("DTVEM_ROOT", originalRoot) + } else { + _ = os.Unsetenv("DTVEM_ROOT") + } + if originalXDG != "" { + _ = os.Setenv("XDG_DATA_HOME", originalXDG) + } else { + _ = os.Unsetenv("XDG_DATA_HOME") + } + }() + + // Set both DTVEM_ROOT and XDG_DATA_HOME + customRoot := filepath.Join(os.TempDir(), "custom-dtvem-root") + _ = os.Setenv("DTVEM_ROOT", customRoot) + _ = os.Setenv("XDG_DATA_HOME", filepath.Join(os.TempDir(), "should-be-ignored")) + + result := ShimsDir() + expected := filepath.Join(customRoot, "shims") + + if result != expected { + t.Errorf("ShimsDir() with DTVEM_ROOT set should return DTVEM_ROOT/shims, got %q, want %q", + result, expected) + } +} + func TestLookPathExcludingShims(t *testing.T) { originalPath := os.Getenv("PATH") defer func() { _ = os.Setenv("PATH", originalPath) }()