fix(cli): correct Homebrew installation detection (#14727)

Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com>
Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
This commit is contained in:
kij
2026-01-19 16:37:32 +01:00
committed by GitHub
parent a4eb04b7d4
commit 4cfbe4c716
2 changed files with 72 additions and 17 deletions

View File

@@ -136,16 +136,30 @@ describe('getInstallationInfo', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
const cliPath = '/usr/local/bin/gemini';
// Use a path that matches what brew would resolve to
const cliPath = '/opt/homebrew/Cellar/gemini-cli/1.0.0/bin/gemini';
process.argv[1] = cliPath;
mockedRealPathSync.mockReturnValue(cliPath);
mockedExecSync.mockReturnValue(Buffer.from('gemini-cli')); // Simulate successful command
mockedExecSync.mockImplementation((cmd) => {
if (typeof cmd === 'string' && cmd.includes('brew --prefix gemini-cli')) {
return '/opt/homebrew/opt/gemini-cli';
}
throw new Error(`Command failed: ${cmd}`);
});
mockedRealPathSync.mockImplementation((p) => {
if (p === cliPath) return cliPath;
if (p === '/opt/homebrew/opt/gemini-cli') {
return '/opt/homebrew/Cellar/gemini-cli/1.0.0';
}
return String(p);
});
const info = getInstallationInfo(projectRoot, true);
expect(mockedExecSync).toHaveBeenCalledWith(
'brew list -1 | grep -q "^gemini-cli$"',
{ stdio: 'ignore' },
expect.stringContaining('brew --prefix gemini-cli'),
expect.anything(),
);
expect(info.packageManager).toBe(PackageManager.HOMEBREW);
expect(info.isGlobal).toBe(true);
@@ -168,8 +182,8 @@ describe('getInstallationInfo', () => {
const info = getInstallationInfo(projectRoot, true);
expect(mockedExecSync).toHaveBeenCalledWith(
'brew list -1 | grep -q "^gemini-cli$"',
{ stdio: 'ignore' },
expect.stringContaining('brew --prefix gemini-cli'),
expect.anything(),
);
// Should fall back to default global npm
expect(info.packageManager).toBe(PackageManager.NPM);
@@ -324,4 +338,39 @@ describe('getInstallationInfo', () => {
const infoDisabled = getInstallationInfo(projectRoot, false);
expect(infoDisabled.updateMessage).toContain('Please run npm install');
});
it('should NOT detect Homebrew if gemini-cli is installed in brew but running from npm location', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
});
// Path looks like standard global NPM
const cliPath =
'/usr/local/lib/node_modules/@google/gemini-cli/dist/index.js';
process.argv[1] = cliPath;
// Setup mocks
mockedExecSync.mockImplementation((cmd) => {
if (typeof cmd === 'string' && cmd.includes('brew list')) {
return Buffer.from('gemini-cli\n');
}
// Future proofing for the fix:
if (typeof cmd === 'string' && cmd.includes('brew --prefix gemini-cli')) {
return '/opt/homebrew/opt/gemini-cli';
}
throw new Error(`Command failed: ${cmd}`);
});
mockedRealPathSync.mockImplementation((p) => {
if (p === cliPath) return cliPath;
// Future proofing for the fix:
if (p === '/opt/homebrew/opt/gemini-cli')
return '/opt/homebrew/Cellar/gemini-cli/1.0.0';
return String(p);
});
const info = getInstallationInfo(projectRoot, false);
expect(info.packageManager).not.toBe(PackageManager.HOMEBREW);
expect(info.packageManager).toBe(PackageManager.NPM);
});
});

View File

@@ -80,16 +80,22 @@ export function getInstallationInfo(
// Check for Homebrew
if (process.platform === 'darwin') {
try {
// The package name in homebrew is gemini-cli
childProcess.execSync('brew list -1 | grep -q "^gemini-cli$"', {
stdio: 'ignore',
});
return {
packageManager: PackageManager.HOMEBREW,
isGlobal: true,
updateMessage:
'Installed via Homebrew. Please update with "brew upgrade gemini-cli".',
};
const brewPrefix = childProcess
.execSync('brew --prefix gemini-cli', {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
.trim();
const brewRealPath = fs.realpathSync(brewPrefix);
if (realPath.startsWith(brewRealPath)) {
return {
packageManager: PackageManager.HOMEBREW,
isGlobal: true,
updateMessage:
'Installed via Homebrew. Please update with "brew upgrade gemini-cli".',
};
}
} catch (_error) {
// Brew is not installed or gemini-cli is not installed via brew.
// Continue to the next check.