diff --git a/internal/update/update.go b/internal/update/update.go index 66cd94d..2350c62 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -208,28 +208,51 @@ func detectInstallFromExecutable() (InstallInfo, error) { return InstallInfo{Method: installMethodUnknown}, nil } + invocationPath := executablePath + if lookupPath, lookupErr := exec.LookPath(os.Args[0]); lookupErr == nil { + invocationPath = lookupPath + } + resolvedPath := executablePath - if evalPath, evalErr := filepath.EvalSymlinks(executablePath); evalErr == nil { + if evalPath, evalErr := filepath.EvalSymlinks(invocationPath); evalErr == nil { + resolvedPath = evalPath + } else if evalPath, evalErr := filepath.EvalSymlinks(executablePath); evalErr == nil { resolvedPath = evalPath } + return detectInstallFromPaths(invocationPath, resolvedPath), nil +} + +func detectInstallFromPaths(executablePath string, resolvedPath string) InstallInfo { candidates := []string{filepath.ToSlash(executablePath), filepath.ToSlash(resolvedPath)} for _, candidate := range candidates { lower := strings.ToLower(candidate) switch { case strings.Contains(candidate, "/Cellar/metorial/"), strings.Contains(candidate, "/homebrew/opt/metorial/"), strings.Contains(candidate, "/linuxbrew/Cellar/metorial/"): - return InstallInfo{Method: installMethodHomebrew}, nil + return InstallInfo{Method: installMethodHomebrew} case strings.Contains(lower, "/scoop/apps/metorial/"): - return InstallInfo{Method: installMethodScoop}, nil + return InstallInfo{Method: installMethodScoop} case strings.Contains(lower, "/chocolatey/"): - return InstallInfo{Method: installMethodChocolatey}, nil - case strings.Contains(candidate, "/.metorial/cli/"): - return InstallInfo{Method: installMethodInstallSH}, nil + return InstallInfo{Method: installMethodChocolatey} } } - return InstallInfo{Method: installMethodUnknown}, nil + if strings.Contains(filepath.ToSlash(executablePath), "/.metorial/cli/") || strings.Contains(filepath.ToSlash(resolvedPath), "/.metorial/cli/") { + install := InstallInfo{ + Method: installMethodInstallSH, + ManagedBinaryPath: resolvedPath, + } + + if executablePath != "" && resolvedPath != "" && executablePath != resolvedPath { + install.BinDir = filepath.Dir(executablePath) + install.SymlinkPath = executablePath + } + + return install + } + + return InstallInfo{Method: installMethodUnknown} } func upgradeHint(install InstallInfo) string { diff --git a/internal/update/update_test.go b/internal/update/update_test.go new file mode 100644 index 0000000..7840915 --- /dev/null +++ b/internal/update/update_test.go @@ -0,0 +1,59 @@ +package update + +import ( + "strings" + "testing" +) + +func TestDetectInstallFromPathsInfersInstallScriptSymlinkMetadata(t *testing.T) { + t.Parallel() + + install := detectInstallFromPaths( + "/usr/local/bin/metorial", + "/home/demo/.metorial/cli/metorial", + ) + + if install.Method != installMethodInstallSH { + t.Fatalf("detectInstallFromPaths() method = %q, want %q", install.Method, installMethodInstallSH) + } + + if install.BinDir != "/usr/local/bin" { + t.Fatalf("detectInstallFromPaths() bin dir = %q, want %q", install.BinDir, "/usr/local/bin") + } + + if install.SymlinkPath != "/usr/local/bin/metorial" { + t.Fatalf("detectInstallFromPaths() symlink path = %q, want %q", install.SymlinkPath, "/usr/local/bin/metorial") + } + + if install.ManagedBinaryPath != "/home/demo/.metorial/cli/metorial" { + t.Fatalf("detectInstallFromPaths() managed binary path = %q, want %q", install.ManagedBinaryPath, "/home/demo/.metorial/cli/metorial") + } +} + +func TestInstallSHUpgradeCommandUsesBinDir(t *testing.T) { + t.Parallel() + + command, args, supported := installSHUpgradeCommand(InstallInfo{ + Method: installMethodInstallSH, + BinDir: "/usr/local/bin", + }) + if !supported { + t.Fatal("installSHUpgradeCommand() supported = false, want true") + } + + if command != "sh" { + t.Fatalf("installSHUpgradeCommand() command = %q, want %q", command, "sh") + } + + if len(args) != 2 { + t.Fatalf("installSHUpgradeCommand() args len = %d, want 2", len(args)) + } + + if !strings.Contains(args[1], `METORIAL_CLI_BIN_DIR="/usr/local/bin"`) { + t.Fatalf("installSHUpgradeCommand() args = %q, want bin dir export", args[1]) + } + + if !strings.Contains(args[1], `https://cli.metorial.com/install.sh`) { + t.Fatalf("installSHUpgradeCommand() args = %q, want install script URL", args[1]) + } +} diff --git a/templates/install.sh b/templates/install.sh index 17fd4ae..4f49cb8 100755 --- a/templates/install.sh +++ b/templates/install.sh @@ -132,6 +132,16 @@ resolve_managed_bin_path() { printf '%s/.metorial/cli/metorial' "$HOME" } +json_escape() { + value="$1" + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + value="${value//$'\n'/\\n}" + value="${value//$'\r'/\\r}" + value="${value//$'\t'/\\t}" + printf '%s' "$value" +} + write_install_metadata() { install_dir="$1" symlink_path="$2" @@ -143,9 +153,9 @@ write_install_metadata() { cat > "$metadata_path" <