diff --git a/scripts/install-cli.sh b/scripts/install-cli.sh index 0be3be12bf8e..2d7b0ef7784c 100755 --- a/scripts/install-cli.sh +++ b/scripts/install-cli.sh @@ -41,27 +41,27 @@ cleanup_tmpfiles() { } trap cleanup_tmpfiles EXIT -resolve_openclaw_effective_home() { - local openclaw_home="${OPENCLAW_HOME:-}" - if [[ -z "$openclaw_home" ]]; then - echo "$HOME" - return 0 - fi - - case "$openclaw_home" in - \~) - echo "$HOME" - ;; - \~/*) - echo "${HOME}/${openclaw_home#~/}" - ;; - *) - echo "$openclaw_home" - ;; +resolve_home_path() { + local input="$1" + case "$input" in + \~) echo "$HOME" ;; + \~/*) echo "${HOME}${input:1}" ;; + *) echo "$input" ;; esac } -OPENCLAW_EFFECTIVE_HOME="$(resolve_openclaw_effective_home)" +INSTALLER_CWD="$(pwd -P)" +resolve_installer_path() { + local input + input="$(resolve_home_path "$1")" + case "$input" in + "") echo "" ;; + /*) echo "$input" ;; + *) echo "${INSTALLER_CWD}/${input}" ;; + esac +} + +OPENCLAW_EFFECTIVE_HOME="$(resolve_home_path "${OPENCLAW_HOME:-$HOME}")" PREFIX="${OPENCLAW_PREFIX:-${HOME}/.openclaw}" OPENCLAW_VERSION="${OPENCLAW_VERSION:-latest}" REQUIRED_COMPATIBLE_VERSION="" @@ -209,9 +209,6 @@ preflight_fresh_git_disk_space() { local available_kib local available_gib - if [[ "$repo_dir" != /* ]]; then - repo_dir="$(pwd)/$repo_dir" - fi if [[ -d "$repo_dir/.git" ]]; then return 0 fi @@ -1356,9 +1353,6 @@ install_openclaw_from_git() { if [[ -z "$repo_dir" ]]; then fail "Git install dir cannot be empty" fi - if [[ "$repo_dir" != /* ]]; then - repo_dir="$(pwd)/$repo_dir" - fi mkdir -p "$(dirname "$repo_dir")" repo_dir="$(cd "$(dirname "$repo_dir")" && pwd)/$(basename "$repo_dir")" @@ -1515,6 +1509,8 @@ refresh_gateway_service_if_loaded() { main() { parse_args "$@" + PREFIX="$(resolve_installer_path "$PREFIX")" + GIT_DIR="$(resolve_installer_path "$GIT_DIR")" if [[ "${OPENCLAW_NO_ONBOARD:-0}" == "1" ]]; then RUN_ONBOARD=0 diff --git a/test/scripts/install-cli.test.ts b/test/scripts/install-cli.test.ts index 62dff3ba5589..f7fdb994cb4f 100644 --- a/test/scripts/install-cli.test.ts +++ b/test/scripts/install-cli.test.ts @@ -509,6 +509,94 @@ describe("install-cli.sh", () => { expect(output).toContain(`git=${join(openclawHome, "openclaw")}`); }); + it.each([ + { input: "arguments", method: "npm" }, + { input: "environment", method: "npm" }, + { input: "literal tilde", method: "npm" }, + { input: "arguments", method: "git" }, + { input: "environment", method: "git" }, + { input: "literal tilde", method: "git" }, + ] as const)( + "keeps a generated $method launcher working after $input supplied paths change cwd", + ({ input, method }) => { + const tmp = mkdtempSync(join(tmpdir(), `openclaw-install-cli-relative-${method}-`)); + const installRoot = join(tmp, "install-root"); + const otherRoot = join(tmp, "other-root"); + const home = join(tmp, "home"); + const prefixInput = input === "literal tilde" ? "~/openclaw-local" : "openclaw-local"; + const prefix = join(input === "literal tilde" ? home : installRoot, "openclaw-local"); + const nodeDir = join(prefix, "tools", "node-v24.15.0"); + const repoInput = input === "literal tilde" ? "~/openclaw-source" : "openclaw-source"; + const repo = join(input === "literal tilde" ? home : installRoot, "openclaw-source"); + mkdirSync(installRoot, { recursive: true }); + mkdirSync(join(nodeDir, "bin"), { recursive: true }); + mkdirSync(join(nodeDir, "lib", "node_modules", "openclaw", "dist"), { recursive: true }); + mkdirSync(join(repo, ".git"), { recursive: true }); + mkdirSync(join(repo, "dist"), { recursive: true }); + mkdirSync(otherRoot, { recursive: true }); + symlinkSync(process.execPath, join(nodeDir, "bin", "node")); + symlinkSync("node-v24.15.0", join(prefix, "tools", "node")); + writeFileSync( + join(nodeDir, "bin", "npm"), + '#!/bin/bash\nif [[ "$1" == "config" ]]; then printf "null\\n"; fi\n', + ); + chmodSync(join(nodeDir, "bin", "npm"), 0o755); + for (const entry of [ + join(nodeDir, "lib", "node_modules", "openclaw", "dist", "entry.js"), + join(repo, "dist", "entry.js"), + ]) { + writeFileSync(entry, 'console.log("fixture cli");\n'); + } + + try { + const args = + input !== "environment" + ? `--prefix ${JSON.stringify(prefixInput)}${ + method === "git" ? ` --git-dir ${JSON.stringify(repoInput)}` : "" + }` + : ""; + const result = runInstallCliShell( + [ + "set -euo pipefail", + `cd ${JSON.stringify(installRoot)}`, + `source ${JSON.stringify(join(process.cwd(), SCRIPT_PATH))}`, + "install_node() { :; }", + "ensure_git() { :; }", + "refresh_gateway_service_if_loaded() { :; }", + ...(method === "git" + ? [ + "preflight_fresh_git_disk_space() { :; }", + "ensure_pnpm() { :; }", + "ensure_pnpm_binary_for_scripts() { :; }", + "ensure_pnpm_git_prepare_allowlist() { :; }", + "activate_repo_pnpm_version() { :; }", + "cleanup_legacy_submodules() { :; }", + "resolve_git_openclaw_ref() { printf 'main\\n'; }", + "checkout_git_openclaw_ref() { :; }", + "git_install_lockfile_flag() { printf '%s\\n' '--no-frozen-lockfile'; }", + "run_pnpm() { :; }", + "git() { return 0; }", + ] + : []), + `main --${method} ${args}`, + `cd ${JSON.stringify(otherRoot)}`, + `${JSON.stringify(join(prefix, "bin", "openclaw"))} --version`, + ].join("\n"), + { + HOME: home, + OPENCLAW_GIT_DIR: input === "environment" && method === "git" ? repoInput : undefined, + OPENCLAW_PREFIX: input === "environment" ? prefixInput : undefined, + }, + ); + + expect(result.status, result.stderr || result.stdout).toBe(0); + expect(result.stdout.trim().split("\n").at(-1)).toBe("fixture cli"); + } finally { + rmSync(tmp, { force: true, recursive: true }); + } + }, + ); + it("resolves requested git install versions to checkout refs", () => { const result = runInstallCliShell(` set -euo pipefail