diff --git a/internal/cmd/upgradecmd.go b/internal/cmd/upgradecmd.go index 28b8220a10fa..c48ffd2798a5 100644 --- a/internal/cmd/upgradecmd.go +++ b/internal/cmd/upgradecmd.go @@ -15,6 +15,7 @@ import ( "net/http" "os" "os/exec" + "path/filepath" "regexp" "runtime" "strings" @@ -545,8 +546,12 @@ func getUpgradeMethod(fileSystem vfs.Stater, executableAbsPath chezmoi.AbsPath) return upgradeMethodBrewUpgrade, nil case runtime.GOOS == "linux" && strings.Contains(executableAbsPath.String(), "/.linuxbrew/"): return upgradeMethodBrewUpgrade, nil - case runtime.GOOS == "windows" && strings.Contains(executableAbsPath.ToSlash().String(), "/WinGet/"): - return upgradeMethodWingetUpgrade, nil + case runtime.GOOS == "windows": + if ok, err := isWinGetInstall(fileSystem, executableAbsPath.String()); err != nil { + return "", err + } else if ok { + return upgradeMethodWingetUpgrade, nil + } } // If the executable is in the user's home directory, then always use @@ -646,3 +651,66 @@ func getReleaseAssetByName(rr *github.RepositoryRelease, name string) *github.Re } return nil } + +type InstallBehavior struct { + PortablePackageUserRoot string `json:"portablePackageUserRoot"` + PortablePackageMachineRoot string `json:"portablePackageMachineRoot"` + DefaultInstallRoot string `json:"defaultInstallRoot"` +} + +func (ib *InstallBehavior) Values() []string { + return []string{ + ib.PortablePackageUserRoot, + ib.PortablePackageMachineRoot, + ib.DefaultInstallRoot, + } +} + +type WinGetSettings struct { + InstallBehavior InstallBehavior `json:"installBehavior"` +} + +// isWinGetInstall determines if executableAbsPath contains a WinGet installation path. +func isWinGetInstall(fileSystem vfs.Stater, executableAbsPath string) (bool, error) { + realExecutableAbsPath := executableAbsPath + fi, err := os.Lstat(executableAbsPath) + if err != nil { + return false, err + } + if fi.Mode().Type() == os.ModeSymlink { + realExecutableAbsPath, err = os.Readlink(executableAbsPath) + if err != nil { + return false, err + } + } + winGetSettings := WinGetSettings{ + InstallBehavior: InstallBehavior{ + PortablePackageUserRoot: os.ExpandEnv(`${LOCALAPPDATA}\Microsoft\WinGet\Packages\`), + PortablePackageMachineRoot: os.ExpandEnv(`${PROGRAMFILES}\WinGet\Packages\`), + }, + } + settingsPaths := []string{ + os.ExpandEnv(`${LOCALAPPDATA}\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json`), + os.ExpandEnv(`${LOCALAPPDATA}\Microsoft\WinGet\Settings\settings.json`), + } + for _, settingsPath := range settingsPaths { + if _, err := os.Stat(settingsPath); err == nil { + winGetSettingsContents, err := os.ReadFile(settingsPath) + if err == nil { + if err := chezmoi.FormatJSONC.Unmarshal([]byte(winGetSettingsContents), &winGetSettings); err != nil { + return false, err + } + } + } + } + for _, path := range winGetSettings.InstallBehavior.Values() { + path = filepath.Clean(path) + if path == "." { + continue + } + if ok, _ := vfs.Contains(fileSystem, realExecutableAbsPath, path); ok { + return true, nil + } + } + return false, nil +}