From 30fffbe60ef069d78aa4596605b3cddc4fd91b78 Mon Sep 17 00:00:00 2001 From: AdamDrewsTR Date: Wed, 13 May 2026 14:27:21 -0500 Subject: [PATCH 1/4] Remove stale goenv shims from v2 installations during upgrade to v3 --- cmd/root.go | 14 +++++++++++++ install.ps1 | 47 ++++++++++++++++++++++++++++++-------------- install.sh | 11 +++++++++++ scripts/swap/main.go | 19 ++++++++++++++++++ 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 164c183b..9eaa07ba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "path/filepath" "strings" "github.com/go-nv/goenv/internal/cmdutil" @@ -62,6 +63,19 @@ var RootCmd = &cobra.Command{ // Store updated context back to command cmd.SetContext(ctx) + // Remove stale goenv shim left over from v2. + // v2's goenv-rehash bakes the Homebrew Cellar path into shims at creation + // time (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv"). + // After upgrading to v3, the old shim may still point to a deleted Cellar + // path, shadowing the real v3 binary. We only remove it if it contains + // "libexec/goenv" — the v2 fingerprint — to avoid deleting anything unexpected. + goenvShim := filepath.Join(cfg.ShimsDir(), "goenv") + if data, err := os.ReadFile(goenvShim); err == nil { + if strings.Contains(string(data), "libexec/goenv") { + _ = os.Remove(goenvShim) + } + } + // Propagate output options utils.SetOutputOptions(NoColor, Plain) }, diff --git a/install.ps1 b/install.ps1 index 609726d4..8ede1153 100644 --- a/install.ps1 +++ b/install.ps1 @@ -3,19 +3,23 @@ $ErrorActionPreference = "Stop" +# Ensure TLS 1.2 for GitHub API/downloads (older Windows defaults to TLS 1.0) +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + # Configuration -$GOENV_ROOT = if ($env:GOENV_ROOT) { $env:GOENV_ROOT } else { "$HOME\.goenv" } +$GOENV_ROOT = if ($env:GOENV_ROOT) { $env:GOENV_ROOT } else { Join-Path (if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME }) ".goenv" } $GITHUB_REPO = "go-nv/goenv" -$INSTALL_DIR = "$GOENV_ROOT\bin" +$INSTALL_DIR = Join-Path $GOENV_ROOT "bin" -# Colors -function Write-ColorOutput($ForegroundColor) { - $fc = $host.UI.RawUI.ForegroundColor - $host.UI.RawUI.ForegroundColor = $ForegroundColor - if ($args) { - Write-Output $args - } - $host.UI.RawUI.ForegroundColor = $fc +# Colors — use Write-Host which works in non-interactive/piped contexts +function Write-ColorOutput { + param( + [Parameter(Position=0)] + [System.ConsoleColor]$ForegroundColor, + [Parameter(Position=1, ValueFromRemainingArguments)] + [string[]]$Message + ) + Write-Host ($Message -join ' ') -ForegroundColor $ForegroundColor } # Detect architecture @@ -85,7 +89,7 @@ function Install-Binary { # Copy binary $binaryPath = Join-Path $tmpDir "goenv.exe" if (Test-Path $binaryPath) { - Copy-Item -Path $binaryPath -Destination "$INSTALL_DIR\goenv.exe" -Force + Copy-Item -Path $binaryPath -Destination (Join-Path $INSTALL_DIR "goenv.exe") -Force } else { throw "Binary not found in archive" } @@ -93,19 +97,32 @@ function Install-Binary { # Copy completions if they exist $completionsPath = Join-Path $tmpDir "completions" if (Test-Path $completionsPath) { - $targetCompletions = "$GOENV_ROOT\completions" + $targetCompletions = Join-Path $GOENV_ROOT "completions" New-Item -ItemType Directory -Path $targetCompletions -Force | Out-Null Copy-Item -Path "$completionsPath\*" -Destination $targetCompletions -Recurse -Force -ErrorAction SilentlyContinue } Write-ColorOutput Green "goenv installed successfully!" + + # Remove stale goenv shim from v2 installations. + # v2's goenv-rehash bakes the Cellar/libexec path into shims at creation time. + # Only remove if it contains "libexec/goenv" — the v2 fingerprint. + $staleShim = Join-Path (Join-Path $GOENV_ROOT "shims") "goenv" + if (Test-Path $staleShim) { + $shimContent = Get-Content $staleShim -Raw -ErrorAction SilentlyContinue + if ($shimContent -and $shimContent -match "libexec/goenv") { + Write-ColorOutput Yellow "Removing stale v2 goenv shim..." + Remove-Item -Path $staleShim -Force -ErrorAction SilentlyContinue + Write-ColorOutput Green "Stale shim removed" + } + } } catch { Write-ColorOutput Red "Installation failed: $_" exit 1 } finally { - # Cleanup + # Cleanup temp directory if (Test-Path $tmpDir) { Remove-Item -Path $tmpDir -Recurse -Force -ErrorAction SilentlyContinue } @@ -113,7 +130,7 @@ function Install-Binary { } # Auto-configure PowerShell profile -function Setup-PowerShellProfile { +function Initialize-PowerShellProfile { $profilePath = $PROFILE # Create profile directory if it doesn't exist @@ -184,7 +201,7 @@ function Main { $version = Get-LatestVersion Install-Binary -Version $version -Arch $arch - Setup-PowerShellProfile + Initialize-PowerShellProfile Show-Instructions } diff --git a/install.sh b/install.sh index 93d36330..b5c350d5 100755 --- a/install.sh +++ b/install.sh @@ -122,6 +122,17 @@ install_binary() { # Cleanup rm -rf "$tmp_dir" + # Remove stale goenv shim from v2 installations. + # v2's goenv-rehash bakes the Homebrew Cellar path into shims at creation time + # (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv"). After + # upgrading to v3, the old shim shadows the real v3 binary. We only remove + # it if it contains "libexec/goenv" — the v2 fingerprint. + if [ -f "$GOENV_ROOT/shims/goenv" ] && grep -q 'libexec/goenv' "$GOENV_ROOT/shims/goenv" 2>/dev/null; then + echo -e "${YELLOW}Removing stale v2 goenv shim...${NC}" + rm -f "$GOENV_ROOT/shims/goenv" + echo -e "${GREEN}✓ Stale shim removed${NC}" + fi + echo -e "${GREEN}✓ goenv installed successfully!${NC}" } diff --git a/scripts/swap/main.go b/scripts/swap/main.go index 58afdda0..9edfe25f 100644 --- a/scripts/swap/main.go +++ b/scripts/swap/main.go @@ -7,6 +7,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strings" "github.com/go-nv/goenv/internal/platform" "github.com/go-nv/goenv/internal/utils" @@ -407,6 +408,24 @@ Options: swapGoenvBinary(target) } + // Remove stale goenv shim from v2 that may shadow the new binary. + // Only remove if it contains "libexec/goenv" — the v2 fingerprint. + goenvRoot := os.Getenv("GOENV_ROOT") + if goenvRoot == "" { + homeDir, _ := os.UserHomeDir() + goenvRoot = filepath.Join(homeDir, ".goenv") + } + staleShim := filepath.Join(goenvRoot, "shims", "goenv") + if data, err := os.ReadFile(staleShim); err == nil { + if strings.Contains(string(data), "libexec/goenv") { + if err := os.Remove(staleShim); err == nil { + success("Removed stale v2 goenv shim from " + staleShim) + } else { + warn(fmt.Sprintf("Could not remove stale shim %s: %v", staleShim, err)) + } + } + } + fmt.Println() success("Switch successful!") warn("IMPORTANT: Reload your shell before testing:") From 8f0fb137f948bb8404f349c79397f7a73f5bfe32 Mon Sep 17 00:00:00 2001 From: "Stathi C." Date: Tue, 19 May 2026 09:50:23 -0500 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- cmd/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 9eaa07ba..de95e734 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,7 +72,9 @@ var RootCmd = &cobra.Command{ goenvShim := filepath.Join(cfg.ShimsDir(), "goenv") if data, err := os.ReadFile(goenvShim); err == nil { if strings.Contains(string(data), "libexec/goenv") { - _ = os.Remove(goenvShim) + if err := os.Remove(goenvShim); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "warning: failed to remove stale v2 goenv shim %q: %v\n", goenvShim, err) + } } } From 5356010cf6abda3a7e5d52cbb97b48d2885eae56 Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 May 2026 10:01:34 -0500 Subject: [PATCH 3/4] consolidate duplicated logic into helper funcs and add tests --- cmd/root.go | 19 +--- install.ps1 | 20 ++-- install.sh | 20 ++-- internal/migration/v2shim.go | 48 +++++++++ internal/migration/v2shim_test.go | 171 ++++++++++++++++++++++++++++++ scripts/swap/main.go | 18 ++-- 6 files changed, 256 insertions(+), 40 deletions(-) create mode 100644 internal/migration/v2shim.go create mode 100644 internal/migration/v2shim_test.go diff --git a/cmd/root.go b/cmd/root.go index de95e734..a75fad12 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,12 +3,12 @@ package cmd import ( "fmt" "os" - "path/filepath" "strings" "github.com/go-nv/goenv/internal/cmdutil" "github.com/go-nv/goenv/internal/config" "github.com/go-nv/goenv/internal/manager" + "github.com/go-nv/goenv/internal/migration" "github.com/go-nv/goenv/internal/utils" "github.com/go-nv/goenv/internal/vscode" "github.com/go-nv/goenv/internal/workflow" @@ -63,19 +63,10 @@ var RootCmd = &cobra.Command{ // Store updated context back to command cmd.SetContext(ctx) - // Remove stale goenv shim left over from v2. - // v2's goenv-rehash bakes the Homebrew Cellar path into shims at creation - // time (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv"). - // After upgrading to v3, the old shim may still point to a deleted Cellar - // path, shadowing the real v3 binary. We only remove it if it contains - // "libexec/goenv" — the v2 fingerprint — to avoid deleting anything unexpected. - goenvShim := filepath.Join(cfg.ShimsDir(), "goenv") - if data, err := os.ReadFile(goenvShim); err == nil { - if strings.Contains(string(data), "libexec/goenv") { - if err := os.Remove(goenvShim); err != nil { - fmt.Fprintf(cmd.ErrOrStderr(), "warning: failed to remove stale v2 goenv shim %q: %v\n", goenvShim, err) - } - } + // Remove stale goenv shim left over from v2 installations. + // Uses helper function to handle both forward and backslash paths. + if _, err := migration.RemoveStaleV2Shim(cfg.ShimsDir()); err != nil { + fmt.Fprintf(cmd.ErrOrStderr(), "warning: %v\n", err) } // Propagate output options diff --git a/install.ps1 b/install.ps1 index 8ede1153..40a82b83 100644 --- a/install.ps1 +++ b/install.ps1 @@ -7,7 +7,9 @@ $ErrorActionPreference = "Stop" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Configuration -$GOENV_ROOT = if ($env:GOENV_ROOT) { $env:GOENV_ROOT } else { Join-Path (if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME }) ".goenv" } +# Use if/else instead of ?? operator for PowerShell 5.1 compatibility +$defaultHome = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME } +$GOENV_ROOT = if ($env:GOENV_ROOT) { $env:GOENV_ROOT } else { Join-Path $defaultHome ".goenv" } $GITHUB_REPO = "go-nv/goenv" $INSTALL_DIR = Join-Path $GOENV_ROOT "bin" @@ -106,14 +108,20 @@ function Install-Binary { # Remove stale goenv shim from v2 installations. # v2's goenv-rehash bakes the Cellar/libexec path into shims at creation time. - # Only remove if it contains "libexec/goenv" — the v2 fingerprint. - $staleShim = Join-Path (Join-Path $GOENV_ROOT "shims") "goenv" + # Match both forward and backslashes to support all v2 installations. + # Use nested Join-Path for PowerShell 5.1 compatibility (no 3-arg support). + $shimsPath = Join-Path $GOENV_ROOT "shims" + $staleShim = Join-Path $shimsPath "goenv" if (Test-Path $staleShim) { $shimContent = Get-Content $staleShim -Raw -ErrorAction SilentlyContinue - if ($shimContent -and $shimContent -match "libexec/goenv") { + if ($shimContent -and $shimContent -match "libexec[\\/]goenv") { Write-ColorOutput Yellow "Removing stale v2 goenv shim..." - Remove-Item -Path $staleShim -Force -ErrorAction SilentlyContinue - Write-ColorOutput Green "Stale shim removed" + $removed = Remove-Item -Path $staleShim -Force -ErrorAction SilentlyContinue -PassThru + if ($removed) { + Write-ColorOutput Green "Stale shim removed" + } else { + Write-ColorOutput Yellow "Warning: Failed to remove stale v2 goenv shim" + } } } } diff --git a/install.sh b/install.sh index b5c350d5..c504d42a 100755 --- a/install.sh +++ b/install.sh @@ -123,15 +123,17 @@ install_binary() { rm -rf "$tmp_dir" # Remove stale goenv shim from v2 installations. - # v2's goenv-rehash bakes the Homebrew Cellar path into shims at creation time - # (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv"). After - # upgrading to v3, the old shim shadows the real v3 binary. We only remove - # it if it contains "libexec/goenv" — the v2 fingerprint. - if [ -f "$GOENV_ROOT/shims/goenv" ] && grep -q 'libexec/goenv' "$GOENV_ROOT/shims/goenv" 2>/dev/null; then - echo -e "${YELLOW}Removing stale v2 goenv shim...${NC}" - rm -f "$GOENV_ROOT/shims/goenv" - echo -e "${GREEN}✓ Stale shim removed${NC}" - fi + # v2's goenv-rehash bakes the Cellar/libexec path into shims at creation time + # (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv"). After + # upgrading to v3, the old shim shadows the real v3 binary. We only remove + # it if it contains "libexec/goenv" or "libexec\goenv" — the v2 fingerprint. + if [ -f "$GOENV_ROOT/shims/goenv" ] && grep -qE 'libexec[/\\]goenv' "$GOENV_ROOT/shims/goenv" 2>/dev/null; then + echo -e "${YELLOW}Removing stale v2 goenv shim...${NC}" + if rm -f "$GOENV_ROOT/shims/goenv" 2>/dev/null; then + echo -e "${GREEN}✓ Stale shim removed${NC}" + else + echo -e "${YELLOW}⚠ Warning: Failed to remove stale v2 goenv shim${NC}" + fi echo -e "${GREEN}✓ goenv installed successfully!${NC}" } diff --git a/internal/migration/v2shim.go b/internal/migration/v2shim.go new file mode 100644 index 00000000..5d943136 --- /dev/null +++ b/internal/migration/v2shim.go @@ -0,0 +1,48 @@ +package migration + +import ( + "fmt" + "os" + "path/filepath" + "regexp" +) + +// V2ShimPattern matches v2's libexec/goenv path with both forward slashes and backslashes. +// The pattern ensures we match "libexec/goenv" or "libexec\goenv" where "goenv" is the final +// path component (followed by quote, space, or directory separator, but not a hyphen or letter). +var V2ShimPattern = regexp.MustCompile(`libexec[\\/]goenv(["'\s/\\]|$)`) + +// RemoveStaleV2Shim removes the stale goenv shim left over from v2 installations. +// v2's goenv-rehash bakes the Homebrew Cellar path into shims at creation time +// (e.g. exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" on macOS/Linux +// or "C:\path\to\libexec\goenv" on Windows). After upgrading to v3, the old shim +// may still point to a deleted path, shadowing the real v3 binary. +// +// We only remove the shim if it contains "libexec/goenv" or "libexec\goenv" — +// the v2 fingerprint — to avoid deleting anything unexpected. +// +// Returns true if the shim was removed, false otherwise. +// If removal fails, it returns an error that can be logged as a warning. +func RemoveStaleV2Shim(shimsDir string) (bool, error) { + goenvShim := filepath.Join(shimsDir, "goenv") + + // Read the shim file + data, err := os.ReadFile(goenvShim) + if err != nil { + // File doesn't exist or can't be read - nothing to remove + return false, nil + } + + // Check if it contains the v2 fingerprint (supports both / and \) + if !V2ShimPattern.Match(data) { + // Not a v2 shim - leave it alone + return false, nil + } + + // Remove the stale shim + if err := os.Remove(goenvShim); err != nil { + return false, fmt.Errorf("failed to remove stale v2 goenv shim %q: %w", goenvShim, err) + } + + return true, nil +} diff --git a/internal/migration/v2shim_test.go b/internal/migration/v2shim_test.go new file mode 100644 index 00000000..a8ac03c9 --- /dev/null +++ b/internal/migration/v2shim_test.go @@ -0,0 +1,171 @@ +package migration + +import ( + "os" + "path/filepath" + "testing" +) + +func TestRemoveStaleV2Shim(t *testing.T) { + // Create a temporary directory for testing + tmpDir, err := os.MkdirTemp("", "goenv-test-*") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + shimsDir := filepath.Join(tmpDir, "shims") + if err := os.MkdirAll(shimsDir, 0755); err != nil { + t.Fatalf("Failed to create shims dir: %v", err) + } + + t.Run("removes v2 shim with forward slash", func(t *testing.T) { + // Create a v2 shim with forward slash + shimPath := filepath.Join(shimsDir, "goenv") + v2Content := `#!/usr/bin/env bash +exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" "$@" +` + if err := os.WriteFile(shimPath, []byte(v2Content), 0755); err != nil { + t.Fatalf("Failed to create shim: %v", err) + } + + removed, err := RemoveStaleV2Shim(shimsDir) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !removed { + t.Error("Expected shim to be removed") + } + if _, err := os.Stat(shimPath); !os.IsNotExist(err) { + t.Error("Shim file should not exist") + } + }) + + t.Run("removes v2 shim with backslash", func(t *testing.T) { + // Create a v2 shim with backslash (Windows-style) + shimPath := filepath.Join(shimsDir, "goenv") + v2Content := `@echo off +"C:\Program Files\goenv\libexec\goenv" %* +` + if err := os.WriteFile(shimPath, []byte(v2Content), 0755); err != nil { + t.Fatalf("Failed to create shim: %v", err) + } + + removed, err := RemoveStaleV2Shim(shimsDir) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if !removed { + t.Error("Expected shim to be removed") + } + if _, err := os.Stat(shimPath); !os.IsNotExist(err) { + t.Error("Shim file should not exist") + } + }) + + t.Run("does not remove non-v2 shim", func(t *testing.T) { + // Create a non-v2 shim + shimPath := filepath.Join(shimsDir, "goenv") + v3Content := `#!/usr/bin/env bash +# This is a v3 shim +exec "goenv" "$@" +` + if err := os.WriteFile(shimPath, []byte(v3Content), 0755); err != nil { + t.Fatalf("Failed to create shim: %v", err) + } + + removed, err := RemoveStaleV2Shim(shimsDir) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if removed { + t.Error("Expected shim to NOT be removed") + } + if _, err := os.Stat(shimPath); os.IsNotExist(err) { + t.Error("Shim file should still exist") + } + }) + + t.Run("handles missing shim file", func(t *testing.T) { + // Ensure no shim exists + shimPath := filepath.Join(shimsDir, "goenv") + os.Remove(shimPath) + + removed, err := RemoveStaleV2Shim(shimsDir) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if removed { + t.Error("Expected no removal when file doesn't exist") + } + }) + + t.Run("returns error when removal fails", func(t *testing.T) { + // This test is platform-dependent and may not work in all environments + // Skip if we can't create a read-only directory + shimPath := filepath.Join(shimsDir, "goenv") + v2Content := `#!/usr/bin/env bash +exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" "$@" +` + if err := os.WriteFile(shimPath, []byte(v2Content), 0755); err != nil { + t.Fatalf("Failed to create shim: %v", err) + } + + // Make directory read-only (Unix-like systems) + if err := os.Chmod(shimsDir, 0555); err != nil { + t.Skip("Cannot test permission error on this platform") + } + defer os.Chmod(shimsDir, 0755) // Restore permissions + + removed, err := RemoveStaleV2Shim(shimsDir) + if err == nil { + t.Error("Expected error when removal fails") + } + if removed { + t.Error("Should not report as removed when error occurs") + } + }) +} + +func TestV2ShimPattern(t *testing.T) { + tests := []struct { + name string + content string + matches bool + }{ + { + name: "forward slash", + content: `exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" "$@"`, + matches: true, + }, + { + name: "backslash", + content: `"C:\Program Files\goenv\libexec\goenv" %*`, + matches: true, + }, + { + name: "no match", + content: `exec "goenv" "$@"`, + matches: false, + }, + { + name: "libexec only", + content: `exec "/usr/local/libexec/goenv-other" "$@"`, + matches: false, + }, + { + name: "goenv only", + content: `exec "/usr/local/bin/goenv" "$@"`, + matches: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matches := V2ShimPattern.MatchString(tt.content) + if matches != tt.matches { + t.Errorf("Expected match=%v, got match=%v for content: %s", tt.matches, matches, tt.content) + } + }) + } +} diff --git a/scripts/swap/main.go b/scripts/swap/main.go index 9edfe25f..1df256a8 100644 --- a/scripts/swap/main.go +++ b/scripts/swap/main.go @@ -7,8 +7,8 @@ import ( "os/exec" "path/filepath" "runtime" - "strings" + "github.com/go-nv/goenv/internal/migration" "github.com/go-nv/goenv/internal/platform" "github.com/go-nv/goenv/internal/utils" ) @@ -409,21 +409,17 @@ Options: } // Remove stale goenv shim from v2 that may shadow the new binary. - // Only remove if it contains "libexec/goenv" — the v2 fingerprint. + // Uses helper function to handle both forward and backslash paths. goenvRoot := os.Getenv("GOENV_ROOT") if goenvRoot == "" { homeDir, _ := os.UserHomeDir() goenvRoot = filepath.Join(homeDir, ".goenv") } - staleShim := filepath.Join(goenvRoot, "shims", "goenv") - if data, err := os.ReadFile(staleShim); err == nil { - if strings.Contains(string(data), "libexec/goenv") { - if err := os.Remove(staleShim); err == nil { - success("Removed stale v2 goenv shim from " + staleShim) - } else { - warn(fmt.Sprintf("Could not remove stale shim %s: %v", staleShim, err)) - } - } + shimsDir := filepath.Join(goenvRoot, "shims") + if removed, err := migration.RemoveStaleV2Shim(shimsDir); err != nil { + warn(err.Error()) + } else if removed { + success("Removed stale v2 goenv shim from " + filepath.Join(shimsDir, "goenv")) } fmt.Println() From f792f2b97043eed672d164371d6b9b9253ba591d Mon Sep 17 00:00:00 2001 From: Efstathios Chouliaris Date: Tue, 19 May 2026 10:06:46 -0500 Subject: [PATCH 4/4] fix windows test failure --- internal/migration/v2shim_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/migration/v2shim_test.go b/internal/migration/v2shim_test.go index a8ac03c9..4c71c609 100644 --- a/internal/migration/v2shim_test.go +++ b/internal/migration/v2shim_test.go @@ -3,6 +3,7 @@ package migration import ( "os" "path/filepath" + "runtime" "testing" ) @@ -101,8 +102,11 @@ exec "goenv" "$@" }) t.Run("returns error when removal fails", func(t *testing.T) { - // This test is platform-dependent and may not work in all environments - // Skip if we can't create a read-only directory + // Skip on Windows - Unix permission model doesn't apply + if runtime.GOOS == "windows" { + t.Skip("Skipping permission test on Windows - different permission model") + } + shimPath := filepath.Join(shimsDir, "goenv") v2Content := `#!/usr/bin/env bash exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" "$@" @@ -111,9 +115,9 @@ exec "/opt/homebrew/Cellar/goenv/2.2.38_1/libexec/goenv" "$@" t.Fatalf("Failed to create shim: %v", err) } - // Make directory read-only (Unix-like systems) + // Make directory read-only (Unix-like systems only) if err := os.Chmod(shimsDir, 0555); err != nil { - t.Skip("Cannot test permission error on this platform") + t.Skipf("Cannot test permission error: %v", err) } defer os.Chmod(shimsDir, 0755) // Restore permissions