summaryrefslogtreecommitdiff
path: root/packages/cli/src/utils/installationInfo.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/utils/installationInfo.ts')
-rw-r--r--packages/cli/src/utils/installationInfo.ts177
1 files changed, 177 insertions, 0 deletions
diff --git a/packages/cli/src/utils/installationInfo.ts b/packages/cli/src/utils/installationInfo.ts
new file mode 100644
index 00000000..ca5733d3
--- /dev/null
+++ b/packages/cli/src/utils/installationInfo.ts
@@ -0,0 +1,177 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { isGitRepository } from '@google/gemini-cli-core';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as childProcess from 'child_process';
+
+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,
+ isAutoUpdateDisabled: 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 {
+ // 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".',
+ };
+ } 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: isAutoUpdateDisabled
+ ? `Please run ${updateCommand} to update`
+ : 'Installed with pnpm. Attempting to automatically update now...',
+ };
+ }
+
+ // 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: isAutoUpdateDisabled
+ ? `Please run ${updateCommand} to update`
+ : 'Installed with yarn. Attempting to automatically update now...',
+ };
+ }
+
+ // 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: isAutoUpdateDisabled
+ ? `Please run ${updateCommand} to update`
+ : 'Installed with bun. Attempting to automatically update now...',
+ };
+ }
+
+ // 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: isAutoUpdateDisabled
+ ? `Please run ${updateCommand} to update`
+ : 'Installed with npm. Attempting to automatically update now...',
+ };
+ } catch (error) {
+ console.log(error);
+ return { packageManager: PackageManager.UNKNOWN, isGlobal: false };
+ }
+}