mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-06 03:10:42 -07:00
Create ExtensionManager class which manages all high level extension tasks (#11667)
This commit is contained in:
@@ -93,9 +93,13 @@ import { useMessageQueue } from './hooks/useMessageQueue.js';
|
||||
import { useAutoAcceptIndicator } from './hooks/useAutoAcceptIndicator.js';
|
||||
import { useSessionStats } from './contexts/SessionContext.js';
|
||||
import { useGitBranchName } from './hooks/useGitBranchName.js';
|
||||
import { useExtensionUpdates } from './hooks/useExtensionUpdates.js';
|
||||
import {
|
||||
useConfirmUpdateRequests,
|
||||
useExtensionUpdates,
|
||||
} from './hooks/useExtensionUpdates.js';
|
||||
import { ShellFocusContext } from './contexts/ShellFocusContext.js';
|
||||
import { ExtensionEnablementManager } from '../config/extensions/extensionEnablement.js';
|
||||
import { ExtensionManager } from '../config/extension-manager.js';
|
||||
import { requestConsentInteractive } from '../config/extensions/consent.js';
|
||||
|
||||
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
||||
const QUEUE_ERROR_DISPLAY_DURATION_MS = 3000;
|
||||
@@ -165,21 +169,28 @@ export const AppContainer = (props: AppContainerProps) => {
|
||||
);
|
||||
|
||||
const extensions = config.getExtensions();
|
||||
const [extensionEnablementManager] = useState<ExtensionEnablementManager>(
|
||||
new ExtensionEnablementManager(config.getEnabledExtensions()),
|
||||
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 { addConfirmUpdateExtensionRequest, confirmUpdateExtensionRequests } =
|
||||
useConfirmUpdateRequests();
|
||||
const {
|
||||
extensionsUpdateState,
|
||||
extensionsUpdateStateInternal,
|
||||
dispatchExtensionStateUpdate,
|
||||
confirmUpdateExtensionRequests,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
} = useExtensionUpdates(
|
||||
extensions,
|
||||
extensionEnablementManager,
|
||||
historyManager.addItem,
|
||||
config.getWorkingDir(),
|
||||
);
|
||||
} = useExtensionUpdates(extensions, extensionManager, historyManager.addItem);
|
||||
|
||||
const [isPermissionsDialogOpen, setPermissionsDialogOpen] = useState(false);
|
||||
const openPermissionsDialog = useCallback(
|
||||
|
||||
@@ -8,7 +8,6 @@ import { vi } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { loadExtension } from '../../config/extension.js';
|
||||
import { createExtension } from '../../test-utils/createExtension.js';
|
||||
import { useExtensionUpdates } from './useExtensionUpdates.js';
|
||||
import { GEMINI_DIR, type GeminiCLIExtension } from '@google/gemini-cli-core';
|
||||
@@ -19,7 +18,8 @@ import {
|
||||
updateExtension,
|
||||
} from '../../config/extensions/update.js';
|
||||
import { ExtensionUpdateState } from '../state/extensions.js';
|
||||
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
|
||||
import { ExtensionManager } from '../../config/extension-manager.js';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
const mockedOs = await importOriginal<typeof os>();
|
||||
@@ -36,17 +36,29 @@ vi.mock('../../config/extensions/update.js', () => ({
|
||||
|
||||
describe('useExtensionUpdates', () => {
|
||||
let tempHomeDir: string;
|
||||
let tempWorkspaceDir: string;
|
||||
let userExtensionsDir: string;
|
||||
let extensionManager: ExtensionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
||||
tempWorkspaceDir = fs.mkdtempSync(
|
||||
path.join(tempHomeDir, 'gemini-cli-test-workspace-'),
|
||||
);
|
||||
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
||||
userExtensionsDir = path.join(tempHomeDir, GEMINI_DIR, 'extensions');
|
||||
fs.mkdirSync(userExtensionsDir, { recursive: true });
|
||||
vi.mocked(checkForAllExtensionUpdates).mockReset();
|
||||
vi.mocked(updateExtension).mockReset();
|
||||
extensionManager = new ExtensionManager({
|
||||
workspaceDir: tempHomeDir,
|
||||
requestConsent: vi.fn(),
|
||||
requestSetting: vi.fn(),
|
||||
loadedSettings: loadSettings(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -71,10 +83,9 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
];
|
||||
const addItem = vi.fn();
|
||||
const cwd = '/test/cwd';
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
async (_extensions, _extensionEnablementManager, dispatch, _cwd) => {
|
||||
async (_extensions, _extensionManager, dispatch) => {
|
||||
dispatch({
|
||||
type: 'SET_STATE',
|
||||
payload: {
|
||||
@@ -88,9 +99,8 @@ describe('useExtensionUpdates', () => {
|
||||
renderHook(() =>
|
||||
useExtensionUpdates(
|
||||
extensions as GeminiCLIExtension[],
|
||||
new ExtensionEnablementManager(),
|
||||
extensionManager,
|
||||
addItem,
|
||||
cwd,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -116,17 +126,12 @@ describe('useExtensionUpdates', () => {
|
||||
autoUpdate: true,
|
||||
},
|
||||
});
|
||||
const extensionEnablementManager = new ExtensionEnablementManager();
|
||||
const extension = loadExtension({
|
||||
extensionDir,
|
||||
workspaceDir: tempHomeDir,
|
||||
extensionEnablementManager,
|
||||
})!;
|
||||
const extension = extensionManager.loadExtension(extensionDir)!;
|
||||
|
||||
const addItem = vi.fn();
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
async (_extensions, _extensionEnablementManager, dispatch, _cwd) => {
|
||||
async (_extensions, _extensionManager, dispatch) => {
|
||||
dispatch({
|
||||
type: 'SET_STATE',
|
||||
payload: {
|
||||
@@ -144,12 +149,7 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useExtensionUpdates(
|
||||
[extension],
|
||||
extensionEnablementManager,
|
||||
addItem,
|
||||
tempHomeDir,
|
||||
),
|
||||
useExtensionUpdates([extension], extensionManager, addItem),
|
||||
);
|
||||
|
||||
await waitFor(
|
||||
@@ -188,24 +188,15 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const extensionEnablementManager = new ExtensionEnablementManager();
|
||||
const extensions = [
|
||||
loadExtension({
|
||||
extensionDir: extensionDir1,
|
||||
workspaceDir: tempHomeDir,
|
||||
extensionEnablementManager,
|
||||
})!,
|
||||
loadExtension({
|
||||
extensionDir: extensionDir2,
|
||||
workspaceDir: tempHomeDir,
|
||||
extensionEnablementManager,
|
||||
})!,
|
||||
extensionManager.loadExtension(extensionDir1)!,
|
||||
extensionManager.loadExtension(extensionDir2)!,
|
||||
];
|
||||
|
||||
const addItem = vi.fn();
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
async (_extensions, _extensionEnablementManager, dispatch, _cwd) => {
|
||||
async (_extensions, _extensionManager, dispatch) => {
|
||||
dispatch({
|
||||
type: 'SET_STATE',
|
||||
payload: {
|
||||
@@ -236,12 +227,7 @@ describe('useExtensionUpdates', () => {
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useExtensionUpdates(
|
||||
extensions,
|
||||
extensionEnablementManager,
|
||||
addItem,
|
||||
tempHomeDir,
|
||||
),
|
||||
useExtensionUpdates(extensions, extensionManager, addItem),
|
||||
);
|
||||
|
||||
await waitFor(
|
||||
@@ -299,10 +285,9 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
];
|
||||
const addItem = vi.fn();
|
||||
const cwd = '/test/cwd';
|
||||
|
||||
vi.mocked(checkForAllExtensionUpdates).mockImplementation(
|
||||
async (_extensions, _extensionEnablementManager, dispatch, _cwd) => {
|
||||
async (_extensions, _extensionManager, dispatch) => {
|
||||
dispatch({ type: 'BATCH_CHECK_START' });
|
||||
dispatch({
|
||||
type: 'SET_STATE',
|
||||
@@ -323,13 +308,11 @@ describe('useExtensionUpdates', () => {
|
||||
},
|
||||
);
|
||||
|
||||
const extensionEnablementManager = new ExtensionEnablementManager();
|
||||
renderHook(() =>
|
||||
useExtensionUpdates(
|
||||
extensions as GeminiCLIExtension[],
|
||||
extensionEnablementManager,
|
||||
extensionManager,
|
||||
addItem,
|
||||
cwd,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -18,12 +18,9 @@ import {
|
||||
checkForAllExtensionUpdates,
|
||||
updateExtension,
|
||||
} from '../../config/extensions/update.js';
|
||||
import {
|
||||
requestConsentInteractive,
|
||||
type ExtensionUpdateInfo,
|
||||
} from '../../config/extension.js';
|
||||
import { type ExtensionUpdateInfo } from '../../config/extension.js';
|
||||
import { checkExhaustive } from '../../utils/checks.js';
|
||||
import type { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
|
||||
import type { ExtensionManager } from '../../config/extension-manager.js';
|
||||
|
||||
type ConfirmationRequestWrapper = {
|
||||
prompt: React.ReactNode;
|
||||
@@ -48,16 +45,7 @@ function confirmationRequestsReducer(
|
||||
}
|
||||
}
|
||||
|
||||
export const useExtensionUpdates = (
|
||||
extensions: GeminiCLIExtension[],
|
||||
extensionEnablementManager: ExtensionEnablementManager,
|
||||
addItem: UseHistoryManagerReturn['addItem'],
|
||||
cwd: string,
|
||||
) => {
|
||||
const [extensionsUpdateState, dispatchExtensionStateUpdate] = useReducer(
|
||||
extensionUpdatesReducer,
|
||||
initialExtensionUpdatesState,
|
||||
);
|
||||
export const useConfirmUpdateRequests = () => {
|
||||
const [
|
||||
confirmUpdateExtensionRequests,
|
||||
dispatchConfirmUpdateExtensionRequests,
|
||||
@@ -82,6 +70,22 @@ export const useExtensionUpdates = (
|
||||
},
|
||||
[dispatchConfirmUpdateExtensionRequests],
|
||||
);
|
||||
return {
|
||||
addConfirmUpdateExtensionRequest,
|
||||
confirmUpdateExtensionRequests,
|
||||
dispatchConfirmUpdateExtensionRequests,
|
||||
};
|
||||
};
|
||||
|
||||
export const useExtensionUpdates = (
|
||||
extensions: GeminiCLIExtension[],
|
||||
extensionManager: ExtensionManager,
|
||||
addItem: UseHistoryManagerReturn['addItem'],
|
||||
) => {
|
||||
const [extensionsUpdateState, dispatchExtensionStateUpdate] = useReducer(
|
||||
extensionUpdatesReducer,
|
||||
initialExtensionUpdatesState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const extensionsToCheck = extensions.filter((extension) => {
|
||||
@@ -95,15 +99,13 @@ export const useExtensionUpdates = (
|
||||
if (extensionsToCheck.length === 0) return;
|
||||
checkForAllExtensionUpdates(
|
||||
extensionsToCheck,
|
||||
extensionEnablementManager,
|
||||
extensionManager,
|
||||
dispatchExtensionStateUpdate,
|
||||
cwd,
|
||||
);
|
||||
}, [
|
||||
extensions,
|
||||
extensionEnablementManager,
|
||||
extensionManager,
|
||||
extensionsUpdateState.extensionStatuses,
|
||||
cwd,
|
||||
dispatchExtensionStateUpdate,
|
||||
]);
|
||||
|
||||
@@ -158,13 +160,7 @@ export const useExtensionUpdates = (
|
||||
} else {
|
||||
const updatePromise = updateExtension(
|
||||
extension,
|
||||
extensionEnablementManager,
|
||||
cwd,
|
||||
(description) =>
|
||||
requestConsentInteractive(
|
||||
description,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
),
|
||||
extensionManager,
|
||||
currentState.status,
|
||||
dispatchExtensionStateUpdate,
|
||||
);
|
||||
@@ -213,14 +209,7 @@ export const useExtensionUpdates = (
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [
|
||||
extensions,
|
||||
extensionEnablementManager,
|
||||
extensionsUpdateState,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
addItem,
|
||||
cwd,
|
||||
]);
|
||||
}, [extensions, extensionManager, extensionsUpdateState, addItem]);
|
||||
|
||||
const extensionsUpdateStateComputed = useMemo(() => {
|
||||
const result = new Map<string, ExtensionUpdateState>();
|
||||
@@ -237,7 +226,5 @@ export const useExtensionUpdates = (
|
||||
extensionsUpdateState: extensionsUpdateStateComputed,
|
||||
extensionsUpdateStateInternal: extensionsUpdateState.extensionStatuses,
|
||||
dispatchExtensionStateUpdate,
|
||||
confirmUpdateExtensionRequests,
|
||||
addConfirmUpdateExtensionRequest,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user