mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com> Co-authored-by: Jack Wotherspoon <jackwoth@google.com>
187 lines
5.5 KiB
TypeScript
187 lines
5.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { debugLogger, isGitRepository } from '@google/gemini-cli-core';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import * as childProcess from 'node:child_process';
|
|
import process from 'node:process';
|
|
|
|
export const isDevelopment = process.env['NODE_ENV'] === 'development';
|
|
|
|
export enum PackageManager {
|
|
NPM = 'npm',
|
|
YARN = 'yarn',
|
|
PNPM = 'pnpm',
|
|
PNPX = 'pnpx',
|
|
BUN = 'bun',
|
|
BUNX = 'bunx',
|
|
HOMEBREW = 'homebrew',
|
|
NPX = 'npx',
|
|
UNKNOWN = 'unknown',
|
|
}
|
|
|
|
export interface InstallationInfo {
|
|
packageManager: PackageManager;
|
|
isGlobal: boolean;
|
|
updateCommand?: string;
|
|
updateMessage?: string;
|
|
}
|
|
|
|
export function getInstallationInfo(
|
|
projectRoot: string,
|
|
isAutoUpdateEnabled: boolean,
|
|
): InstallationInfo {
|
|
const cliPath = process.argv[1];
|
|
if (!cliPath) {
|
|
return { packageManager: PackageManager.UNKNOWN, isGlobal: false };
|
|
}
|
|
|
|
try {
|
|
// Normalize path separators to forward slashes for consistent matching.
|
|
const realPath = fs.realpathSync(cliPath).replace(/\\/g, '/');
|
|
const normalizedProjectRoot = projectRoot?.replace(/\\/g, '/');
|
|
const isGit = isGitRepository(process.cwd());
|
|
|
|
// Check for local git clone first
|
|
if (
|
|
isGit &&
|
|
normalizedProjectRoot &&
|
|
realPath.startsWith(normalizedProjectRoot) &&
|
|
!realPath.includes('/node_modules/')
|
|
) {
|
|
return {
|
|
packageManager: PackageManager.UNKNOWN, // Not managed by a package manager in this sense
|
|
isGlobal: false,
|
|
updateMessage:
|
|
'Running from a local git clone. Please update with "git pull".',
|
|
};
|
|
}
|
|
|
|
// Check for npx/pnpx
|
|
if (realPath.includes('/.npm/_npx') || realPath.includes('/npm/_npx')) {
|
|
return {
|
|
packageManager: PackageManager.NPX,
|
|
isGlobal: false,
|
|
updateMessage: 'Running via npx, update not applicable.',
|
|
};
|
|
}
|
|
if (realPath.includes('/.pnpm/_pnpx')) {
|
|
return {
|
|
packageManager: PackageManager.PNPX,
|
|
isGlobal: false,
|
|
updateMessage: 'Running via pnpx, update not applicable.',
|
|
};
|
|
}
|
|
|
|
// Check for Homebrew
|
|
if (process.platform === 'darwin') {
|
|
try {
|
|
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.
|
|
}
|
|
}
|
|
|
|
// Check for pnpm
|
|
if (realPath.includes('/.pnpm/global')) {
|
|
const updateCommand = 'pnpm add -g @google/gemini-cli@latest';
|
|
return {
|
|
packageManager: PackageManager.PNPM,
|
|
isGlobal: true,
|
|
updateCommand,
|
|
updateMessage: isAutoUpdateEnabled
|
|
? 'Installed with pnpm. Attempting to automatically update now...'
|
|
: `Please run ${updateCommand} to update`,
|
|
};
|
|
}
|
|
|
|
// Check for yarn
|
|
if (realPath.includes('/.yarn/global')) {
|
|
const updateCommand = 'yarn global add @google/gemini-cli@latest';
|
|
return {
|
|
packageManager: PackageManager.YARN,
|
|
isGlobal: true,
|
|
updateCommand,
|
|
updateMessage: isAutoUpdateEnabled
|
|
? 'Installed with yarn. Attempting to automatically update now...'
|
|
: `Please run ${updateCommand} to update`,
|
|
};
|
|
}
|
|
|
|
// Check for bun
|
|
if (realPath.includes('/.bun/install/cache')) {
|
|
return {
|
|
packageManager: PackageManager.BUNX,
|
|
isGlobal: false,
|
|
updateMessage: 'Running via bunx, update not applicable.',
|
|
};
|
|
}
|
|
if (realPath.includes('/.bun/bin')) {
|
|
const updateCommand = 'bun add -g @google/gemini-cli@latest';
|
|
return {
|
|
packageManager: PackageManager.BUN,
|
|
isGlobal: true,
|
|
updateCommand,
|
|
updateMessage: isAutoUpdateEnabled
|
|
? 'Installed with bun. Attempting to automatically update now...'
|
|
: `Please run ${updateCommand} to update`,
|
|
};
|
|
}
|
|
|
|
// Check for local install
|
|
if (
|
|
normalizedProjectRoot &&
|
|
realPath.startsWith(`${normalizedProjectRoot}/node_modules`)
|
|
) {
|
|
let pm = PackageManager.NPM;
|
|
if (fs.existsSync(path.join(projectRoot, 'yarn.lock'))) {
|
|
pm = PackageManager.YARN;
|
|
} else if (fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) {
|
|
pm = PackageManager.PNPM;
|
|
} else if (fs.existsSync(path.join(projectRoot, 'bun.lockb'))) {
|
|
pm = PackageManager.BUN;
|
|
}
|
|
return {
|
|
packageManager: pm,
|
|
isGlobal: false,
|
|
updateMessage:
|
|
"Locally installed. Please update via your project's package.json.",
|
|
};
|
|
}
|
|
|
|
// Assume global npm
|
|
const updateCommand = 'npm install -g @google/gemini-cli@latest';
|
|
return {
|
|
packageManager: PackageManager.NPM,
|
|
isGlobal: true,
|
|
updateCommand,
|
|
updateMessage: isAutoUpdateEnabled
|
|
? 'Installed with npm. Attempting to automatically update now...'
|
|
: `Please run ${updateCommand} to update`,
|
|
};
|
|
} catch (error) {
|
|
debugLogger.log(error);
|
|
return { packageManager: PackageManager.UNKNOWN, isGlobal: false };
|
|
}
|
|
}
|