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