mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-25 05:21:03 -07:00
Add ExtensionLoader interface, use that on Config object (#12116)
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type Mock,
|
||||
type MockedObject,
|
||||
} from 'vitest';
|
||||
import { render, cleanup } from 'ink-testing-library';
|
||||
import { AppContainer } from './AppContainer.js';
|
||||
@@ -131,11 +132,13 @@ import { useKeypress, type Key } from './hooks/useKeypress.js';
|
||||
import { measureElement } from 'ink';
|
||||
import { useTerminalSize } from './hooks/useTerminalSize.js';
|
||||
import { ShellExecutionService } from '@google/gemini-cli-core';
|
||||
import { type ExtensionManager } from '../config/extension-manager.js';
|
||||
|
||||
describe('AppContainer State Management', () => {
|
||||
let mockConfig: Config;
|
||||
let mockSettings: LoadedSettings;
|
||||
let mockInitResult: InitializationResult;
|
||||
let mockExtensionManager: MockedObject<ExtensionManager>;
|
||||
|
||||
// Create typed mocks for all hooks
|
||||
const mockedUseQuotaAndFallback = useQuotaAndFallback as Mock;
|
||||
@@ -282,6 +285,15 @@ describe('AppContainer State Management', () => {
|
||||
// Mock config's getTargetDir to return consistent workspace directory
|
||||
vi.spyOn(mockConfig, 'getTargetDir').mockReturnValue('/test/workspace');
|
||||
|
||||
mockExtensionManager = vi.mockObject({
|
||||
getExtensions: vi.fn().mockReturnValue([]),
|
||||
setRequestConsent: vi.fn(),
|
||||
setRequestSetting: vi.fn(),
|
||||
} as unknown as ExtensionManager);
|
||||
vi.spyOn(mockConfig, 'getExtensionLoader').mockReturnValue(
|
||||
mockExtensionManager,
|
||||
);
|
||||
|
||||
// Mock LoadedSettings
|
||||
mockSettings = {
|
||||
merged: {
|
||||
|
||||
@@ -98,7 +98,7 @@ import {
|
||||
useExtensionUpdates,
|
||||
} from './hooks/useExtensionUpdates.js';
|
||||
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
||||
import { ExtensionManager } from '../config/extension-manager.js';
|
||||
import { type ExtensionManager } from '../config/extension-manager.js';
|
||||
import { requestConsentInteractive } from '../config/extensions/consent.js';
|
||||
|
||||
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
||||
@@ -168,21 +168,12 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
null,
|
||||
);
|
||||
|
||||
const extensions = config.getExtensions();
|
||||
const [extensionManager] = useState<ExtensionManager>(
|
||||
new ExtensionManager({
|
||||
enabledExtensionOverrides: config.getEnabledExtensions(),
|
||||
workspaceDir: config.getWorkingDir(),
|
||||
requestConsent: (description) =>
|
||||
requestConsentInteractive(
|
||||
description,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
),
|
||||
// TODO: Support requesting settings in the interactive CLI
|
||||
requestSetting: null,
|
||||
loadedSettings: settings,
|
||||
}),
|
||||
const extensionManager = config.getExtensionLoader() as ExtensionManager;
|
||||
// We are in the interactive CLI, update how we request consent and settings.
|
||||
extensionManager.setRequestConsent((description) =>
|
||||
requestConsentInteractive(description, addConfirmUpdateExtensionRequest),
|
||||
);
|
||||
extensionManager.setRequestSetting();
|
||||
|
||||
const { addConfirmUpdateExtensionRequest, confirmUpdateExtensionRequests } =
|
||||
useConfirmUpdateRequests();
|
||||
@@ -190,7 +181,7 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
extensionsUpdateState,
|
||||
extensionsUpdateStateInternal,
|
||||
dispatchExtensionStateUpdate,
|
||||
} = useExtensionUpdates(extensions, extensionManager, historyManager.addItem);
|
||||
} = useExtensionUpdates(extensionManager, historyManager.addItem);
|
||||
|
||||
const [isPermissionsDialogOpen, setPermissionsDialogOpen] = useState(false);
|
||||
const openPermissionsDialog = useCallback(
|
||||
@@ -548,7 +539,7 @@ Logging in with Google... Please restart Gemini CLI to continue.
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
settings.merged,
|
||||
config.getExtensions(),
|
||||
config.getExtensionLoader(),
|
||||
config.isTrustedFolder(),
|
||||
settings.merged.context?.importFormat || 'tree', // Use setting or default to 'tree'
|
||||
config.getFileFilteringOptions(),
|
||||
|
||||
@@ -103,7 +103,7 @@ export const directoryCommand: SlashCommand = {
|
||||
],
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
config.getExtensions(),
|
||||
config.getExtensionLoader(),
|
||||
config.getFolderTrust(),
|
||||
context.services.settings.merged.context?.importFormat ||
|
||||
'tree', // Use setting or default to 'tree'
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MessageType } from '../types.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import {
|
||||
getErrorMessage,
|
||||
SimpleExtensionLoader,
|
||||
type FileDiscoveryService,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { LoadServerHierarchicalMemoryResponse } from '@google/gemini-cli-core/index.js';
|
||||
@@ -72,6 +73,7 @@ describe('memoryCommand', () => {
|
||||
config: {
|
||||
getUserMemory: mockGetUserMemory,
|
||||
getGeminiMdFileCount: mockGetGeminiMdFileCount,
|
||||
getExtensionLoader: () => new SimpleExtensionLoader([]),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -176,6 +178,7 @@ describe('memoryCommand', () => {
|
||||
getWorkingDir: () => '/test/dir',
|
||||
getDebugMode: () => false,
|
||||
getFileService: () => ({}) as FileDiscoveryService,
|
||||
getExtensionLoader: () => new SimpleExtensionLoader([]),
|
||||
getExtensions: () => [],
|
||||
shouldLoadMemoryFromIncludeDirectories: () => false,
|
||||
getWorkspaceContext: () => ({
|
||||
|
||||
@@ -91,7 +91,7 @@ export const memoryCommand: SlashCommand = {
|
||||
config.getDebugMode(),
|
||||
config.getFileService(),
|
||||
settings.merged,
|
||||
config.getExtensions(),
|
||||
config.getExtensionLoader(),
|
||||
config.isTrustedFolder(),
|
||||
settings.merged.context?.importFormat || 'tree',
|
||||
config.getFileFilteringOptions(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { createExtension } from '../../test-utils/createExtension.js';
|
||||
import { useExtensionUpdates } from './useExtensionUpdates.js';
|
||||
import { GEMINI_DIR, type GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
import { GEMINI_DIR } from '@google/gemini-cli-core';
|
||||
import { render } from 'ink-testing-library';
|
||||
import { MessageType } from '../types.js';
|
||||
import {
|
||||
@@ -57,7 +57,7 @@ describe('useExtensionUpdates', () => {
|
||||
workspaceDir: tempHomeDir,
|
||||
requestConsent: vi.fn(),
|
||||
requestSetting: vi.fn(),
|
||||
loadedSettings: loadSettings(),
|
||||
settings: loadSettings().merged,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,11 +66,10 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
it('should check for updates and log a message if an update is available', async () => {
|
||||
const extensions = [
|
||||
vi.spyOn(extensionManager, 'getExtensions').mockReturnValue([
|
||||
{
|
||||
name: 'test-extension',
|
||||
id: 'test-extension-id',
|
||||
type: 'git',
|
||||
version: '1.0.0',
|
||||
path: '/some/path',
|
||||
isActive: true,
|
||||
@@ -81,7 +80,7 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
contextFiles: [],
|
||||
},
|
||||
];
|
||||
]);
|
||||
const addItem = vi.fn();
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
@@ -97,11 +96,7 @@ describe('useExtensionUpdates', () => {
|
||||
);
|
||||
|
||||
function TestComponent() {
|
||||
useExtensionUpdates(
|
||||
extensions as GeminiCLIExtension[],
|
||||
extensionManager,
|
||||
addItem,
|
||||
);
|
||||
useExtensionUpdates(extensionManager, addItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -119,7 +114,7 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
it('should check for updates and automatically update if autoUpdate is true', async () => {
|
||||
const extensionDir = createExtension({
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'test-extension',
|
||||
version: '1.0.0',
|
||||
@@ -129,7 +124,6 @@ describe('useExtensionUpdates', () => {
|
||||
autoUpdate: true,
|
||||
},
|
||||
});
|
||||
const extension = extensionManager.loadExtension(extensionDir)!;
|
||||
|
||||
const addItem = vi.fn();
|
||||
|
||||
@@ -151,8 +145,9 @@ describe('useExtensionUpdates', () => {
|
||||
name: '',
|
||||
});
|
||||
|
||||
extensionManager.loadExtensions();
|
||||
function TestComponent() {
|
||||
useExtensionUpdates([extension], extensionManager, addItem);
|
||||
useExtensionUpdates(extensionManager, addItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -173,7 +168,7 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
it('should batch update notifications for multiple extensions', async () => {
|
||||
const extensionDir1 = createExtension({
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'test-extension-1',
|
||||
version: '1.0.0',
|
||||
@@ -183,7 +178,7 @@ describe('useExtensionUpdates', () => {
|
||||
autoUpdate: true,
|
||||
},
|
||||
});
|
||||
const extensionDir2 = createExtension({
|
||||
createExtension({
|
||||
extensionsDir: userExtensionsDir,
|
||||
name: 'test-extension-2',
|
||||
version: '2.0.0',
|
||||
@@ -194,10 +189,7 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const extensions = [
|
||||
extensionManager.loadExtension(extensionDir1)!,
|
||||
extensionManager.loadExtension(extensionDir2)!,
|
||||
];
|
||||
extensionManager.loadExtensions();
|
||||
|
||||
const addItem = vi.fn();
|
||||
|
||||
@@ -233,7 +225,7 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
function TestComponent() {
|
||||
useExtensionUpdates(extensions, extensionManager, addItem);
|
||||
useExtensionUpdates(extensionManager, addItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -262,11 +254,10 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
it('should batch update notifications for multiple extensions with autoUpdate: false', async () => {
|
||||
const extensions = [
|
||||
vi.spyOn(extensionManager, 'getExtensions').mockReturnValue([
|
||||
{
|
||||
name: 'test-extension-1',
|
||||
id: 'test-extension-1-id',
|
||||
type: 'git',
|
||||
version: '1.0.0',
|
||||
path: '/some/path1',
|
||||
isActive: true,
|
||||
@@ -281,7 +272,6 @@ describe('useExtensionUpdates', () => {
|
||||
name: 'test-extension-2',
|
||||
id: 'test-extension-2-id',
|
||||
|
||||
type: 'git',
|
||||
version: '2.0.0',
|
||||
path: '/some/path2',
|
||||
isActive: true,
|
||||
@@ -292,7 +282,7 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
contextFiles: [],
|
||||
},
|
||||
];
|
||||
]);
|
||||
const addItem = vi.fn();
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
@@ -318,11 +308,7 @@ describe('useExtensionUpdates', () => {
|
||||
);
|
||||
|
||||
function TestComponent() {
|
||||
useExtensionUpdates(
|
||||
extensions as GeminiCLIExtension[],
|
||||
extensionManager,
|
||||
addItem,
|
||||
);
|
||||
useExtensionUpdates(extensionManager, addItem);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ export const useConfirmUpdateRequests = () => {
|
||||
};
|
||||
|
||||
export const useExtensionUpdates = (
|
||||
extensions: GeminiCLIExtension[],
|
||||
extensionManager: ExtensionManager,
|
||||
addItem: UseHistoryManagerReturn['addItem'],
|
||||
) => {
|
||||
@@ -86,6 +85,7 @@ export const useExtensionUpdates = (
|
||||
extensionUpdatesReducer,
|
||||
initialExtensionUpdatesState,
|
||||
);
|
||||
const extensions = extensionManager.getExtensions();
|
||||
|
||||
useEffect(() => {
|
||||
const extensionsToCheck = extensions.filter((extension) => {
|
||||
|
||||
Reference in New Issue
Block a user