diff --git a/.github/workflows/test-update-instructions.yml b/.github/workflows/test-update-instructions.yml index b969535d..ef82306d 100644 --- a/.github/workflows/test-update-instructions.yml +++ b/.github/workflows/test-update-instructions.yml @@ -115,3 +115,22 @@ jobs: output=$($HOME/.npm-global/lib/node_modules/@infisical/cli/bin/update-hint) echo "Output: $output" echo "$output" | grep -q "npm update -g @infisical/cli" || (echo "Expected npm instruction, got: $output" && exit 1) + + # The runner has apt-get, so without install-source detection this falls + # through to the apt instruction. Asserting empty output is what makes + # this a real regression test for the nix case. + - name: Test nix path detection + run: | + mkdir -p $HOME/nix/store/abc123-infisical-0.0.1/bin + cp update-hint $HOME/nix/store/abc123-infisical-0.0.1/bin/update-hint + output=$($HOME/nix/store/abc123-infisical-0.0.1/bin/update-hint) + echo "Output: $output" + [ -z "$output" ] || (echo "Expected no instruction for nix, got: $output" && exit 1) + + - name: Test linuxbrew path detection + run: | + mkdir -p $HOME/linuxbrew/.linuxbrew/bin + cp update-hint $HOME/linuxbrew/.linuxbrew/bin/update-hint + output=$($HOME/linuxbrew/.linuxbrew/bin/update-hint) + echo "Output: $output" + echo "$output" | grep -q "brew update" || (echo "Expected brew instruction, got: $output" && exit 1) diff --git a/packages/util/check-for-update.go b/packages/util/check-for-update.go index dc90713b..01ba0702 100644 --- a/packages/util/check-for-update.go +++ b/packages/util/check-for-update.go @@ -407,6 +407,19 @@ func getUpdateInstructions(goos string, execPath string) string { if isNpm { return "To update, run: npm update -g @infisical/cli" } + // Nix-managed binaries live in the immutable store. The system package + // manager did not install them and cannot update them, so stay quiet + // rather than print a command that will not work. + if strings.Contains(p, "/nix/store/") { + return "" + } + // Trailing slash so this matches the linuxbrew prefix directory in both + // the multi-user (/home/linuxbrew/.linuxbrew) and single-user + // (~/.linuxbrew) layouts, without classifying unrelated paths such as + // /opt/linuxbrew-tools as a Homebrew install. + if strings.Contains(p, "linuxbrew/") { + return "To update, run: brew update && brew upgrade infisical" + } pkgManager := getLinuxPackageManager() switch pkgManager { case "apt-get": diff --git a/packages/util/check-for-update_test.go b/packages/util/check-for-update_test.go index f460118f..8a09e8e9 100644 --- a/packages/util/check-for-update_test.go +++ b/packages/util/check-for-update_test.go @@ -23,7 +23,8 @@ func TestGetUpdateInstructions(t *testing.T) { goos string execPath string expected string - expectEmpty bool // true means assert empty result (vs. skipping runtime-dependent cases) + expectEmpty bool // true means assert empty result (vs. skipping runtime-dependent cases) + notExpected string // assert the result does NOT contain this, for paths that must not match a detector }{ // darwin { @@ -96,6 +97,30 @@ func TestGetUpdateInstructions(t *testing.T) { execPath: "/home/user/.npm-global/lib/node_modules/@infisical/cli/bin/infisical", expected: "npm update -g @infisical/cli", }, + { + name: "linux nix returns empty", + goos: "linux", + execPath: "/nix/store/abc123-infisical-0.41.90/bin/infisical", + expectEmpty: true, + }, + { + name: "linux linuxbrew", + goos: "linux", + execPath: "/home/linuxbrew/.linuxbrew/bin/infisical", + expected: "brew update && brew upgrade infisical", + }, + { + name: "linux linuxbrew single-user prefix", + goos: "linux", + execPath: "/home/user/.linuxbrew/bin/infisical", + expected: "brew update && brew upgrade infisical", + }, + { + name: "linux linuxbrew-tools is not a brew install", + goos: "linux", + execPath: "/opt/linuxbrew-tools/bin/infisical", + notExpected: "brew", + }, } for _, tt := range tests { @@ -107,6 +132,12 @@ func TestGetUpdateInstructions(t *testing.T) { } return } + if tt.notExpected != "" { + if strings.Contains(result, tt.notExpected) { + t.Errorf("expected %q not to contain %q", result, tt.notExpected) + } + return + } if tt.expected == "" { return // skip runtime-dependent cases }