From d13a0396f904b53041924d985b5db37b842de17e Mon Sep 17 00:00:00 2001 From: 7418 <7418@openclaw.ai> Date: Fri, 27 Feb 2026 15:30:32 +0800 Subject: [PATCH] fix: replace tarball fallback with Homebrew install guide for macOS without brew - electron/main.ts: check-prerequisites now returns hasHomebrew + platform so the frontend can distinguish macOS-without-Homebrew from other scenarios - electron/main.ts: remove untested tarball fallback (hardcoded Node version, custom ~/.codepilot/node/bin PATH), replace with clean Homebrew-only path that returns a clear error message when Homebrew is absent - electron/main.ts: remove ~/.codepilot/node/bin from getExpandedShellPath() since the tarball install target no longer exists - InstallWizard.tsx: when Node.js is missing AND Homebrew is absent on macOS, show a guided UI with the Homebrew install command, copy button, and step-by-step instructions instead of a broken "Install" button - InstallWizard.tsx: footer shows "Recheck" button (re-runs checkPrereqs) instead of "Install" in the no-Homebrew case - en.ts/zh.ts: add 8 new i18n keys for the Homebrew guidance UI Root cause: the previous tarball fallback was untested, pinned to a specific Node version (v22.12.0), and required maintaining a custom PATH entry. Guiding users to install Homebrew first is more reliable and maintainable. Co-Authored-By: Claude Opus 4.6 --- electron/main.ts | 25 ++++++----- src/components/layout/InstallWizard.tsx | 58 +++++++++++++++++++++++-- src/i18n/en.ts | 9 ++++ src/i18n/zh.ts | 9 ++++ 4 files changed, 85 insertions(+), 16 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 9925665..16664cb 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -199,7 +199,7 @@ function getExpandedShellPath(): string { return [...new Set(allParts)].join(sep); } else { const basePath = `/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin`; - const raw = `${basePath}:${home}/.npm-global/bin:${home}/.local/bin:${home}/.claude/bin:${home}/.codepilot/node/bin:${shellPath}`; + const raw = `${basePath}:${home}/.npm-global/bin:${home}/.local/bin:${home}/.claude/bin:${shellPath}`; const allParts = raw.split(':').filter(Boolean); return [...new Set(allParts)].join(':'); } @@ -459,7 +459,14 @@ app.whenReady().then(async () => { // claude not found } - return { hasNode, nodeVersion, hasClaude, claudeVersion }; + // Check Homebrew on macOS + let hasHomebrew = false; + if (process.platform === 'darwin') { + const brewPaths = ['/opt/homebrew/bin/brew', '/usr/local/bin/brew']; + hasHomebrew = brewPaths.some(p => fs.existsSync(p)); + } + + return { hasNode, nodeVersion, hasClaude, claudeVersion, hasHomebrew, platform: process.platform }; }); ipcMain.handle('install:start', (_event: Electron.IpcMainInvokeEvent, options?: { includeNode?: boolean }) => { @@ -528,7 +535,7 @@ app.whenReady().then(async () => { let args: string[]; if (isMac) { - // Try Homebrew first, fallback to official tarball download + // macOS: Homebrew only — UI should guide the user to install Homebrew first const brewPaths = ['/opt/homebrew/bin/brew', '/usr/local/bin/brew']; const brewPath = brewPaths.find(p => fs.existsSync(p)); if (brewPath) { @@ -536,15 +543,9 @@ app.whenReady().then(async () => { args = ['install', 'node']; addLog(`Running: ${brewPath} install node`); } else { - // Fallback: download official Node.js binary tarball (no Homebrew/sudo needed) - const arch = process.arch === 'arm64' ? 'arm64' : 'x64'; - const nodeVersion = 'v22.12.0'; - const nodeDir = path.join(home, '.codepilot', 'node'); - const tarballUrl = `https://nodejs.org/dist/${nodeVersion}/node-${nodeVersion}-darwin-${arch}.tar.gz`; - addLog(`Homebrew not found. Downloading Node.js ${nodeVersion} from nodejs.org...`); - addLog(`Architecture: ${arch}, Target: ${nodeDir}`); - cmd = 'sh'; - args = ['-c', `mkdir -p "${nodeDir}" && curl -fsSL "${tarballUrl}" | tar xz --strip-components=1 -C "${nodeDir}"`]; + addLog('Homebrew is required. Please install Homebrew first and retry.'); + resolve(false); + return; } } else if (isWin) { cmd = 'winget'; diff --git a/src/components/layout/InstallWizard.tsx b/src/components/layout/InstallWizard.tsx index e0ca98b..a8467d4 100644 --- a/src/components/layout/InstallWizard.tsx +++ b/src/components/layout/InstallWizard.tsx @@ -54,6 +54,8 @@ interface PrereqResult { nodeVersion?: string; hasClaude: boolean; claudeVersion?: string; + hasHomebrew?: boolean; + platform?: string; } function getInstallAPI() { @@ -88,6 +90,7 @@ export function InstallWizard({ const [progress, setProgress] = useState(null); const [logs, setLogs] = useState([]); const [copied, setCopied] = useState(false); + const [copiedBrew, setCopiedBrew] = useState(false); const [prereqs, setPrereqs] = useState(null); const logEndRef = useRef(null); const cleanupRef = useRef<(() => void) | null>(null); @@ -223,6 +226,7 @@ export function InstallWizard({ setLogs([]); // eslint-disable-line react-hooks/set-state-in-effect setProgress(null); // eslint-disable-line react-hooks/set-state-in-effect setCopied(false); // eslint-disable-line react-hooks/set-state-in-effect + setCopiedBrew(false); // eslint-disable-line react-hooks/set-state-in-effect setPrereqs(null); // eslint-disable-line react-hooks/set-state-in-effect checkPrereqs(); } @@ -288,12 +292,53 @@ export function InstallWizard({ )} {/* Phase: confirm — ask user before installing */} - {phase === "confirm" && ( + {phase === "confirm" && prereqs && !prereqs.hasNode && !prereqs.hasHomebrew && prereqs.platform === "darwin" && ( +
+
+

+ {t('install.homebrewRequired')} +

+

+ {t('install.homebrewDescription')} +

+
+
+ + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + +
+
+

{t('install.homebrewSteps')}

+
    +
  1. {t('install.homebrewStep1')}
  2. +
  3. {t('install.homebrewStep2')}
  4. +
  5. {t('install.homebrewStep3')}
  6. +
  7. {t('install.homebrewStep4')}
  8. +
+
+
+ )} + {phase === "confirm" && !(prereqs && !prereqs.hasNode && !prereqs.hasHomebrew && prereqs.platform === "darwin") && (
{prereqs && !prereqs.hasNode && (

- Node.js — not found (will be installed via {process.platform === "win32" ? "winget" : "Homebrew"}) + Node.js — not found (will be installed via {prereqs.platform === "win32" ? "winget" : "Homebrew"})

)} {prereqs?.hasNode && ( @@ -369,8 +414,13 @@ export function InstallWizard({ )} - {/* Confirm phase: single "Install" button */} - {phase === "confirm" && ( + {/* Confirm phase: "Recheck" when Homebrew missing on macOS, otherwise "Install" */} + {phase === "confirm" && prereqs && !prereqs.hasNode && !prereqs.hasHomebrew && prereqs.platform === "darwin" && ( + + )} + {phase === "confirm" && !(prereqs && !prereqs.hasNode && !prereqs.hasHomebrew && prereqs.platform === "darwin") && (