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

View File

@@ -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.

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,
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'));