diff --git a/docs/install/installer.md b/docs/install/installer.md index e8714d079b3d..841169a9cbd8 100644 --- a/docs/install/installer.md +++ b/docs/install/installer.md @@ -68,14 +68,14 @@ Recommended for most interactive installs on macOS/Linux/WSL. - Supports macOS and Linux (including WSL). If macOS is detected, installs Homebrew if missing. + Supports macOS and Linux (including WSL). - Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). OpenClaw still supports Node 22 LTS, currently `22.19+`, for compatibility. + Checks Node version and installs Node 24 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum). On macOS, Homebrew is installed only when the installer needs it for Node or Git. OpenClaw still supports Node 22 LTS, currently `22.19+`, for compatibility. On Alpine/musl Linux, the installer uses apk packages instead of NodeSource; the configured Alpine repositories must provide Node `22.19+` (Alpine 3.21 or newer at the time of writing). - Installs Git if missing using the detected package manager, including apk on Alpine. + Installs Git if missing using the detected package manager, including Homebrew on macOS and apk on Alpine. - `npm` method (default): global npm install diff --git a/scripts/install.sh b/scripts/install.sh index 430384f77010..6054b3498af5 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1914,6 +1914,7 @@ require_sudo() { install_git() { if [[ "$OS" == "macos" ]]; then + install_homebrew run_quiet_step "Installing Git" brew install git elif [[ "$OS" == "linux" ]]; then require_sudo @@ -3100,12 +3101,11 @@ main() { ui_stage "Preparing environment" - # Step 1: Homebrew (macOS only) - install_homebrew - - # Step 2: Node.js + # Step 1: Node.js. macOS package-manager branches install Homebrew lazily + # only when they are about to call brew. load_nvm_for_node_detection if ! check_node; then + install_homebrew install_node fi activate_supported_node_on_path || true diff --git a/src/scripts/test-live-media.test.ts b/src/scripts/test-live-media.test.ts index f4bba37675ff..52858d338321 100644 --- a/src/scripts/test-live-media.test.ts +++ b/src/scripts/test-live-media.test.ts @@ -49,7 +49,7 @@ describe("test-live-media", () => { "openai", "vydra", ]); - expect(requirePlanEntry(plan, "music").providers).toEqual(["google", "minimax"]); + expect(requirePlanEntry(plan, "music").providers).toEqual(["fal", "google", "minimax"]); expect(requirePlanEntry(plan, "video").providers).toEqual([ "google", "minimax", diff --git a/test/scripts/install-sh.test.ts b/test/scripts/install-sh.test.ts index 4cc98960c1ee..1f7d109ab6be 100644 --- a/test/scripts/install-sh.test.ts +++ b/test/scripts/install-sh.test.ts @@ -826,7 +826,7 @@ describe("install.sh", () => { it("loads nvm before checking Node.js so stale system Node does not win", () => { expect(script).toMatch( - /# Step 2: Node\.js\s+load_nvm_for_node_detection\s+if ! check_node; then/, + /# Step 1: Node\.js[\s\S]*?load_nvm_for_node_detection\s+if ! check_node; then/, ); const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-nvm-")); @@ -890,6 +890,22 @@ describe("install.sh", () => { expect(output).toContain("version=v22.22.1"); }); + it("installs Homebrew lazily before macOS Git installs", () => { + const result = runInstallShell(` + set -euo pipefail + source "${SCRIPT_PATH}" + OS=macos + install_homebrew() { echo "install_homebrew"; } + run_quiet_step() { echo "run_quiet_step:$*"; return 0; } + install_git + `); + + expect(result.status).toBe(0); + expect(result.stdout).toMatch( + /install_homebrew\s+run_quiet_step:Installing Git brew install git/, + ); + }); + it("promotes a supported Linux Node binary over stale PATH entries", () => { const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-node-promote-")); const staleBin = join(tmp, "usr-local-bin");