mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-16 09:01:17 -07:00
169 lines
5.1 KiB
TypeScript
169 lines
5.1 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import type { CommandModule } from 'yargs';
|
|
import {
|
|
updateAllUpdatableExtensions,
|
|
type ExtensionUpdateInfo,
|
|
checkForAllExtensionUpdates,
|
|
updateExtension,
|
|
} from '../../config/extensions/update.js';
|
|
import { checkForExtensionUpdate } from '../../config/extensions/github.js';
|
|
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
|
|
import {
|
|
coreEvents,
|
|
debugLogger,
|
|
getErrorMessage,
|
|
} from '@google/gemini-cli-core';
|
|
import { ExtensionManager } from '../../config/extension-manager.js';
|
|
import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
|
|
import { loadSettings } from '../../config/settings.js';
|
|
import { promptForSetting } from '../../config/extensions/extensionSettings.js';
|
|
import { exitCli } from '../utils.js';
|
|
|
|
interface UpdateArgs {
|
|
name?: string;
|
|
all?: boolean;
|
|
}
|
|
|
|
const updateOutput = (info: ExtensionUpdateInfo) =>
|
|
`Extension "${info.name}" successfully updated: ${info.originalVersion} → ${info.updatedVersion}.`;
|
|
|
|
export async function handleUpdate(args: UpdateArgs) {
|
|
const workspaceDir = process.cwd();
|
|
const settings = loadSettings(workspaceDir).merged;
|
|
const extensionManager = new ExtensionManager({
|
|
workspaceDir,
|
|
requestConsent: requestConsentNonInteractive,
|
|
requestSetting: promptForSetting,
|
|
settings,
|
|
});
|
|
|
|
const extensions = await extensionManager.loadExtensions();
|
|
if (args.name) {
|
|
try {
|
|
const extension = extensions.find(
|
|
(extension) => extension.name === args.name,
|
|
);
|
|
if (!extension) {
|
|
if (extensions.length === 0) {
|
|
coreEvents.emitFeedback(
|
|
'error',
|
|
`Extension "${args.name}" not found.\n\nNo extensions installed.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const installedExtensions = extensions
|
|
.map((extension) => `${extension.name} (${extension.version})`)
|
|
.join('\n');
|
|
coreEvents.emitFeedback(
|
|
'error',
|
|
`Extension "${args.name}" not found.\n\nInstalled extensions:\n${installedExtensions}\n\nRun "gemini extensions list" for details.`,
|
|
);
|
|
return;
|
|
}
|
|
if (!extension.installMetadata) {
|
|
debugLogger.log(
|
|
`Unable to install extension "${args.name}" due to missing install metadata`,
|
|
);
|
|
return;
|
|
}
|
|
const updateState = await checkForExtensionUpdate(
|
|
extension,
|
|
extensionManager,
|
|
);
|
|
if (updateState !== ExtensionUpdateState.UPDATE_AVAILABLE) {
|
|
debugLogger.log(`Extension "${args.name}" is already up to date.`);
|
|
return;
|
|
}
|
|
const updatedExtensionInfo = (await updateExtension(
|
|
extension,
|
|
extensionManager,
|
|
updateState,
|
|
() => {},
|
|
settings.experimental?.extensionReloading,
|
|
))!;
|
|
if (
|
|
updatedExtensionInfo.originalVersion !==
|
|
updatedExtensionInfo.updatedVersion
|
|
) {
|
|
debugLogger.log(
|
|
`Extension "${args.name}" successfully updated: ${updatedExtensionInfo.originalVersion} → ${updatedExtensionInfo.updatedVersion}.`,
|
|
);
|
|
} else {
|
|
debugLogger.log(`Extension "${args.name}" is already up to date.`);
|
|
}
|
|
} catch (error) {
|
|
debugLogger.error(getErrorMessage(error));
|
|
}
|
|
}
|
|
if (args.all) {
|
|
try {
|
|
const extensionState = new Map();
|
|
await checkForAllExtensionUpdates(
|
|
extensions,
|
|
extensionManager,
|
|
(action) => {
|
|
if (action.type === 'SET_STATE') {
|
|
extensionState.set(action.payload.name, {
|
|
status: action.payload.state,
|
|
});
|
|
}
|
|
},
|
|
);
|
|
let updateInfos = await updateAllUpdatableExtensions(
|
|
extensions,
|
|
extensionState,
|
|
extensionManager,
|
|
() => {},
|
|
);
|
|
updateInfos = updateInfos.filter(
|
|
(info) => info.originalVersion !== info.updatedVersion,
|
|
);
|
|
if (updateInfos.length === 0) {
|
|
debugLogger.log('No extensions to update.');
|
|
return;
|
|
}
|
|
debugLogger.log(updateInfos.map((info) => updateOutput(info)).join('\n'));
|
|
} catch (error) {
|
|
debugLogger.error(getErrorMessage(error));
|
|
}
|
|
}
|
|
}
|
|
|
|
export const updateCommand: CommandModule = {
|
|
command: 'update [<name>] [--all]',
|
|
describe:
|
|
'Updates all extensions or a named extension to the latest version.',
|
|
builder: (yargs) =>
|
|
yargs
|
|
.positional('name', {
|
|
describe: 'The name of the extension to update.',
|
|
type: 'string',
|
|
})
|
|
.option('all', {
|
|
describe: 'Update all extensions.',
|
|
type: 'boolean',
|
|
})
|
|
.conflicts('name', 'all')
|
|
.check((argv) => {
|
|
if (!argv.all && !argv.name) {
|
|
throw new Error('Either an extension name or --all must be provided');
|
|
}
|
|
return true;
|
|
}),
|
|
handler: async (argv) => {
|
|
await handleUpdate({
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
name: argv['name'] as string | undefined,
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
all: argv['all'] as boolean | undefined,
|
|
});
|
|
await exitCli();
|
|
},
|
|
};
|