mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-14 16:10:59 -07:00
Add an experimental setting for extension config (#16506)
This commit is contained in:
@@ -830,6 +830,11 @@ their corresponding top-level category object in your `settings.json` file.
|
|||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
- **Requires restart:** Yes
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
|
- **`experimental.extensionConfig`** (boolean):
|
||||||
|
- **Description:** Enable requesting and fetching of extension settings.
|
||||||
|
- **Default:** `false`
|
||||||
|
- **Requires restart:** Yes
|
||||||
|
|
||||||
- **`experimental.extensionReloading`** (boolean):
|
- **`experimental.extensionReloading`** (boolean):
|
||||||
- **Description:** Enables extension loading/unloading within the CLI session.
|
- **Description:** Enables extension loading/unloading within the CLI session.
|
||||||
- **Default:** `false`
|
- **Default:** `false`
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import {
|
|||||||
getScopedEnvContents,
|
getScopedEnvContents,
|
||||||
} from '../../config/extensions/extensionSettings.js';
|
} from '../../config/extensions/extensionSettings.js';
|
||||||
import { getExtensionAndManager, getExtensionManager } from './utils.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 { exitCli } from '../utils.js';
|
||||||
import prompts from 'prompts';
|
import prompts from 'prompts';
|
||||||
import type { ExtensionConfig } from '../../config/extension.js';
|
import type { ExtensionConfig } from '../../config/extension.js';
|
||||||
@@ -43,6 +44,16 @@ export const configureCommand: CommandModule<object, ConfigureArgs> = {
|
|||||||
}),
|
}),
|
||||||
handler: async (args) => {
|
handler: async (args) => {
|
||||||
const { name, setting, scope } = 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) {
|
||||||
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
||||||
|
|||||||
@@ -109,6 +109,9 @@ describe('ExtensionManager Settings Scope', () => {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
extensionConfig: true,
|
||||||
|
},
|
||||||
} as Settings,
|
} as Settings,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,6 +151,9 @@ describe('ExtensionManager Settings Scope', () => {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
extensionConfig: true,
|
||||||
|
},
|
||||||
} as Settings,
|
} as Settings,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -185,6 +191,9 @@ describe('ExtensionManager Settings Scope', () => {
|
|||||||
telemetry: {
|
telemetry: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
extensionConfig: true,
|
||||||
|
},
|
||||||
} as Settings,
|
} as Settings,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,10 @@ Would you like to attempt to install via "git clone" instead?`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fs.promises.mkdir(destinationPath, { recursive: true });
|
await fs.promises.mkdir(destinationPath, { recursive: true });
|
||||||
if (this.requestSetting) {
|
if (
|
||||||
|
this.requestSetting &&
|
||||||
|
(this.settings.experimental?.extensionConfig ?? false)
|
||||||
|
) {
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
await maybePromptForSettings(
|
await maybePromptForSettings(
|
||||||
newExtensionConfig,
|
newExtensionConfig,
|
||||||
@@ -305,11 +308,14 @@ Would you like to attempt to install via "git clone" instead?`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const missingSettings = await getMissingSettings(
|
const missingSettings =
|
||||||
newExtensionConfig,
|
(this.settings.experimental?.extensionConfig ?? false)
|
||||||
extensionId,
|
? await getMissingSettings(
|
||||||
this.workspaceDir,
|
newExtensionConfig,
|
||||||
);
|
extensionId,
|
||||||
|
this.workspaceDir,
|
||||||
|
)
|
||||||
|
: [];
|
||||||
if (missingSettings.length > 0) {
|
if (missingSettings.length > 0) {
|
||||||
const message = `Extension "${newExtensionConfig.name}" has missing settings: ${missingSettings
|
const message = `Extension "${newExtensionConfig.name}" has missing settings: ${missingSettings
|
||||||
.map((s) => s.name)
|
.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 extensionId = getExtensionId(config, installMetadata);
|
||||||
|
|
||||||
const userSettings = await getScopedEnvContents(
|
let userSettings: Record<string, string> = {};
|
||||||
config,
|
let workspaceSettings: Record<string, string> = {};
|
||||||
extensionId,
|
|
||||||
ExtensionSettingScope.USER,
|
if (this.settings.experimental?.extensionConfig ?? false) {
|
||||||
);
|
userSettings = await getScopedEnvContents(
|
||||||
const workspaceSettings = await getScopedEnvContents(
|
config,
|
||||||
config,
|
extensionId,
|
||||||
extensionId,
|
ExtensionSettingScope.USER,
|
||||||
ExtensionSettingScope.WORKSPACE,
|
);
|
||||||
this.workspaceDir,
|
workspaceSettings = await getScopedEnvContents(
|
||||||
);
|
config,
|
||||||
|
extensionId,
|
||||||
|
ExtensionSettingScope.WORKSPACE,
|
||||||
|
this.workspaceDir,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const customEnv = { ...userSettings, ...workspaceSettings };
|
const customEnv = { ...userSettings, ...workspaceSettings };
|
||||||
config = resolveEnvVarsInObject(config, customEnv);
|
config = resolveEnvVarsInObject(config, customEnv);
|
||||||
|
|
||||||
const resolvedSettings: ResolvedExtensionSetting[] = [];
|
const resolvedSettings: ResolvedExtensionSetting[] = [];
|
||||||
if (config.settings) {
|
if (
|
||||||
|
config.settings &&
|
||||||
|
(this.settings.experimental?.extensionConfig ?? false)
|
||||||
|
) {
|
||||||
for (const setting of config.settings) {
|
for (const setting of config.settings) {
|
||||||
const value = customEnv[setting.envVar];
|
const value = customEnv[setting.envVar];
|
||||||
let scope: 'user' | 'workspace' | undefined;
|
let scope: 'user' | 'workspace' | undefined;
|
||||||
|
|||||||
@@ -200,11 +200,13 @@ describe('extension tests', () => {
|
|||||||
source: undefined,
|
source: undefined,
|
||||||
});
|
});
|
||||||
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
||||||
|
const settings = loadSettings(tempWorkspaceDir).merged;
|
||||||
|
(settings.experimental ??= {}).extensionConfig = true;
|
||||||
extensionManager = new ExtensionManager({
|
extensionManager = new ExtensionManager({
|
||||||
workspaceDir: tempWorkspaceDir,
|
workspaceDir: tempWorkspaceDir,
|
||||||
requestConsent: mockRequestConsent,
|
requestConsent: mockRequestConsent,
|
||||||
requestSetting: mockPromptForSettings,
|
requestSetting: mockPromptForSettings,
|
||||||
settings: loadSettings(tempWorkspaceDir).merged,
|
settings,
|
||||||
});
|
});
|
||||||
resetTrustedFoldersForTesting();
|
resetTrustedFoldersForTesting();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import * as fs from 'node:fs';
|
|||||||
import { getMissingSettings } from './extensionSettings.js';
|
import { getMissingSettings } from './extensionSettings.js';
|
||||||
import type { ExtensionConfig } from '../extension.js';
|
import type { ExtensionConfig } from '../extension.js';
|
||||||
import { ExtensionStorage } from './storage.js';
|
import { ExtensionStorage } from './storage.js';
|
||||||
|
import type { Settings } from '../settings.js';
|
||||||
import {
|
import {
|
||||||
KeychainTokenStorage,
|
KeychainTokenStorage,
|
||||||
debugLogger,
|
debugLogger,
|
||||||
@@ -245,8 +246,13 @@ describe('extensionUpdates', () => {
|
|||||||
|
|
||||||
const manager = new ExtensionManager({
|
const manager = new ExtensionManager({
|
||||||
workspaceDir: tempWorkspaceDir,
|
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),
|
requestConsent: vi.fn().mockResolvedValue(true),
|
||||||
requestSetting: null, // Simulate non-interactive
|
requestSetting: null, // Simulate non-interactive
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1413,6 +1413,15 @@ const SETTINGS_SCHEMA = {
|
|||||||
description: 'Enable extension management features.',
|
description: 'Enable extension management features.',
|
||||||
showInDialog: false,
|
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: {
|
extensionReloading: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
label: 'Extension Reloading',
|
label: 'Extension Reloading',
|
||||||
|
|||||||
@@ -1393,6 +1393,13 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"type": "boolean"
|
"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": {
|
"extensionReloading": {
|
||||||
"title": "Extension Reloading",
|
"title": "Extension Reloading",
|
||||||
"description": "Enables extension loading/unloading within the CLI session.",
|
"description": "Enables extension loading/unloading within the CLI session.",
|
||||||
|
|||||||
Reference in New Issue
Block a user