2025-07-30 21:26:31 +00:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-08-25 22:11:27 +02:00
import * as child_process from 'node:child_process' ;
import * as process from 'node:process' ;
import * as path from 'node:path' ;
import * as fs from 'node:fs' ;
import * as os from 'node:os' ;
2025-09-18 15:23:24 -04:00
import { IDE_DEFINITIONS , type IdeInfo } from './detect-ide.js' ;
2025-08-14 14:57:36 +00:00
import { GEMINI_CLI_COMPANION_EXTENSION_NAME } from './constants.js' ;
2025-07-30 21:26:31 +00:00
2025-08-25 17:10:36 -04:00
function getVsCodeCommand ( platform : NodeJS.Platform = process . platform ) {
return platform === 'win32' ? 'code.cmd' : 'code' ;
}
2025-07-30 21:26:31 +00:00
export interface IdeInstaller {
install ( ) : Promise < InstallResult > ;
}
export interface InstallResult {
success : boolean ;
message : string ;
}
2025-08-25 17:10:36 -04:00
async function findVsCodeCommand (
platform : NodeJS.Platform = process . platform ,
) : Promise < string | null > {
2025-07-30 21:26:31 +00:00
// 1. Check PATH first.
2025-08-25 17:10:36 -04:00
const vscodeCommand = getVsCodeCommand ( platform ) ;
2025-07-30 21:26:31 +00:00
try {
2025-08-25 17:10:36 -04:00
if ( platform === 'win32' ) {
2025-08-19 13:25:11 -07:00
const result = child_process
2025-08-25 17:10:36 -04:00
. execSync ( ` where.exe ${ vscodeCommand } ` )
2025-08-19 13:25:11 -07:00
. toString ( )
. trim ( ) ;
// `where.exe` can return multiple paths. Return the first one.
const firstPath = result . split ( /\r?\n/ ) [ 0 ] ;
if ( firstPath ) {
return firstPath ;
}
} else {
2025-08-25 17:10:36 -04:00
child_process . execSync ( ` command -v ${ vscodeCommand } ` , {
2025-08-19 13:25:11 -07:00
stdio : 'ignore' ,
} ) ;
2025-08-25 17:10:36 -04:00
return vscodeCommand ;
2025-08-19 13:25:11 -07:00
}
2025-07-30 21:26:31 +00:00
} catch {
// Not in PATH, continue to check common locations.
}
// 2. Check common installation locations.
const locations : string [ ] = [ ] ;
const homeDir = os . homedir ( ) ;
if ( platform === 'darwin' ) {
// macOS
locations . push (
'/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code' ,
path . join ( homeDir , 'Library/Application Support/Code/bin/code' ) ,
) ;
} else if ( platform === 'linux' ) {
// Linux
locations . push (
'/usr/share/code/bin/code' ,
'/snap/bin/code' ,
path . join ( homeDir , '.local/share/code/bin/code' ) ,
) ;
} else if ( platform === 'win32' ) {
// Windows
locations . push (
path . join (
2025-08-17 12:43:21 -04:00
process . env [ 'ProgramFiles' ] || 'C:\\Program Files' ,
2025-07-30 21:26:31 +00:00
'Microsoft VS Code' ,
'bin' ,
'code.cmd' ,
) ,
path . join (
homeDir ,
'AppData' ,
'Local' ,
'Programs' ,
'Microsoft VS Code' ,
'bin' ,
'code.cmd' ,
) ,
) ;
}
for ( const location of locations ) {
if ( fs . existsSync ( location ) ) {
return location ;
}
}
return null ;
}
class VsCodeInstaller implements IdeInstaller {
private vsCodeCommand : Promise < string | null > ;
2025-08-25 17:10:36 -04:00
constructor (
2025-09-18 15:23:24 -04:00
readonly ideInfo : IdeInfo ,
2025-08-25 17:10:36 -04:00
readonly platform = process . platform ,
) {
this . vsCodeCommand = findVsCodeCommand ( platform ) ;
2025-07-30 21:26:31 +00:00
}
async install ( ) : Promise < InstallResult > {
const commandPath = await this . vsCodeCommand ;
if ( ! commandPath ) {
return {
success : false ,
2025-08-25 17:10:36 -04:00
message : ` ${ this . ideInfo . displayName } CLI not found. Please ensure 'code' is in your system's PATH. For help, see https://code.visualstudio.com/docs/configure/command-line#_code-is-not-recognized-as-an-internal-or-external-command. You can also install the ' ${ GEMINI_CLI_COMPANION_EXTENSION_NAME } ' extension manually from the VS Code marketplace. ` ,
2025-07-30 21:26:31 +00:00
} ;
}
try {
2025-09-16 12:03:17 -07:00
const result = child_process . spawnSync (
commandPath ,
[
'--install-extension' ,
'google.gemini-cli-vscode-ide-companion' ,
'--force' ,
] ,
2025-10-08 14:21:23 -07:00
{ stdio : 'pipe' , shell : this.platform === 'win32' } ,
2025-09-16 12:03:17 -07:00
) ;
if ( result . status !== 0 ) {
throw new Error (
` Failed to install extension: ${ result . stderr ? . toString ( ) } ` ,
) ;
}
2025-07-30 21:26:31 +00:00
return {
success : true ,
2025-08-25 17:10:36 -04:00
message : ` ${ this . ideInfo . displayName } companion extension was installed successfully. ` ,
2025-07-30 21:26:31 +00:00
} ;
} catch ( _error ) {
return {
success : false ,
2025-08-25 17:10:36 -04:00
message : ` Failed to install ${ this . ideInfo . displayName } companion extension. Please try installing ' ${ GEMINI_CLI_COMPANION_EXTENSION_NAME } ' manually from the ${ this . ideInfo . displayName } extension marketplace. ` ,
2025-07-30 21:26:31 +00:00
} ;
}
}
}
2025-08-25 17:10:36 -04:00
export function getIdeInstaller (
2025-09-18 15:23:24 -04:00
ide : IdeInfo ,
2025-08-25 17:10:36 -04:00
platform = process . platform ,
) : IdeInstaller | null {
2025-09-18 15:23:24 -04:00
switch ( ide . name ) {
case IDE_DEFINITIONS.vscode.name :
case IDE_DEFINITIONS.firebasestudio.name :
2025-08-25 17:10:36 -04:00
return new VsCodeInstaller ( ide , platform ) ;
2025-07-30 21:26:31 +00:00
default :
2025-08-12 20:08:47 +00:00
return null ;
2025-07-30 21:26:31 +00:00
}
}