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") && (