Add an experimental setting for extension config (#16506)

This commit is contained in:
christine betts
2026-01-14 10:16:42 -05:00
committed by GitHub
parent f6c2d61906
commit 3b55581aaf
8 changed files with 85 additions and 22 deletions

View File

@@ -830,6 +830,11 @@ their corresponding top-level category object in your `settings.json` file.
- **Default:** `true`
- **Requires restart:** Yes
- **`experimental.extensionConfig`** (boolean):
- **Description:** Enable requesting and fetching of extension settings.
- **Default:** `false`
- **Requires restart:** Yes
- **`experimental.extensionReloading`** (boolean):
- **Description:** Enables extension loading/unloading within the CLI session.
- **Default:** `false`

View File

@@ -12,7 +12,8 @@ import {
getScopedEnvContents,
} from '../../config/extensions/extensionSettings.js';
import { getExtensionAndManager, getExtensionManager } from './utils.js';
import { debugLogger } from '@google/gemini-cli-core';
import { loadSettings } from '../../config/settings.js';
import { debugLogger, coreEvents } from '@google/gemini-cli-core';
import { exitCli } from '../utils.js';
import prompts from 'prompts';
import type { ExtensionConfig } from '../../config/extension.js';
@@ -43,6 +44,16 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
}),
handler: async (args) => {
const { name, setting, scope } = args;
const settings = loadSettings(process.cwd()).merged;
if (!(settings.experimental?.extensionConfig ?? true)) {
coreEvents.emitFeedback(
'error',
'Extension configuration is currently disabled. Enable it by setting "experimental.extensionConfig" to true.',
);
await exitCli();
return;
}
if (name) {
if (name.includes('/') || name.includes('\\') || name.includes('..')) {

View File

@@ -109,6 +109,9 @@ describe('ExtensionManager Settings Scope', () => {
telemetry: {
enabled: false,
},
experimental: {
extensionConfig: true,
},
} as Settings,
});
@@ -148,6 +151,9 @@ describe('ExtensionManager Settings Scope', () => {
telemetry: {
enabled: false,
},
experimental: {
extensionConfig: true,
},
} as Settings,
});
@@ -185,6 +191,9 @@ describe('ExtensionManager Settings Scope', () => {
telemetry: {
enabled: false,
},
experimental: {
extensionConfig: true,
},
} as Settings,
});

View File

@@ -287,7 +287,10 @@ Would you like to attempt to install via "git clone" instead?`,
}
await fs.promises.mkdir(destinationPath, { recursive: true });
if (this.requestSetting) {
if (
this.requestSetting &&
(this.settings.experimental?.extensionConfig ?? false)
) {
if (isUpdate) {
await maybePromptForSettings(
newExtensionConfig,
@@ -305,11 +308,14 @@ Would you like to attempt to install via "git clone" instead?`,
}
}
const missingSettings = await getMissingSettings(
newExtensionConfig,
extensionId,
this.workspaceDir,
);
const missingSettings =
(this.settings.experimental?.extensionConfig ?? false)
? await getMissingSettings(
newExtensionConfig,
extensionId,
this.workspaceDir,
)
: [];
if (missingSettings.length > 0) {
const message = `Extension "${newExtensionConfig.name}" has missing settings: ${missingSettings
.map((s) => s.name)
@@ -526,23 +532,31 @@ Would you like to attempt to install via "git clone" instead?`,
const extensionId = getExtensionId(config, installMetadata);
const userSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.USER,
);
const workspaceSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.WORKSPACE,
this.workspaceDir,
);
let userSettings: Record<string, string> = {};
let workspaceSettings: Record<string, string> = {};
if (this.settings.experimental?.extensionConfig ?? false) {
userSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.USER,
);
workspaceSettings = await getScopedEnvContents(
config,
extensionId,
ExtensionSettingScope.WORKSPACE,
this.workspaceDir,
);
}
const customEnv = { ...userSettings, ...workspaceSettings };
config = resolveEnvVarsInObject(config, customEnv);
const resolvedSettings: ResolvedExtensionSetting[] = [];
if (config.settings) {
if (
config.settings &&
(this.settings.experimental?.extensionConfig ?? false)
) {
for (const setting of config.settings) {
const value = customEnv[setting.envVar];
let scope: 'user' | 'workspace' | undefined;

View File

@@ -200,11 +200,13 @@ describe('extension tests', () => {
source: undefined,
});
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
const settings = loadSettings(tempWorkspaceDir).merged;
(settings.experimental ??= {}).extensionConfig = true;
extensionManager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
requestConsent: mockRequestConsent,
requestSetting: mockPromptForSettings,
settings: loadSettings(tempWorkspaceDir).merged,
settings,
});
resetTrustedFoldersForTesting();
});

View File

@@ -11,6 +11,7 @@ import * as fs from 'node:fs';
import { getMissingSettings } from './extensionSettings.js';
import type { ExtensionConfig } from '../extension.js';
import { ExtensionStorage } from './storage.js';
import type { Settings } from '../settings.js';
import {
KeychainTokenStorage,
debugLogger,
@@ -245,8 +246,13 @@ describe('extensionUpdates', () => {
const manager = new ExtensionManager({
workspaceDir: tempWorkspaceDir,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
settings: { telemetry: {} } as any,
settings: {
telemetry: {
enabled: false,
},
experimental: { extensionConfig: true },
} as unknown as Settings,
requestConsent: vi.fn().mockResolvedValue(true),
requestSetting: null, // Simulate non-interactive
});

View File

@@ -1413,6 +1413,15 @@ const SETTINGS_SCHEMA = {
description: 'Enable extension management features.',
showInDialog: false,
},
extensionConfig: {
type: 'boolean',
label: 'Extension Configuration',
category: 'Experimental',
requiresRestart: true,
default: false,
description: 'Enable requesting and fetching of extension settings.',
showInDialog: false,
},
extensionReloading: {
type: 'boolean',
label: 'Extension Reloading',

View File

@@ -1393,6 +1393,13 @@
"default": true,
"type": "boolean"
},
"extensionConfig": {
"title": "Extension Configuration",
"description": "Enable requesting and fetching of extension settings.",
"markdownDescription": "Enable requesting and fetching of extension settings.\n\n- Category: `Experimental`\n- Requires restart: `yes`\n- Default: `false`",
"default": false,
"type": "boolean"
},
"extensionReloading": {
"title": "Extension Reloading",
"description": "Enables extension loading/unloading within the CLI session.",