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