mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
Show final install path in extension consent dialog and fix isWorkspaceTrusted check (#10830)
This commit is contained in:
@@ -116,7 +116,10 @@ describe('extension tests', () => {
|
|||||||
fs.mkdirSync(userExtensionsDir, { recursive: true });
|
fs.mkdirSync(userExtensionsDir, { recursive: true });
|
||||||
|
|
||||||
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
vi.mocked(os.homedir).mockReturnValue(tempHomeDir);
|
||||||
vi.mocked(isWorkspaceTrusted).mockReturnValue(true);
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||||
|
isTrusted: true,
|
||||||
|
source: undefined,
|
||||||
|
});
|
||||||
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir);
|
||||||
vi.mocked(execSync).mockClear();
|
vi.mocked(execSync).mockClear();
|
||||||
Object.values(mockGit).forEach((fn) => fn.mockReset());
|
Object.values(mockGit).forEach((fn) => fn.mockReset());
|
||||||
@@ -285,8 +288,8 @@ describe('extension tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve environment variables in extension configuration', () => {
|
it('should resolve environment variables in extension configuration', () => {
|
||||||
process.env.TEST_API_KEY = 'test-api-key-123';
|
process.env['TEST_API_KEY'] = 'test-api-key-123';
|
||||||
process.env.TEST_DB_URL = 'postgresql://localhost:5432/testdb';
|
process.env['TEST_DB_URL'] = 'postgresql://localhost:5432/testdb';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userExtensionsDir = path.join(
|
const userExtensionsDir = path.join(
|
||||||
@@ -331,14 +334,14 @@ describe('extension tests', () => {
|
|||||||
const serverConfig = extension.mcpServers?.['test-server'];
|
const serverConfig = extension.mcpServers?.['test-server'];
|
||||||
expect(serverConfig).toBeDefined();
|
expect(serverConfig).toBeDefined();
|
||||||
expect(serverConfig?.env).toBeDefined();
|
expect(serverConfig?.env).toBeDefined();
|
||||||
expect(serverConfig?.env?.API_KEY).toBe('test-api-key-123');
|
expect(serverConfig?.env?.['API_KEY']).toBe('test-api-key-123');
|
||||||
expect(serverConfig?.env?.DATABASE_URL).toBe(
|
expect(serverConfig?.env?.['DATABASE_URL']).toBe(
|
||||||
'postgresql://localhost:5432/testdb',
|
'postgresql://localhost:5432/testdb',
|
||||||
);
|
);
|
||||||
expect(serverConfig?.env?.STATIC_VALUE).toBe('no-substitution');
|
expect(serverConfig?.env?.['STATIC_VALUE']).toBe('no-substitution');
|
||||||
} finally {
|
} finally {
|
||||||
delete process.env.TEST_API_KEY;
|
delete process.env['TEST_API_KEY'];
|
||||||
delete process.env.TEST_DB_URL;
|
delete process.env['TEST_DB_URL'];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -380,8 +383,8 @@ describe('extension tests', () => {
|
|||||||
const extension = extensions[0];
|
const extension = extensions[0];
|
||||||
const serverConfig = extension.mcpServers!['test-server'];
|
const serverConfig = extension.mcpServers!['test-server'];
|
||||||
expect(serverConfig.env).toBeDefined();
|
expect(serverConfig.env).toBeDefined();
|
||||||
expect(serverConfig.env!.MISSING_VAR).toBe('$UNDEFINED_ENV_VAR');
|
expect(serverConfig.env!['MISSING_VAR']).toBe('$UNDEFINED_ENV_VAR');
|
||||||
expect(serverConfig.env!.MISSING_VAR_BRACES).toBe('${ALSO_UNDEFINED}');
|
expect(serverConfig.env!['MISSING_VAR_BRACES']).toBe('${ALSO_UNDEFINED}');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip extensions with invalid JSON and log a warning', () => {
|
it('should skip extensions with invalid JSON and log a warning', () => {
|
||||||
@@ -1015,7 +1018,7 @@ This extension will run the following MCP servers:
|
|||||||
await expect(
|
await expect(
|
||||||
installExtension(
|
installExtension(
|
||||||
{ source: sourceExtDir, type: 'local' },
|
{ source: sourceExtDir, type: 'local' },
|
||||||
async () => true,
|
async (_) => true,
|
||||||
),
|
),
|
||||||
).rejects.toThrow('Invalid extension name: "bad_name"');
|
).rejects.toThrow('Invalid extension name: "bad_name"');
|
||||||
});
|
});
|
||||||
@@ -1134,25 +1137,34 @@ This extension will run the following MCP servers:
|
|||||||
|
|
||||||
describe('folder trust', () => {
|
describe('folder trust', () => {
|
||||||
it('refuses to install extensions from untrusted folders', async () => {
|
it('refuses to install extensions from untrusted folders', async () => {
|
||||||
vi.mocked(isWorkspaceTrusted).mockReturnValue(false);
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||||
|
isTrusted: false,
|
||||||
|
source: undefined,
|
||||||
|
});
|
||||||
const ext1Path = createExtension({
|
const ext1Path = createExtension({
|
||||||
extensionsDir: workspaceExtensionsDir,
|
extensionsDir: workspaceExtensionsDir,
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const failed = await performWorkspaceExtensionMigration([
|
const failed = await performWorkspaceExtensionMigration(
|
||||||
|
[
|
||||||
loadExtension({
|
loadExtension({
|
||||||
extensionDir: ext1Path,
|
extensionDir: ext1Path,
|
||||||
workspaceDir: tempWorkspaceDir,
|
workspaceDir: tempWorkspaceDir,
|
||||||
})!,
|
})!,
|
||||||
]);
|
],
|
||||||
|
async () => true,
|
||||||
|
);
|
||||||
|
|
||||||
expect(failed).toEqual(['ext1']);
|
expect(failed).toEqual(['ext1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not copy extensions to the user dir', async () => {
|
it('does not copy extensions to the user dir', async () => {
|
||||||
vi.mocked(isWorkspaceTrusted).mockReturnValue(false);
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||||
|
isTrusted: false,
|
||||||
|
source: undefined,
|
||||||
|
});
|
||||||
const ext1Path = createExtension({
|
const ext1Path = createExtension({
|
||||||
extensionsDir: workspaceExtensionsDir,
|
extensionsDir: workspaceExtensionsDir,
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
@@ -1178,7 +1190,10 @@ This extension will run the following MCP servers:
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('does not load any extensions in the workspace config', async () => {
|
it('does not load any extensions in the workspace config', async () => {
|
||||||
vi.mocked(isWorkspaceTrusted).mockReturnValue(false);
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||||
|
isTrusted: false,
|
||||||
|
source: undefined,
|
||||||
|
});
|
||||||
const ext1Path = createExtension({
|
const ext1Path = createExtension({
|
||||||
extensionsDir: workspaceExtensionsDir,
|
extensionsDir: workspaceExtensionsDir,
|
||||||
name: 'ext1',
|
name: 'ext1',
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ import * as path from 'node:path';
|
|||||||
import * as os from 'node:os';
|
import * as os from 'node:os';
|
||||||
import { SettingScope, loadSettings } from '../config/settings.js';
|
import { SettingScope, loadSettings } from '../config/settings.js';
|
||||||
import { getErrorMessage } from '../utils/errors.js';
|
import { getErrorMessage } from '../utils/errors.js';
|
||||||
import { recursivelyHydrateStrings } from './extensions/variables.js';
|
import {
|
||||||
|
recursivelyHydrateStrings,
|
||||||
|
type JsonObject,
|
||||||
|
} from './extensions/variables.js';
|
||||||
import { isWorkspaceTrusted } from './trustedFolders.js';
|
import { isWorkspaceTrusted } from './trustedFolders.js';
|
||||||
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
|
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
@@ -156,7 +159,7 @@ export function loadExtensions(
|
|||||||
const allExtensions = [...loadUserExtensions()];
|
const allExtensions = [...loadUserExtensions()];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(isWorkspaceTrusted(settings) ?? true) &&
|
isWorkspaceTrusted(settings).isTrusted &&
|
||||||
// Default management setting to true
|
// Default management setting to true
|
||||||
!(settings.experimental?.extensionManagement ?? true)
|
!(settings.experimental?.extensionManagement ?? true)
|
||||||
) {
|
) {
|
||||||
@@ -435,7 +438,7 @@ export async function installExtension(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = loadSettings(cwd).merged;
|
const settings = loadSettings(cwd).merged;
|
||||||
if (!isWorkspaceTrusted(settings)) {
|
if (!isWorkspaceTrusted(settings).isTrusted) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not install extension from untrusted folder at ${installMetadata.source}`,
|
`Could not install extension from untrusted folder at ${installMetadata.source}`,
|
||||||
);
|
);
|
||||||
@@ -646,17 +649,23 @@ export function loadExtensionConfig(
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const configContent = fs.readFileSync(configFilePath, 'utf-8');
|
const configContent = fs.readFileSync(configFilePath, 'utf-8');
|
||||||
const config = recursivelyHydrateStrings(JSON.parse(configContent), {
|
const rawConfig = JSON.parse(configContent) as ExtensionConfig;
|
||||||
extensionPath: extensionDir,
|
if (!rawConfig.name || !rawConfig.version) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid configuration in ${configFilePath}: missing ${!rawConfig.name ? '"name"' : '"version"'}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const installDir = new ExtensionStorage(rawConfig.name).getExtensionDir();
|
||||||
|
const config = recursivelyHydrateStrings(
|
||||||
|
rawConfig as unknown as JsonObject,
|
||||||
|
{
|
||||||
|
extensionPath: installDir,
|
||||||
workspacePath: workspaceDir,
|
workspacePath: workspaceDir,
|
||||||
'/': path.sep,
|
'/': path.sep,
|
||||||
pathSeparator: path.sep,
|
pathSeparator: path.sep,
|
||||||
}) as unknown as ExtensionConfig;
|
},
|
||||||
if (!config.name || !config.version) {
|
) as unknown as ExtensionConfig;
|
||||||
throw new Error(
|
|
||||||
`Invalid configuration in ${configFilePath}: missing ${!config.name ? '"name"' : '"version"'}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
validateName(config.name);
|
validateName(config.name);
|
||||||
return config;
|
return config;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user