refactor(cli): consolidate getErrorMessage utility to core (#22190)

This commit is contained in:
Tommaso Sciortino
2026-03-13 15:40:29 +00:00
committed by GitHub
parent 8d0b2d7f1b
commit 2a7e602356
25 changed files with 56 additions and 74 deletions
+5 -2
View File
@@ -4,13 +4,16 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import { listExtensions, type Config } from '@google/gemini-cli-core'; import {
listExtensions,
type Config,
getErrorMessage,
} from '@google/gemini-cli-core';
import { SettingScope } from '../../config/settings.js'; import { SettingScope } from '../../config/settings.js';
import { import {
ExtensionManager, ExtensionManager,
inferInstallMetadata, inferInstallMetadata,
} from '../../config/extension-manager.js'; } from '../../config/extension-manager.js';
import { getErrorMessage } from '../../utils/errors.js';
import { McpServerEnablementManager } from '../../config/mcp/mcpServerEnablement.js'; import { McpServerEnablementManager } from '../../config/mcp/mcpServerEnablement.js';
import { stat } from 'node:fs/promises'; import { stat } from 'node:fs/promises';
import type { import type {
@@ -22,7 +22,7 @@ import {
SettingScope, SettingScope,
type LoadedSettings, type LoadedSettings,
} from '../../config/settings.js'; } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js'; import { getErrorMessage } from '@google/gemini-cli-core';
// Mock dependencies // Mock dependencies
const emitConsoleLog = vi.hoisted(() => vi.fn()); const emitConsoleLog = vi.hoisted(() => vi.fn());
@@ -44,12 +44,12 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
emitConsoleLog, emitConsoleLog,
}, },
debugLogger, debugLogger,
getErrorMessage: vi.fn(),
}; };
}); });
vi.mock('../../config/extension-manager.js'); vi.mock('../../config/extension-manager.js');
vi.mock('../../config/settings.js'); vi.mock('../../config/settings.js');
vi.mock('../../utils/errors.js');
vi.mock('../../config/extensions/consent.js', () => ({ vi.mock('../../config/extensions/consent.js', () => ({
requestConsentNonInteractive: vi.fn(), requestConsentNonInteractive: vi.fn(),
})); }));
@@ -6,8 +6,7 @@
import { type CommandModule } from 'yargs'; import { type CommandModule } from 'yargs';
import { loadSettings, SettingScope } from '../../config/settings.js'; import { loadSettings, SettingScope } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { promptForSetting } from '../../config/extensions/extensionSettings.js'; import { promptForSetting } from '../../config/extensions/extensionSettings.js';
@@ -11,8 +11,8 @@ import {
debugLogger, debugLogger,
FolderTrustDiscoveryService, FolderTrustDiscoveryService,
getRealPath, getRealPath,
getErrorMessage,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { getErrorMessage } from '../../utils/errors.js';
import { import {
INSTALL_WARNING_MESSAGE, INSTALL_WARNING_MESSAGE,
promptForConsentNonInteractive, promptForConsentNonInteractive,
@@ -13,26 +13,24 @@ import {
afterEach, afterEach,
type Mock, type Mock,
} from 'vitest'; } from 'vitest';
import { coreEvents } from '@google/gemini-cli-core'; import { coreEvents, getErrorMessage } from '@google/gemini-cli-core';
import { type Argv } from 'yargs'; import { type Argv } from 'yargs';
import { handleLink, linkCommand } from './link.js'; import { handleLink, linkCommand } from './link.js';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js'; import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => { vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import( const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js' '../../test-utils/mockDebugLogger.js'
); );
return mockCoreDebugLogger( const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>(), await importOriginal<typeof import('@google/gemini-cli-core')>();
{ stripAnsi: true }, const mocked = mockCoreDebugLogger(actual, { stripAnsi: true });
); return { ...mocked, getErrorMessage: vi.fn() };
}); });
vi.mock('../../config/extension-manager.js'); vi.mock('../../config/extension-manager.js');
vi.mock('../../config/settings.js'); vi.mock('../../config/settings.js');
vi.mock('../../utils/errors.js');
vi.mock('../../config/extensions/consent.js', () => ({ vi.mock('../../config/extensions/consent.js', () => ({
requestConsentNonInteractive: vi.fn(), requestConsentNonInteractive: vi.fn(),
})); }));
+1 -1
View File
@@ -8,10 +8,10 @@ import type { CommandModule } from 'yargs';
import chalk from 'chalk'; import chalk from 'chalk';
import { import {
debugLogger, debugLogger,
getErrorMessage,
type ExtensionInstallMetadata, type ExtensionInstallMetadata,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { getErrorMessage } from '../../utils/errors.js';
import { import {
INSTALL_WARNING_MESSAGE, INSTALL_WARNING_MESSAGE,
requestConsentNonInteractive, requestConsentNonInteractive,
@@ -5,27 +5,23 @@
*/ */
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { coreEvents } from '@google/gemini-cli-core'; import { coreEvents, getErrorMessage } from '@google/gemini-cli-core';
import { handleList, listCommand } from './list.js'; import { handleList, listCommand } from './list.js';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js'; import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js';
vi.mock('@google/gemini-cli-core', async (importOriginal) => { vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const { mockCoreDebugLogger } = await import( const { mockCoreDebugLogger } = await import(
'../../test-utils/mockDebugLogger.js' '../../test-utils/mockDebugLogger.js'
); );
return mockCoreDebugLogger( const actual =
await importOriginal<typeof import('@google/gemini-cli-core')>(), await importOriginal<typeof import('@google/gemini-cli-core')>();
{ const mocked = mockCoreDebugLogger(actual, { stripAnsi: false });
stripAnsi: false, return { ...mocked, getErrorMessage: vi.fn() };
},
);
}); });
vi.mock('../../config/extension-manager.js'); vi.mock('../../config/extension-manager.js');
vi.mock('../../config/settings.js'); vi.mock('../../config/settings.js');
vi.mock('../../utils/errors.js');
vi.mock('../../config/extensions/consent.js', () => ({ vi.mock('../../config/extensions/consent.js', () => ({
requestConsentNonInteractive: vi.fn(), requestConsentNonInteractive: vi.fn(),
})); }));
+1 -2
View File
@@ -5,8 +5,7 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { getErrorMessage } from '../../utils/errors.js'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { loadSettings } from '../../config/settings.js'; import { loadSettings } from '../../config/settings.js';
@@ -18,7 +18,7 @@ import { type Argv } from 'yargs';
import { handleUninstall, uninstallCommand } from './uninstall.js'; import { handleUninstall, uninstallCommand } from './uninstall.js';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings, type LoadedSettings } from '../../config/settings.js'; import { loadSettings, type LoadedSettings } from '../../config/settings.js';
import { getErrorMessage } from '../../utils/errors.js'; import { getErrorMessage } from '@google/gemini-cli-core';
// NOTE: This file uses vi.hoisted() mocks to enable testing of sequential // NOTE: This file uses vi.hoisted() mocks to enable testing of sequential
// mock behaviors (mockResolvedValueOnce/mockRejectedValueOnce chaining). // mock behaviors (mockResolvedValueOnce/mockRejectedValueOnce chaining).
@@ -66,11 +66,11 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
emitConsoleLog, emitConsoleLog,
}, },
debugLogger, debugLogger,
getErrorMessage: vi.fn(),
}; };
}); });
vi.mock('../../config/settings.js'); vi.mock('../../config/settings.js');
vi.mock('../../utils/errors.js');
vi.mock('../../config/extensions/consent.js', () => ({ vi.mock('../../config/extensions/consent.js', () => ({
requestConsentNonInteractive: vi.fn(), requestConsentNonInteractive: vi.fn(),
})); }));
@@ -5,8 +5,7 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { getErrorMessage } from '../../utils/errors.js'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import { debugLogger } from '@google/gemini-cli-core';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { loadSettings } from '../../config/settings.js'; import { loadSettings } from '../../config/settings.js';
@@ -12,9 +12,12 @@ import {
updateExtension, updateExtension,
} from '../../config/extensions/update.js'; } from '../../config/extensions/update.js';
import { checkForExtensionUpdate } from '../../config/extensions/github.js'; import { checkForExtensionUpdate } from '../../config/extensions/github.js';
import { getErrorMessage } from '../../utils/errors.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js'; import { ExtensionUpdateState } from '../../ui/state/extensions.js';
import { coreEvents, debugLogger } from '@google/gemini-cli-core'; import {
coreEvents,
debugLogger,
getErrorMessage,
} from '@google/gemini-cli-core';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
import { loadSettings } from '../../config/settings.js'; import { loadSettings } from '../../config/settings.js';
@@ -5,11 +5,10 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { debugLogger } from '@google/gemini-cli-core'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import semver from 'semver'; import semver from 'semver';
import { getErrorMessage } from '../../utils/errors.js';
import type { ExtensionConfig } from '../../config/extension.js'; import type { ExtensionConfig } from '../../config/extension.js';
import { ExtensionManager } from '../../config/extension-manager.js'; import { ExtensionManager } from '../../config/extension-manager.js';
import { requestConsentNonInteractive } from '../../config/extensions/consent.js'; import { requestConsentNonInteractive } from '../../config/extensions/consent.js';
@@ -28,6 +28,9 @@ const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
vi.mock('@google/gemini-cli-core', () => ({ vi.mock('@google/gemini-cli-core', () => ({
debugLogger, debugLogger,
getErrorMessage: vi.fn((e: unknown) =>
e instanceof Error ? e.message : String(e),
),
})); }));
import { handleInstall, installCommand } from './install.js'; import { handleInstall, installCommand } from './install.js';
+5 -2
View File
@@ -5,8 +5,11 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { debugLogger, type SkillDefinition } from '@google/gemini-cli-core'; import {
import { getErrorMessage } from '../../utils/errors.js'; debugLogger,
type SkillDefinition,
getErrorMessage,
} from '@google/gemini-cli-core';
import { exitCli } from '../utils.js'; import { exitCli } from '../utils.js';
import { installSkill } from '../../utils/skillUtils.js'; import { installSkill } from '../../utils/skillUtils.js';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -24,6 +24,9 @@ const { debugLogger } = await vi.hoisted(async () => {
vi.mock('@google/gemini-cli-core', () => ({ vi.mock('@google/gemini-cli-core', () => ({
debugLogger, debugLogger,
getErrorMessage: vi.fn((e: unknown) =>
e instanceof Error ? e.message : String(e),
),
})); }));
vi.mock('../../config/extensions/consent.js', () => ({ vi.mock('../../config/extensions/consent.js', () => ({
+1 -2
View File
@@ -5,10 +5,9 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { debugLogger } from '@google/gemini-cli-core'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import chalk from 'chalk'; import chalk from 'chalk';
import { getErrorMessage } from '../../utils/errors.js';
import { exitCli } from '../utils.js'; import { exitCli } from '../utils.js';
import { import {
requestConsentNonInteractive, requestConsentNonInteractive,
@@ -21,6 +21,9 @@ const { debugLogger, emitConsoleLog } = await vi.hoisted(async () => {
vi.mock('@google/gemini-cli-core', () => ({ vi.mock('@google/gemini-cli-core', () => ({
debugLogger, debugLogger,
getErrorMessage: vi.fn((e: unknown) =>
e instanceof Error ? e.message : String(e),
),
})); }));
import { handleUninstall, uninstallCommand } from './uninstall.js'; import { handleUninstall, uninstallCommand } from './uninstall.js';
@@ -5,8 +5,7 @@
*/ */
import type { CommandModule } from 'yargs'; import type { CommandModule } from 'yargs';
import { debugLogger } from '@google/gemini-cli-core'; import { debugLogger, getErrorMessage } from '@google/gemini-cli-core';
import { getErrorMessage } from '../../utils/errors.js';
import { exitCli } from '../utils.js'; import { exitCli } from '../utils.js';
import { uninstallSkill } from '../../utils/skillUtils.js'; import { uninstallSkill } from '../../utils/skillUtils.js';
import chalk from 'chalk'; import chalk from 'chalk';
+1 -1
View File
@@ -5,9 +5,9 @@
*/ */
import { simpleGit } from 'simple-git'; import { simpleGit } from 'simple-git';
import { getErrorMessage } from '../../utils/errors.js';
import { import {
debugLogger, debugLogger,
getErrorMessage,
type ExtensionInstallMetadata, type ExtensionInstallMetadata,
type GeminiCLIExtension, type GeminiCLIExtension,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
+5 -2
View File
@@ -11,9 +11,12 @@ import {
} from '../../ui/state/extensions.js'; } from '../../ui/state/extensions.js';
import { loadInstallMetadata } from '../extension.js'; import { loadInstallMetadata } from '../extension.js';
import { checkForExtensionUpdate } from './github.js'; import { checkForExtensionUpdate } from './github.js';
import { debugLogger, type GeminiCLIExtension } from '@google/gemini-cli-core'; import {
debugLogger,
getErrorMessage,
type GeminiCLIExtension,
} from '@google/gemini-cli-core';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { getErrorMessage } from '../../utils/errors.js';
import { copyExtension, type ExtensionManager } from '../extension-manager.js'; import { copyExtension, type ExtensionManager } from '../extension-manager.js';
import { ExtensionStorage } from './storage.js'; import { ExtensionStorage } from './storage.js';
@@ -7,10 +7,10 @@
import { import {
debugLogger, debugLogger,
listExtensions, listExtensions,
getErrorMessage,
type ExtensionInstallMetadata, type ExtensionInstallMetadata,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import type { ExtensionUpdateInfo } from '../../config/extension.js'; import type { ExtensionUpdateInfo } from '../../config/extension.js';
import { getErrorMessage } from '../../utils/errors.js';
import { import {
emptyIcon, emptyIcon,
MessageType, MessageType,
@@ -16,9 +16,8 @@ import {
MessageType, MessageType,
} from '../types.js'; } from '../types.js';
import { disableSkill, enableSkill } from '../../utils/skillSettings.js'; import { disableSkill, enableSkill } from '../../utils/skillSettings.js';
import { getErrorMessage } from '../../utils/errors.js';
import { getAdminErrorMessage } from '@google/gemini-cli-core'; import { getAdminErrorMessage, getErrorMessage } from '@google/gemini-cli-core';
import { import {
linkSkill, linkSkill,
renderSkillActionFeedback, renderSkillActionFeedback,
@@ -7,9 +7,9 @@
import { import {
debugLogger, debugLogger,
checkExhaustive, checkExhaustive,
getErrorMessage,
type GeminiCLIExtension, type GeminiCLIExtension,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { getErrorMessage } from '../../utils/errors.js';
import { import {
ExtensionUpdateState, ExtensionUpdateState,
extensionUpdatesReducer, extensionUpdatesReducer,
-20
View File
@@ -21,7 +21,6 @@ import {
coreEvents, coreEvents,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { import {
getErrorMessage,
handleError, handleError,
handleToolError, handleToolError,
handleCancellationError, handleCancellationError,
@@ -152,25 +151,6 @@ describe('errors', () => {
processExitSpy.mockRestore(); processExitSpy.mockRestore();
}); });
describe('getErrorMessage', () => {
it('should return error message for Error instances', () => {
const error = new Error('Test error message');
expect(getErrorMessage(error)).toBe('Test error message');
});
it('should convert non-Error values to strings', () => {
expect(getErrorMessage('string error')).toBe('string error');
expect(getErrorMessage(123)).toBe('123');
expect(getErrorMessage(null)).toBe('null');
expect(getErrorMessage(undefined)).toBe('undefined');
});
it('should handle objects', () => {
const obj = { message: 'test' };
expect(getErrorMessage(obj)).toBe('[object Object]');
});
});
describe('handleError', () => { describe('handleError', () => {
describe('in text mode', () => { describe('in text mode', () => {
beforeEach(() => { beforeEach(() => {
+1 -7
View File
@@ -18,16 +18,10 @@ import {
isFatalToolError, isFatalToolError,
debugLogger, debugLogger,
coreEvents, coreEvents,
getErrorMessage,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { runSyncCleanup } from './cleanup.js'; import { runSyncCleanup } from './cleanup.js';
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
return String(error);
}
interface ErrorWithCode extends Error { interface ErrorWithCode extends Error {
exitCode?: number; exitCode?: number;
code?: string | number; code?: string | number;