remove support for workspace extensions and migrations (#11324)

This commit is contained in:
Jacob MacDonald
2025-10-17 16:08:57 -07:00
committed by GitHub
parent 9b9ab60985
commit f4330c9f5e
19 changed files with 214 additions and 1063 deletions
File diff suppressed because it is too large Load Diff
+25 -264
View File
@@ -21,7 +21,6 @@ import {
loadExtension,
loadExtensionConfig,
loadExtensions,
performWorkspaceExtensionMigration,
uninstallExtension,
} from './extension.js';
import {
@@ -155,9 +154,7 @@ describe('extension tests', () => {
version: '1.0.0',
});
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
expect(extensions[0].path).toBe(extensionDir);
expect(extensions[0].name).toBe('test-extension');
@@ -176,9 +173,7 @@ describe('extension tests', () => {
version: '2.0.0',
});
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(2);
const ext1 = extensions.find((e) => e.name === 'ext1');
@@ -198,9 +193,7 @@ describe('extension tests', () => {
contextFileName: 'my-context-file.md',
});
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
const ext1 = extensions.find((e) => e.name === 'ext1');
@@ -225,9 +218,7 @@ describe('extension tests', () => {
SettingScope.User,
tempWorkspaceDir,
);
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const manager = new ExtensionEnablementManager();
const extensions = loadExtensions(manager);
const activeExtensions = annotateActiveExtensions(
extensions,
@@ -252,9 +243,7 @@ describe('extension tests', () => {
},
});
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
const expectedCwd = path.join(
userExtensionsDir,
@@ -282,9 +271,7 @@ describe('extension tests', () => {
);
expect(extensionName).toEqual('my-linked-extension');
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
const linkedExt = extensions[0];
@@ -333,11 +320,7 @@ describe('extension tests', () => {
};
fs.writeFileSync(configPath, JSON.stringify(extensionConfig));
const extensions = loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
const extension = extensions[0];
@@ -388,9 +371,7 @@ describe('extension tests', () => {
JSON.stringify(extensionConfig),
);
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
const extension = extensions[0];
@@ -418,9 +399,7 @@ describe('extension tests', () => {
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
fs.writeFileSync(badConfigPath, '{ "name": "bad-ext"'); // Malformed
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
expect(extensions[0].name).toBe('good-ext');
@@ -452,9 +431,7 @@ describe('extension tests', () => {
const badConfigPath = path.join(badExtDir, EXTENSIONS_CONFIG_FILENAME);
fs.writeFileSync(badConfigPath, JSON.stringify({ version: '1.0.0' }));
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
expect(extensions[0].name).toBe('good-ext');
@@ -482,9 +459,7 @@ describe('extension tests', () => {
},
});
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
const extensions = loadExtensions(new ExtensionEnablementManager());
expect(extensions).toHaveLength(1);
expect(extensions[0].mcpServers?.['test-server'].trust).toBeUndefined();
});
@@ -698,7 +673,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.every((e) => e.isActive)).toBe(true);
@@ -708,10 +683,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['ext1', 'ext3'],
),
new ExtensionEnablementManager(['ext1', 'ext3']),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.find((e) => e.name === 'ext1')?.isActive).toBe(
@@ -729,10 +701,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['none'],
),
new ExtensionEnablementManager(['none']),
);
expect(activeExtensions).toHaveLength(3);
expect(activeExtensions.every((e) => !e.isActive)).toBe(true);
@@ -742,10 +711,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['EXT1'],
),
new ExtensionEnablementManager(['EXT1']),
);
expect(activeExtensions.find((e) => e.name === 'ext1')?.isActive).toBe(
true,
@@ -759,10 +725,7 @@ describe('extension tests', () => {
annotateActiveExtensions(
extensions,
'/path/to/workspace',
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
['ext4'],
),
new ExtensionEnablementManager(['ext4']),
);
expect(consoleSpy).toHaveBeenCalledWith('Extension not found: ext4');
consoleSpy.mockRestore();
@@ -773,9 +736,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensions,
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
new ExtensionEnablementManager(),
);
expect(
activeExtensions.every(
@@ -797,9 +758,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensionsWithAutoUpdate,
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
new ExtensionEnablementManager(),
);
expect(
activeExtensions.every((e) => e.installMetadata?.autoUpdate === true),
@@ -843,9 +802,7 @@ describe('extension tests', () => {
const activeExtensions = annotateActiveExtensions(
extensionsWithAutoUpdate,
tempHomeDir,
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
new ExtensionEnablementManager(),
);
expect(
activeExtensions.find((e) => e.name === 'ext1')?.installMetadata
@@ -1073,9 +1030,7 @@ describe('extension tests', () => {
});
it(`should ${isUpdate ? 'not ' : ''} alter the extension enablement configuration`, async () => {
const enablementManager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const enablementManager = new ExtensionEnablementManager();
enablementManager.enable('my-local-extension', true, '/some/scope');
await installOrUpdateExtension(
@@ -1457,13 +1412,7 @@ This extension will run the following MCP servers:
await uninstallExtension('my-local-extension', false);
expect(fs.existsSync(sourceExtDir)).toBe(false);
expect(
loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
),
).toHaveLength(1);
expect(loadExtensions(new ExtensionEnablementManager())).toHaveLength(1);
expect(fs.existsSync(otherExtDir)).toBe(true);
});
@@ -1501,9 +1450,7 @@ This extension will run the following MCP servers:
name: 'test-extension',
version: '1.0.0',
});
const enablementManager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const enablementManager = new ExtensionEnablementManager();
enablementManager.enable('test-extension', true, '/some/scope');
await uninstallExtension('test-extension', isUpdate);
@@ -1557,182 +1504,6 @@ This extension will run the following MCP servers:
});
});
describe('performWorkspaceExtensionMigration', () => {
let workspaceExtensionsDir: string;
beforeEach(() => {
workspaceExtensionsDir = path.join(
tempWorkspaceDir,
EXTENSIONS_DIRECTORY_NAME,
);
fs.mkdirSync(workspaceExtensionsDir, { recursive: true });
});
afterEach(() => {
fs.rmSync(workspaceExtensionsDir, { recursive: true, force: true });
});
describe('folder trust', () => {
it('refuses to install extensions from untrusted folders', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: false,
source: undefined,
});
const ext1Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
const failed = await performWorkspaceExtensionMigration(
[
loadExtension({
extensionDir: ext1Path,
workspaceDir: tempWorkspaceDir,
})!,
],
async () => true,
);
expect(failed).toEqual(['ext1']);
});
it('does not copy extensions to the user dir', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: false,
source: undefined,
});
const ext1Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
await performWorkspaceExtensionMigration(
[
loadExtension({
extensionDir: ext1Path,
workspaceDir: tempWorkspaceDir,
})!,
],
async (_) => true,
);
const userExtensionsDir = path.join(
tempHomeDir,
GEMINI_DIR,
'extensions',
);
expect(fs.readdirSync(userExtensionsDir).length).toBe(0);
});
it('does not load any extensions in the workspace config', async () => {
vi.mocked(isWorkspaceTrusted).mockReturnValue({
isTrusted: false,
source: undefined,
});
const ext1Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
await performWorkspaceExtensionMigration(
[
loadExtension({
extensionDir: ext1Path,
workspaceDir: tempWorkspaceDir,
})!,
],
async (_) => true,
);
const extensions = loadExtensions(
new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
),
);
expect(extensions).toEqual([]);
});
});
it('should install the extensions in the user directory', async () => {
const ext1Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
const ext2Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext2',
version: '1.0.0',
});
const extensionsToMigrate: GeminiCLIExtension[] = [
loadExtension({
extensionDir: ext1Path,
workspaceDir: tempWorkspaceDir,
})!,
loadExtension({
extensionDir: ext2Path,
workspaceDir: tempWorkspaceDir,
})!,
];
const failed = await performWorkspaceExtensionMigration(
extensionsToMigrate,
async (_) => true,
);
expect(failed).toEqual([]);
const userExtensionsDir = path.join(
tempHomeDir,
GEMINI_DIR,
'extensions',
);
const userExt1Path = path.join(userExtensionsDir, 'ext1');
const extensions = loadExtensions(
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
);
expect(extensions).toHaveLength(2);
const metadataPath = path.join(userExt1Path, INSTALL_METADATA_FILENAME);
expect(fs.existsSync(metadataPath)).toBe(true);
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
expect(metadata).toEqual({
source: ext1Path,
type: 'local',
});
});
it('should return the names of failed installations', async () => {
const ext1Path = createExtension({
extensionsDir: workspaceExtensionsDir,
name: 'ext1',
version: '1.0.0',
});
const extensions: GeminiCLIExtension[] = [
loadExtension({
extensionDir: ext1Path,
workspaceDir: tempWorkspaceDir,
})!,
{
path: '/ext/path/1',
name: 'ext2',
version: '1.0.0',
contextFiles: [],
isActive: true,
},
];
const failed = await performWorkspaceExtensionMigration(
extensions,
async (_) => true,
);
expect(failed).toEqual(['ext2']);
});
});
describe('disableExtension', () => {
it('should disable an extension at the user scope', () => {
createExtension({
@@ -1745,7 +1516,6 @@ This extension will run the following MCP servers:
expect(
isEnabled({
name: 'my-extension',
configDir: userExtensionsDir,
enabledForPath: tempWorkspaceDir,
}),
).toBe(false);
@@ -1766,14 +1536,12 @@ This extension will run the following MCP servers:
expect(
isEnabled({
name: 'my-extension',
configDir: userExtensionsDir,
enabledForPath: tempHomeDir,
}),
).toBe(true);
expect(
isEnabled({
name: 'my-extension',
configDir: userExtensionsDir,
enabledForPath: tempWorkspaceDir,
}),
).toBe(false);
@@ -1791,7 +1559,6 @@ This extension will run the following MCP servers:
expect(
isEnabled({
name: 'my-extension',
configDir: userExtensionsDir,
enabledForPath: tempWorkspaceDir,
}),
).toBe(false);
@@ -1826,9 +1593,7 @@ This extension will run the following MCP servers:
});
const getActiveExtensions = (): GeminiCLIExtension[] => {
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const manager = new ExtensionEnablementManager();
const extensions = loadExtensions(manager);
const activeExtensions = annotateActiveExtensions(
extensions,
@@ -1888,11 +1653,7 @@ This extension will run the following MCP servers:
});
});
function isEnabled(options: {
name: string;
configDir: string;
enabledForPath: string;
}) {
const manager = new ExtensionEnablementManager(options.configDir);
function isEnabled(options: { name: string; enabledForPath: string }) {
const manager = new ExtensionEnablementManager();
return manager.isEnabled(options.name, options.enabledForPath);
}
+28 -87
View File
@@ -104,16 +104,6 @@ export class ExtensionStorage {
}
}
export function getWorkspaceExtensions(
workspaceDir: string,
): GeminiCLIExtension[] {
// If the workspace dir is the user extensions dir, there are no workspace extensions.
if (path.resolve(workspaceDir) === path.resolve(os.homedir())) {
return [];
}
return loadExtensionsFromDir(workspaceDir);
}
export async function copyExtension(
source: string,
destination: string,
@@ -121,26 +111,6 @@ export async function copyExtension(
await fs.promises.cp(source, destination, { recursive: true });
}
export async function performWorkspaceExtensionMigration(
extensions: GeminiCLIExtension[],
requestConsent: (consent: string) => Promise<boolean>,
): Promise<string[]> {
const failedInstallNames: string[] = [];
for (const extension of extensions) {
try {
const installMetadata: ExtensionInstallMetadata = {
source: extension.path,
type: 'local',
};
await installOrUpdateExtension(installMetadata, requestConsent);
} catch (_) {
failedInstallNames.push(extension.name);
}
}
return failedInstallNames;
}
function getTelemetryConfig(cwd: string) {
const settings = loadSettings(cwd);
const config = new Config({
@@ -159,20 +129,27 @@ export function loadExtensions(
extensionEnablementManager: ExtensionEnablementManager,
workspaceDir: string = process.cwd(),
): GeminiCLIExtension[] {
const settings = loadSettings(workspaceDir).merged;
const allExtensions = [...loadUserExtensions()];
const extensionsDir = ExtensionStorage.getUserExtensionsDir();
if (!fs.existsSync(extensionsDir)) {
return [];
}
if (
isWorkspaceTrusted(settings).isTrusted &&
// Default management setting to true
!(settings.experimental?.extensionManagement ?? true)
) {
allExtensions.push(...getWorkspaceExtensions(workspaceDir));
const extensions: GeminiCLIExtension[] = [];
for (const subdir of fs.readdirSync(extensionsDir)) {
const extensionDir = path.join(extensionsDir, subdir);
const extension = loadExtension({
extensionDir,
workspaceDir,
});
if (extension != null) {
extensions.push(extension);
}
}
const uniqueExtensions = new Map<string, GeminiCLIExtension>();
for (const extension of allExtensions) {
for (const extension of extensions) {
if (
!uniqueExtensions.has(extension.name) &&
extensionEnablementManager.isEnabled(extension.name, workspaceDir)
@@ -184,38 +161,6 @@ export function loadExtensions(
return Array.from(uniqueExtensions.values());
}
export function loadUserExtensions(): GeminiCLIExtension[] {
const userExtensions = loadExtensionsFromDir(os.homedir());
const uniqueExtensions = new Map<string, GeminiCLIExtension>();
for (const extension of userExtensions) {
if (!uniqueExtensions.has(extension.name)) {
uniqueExtensions.set(extension.name, extension);
}
}
return Array.from(uniqueExtensions.values());
}
export function loadExtensionsFromDir(dir: string): GeminiCLIExtension[] {
const storage = new Storage(dir);
const extensionsDir = storage.getExtensionsDir();
if (!fs.existsSync(extensionsDir)) {
return [];
}
const extensions: GeminiCLIExtension[] = [];
for (const subdir of fs.readdirSync(extensionsDir)) {
const extensionDir = path.join(extensionsDir, subdir);
const extension = loadExtension({ extensionDir, workspaceDir: dir });
if (extension != null) {
extensions.push(extension);
}
}
return extensions;
}
export function loadExtension(
context: LoadExtensionContext,
): GeminiCLIExtension | null {
@@ -538,7 +483,10 @@ export async function installOrUpdateExtension(
const newExtensionName = newExtensionConfig.name;
if (!isUpdate) {
const installedExtensions = loadUserExtensions();
const installedExtensions = loadExtensions(
new ExtensionEnablementManager(),
cwd,
);
if (
installedExtensions.some(
(installed) => installed.name === newExtensionName,
@@ -762,7 +710,10 @@ export async function uninstallExtension(
isUpdate: boolean,
cwd: string = process.cwd(),
): Promise<void> {
const installedExtensions = loadUserExtensions();
const installedExtensions = loadExtensions(
new ExtensionEnablementManager(),
cwd,
);
const extensionName = installedExtensions.find(
(installed) =>
installed.name.toLowerCase() === extensionIdentifier.toLowerCase() ||
@@ -783,10 +734,7 @@ export async function uninstallExtension(
// uninstalls related to updates.
if (isUpdate) return;
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
[extensionName],
);
const manager = new ExtensionEnablementManager([extensionName]);
manager.remove(extensionName);
const telemetryConfig = getTelemetryConfig(cwd);
@@ -800,9 +748,7 @@ export function toOutputString(
extension: GeminiCLIExtension,
workspaceDir: string,
): string {
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const manager = new ExtensionEnablementManager();
const userEnabled = manager.isEnabled(extension.name, os.homedir());
const workspaceEnabled = manager.isEnabled(extension.name, workspaceDir);
@@ -855,10 +801,7 @@ export function disableExtension(
throw new Error(`Extension with name ${name} does not exist.`);
}
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
[name],
);
const manager = new ExtensionEnablementManager([name]);
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
manager.disable(name, true, scopePath);
logExtensionDisable(config, new ExtensionDisableEvent(name, scope));
@@ -876,9 +819,7 @@ export function enableExtension(
if (!extension) {
throw new Error(`Extension with name ${name} does not exist.`);
}
const manager = new ExtensionEnablementManager(
ExtensionStorage.getUserExtensionsDir(),
);
const manager = new ExtensionEnablementManager();
const scopePath = scope === SettingScope.Workspace ? cwd : os.homedir();
manager.enable(name, true, scopePath);
const config = getTelemetryConfig(cwd);
@@ -6,12 +6,20 @@
import * as path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import * as os from 'node:os';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { ExtensionEnablementManager, Override } from './extensionEnablement.js';
import { GEMINI_DIR, type GeminiCLIExtension } from '@google/gemini-cli-core';
vi.mock('os', async (importOriginal) => {
const mockedOs = await importOriginal<typeof os>();
return {
...mockedOs,
homedir: vi.fn(),
};
});
// Helper to create a temporary directory for testing
function createTestDir() {
const dirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'gemini-test-'));
@@ -22,14 +30,13 @@ function createTestDir() {
}
let testDir: { path: string; cleanup: () => void };
let configDir: string;
let manager: ExtensionEnablementManager;
describe('ExtensionEnablementManager', () => {
beforeEach(() => {
testDir = createTestDir();
configDir = path.join(testDir.path, GEMINI_DIR);
manager = new ExtensionEnablementManager(configDir);
vi.mocked(os.homedir).mockReturnValue(path.join(testDir.path, GEMINI_DIR));
manager = new ExtensionEnablementManager();
});
afterEach(() => {
@@ -230,7 +237,7 @@ describe('ExtensionEnablementManager', () => {
describe('extension overrides (-e <name>)', () => {
beforeEach(() => {
manager = new ExtensionEnablementManager(configDir, ['ext-test']);
manager = new ExtensionEnablementManager(['ext-test']);
});
it('can enable extensions, case-insensitive', () => {
@@ -238,29 +245,29 @@ describe('ExtensionEnablementManager', () => {
expect(manager.isEnabled('ext-test', '/')).toBe(true);
expect(manager.isEnabled('Ext-Test', '/')).toBe(true);
// Double check that it would have been disabled otherwise
expect(
new ExtensionEnablementManager(configDir).isEnabled('ext-test', '/'),
).toBe(false);
expect(new ExtensionEnablementManager().isEnabled('ext-test', '/')).toBe(
false,
);
});
it('disable all other extensions', () => {
manager = new ExtensionEnablementManager(configDir, ['ext-test']);
manager = new ExtensionEnablementManager(['ext-test']);
manager.enable('ext-test-2', true, '/');
expect(manager.isEnabled('ext-test-2', '/')).toBe(false);
// Double check that it would have been enabled otherwise
expect(
new ExtensionEnablementManager(configDir).isEnabled('ext-test-2', '/'),
new ExtensionEnablementManager().isEnabled('ext-test-2', '/'),
).toBe(true);
});
it('none disables all extensions', () => {
manager = new ExtensionEnablementManager(configDir, ['none']);
manager = new ExtensionEnablementManager(['none']);
manager.enable('ext-test', true, '/');
expect(manager.isEnabled('ext-test', '/path/to/dir')).toBe(false);
// Double check that it would have been enabled otherwise
expect(
new ExtensionEnablementManager(configDir).isEnabled('ext-test', '/'),
).toBe(true);
expect(new ExtensionEnablementManager().isEnabled('ext-test', '/')).toBe(
true,
);
});
});
@@ -276,16 +283,13 @@ describe('ExtensionEnablementManager', () => {
});
it('should not log an error if enabledExtensionNamesOverride is empty', () => {
const manager = new ExtensionEnablementManager(configDir, []);
const manager = new ExtensionEnablementManager([]);
manager.validateExtensionOverrides([]);
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
it('should not log an error if all enabledExtensionNamesOverride are valid', () => {
const manager = new ExtensionEnablementManager(configDir, [
'ext-one',
'ext-two',
]);
const manager = new ExtensionEnablementManager(['ext-one', 'ext-two']);
const extensions = [
{ name: 'ext-one' },
{ name: 'ext-two' },
@@ -295,7 +299,7 @@ describe('ExtensionEnablementManager', () => {
});
it('should log an error for each invalid extension name in enabledExtensionNamesOverride', () => {
const manager = new ExtensionEnablementManager(configDir, [
const manager = new ExtensionEnablementManager([
'ext-one',
'ext-invalid',
'ext-another-invalid',
@@ -315,7 +319,7 @@ describe('ExtensionEnablementManager', () => {
});
it('should not log an error if "none" is in enabledExtensionNamesOverride', () => {
const manager = new ExtensionEnablementManager(configDir, ['none']);
const manager = new ExtensionEnablementManager(['none']);
manager.validateExtensionOverrides([]);
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
@@ -7,6 +7,7 @@
import fs from 'node:fs';
import path from 'node:path';
import type { GeminiCLIExtension } from '@google/gemini-cli-core';
import { ExtensionStorage } from '../extension.js';
export interface ExtensionEnablementConfig {
overrides: string[];
@@ -112,9 +113,12 @@ export class ExtensionEnablementManager {
// only the ones in this list.
private enabledExtensionNamesOverride: string[];
constructor(configDir: string, enabledExtensionNames?: string[]) {
this.configDir = configDir;
this.configFilePath = path.join(configDir, 'extension-enablement.json');
constructor(enabledExtensionNames?: string[]) {
this.configDir = ExtensionStorage.getUserExtensionsDir();
this.configFilePath = path.join(
this.configDir,
'extension-enablement.json',
);
this.enabledExtensionNamesOverride =
enabledExtensionNames?.map((name) => name.toLowerCase()) ?? [];
}
@@ -10,7 +10,6 @@ import * as os from 'node:os';
import * as path from 'node:path';
import {
EXTENSIONS_CONFIG_FILENAME,
ExtensionStorage,
INSTALL_METADATA_FILENAME,
annotateActiveExtensions,
loadExtension,
@@ -137,7 +136,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
const updateInfo = await updateExtension(
extension,
@@ -194,7 +193,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
await updateExtension(
extension,
@@ -244,7 +243,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
await expect(
updateExtension(
@@ -292,7 +291,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
mockGit.getRemotes.mockResolvedValue([
@@ -334,7 +333,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
mockGit.getRemotes.mockResolvedValue([
@@ -380,7 +379,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
@@ -419,7 +418,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
const dispatch = vi.fn();
await checkForAllExtensionUpdates(
@@ -454,7 +453,7 @@ describe('update tests', () => {
})!,
],
process.cwd(),
new ExtensionEnablementManager(ExtensionStorage.getUserExtensionsDir()),
new ExtensionEnablementManager(),
)[0];
mockGit.getRemotes.mockRejectedValue(new Error('Git error'));