From a91f4e692aa067b2137cf70dd7f786957343fb2e Mon Sep 17 00:00:00 2001 From: Tu Shaokun <53142663+tt-a1i@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:13:40 +0800 Subject: [PATCH] fix(cli): list installed extensions when update target missing (#17082) --- .../src/commands/extensions/update.test.ts | 38 +++++++++++++++++++ .../cli/src/commands/extensions/update.ts | 19 ++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/extensions/update.test.ts b/packages/cli/src/commands/extensions/update.test.ts index 50847ca9bb..886a7d2b0c 100644 --- a/packages/cli/src/commands/extensions/update.test.ts +++ b/packages/cli/src/commands/extensions/update.test.ts @@ -24,6 +24,7 @@ import { ExtensionUpdateState } from '../../ui/state/extensions.js'; // Mock dependencies const emitConsoleLog = vi.hoisted(() => vi.fn()); +const emitFeedback = vi.hoisted(() => vi.fn()); const debugLogger = vi.hoisted(() => ({ log: vi.fn((message, ...args) => { emitConsoleLog('log', format(message, ...args)); @@ -40,6 +41,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { ...actual, coreEvents: { emitConsoleLog, + emitFeedback, }, debugLogger, }; @@ -84,6 +86,42 @@ describe('extensions update command', () => { }); describe('handleUpdate', () => { + it('should list installed extensions when requested extension is not found', async () => { + const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + const extensions = [ + { name: 'ext1', version: '1.0.0' }, + { name: 'ext2', version: '2.0.0' }, + ]; + mockExtensionManager.prototype.loadExtensions = vi + .fn() + .mockResolvedValue(extensions); + + await handleUpdate({ name: 'missing-extension' }); + + expect(emitFeedback).toHaveBeenCalledWith( + 'error', + 'Extension "missing-extension" not found.\n\nInstalled extensions:\next1 (1.0.0)\next2 (2.0.0)\n\nRun "gemini extensions list" for details.', + ); + expect(mockUpdateExtension).not.toHaveBeenCalled(); + mockCwd.mockRestore(); + }); + + it('should log a helpful message when no extensions are installed and requested extension is not found', async () => { + const mockCwd = vi.spyOn(process, 'cwd').mockReturnValue('/test/dir'); + mockExtensionManager.prototype.loadExtensions = vi + .fn() + .mockResolvedValue([]); + + await handleUpdate({ name: 'missing-extension' }); + + expect(emitFeedback).toHaveBeenCalledWith( + 'error', + 'Extension "missing-extension" not found.\n\nNo extensions installed.', + ); + expect(mockUpdateExtension).not.toHaveBeenCalled(); + mockCwd.mockRestore(); + }); + it.each([ { state: ExtensionUpdateState.UPDATE_AVAILABLE, diff --git a/packages/cli/src/commands/extensions/update.ts b/packages/cli/src/commands/extensions/update.ts index ba7c865899..4798892551 100644 --- a/packages/cli/src/commands/extensions/update.ts +++ b/packages/cli/src/commands/extensions/update.ts @@ -14,7 +14,7 @@ import { import { checkForExtensionUpdate } from '../../config/extensions/github.js'; import { getErrorMessage } from '../../utils/errors.js'; import { ExtensionUpdateState } from '../../ui/state/extensions.js'; -import { debugLogger } from '@google/gemini-cli-core'; +import { coreEvents, debugLogger } 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'; @@ -46,7 +46,21 @@ export async function handleUpdate(args: UpdateArgs) { (extension) => extension.name === args.name, ); if (!extension) { - debugLogger.log(`Extension "${args.name}" not found.`); + 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) { @@ -63,7 +77,6 @@ export async function handleUpdate(args: UpdateArgs) { debugLogger.log(`Extension "${args.name}" is already up to date.`); return; } - // TODO(chrstnb): we should list extensions if the requested extension is not installed. const updatedExtensionInfo = (await updateExtension( extension, extensionManager,