diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index 3c8d0a76d8..d845c33692 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -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` diff --git a/packages/cli/src/commands/extensions/configure.ts b/packages/cli/src/commands/extensions/configure.ts index 4ea3299610..7d16179cc0 100644 --- a/packages/cli/src/commands/extensions/configure.ts +++ b/packages/cli/src/commands/extensions/configure.ts @@ -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 = { }), 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('..')) { diff --git a/packages/cli/src/config/extension-manager-scope.test.ts b/packages/cli/src/config/extension-manager-scope.test.ts index a9e0738c87..6d3e51b4d8 100644 --- a/packages/cli/src/config/extension-manager-scope.test.ts +++ b/packages/cli/src/config/extension-manager-scope.test.ts @@ -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, }); diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index fafa801bf2..75416f1909 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -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 = {}; + let workspaceSettings: Record = {}; + + 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; diff --git a/packages/cli/src/config/extension.test.ts b/packages/cli/src/config/extension.test.ts index d1999d60c8..6619befc66 100644 --- a/packages/cli/src/config/extension.test.ts +++ b/packages/cli/src/config/extension.test.ts @@ -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(); }); diff --git a/packages/cli/src/config/extensions/extensionUpdates.test.ts b/packages/cli/src/config/extensions/extensionUpdates.test.ts index 1e30e0b898..a9240a1676 100644 --- a/packages/cli/src/config/extensions/extensionUpdates.test.ts +++ b/packages/cli/src/config/extensions/extensionUpdates.test.ts @@ -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 }); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 424f2e1906..229eebf81d 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -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', diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 1e3e1f0923..a976c19fd6 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -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.",