Fix -e <extension> for disabled extensions (#9994)

This commit is contained in:
Jacob MacDonald
2025-09-29 06:53:19 -07:00
committed by GitHub
parent d1485d4672
commit ea061f52b0
12 changed files with 1260 additions and 208 deletions
+10 -2
View File
@@ -8,6 +8,7 @@ import type { CommandModule } from 'yargs';
import {
loadExtensions,
annotateActiveExtensions,
ExtensionStorage,
requestConsentNonInteractive,
} from '../../config/extension.js';
import {
@@ -19,6 +20,7 @@ import {
import { checkForExtensionUpdate } from '../../config/extensions/github.js';
import { getErrorMessage } from '../../utils/errors.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
interface UpdateArgs {
name?: string;
@@ -30,11 +32,17 @@ const updateOutput = (info: ExtensionUpdateInfo) =>
export async function handleUpdate(args: UpdateArgs) {
const workingDir = process.cwd();
const allExtensions = loadExtensions();
const extensionEnablementManager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
// Force enable named extensions, otherwise we will only update the enabled
// ones.
args.name ? [args.name] : [],
);
const allExtensions = loadExtensions(extensionEnablementManager);
const extensions = annotateActiveExtensions(
allExtensions,
allExtensions.map((e) => e.config.name),
workingDir,
extensionEnablementManager,
);
if (args.name) {
try {
+8 -1
View File
@@ -7,7 +7,7 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { listMcpServers } from './list.js';
import { loadSettings } from '../../config/settings.js';
import { loadExtensions } from '../../config/extension.js';
import { ExtensionStorage, loadExtensions } from '../../config/extension.js';
import { createTransport } from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -16,6 +16,9 @@ vi.mock('../../config/settings.js', () => ({
}));
vi.mock('../../config/extension.js', () => ({
loadExtensions: vi.fn(),
ExtensionStorage: {
getUserExtensionsDir: vi.fn(),
},
}));
vi.mock('@google/gemini-cli-core', () => ({
createTransport: vi.fn(),
@@ -34,6 +37,7 @@ vi.mock('@google/gemini-cli-core', () => ({
}));
vi.mock('@modelcontextprotocol/sdk/client/index.js');
const mockedExtensionStorage = ExtensionStorage as vi.Mock;
const mockedLoadSettings = loadSettings as vi.Mock;
const mockedLoadExtensions = loadExtensions as vi.Mock;
const mockedCreateTransport = createTransport as vi.Mock;
@@ -69,6 +73,9 @@ describe('mcp list command', () => {
MockedClient.mockImplementation(() => mockClient);
mockedCreateTransport.mockResolvedValue(mockTransport);
mockedLoadExtensions.mockReturnValue([]);
mockedExtensionStorage.getUserExtensionsDir.mockReturnValue(
'/mocked/extensions/dir',
);
});
afterEach(() => {
+5 -2
View File
@@ -10,7 +10,8 @@ import { loadSettings } from '../../config/settings.js';
import type { MCPServerConfig } from '@google/gemini-cli-core';
import { MCPServerStatus, createTransport } from '@google/gemini-cli-core';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { loadExtensions } from '../../config/extension.js';
import { ExtensionStorage, loadExtensions } from '../../config/extension.js';
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
const COLOR_GREEN = '\u001b[32m';
const COLOR_YELLOW = '\u001b[33m';
@@ -21,7 +22,9 @@ async function getMcpServersFromConfig(): Promise<
Record<string, MCPServerConfig>
> {
const settings = loadSettings();
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const mcpServers = { ...(settings.merged.mcpServers || {}) };
for (const extension of extensions) {
Object.entries(extension.config.mcpServers || {}).forEach(
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -45,6 +45,7 @@ import { appEvents } from '../utils/events.js';
import { isWorkspaceTrusted } from './trustedFolders.js';
import { createPolicyEngineConfig } from './policy.js';
import type { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
// Simple console logger for now - replace with actual logger if available
const logger = {
@@ -408,6 +409,7 @@ export function isDebugMode(argv: CliArgs): boolean {
export async function loadCliConfig(
settings: Settings,
extensions: Extension[],
extensionEnablementManager: ExtensionEnablementManager,
sessionId: string,
argv: CliArgs,
cwd: string = process.cwd(),
@@ -423,8 +425,8 @@ export async function loadCliConfig(
const allExtensions = annotateActiveExtensions(
extensions,
argv.extensions || [],
cwd,
extensionEnablementManager,
);
const activeExtensions = extensions.filter(
+88 -25
View File
@@ -10,6 +10,7 @@ import * as os from 'node:os';
import * as path from 'node:path';
import {
EXTENSIONS_CONFIG_FILENAME,
ExtensionStorage,
INSTALL_METADATA_FILENAME,
annotateActiveExtensions,
disableExtension,
@@ -152,7 +153,9 @@ describe('extension tests', () => {
version: '1.0.0',
});
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
expect(extensions[0].path).toBe(extensionDir);
expect(extensions[0].config.name).toBe('test-extension');
@@ -171,7 +174,9 @@ describe('extension tests', () => {
version: '2.0.0',
});
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(2);
const ext1 = extensions.find((e) => e.config.name === 'ext1');
@@ -191,7 +196,9 @@ describe('extension tests', () => {
contextFileName: 'my-context-file.md',
});
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
const ext1 = extensions.find((e) => e.config.name === 'ext1');
@@ -216,11 +223,14 @@ describe('extension tests', () => {
SettingScope.User,
tempWorkspaceDir,
);
const extensions = loadExtensions();
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const extensions = loadExtensions(manager);
const activeExtensions = annotateActiveExtensions(
extensions,
[],
tempWorkspaceDir,
manager,
).filter((e) => e.isActive);
expect(activeExtensions).toHaveLength(1);
expect(activeExtensions[0].name).toBe('enabled-extension');
@@ -240,7 +250,9 @@ describe('extension tests', () => {
},
});
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
const loadedConfig = extensions[0].config;
const expectedCwd = path.join(
@@ -269,7 +281,9 @@ describe('extension tests', () => {
);
expect(extensionName).toEqual('my-linked-extension');
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
const linkedExt = extensions[0];
@@ -318,7 +332,11 @@ describe('extension tests', () => {
};
fs.writeFileSync(configPath, JSON.stringify(extensionConfig));
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(extensions).toHaveLength(1);
const extension = extensions[0];
@@ -369,7 +387,9 @@ describe('extension tests', () => {
JSON.stringify(extensionConfig),
);
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
const extension = extensions[0];
@@ -397,7 +417,9 @@ describe('extension tests', () => {
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
fs.writeFileSync(badConfigPath, '{ "name": "bad-ext"'); // Malformed
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
expect(extensions[0].config.name).toBe('good-ext');
@@ -429,7 +451,9 @@ describe('extension tests', () => {
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
fs.writeFileSync(badConfigPath, JSON.stringify({ version: '1.0.0' }));
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
expect(extensions[0].config.name).toBe('good-ext');
@@ -457,7 +481,9 @@ describe('extension tests', () => {
},
});
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(1);
const loadedConfig = extensions[0].config;
expect(loadedConfig.mcpServers?.['test-server'].trust).toBeUndefined();
@@ -508,8 +534,8 @@ describe('extension tests', () => {
it('should mark all extensions as active if no enabled extensions are provided', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
[],
'/path/to/workspace',
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.every((e) => e.isActive)).toBe(true);
@@ -518,8 +544,11 @@ describe('extension tests', () => {
it('should mark only the enabled extensions as active', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
['ext1', 'ext3'],
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['ext1', 'ext3'],
),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.find((e) => e.name === 'ext1')?.isActive).toBe(
@@ -536,8 +565,11 @@ describe('extension tests', () => {
it('should mark all extensions as inactive when "none" is provided', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
['none'],
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['none'],
),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.every((e) => !e.isActive)).toBe(true);
@@ -546,8 +578,11 @@ describe('extension tests', () => {
it('should handle case-insensitivity', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
['EXT1'],
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['EXT1'],
),
);
expect(activeExtensions.find((e) => e.name === 'ext1')?.isActive).toBe(
true,
@@ -558,7 +593,14 @@ describe('extension tests', () => {
const consoleSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
annotateActiveExtensions(extensions, ['ext4'], '/path/to/workspace');
annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['ext4'],
),
);
expect(consoleSpy).toHaveBeenCalledWith('Extension not found: ext4');
consoleSpy.mockRestore();
});
@@ -567,8 +609,10 @@ describe('extension tests', () => {
it('should be false if autoUpdate is not set in install metadata', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
[],
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(
activeExtensions.every(
@@ -587,8 +631,10 @@ describe('extension tests', () => {
}));
const activeExtensions = annotateActiveExtensions(
extensionsWithAutoUpdate,
[],
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(
activeExtensions.every((e) => e.installMetadata?.autoUpdate === true),
@@ -625,8 +671,10 @@ describe('extension tests', () => {
];
const activeExtensions = annotateActiveExtensions(
extensionsWithAutoUpdate,
[],
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(
activeExtensions.find((e) => e.name === 'ext1')?.installMetadata
@@ -1015,7 +1063,13 @@ This extension will run the following MCP servers:
await uninstallExtension('my-local-extension');
expect(fs.existsSync(sourceExtDir)).toBe(false);
expect(loadExtensions()).toHaveLength(1);
expect(
loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
),
).toHaveLength(1);
expect(fs.existsSync(otherExtDir)).toBe(true);
});
@@ -1154,7 +1208,11 @@ This extension will run the following MCP servers:
],
async (_) => true,
);
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(extensions).toEqual([]);
});
@@ -1194,7 +1252,9 @@ This extension will run the following MCP servers:
'extensions',
);
const userExt1Path = path.join(userExtensionsDir, 'ext1');
const extensions = loadExtensions();
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(2);
const metadataPath = path.join(userExt1Path, INSTALL_METADATA_FILENAME);
@@ -1326,11 +1386,14 @@ This extension will run the following MCP servers:
});
const getActiveExtensions = (): GeminiCLIExtension[] => {
const extensions = loadExtensions();
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const extensions = loadExtensions(manager);
const activeExtensions = annotateActiveExtensions(
extensions,
[],
tempWorkspaceDir,
manager,
);
return activeExtensions.filter((e) => e.isActive);
};
+13 -60
View File
@@ -146,6 +146,7 @@ function getTelemetryConfig(cwd: string) {
}
export function loadExtensions(
extensionEnablementManager: ExtensionEnablementManager,
workspaceDir: string = process.cwd(),
): Extension[] {
const settings = loadSettings(workspaceDir).merged;
@@ -160,14 +161,11 @@ export function loadExtensions(
}
const uniqueExtensions = new Map<string, Extension>();
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
for (const extension of allExtensions) {
if (
!uniqueExtensions.has(extension.config.name) &&
manager.isEnabled(extension.config.name, workspaceDir)
extensionEnablementManager.isEnabled(extension.config.name, workspaceDir)
) {
uniqueExtensions.set(extension.config.name, extension);
}
@@ -323,64 +321,17 @@ function getContextFileNames(config: ExtensionConfig): string[] {
*/
export function annotateActiveExtensions(
extensions: Extension[],
enabledExtensionNames: string[],
workspaceDir: string,
manager: ExtensionEnablementManager,
): GeminiCLIExtension[] {
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const annotatedExtensions: GeminiCLIExtension[] = [];
if (enabledExtensionNames.length === 0) {
return extensions.map((extension) => ({
name: extension.config.name,
version: extension.config.version,
isActive: manager.isEnabled(extension.config.name, workspaceDir),
path: extension.path,
installMetadata: extension.installMetadata,
}));
}
const lowerCaseEnabledExtensions = new Set(
enabledExtensionNames.map((e) => e.trim().toLowerCase()),
);
if (
lowerCaseEnabledExtensions.size === 1 &&
lowerCaseEnabledExtensions.has('none')
) {
return extensions.map((extension) => ({
name: extension.config.name,
version: extension.config.version,
isActive: false,
path: extension.path,
installMetadata: extension.installMetadata,
}));
}
const notFoundNames = new Set(lowerCaseEnabledExtensions);
for (const extension of extensions) {
const lowerCaseName = extension.config.name.toLowerCase();
const isActive = lowerCaseEnabledExtensions.has(lowerCaseName);
if (isActive) {
notFoundNames.delete(lowerCaseName);
}
annotatedExtensions.push({
name: extension.config.name,
version: extension.config.version,
isActive,
path: extension.path,
installMetadata: extension.installMetadata,
});
}
for (const requestedName of notFoundNames) {
console.error(`Extension not found: ${requestedName}`);
}
return annotatedExtensions;
manager.validateExtensionOverrides(extensions);
return extensions.map((extension) => ({
name: extension.config.name,
version: extension.config.version,
isActive: manager.isEnabled(extension.config.name, workspaceDir),
path: extension.path,
installMetadata: extension.installMetadata,
}));
}
/**
@@ -711,6 +662,7 @@ export async function uninstallExtension(
}
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
[extensionName],
);
manager.remove(extensionName);
const storage = new ExtensionStorage(extensionName);
@@ -789,6 +741,7 @@ export function disableExtension(
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
[name],
);
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
manager.disable(name, true, scopePath);
@@ -6,6 +6,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { type Extension } from '../extension.js';
export interface ExtensionEnablementConfig {
overrides: string[];
@@ -104,24 +105,56 @@ function globToRegex(glob: string): RegExp {
return new RegExp(`^${regexString}$`);
}
/**
* Determines if an extension is enabled based on the configuration and current path.
* The last matching rule in the overrides list wins.
*
* @param config The enablement configuration for a single extension.
* @param currentPath The absolute path of the current working directory.
* @returns True if the extension is enabled, false otherwise.
*/
export class ExtensionEnablementManager {
private configFilePath: string;
private configDir: string;
// If non-empty, this overrides all other extension configuration and enables
// only the ones in this list.
private enabledExtensionNamesOverride: string[];
constructor(configDir: string) {
constructor(configDir: string, enabledExtensionNames?: string[]) {
this.configDir = configDir;
this.configFilePath = path.join(configDir, 'extension-enablement.json');
this.enabledExtensionNamesOverride =
enabledExtensionNames?.map((name) => name.toLowerCase()) ?? [];
}
validateExtensionOverrides(extensions: Extension[]) {
for (const name of this.enabledExtensionNamesOverride) {
if (
!extensions.some(
(ext) => ext.config.name.toLowerCase() === name.toLowerCase(),
)
) {
console.error(`Extension not found: ${name}`);
}
}
}
/**
* Determines if an extension is enabled based on its name and the current
* path. The last matching rule in the overrides list wins.
*
* @param extensionName The name of the extension.
* @param currentPath The absolute path of the current working directory.
* @returns True if the extension is enabled, false otherwise.
*/
isEnabled(extensionName: string, currentPath: string): boolean {
// If we have a single override called 'none', this disables all extensions.
// Typically, this comes from the user passing `-e none`.
if (
this.enabledExtensionNamesOverride.length === 1 &&
this.enabledExtensionNamesOverride[0] === 'none'
) {
return false;
}
// If we have explicit overrides, only enable those extensions.
if (this.enabledExtensionNamesOverride.length > 0) {
return this.enabledExtensionNamesOverride.includes(extensionName);
}
// Otherwise, we use the configuration settings
const config = this.readConfig();
const extensionConfig = config[extensionName];
// Extensions are enabled by default.
@@ -10,6 +10,7 @@ import * as os from 'node:os';
import * as path from 'node:path';
import {
EXTENSIONS_CONFIG_FILENAME,
ExtensionStorage,
INSTALL_METADATA_FILENAME,
annotateActiveExtensions,
loadExtension,
@@ -19,6 +20,7 @@ import { GEMINI_DIR } from '@google/gemini-cli-core';
import { isWorkspaceTrusted } from '../trustedFolders.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
import { createExtension } from '../../test-utils/createExtension.js';
import { ExtensionEnablementManager } from './extensionEnablement.js';
const mockGit = {
clone: vi.fn(),
@@ -134,8 +136,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
const updateInfo = await updateExtension(
extension,
@@ -192,8 +194,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
await updateExtension(
extension,
@@ -234,8 +236,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
await expect(
updateExtension(
@@ -274,8 +276,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
mockGit.getRemotes.mockResolvedValue([
@@ -317,8 +319,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
mockGit.getRemotes.mockResolvedValue([
@@ -364,8 +366,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
let extensionState = new Map();
const results = await checkForAllExtensionUpdates(
@@ -405,8 +407,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
let extensionState = new Map();
const results = await checkForAllExtensionUpdates(
@@ -442,8 +444,8 @@ describe('update tests', () => {
workspaceDir: tempWorkspaceDir,
})!,
],
[],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
mockGit.getRemotes.mockRejectedValue(new Error('Git error'));
+9 -2
View File
@@ -26,7 +26,7 @@ import { getStartupWarnings } from './utils/startupWarnings.js';
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { runNonInteractive } from './nonInteractiveCli.js';
import { loadExtensions } from './config/extension.js';
import { ExtensionStorage, loadExtensions } from './config/extension.js';
import {
cleanupCheckpoints,
registerCleanup,
@@ -113,6 +113,7 @@ function getNodeMemoryArgs(isDebugMode: boolean): string[] {
import { runZedIntegration } from './zed-integration/zedIntegration.js';
import { loadSandboxConfig } from './config/sandboxConfig.js';
import { ExtensionEnablementManager } from './config/extensions/extensionEnablement.js';
export function setupUnhandledRejectionHandler() {
let unhandledRejectionOccurred = false;
@@ -266,6 +267,7 @@ export async function main() {
const partialConfig = await loadCliConfig(
settings.merged,
[],
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
sessionId,
argv,
);
@@ -336,10 +338,15 @@ export async function main() {
// to run Gemini CLI. It is now safe to perform expensive initialization that
// may have side effects.
{
const extensions = loadExtensions();
const extensionEnablementManager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
argv.extensions,
);
const extensions = loadExtensions(extensionEnablementManager);
const config = await loadCliConfig(
settings.merged,
extensions,
extensionEnablementManager,
sessionId,
argv,
);
@@ -10,6 +10,7 @@ import * as os from 'node:os';
import * as path from 'node:path';
import {
EXTENSIONS_CONFIG_FILENAME,
ExtensionStorage,
annotateActiveExtensions,
loadExtension,
} from '../../config/extension.js';
@@ -19,6 +20,7 @@ import { GEMINI_DIR, type GeminiCLIExtension } from '@google/gemini-cli-core';
import { isWorkspaceTrusted } from '../../config/trustedFolders.js';
import { renderHook, waitFor } from '@testing-library/react';
import { MessageType } from '../types.js';
import { ExtensionEnablementManager } from '../../config/extensions/extensionEnablement.js';
const mockGit = {
clone: vi.fn(),
@@ -163,8 +165,8 @@ describe('useExtensionUpdates', () => {
});
const extension = annotateActiveExtensions(
[loadExtension({ extensionDir, workspaceDir: tempHomeDir })!],
[],
tempHomeDir,
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
)[0];
const addItem = vi.fn();
@@ -40,9 +40,10 @@ import * as path from 'node:path';
import { z } from 'zod';
import { randomUUID } from 'node:crypto';
import type { Extension } from '../config/extension.js';
import { ExtensionStorage, type Extension } from '../config/extension.js';
import type { CliArgs } from '../config/config.js';
import { loadCliConfig } from '../config/config.js';
import { ExtensionEnablementManager } from '../config/extensions/extensionEnablement.js';
/**
* Resolves the model to use based on the current configuration.
@@ -204,6 +205,10 @@ class GeminiAgent {
const config = await loadCliConfig(
settings,
this.extensions,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
this.argv.extensions,
),
sessionId,
this.argv,
cwd,