mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 14:04:41 -07:00
feat: apply remote admin settings (no-op) (#16106)
This commit is contained in:
@@ -2476,6 +2476,187 @@ describe('Settings Loading and Merging', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('LoadedSettings and remote admin settings', () => {
|
||||||
|
it('should prioritize remote admin settings over file-based admin settings', () => {
|
||||||
|
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||||
|
const systemSettingsContent = {
|
||||||
|
admin: {
|
||||||
|
// These should be ignored
|
||||||
|
secureModeEnabled: true,
|
||||||
|
mcp: { enabled: false },
|
||||||
|
extensions: { enabled: false },
|
||||||
|
},
|
||||||
|
// A non-admin setting to ensure it's still processed
|
||||||
|
ui: { theme: 'system-theme' },
|
||||||
|
};
|
||||||
|
|
||||||
|
(fs.readFileSync as Mock).mockImplementation(
|
||||||
|
(p: fs.PathOrFileDescriptor) => {
|
||||||
|
if (p === getSystemSettingsPath()) {
|
||||||
|
return JSON.stringify(systemSettingsContent);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||||
|
|
||||||
|
// 1. Verify that on initial load, file-based admin settings are ignored
|
||||||
|
// and schema defaults are used instead.
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false); // default: false
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // default: true
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // default: true
|
||||||
|
expect(loadedSettings.merged.ui?.theme).toBe('system-theme'); // non-admin setting should be loaded
|
||||||
|
|
||||||
|
// 2. Now, set remote admin settings.
|
||||||
|
loadedSettings.setRemoteAdminSettings({
|
||||||
|
secureModeEnabled: true,
|
||||||
|
mcpSetting: { mcpEnabled: false },
|
||||||
|
cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Verify that remote admin settings take precedence.
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||||
|
// non-admin setting should remain unchanged
|
||||||
|
expect(loadedSettings.merged.ui?.theme).toBe('system-theme');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set remote admin settings and recompute merged settings', () => {
|
||||||
|
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||||
|
const systemSettingsContent = {
|
||||||
|
admin: {
|
||||||
|
secureModeEnabled: false,
|
||||||
|
mcp: { enabled: false },
|
||||||
|
extensions: { enabled: false },
|
||||||
|
},
|
||||||
|
ui: { theme: 'initial-theme' },
|
||||||
|
};
|
||||||
|
|
||||||
|
(fs.readFileSync as Mock).mockImplementation(
|
||||||
|
(p: fs.PathOrFileDescriptor) => {
|
||||||
|
if (p === getSystemSettingsPath()) {
|
||||||
|
return JSON.stringify(systemSettingsContent);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||||
|
// Ensure initial state from defaults (as file-based admin settings are ignored)
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||||
|
|
||||||
|
const newRemoteSettings = {
|
||||||
|
secureModeEnabled: true,
|
||||||
|
mcpSetting: { mcpEnabled: false },
|
||||||
|
cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
|
||||||
|
};
|
||||||
|
|
||||||
|
loadedSettings.setRemoteAdminSettings(newRemoteSettings);
|
||||||
|
|
||||||
|
// Verify that remote admin settings are applied
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||||
|
// Non-admin settings should remain untouched
|
||||||
|
expect(loadedSettings.merged.ui?.theme).toBe('initial-theme');
|
||||||
|
|
||||||
|
// Verify that calling setRemoteAdminSettings with partial data overwrites previous remote settings
|
||||||
|
// and missing properties revert to schema defaults.
|
||||||
|
loadedSettings.setRemoteAdminSettings({ secureModeEnabled: false });
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true); // Reverts to default: true
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true); // Reverts to default: true
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly handle undefined remote admin settings', () => {
|
||||||
|
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||||
|
const systemSettingsContent = {
|
||||||
|
ui: { theme: 'initial-theme' },
|
||||||
|
};
|
||||||
|
|
||||||
|
(fs.readFileSync as Mock).mockImplementation(
|
||||||
|
(p: fs.PathOrFileDescriptor) => {
|
||||||
|
if (p === getSystemSettingsPath()) {
|
||||||
|
return JSON.stringify(systemSettingsContent);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||||
|
// Should have default admin settings
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
|
||||||
|
loadedSettings.setRemoteAdminSettings({}); // Set empty remote settings
|
||||||
|
|
||||||
|
// Admin settings should revert to defaults because there are no remote overrides
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly handle missing properties in remote admin settings', () => {
|
||||||
|
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||||
|
const systemSettingsContent = {
|
||||||
|
admin: {
|
||||||
|
secureModeEnabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(fs.readFileSync as Mock).mockImplementation(
|
||||||
|
(p: fs.PathOrFileDescriptor) => {
|
||||||
|
if (p === getSystemSettingsPath()) {
|
||||||
|
return JSON.stringify(systemSettingsContent);
|
||||||
|
}
|
||||||
|
return '{}';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadedSettings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||||
|
// Ensure initial state from defaults (as file-based admin settings are ignored)
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
|
||||||
|
// Set remote settings with only secureModeEnabled
|
||||||
|
loadedSettings.setRemoteAdminSettings({
|
||||||
|
secureModeEnabled: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify secureModeEnabled is updated, others remain defaults
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
|
||||||
|
// Set remote settings with only mcpSetting.mcpEnabled
|
||||||
|
loadedSettings.setRemoteAdminSettings({
|
||||||
|
mcpSetting: { mcpEnabled: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify mcpEnabled is updated, others remain defaults (secureModeEnabled reverts to default:false)
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(true);
|
||||||
|
|
||||||
|
// Set remote settings with only cliFeatureSetting.extensionsSetting.extensionsEnabled
|
||||||
|
loadedSettings.setRemoteAdminSettings({
|
||||||
|
cliFeatureSetting: { extensionsSetting: { extensionsEnabled: false } },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify extensionsEnabled is updated, others remain defaults
|
||||||
|
expect(loadedSettings.merged.admin?.secureModeEnabled).toBe(false);
|
||||||
|
expect(loadedSettings.merged.admin?.mcp?.enabled).toBe(true);
|
||||||
|
expect(loadedSettings.merged.admin?.extensions?.enabled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getDefaultsFromSchema', () => {
|
describe('getDefaultsFromSchema', () => {
|
||||||
it('should extract defaults from a schema', () => {
|
it('should extract defaults from a schema', () => {
|
||||||
const mockSchema = {
|
const mockSchema = {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
Storage,
|
Storage,
|
||||||
coreEvents,
|
coreEvents,
|
||||||
homedir,
|
homedir,
|
||||||
|
type GeminiCodeAssistSetting,
|
||||||
} from '@google/gemini-cli-core';
|
} from '@google/gemini-cli-core';
|
||||||
import stripJsonComments from 'strip-json-comments';
|
import stripJsonComments from 'strip-json-comments';
|
||||||
import { DefaultLight } from '../ui/themes/default-light.js';
|
import { DefaultLight } from '../ui/themes/default-light.js';
|
||||||
@@ -499,19 +500,37 @@ export class LoadedSettings {
|
|||||||
readonly errors: SettingsError[];
|
readonly errors: SettingsError[];
|
||||||
|
|
||||||
private _merged: Settings;
|
private _merged: Settings;
|
||||||
|
private _remoteAdminSettings: Partial<Settings> | undefined;
|
||||||
|
|
||||||
get merged(): Settings {
|
get merged(): Settings {
|
||||||
return this._merged;
|
return this._merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeMergedSettings(): Settings {
|
private computeMergedSettings(): Settings {
|
||||||
return mergeSettings(
|
const merged = mergeSettings(
|
||||||
this.system.settings,
|
this.system.settings,
|
||||||
this.systemDefaults.settings,
|
this.systemDefaults.settings,
|
||||||
this.user.settings,
|
this.user.settings,
|
||||||
this.workspace.settings,
|
this.workspace.settings,
|
||||||
this.isTrusted,
|
this.isTrusted,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Remote admin settings always take precedence and file-based admin settings
|
||||||
|
// are ignored.
|
||||||
|
const adminSettingSchema = getSettingsSchema().admin;
|
||||||
|
if (adminSettingSchema?.properties) {
|
||||||
|
const adminSchema = adminSettingSchema.properties;
|
||||||
|
const adminDefaults = getDefaultsFromSchema(adminSchema);
|
||||||
|
|
||||||
|
// The final admin settings are the defaults overridden by remote settings.
|
||||||
|
// Any admin settings from files are ignored.
|
||||||
|
merged.admin = customDeepMerge(
|
||||||
|
(path: string[]) => getMergeStrategyForPath(['admin', ...path]),
|
||||||
|
adminDefaults,
|
||||||
|
this._remoteAdminSettings?.admin ?? {},
|
||||||
|
) as Settings['admin'];
|
||||||
|
}
|
||||||
|
return merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
forScope(scope: LoadableSettingScope): SettingsFile {
|
forScope(scope: LoadableSettingScope): SettingsFile {
|
||||||
@@ -537,6 +556,31 @@ export class LoadedSettings {
|
|||||||
saveSettings(settingsFile);
|
saveSettings(settingsFile);
|
||||||
coreEvents.emitSettingsChanged();
|
coreEvents.emitSettingsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRemoteAdminSettings(remoteSettings: GeminiCodeAssistSetting): void {
|
||||||
|
const admin: Settings['admin'] = {};
|
||||||
|
|
||||||
|
if (remoteSettings.secureModeEnabled !== undefined) {
|
||||||
|
admin.secureModeEnabled = remoteSettings.secureModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteSettings.mcpSetting?.mcpEnabled !== undefined) {
|
||||||
|
admin.mcp = { enabled: remoteSettings.mcpSetting.mcpEnabled };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
remoteSettings.cliFeatureSetting?.extensionsSetting?.extensionsEnabled !==
|
||||||
|
undefined
|
||||||
|
) {
|
||||||
|
admin.extensions = {
|
||||||
|
enabled:
|
||||||
|
remoteSettings.cliFeatureSetting.extensionsSetting.extensionsEnabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this._remoteAdminSettings = { admin };
|
||||||
|
this._merged = this.computeMergedSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findEnvFile(startDir: string): string | null {
|
function findEnvFile(startDir: string): string | null {
|
||||||
|
|||||||
@@ -230,91 +230,6 @@ describe('gemini.tsx main function', () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('verifies that we dont load the config before relaunchAppInChildProcess', async () => {
|
|
||||||
const processExitSpy = vi
|
|
||||||
.spyOn(process, 'exit')
|
|
||||||
.mockImplementation((code) => {
|
|
||||||
throw new MockProcessExitError(code);
|
|
||||||
});
|
|
||||||
const { relaunchAppInChildProcess } = await import('./utils/relaunch.js');
|
|
||||||
const { loadCliConfig } = await import('./config/config.js');
|
|
||||||
const { loadSettings } = await import('./config/settings.js');
|
|
||||||
const { loadSandboxConfig } = await import('./config/sandboxConfig.js');
|
|
||||||
vi.mocked(loadSandboxConfig).mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
const callOrder: string[] = [];
|
|
||||||
vi.mocked(relaunchAppInChildProcess).mockImplementation(async () => {
|
|
||||||
callOrder.push('relaunch');
|
|
||||||
});
|
|
||||||
vi.mocked(loadCliConfig).mockImplementation(async () => {
|
|
||||||
callOrder.push('loadCliConfig');
|
|
||||||
return {
|
|
||||||
isInteractive: () => false,
|
|
||||||
getQuestion: () => '',
|
|
||||||
getSandbox: () => false,
|
|
||||||
getDebugMode: () => false,
|
|
||||||
getListExtensions: () => false,
|
|
||||||
getListSessions: () => false,
|
|
||||||
getDeleteSession: () => undefined,
|
|
||||||
getMcpServers: () => ({}),
|
|
||||||
getMcpClientManager: vi.fn(),
|
|
||||||
initialize: vi.fn(),
|
|
||||||
getIdeMode: () => false,
|
|
||||||
getExperimentalZedIntegration: () => false,
|
|
||||||
getScreenReader: () => false,
|
|
||||||
getGeminiMdFileCount: () => 0,
|
|
||||||
getProjectRoot: () => '/',
|
|
||||||
getPolicyEngine: vi.fn(),
|
|
||||||
getMessageBus: () => ({
|
|
||||||
subscribe: vi.fn(),
|
|
||||||
}),
|
|
||||||
getEnableHooks: () => false,
|
|
||||||
getHookSystem: () => undefined,
|
|
||||||
getToolRegistry: vi.fn(),
|
|
||||||
getContentGeneratorConfig: vi.fn(),
|
|
||||||
getModel: () => 'gemini-pro',
|
|
||||||
getEmbeddingModel: () => 'embedding-001',
|
|
||||||
getApprovalMode: () => 'default',
|
|
||||||
getCoreTools: () => [],
|
|
||||||
getTelemetryEnabled: () => false,
|
|
||||||
getTelemetryLogPromptsEnabled: () => false,
|
|
||||||
getFileFilteringRespectGitIgnore: () => true,
|
|
||||||
getOutputFormat: () => 'text',
|
|
||||||
getExtensions: () => [],
|
|
||||||
getUsageStatisticsEnabled: () => false,
|
|
||||||
refreshAuth: vi.fn(),
|
|
||||||
setTerminalBackground: vi.fn(),
|
|
||||||
} as unknown as Config;
|
|
||||||
});
|
|
||||||
vi.mocked(loadSettings).mockReturnValue({
|
|
||||||
errors: [],
|
|
||||||
merged: {
|
|
||||||
advanced: { autoConfigureMemory: true },
|
|
||||||
security: { auth: {} },
|
|
||||||
ui: {},
|
|
||||||
},
|
|
||||||
workspace: { settings: {} },
|
|
||||||
setValue: vi.fn(),
|
|
||||||
forScope: () => ({ settings: {}, originalSettings: {}, path: '' }),
|
|
||||||
} as never);
|
|
||||||
try {
|
|
||||||
await main();
|
|
||||||
} catch (e) {
|
|
||||||
// Mocked process exit throws an error.
|
|
||||||
if (!(e instanceof MockProcessExitError)) throw e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is critical that we call relaunch before loadCliConfig to avoid
|
|
||||||
// loading config in the outer process when we are going to relaunch.
|
|
||||||
// By ensuring we don't load the config we also ensure we don't trigger any
|
|
||||||
// operations that might require loading the config such as such as
|
|
||||||
// initializing mcp servers.
|
|
||||||
// For the sandbox case we still have to load a partial cli config.
|
|
||||||
// we can authorize outside the sandbox.
|
|
||||||
expect(callOrder).toEqual(['relaunch', 'loadCliConfig']);
|
|
||||||
processExitSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should log unhandled promise rejections and open debug console on first error', async () => {
|
it('should log unhandled promise rejections and open debug console on first error', async () => {
|
||||||
const processExitSpy = vi
|
const processExitSpy = vi
|
||||||
.spyOn(process, 'exit')
|
.spyOn(process, 'exit')
|
||||||
@@ -519,6 +434,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getOutputFormat: () => 'text',
|
getOutputFormat: () => 'text',
|
||||||
getExtensions: () => [],
|
getExtensions: () => [],
|
||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as unknown as Config);
|
} as unknown as Config);
|
||||||
vi.mocked(loadSettings).mockReturnValue({
|
vi.mocked(loadSettings).mockReturnValue({
|
||||||
@@ -621,6 +537,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getScreenReader: () => false,
|
getScreenReader: () => false,
|
||||||
getGeminiMdFileCount: () => 0,
|
getGeminiMdFileCount: () => 0,
|
||||||
getProjectRoot: () => '/',
|
getProjectRoot: () => '/',
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
@@ -706,6 +623,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getGeminiMdFileCount: () => 0,
|
getGeminiMdFileCount: () => 0,
|
||||||
getProjectRoot: () => '/',
|
getProjectRoot: () => '/',
|
||||||
refreshAuth: vi.fn(),
|
refreshAuth: vi.fn(),
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as unknown as Config;
|
} as unknown as Config;
|
||||||
|
|
||||||
@@ -790,6 +708,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
refreshAuth: vi.fn(),
|
refreshAuth: vi.fn(),
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
vi.spyOn(themeManager, 'setActiveTheme').mockReturnValue(false);
|
vi.spyOn(themeManager, 'setActiveTheme').mockReturnValue(false);
|
||||||
@@ -872,6 +791,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getFileFilteringRespectGitIgnore: () => true,
|
getFileFilteringRespectGitIgnore: () => true,
|
||||||
getOutputFormat: () => 'text',
|
getOutputFormat: () => 'text',
|
||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
@@ -953,6 +873,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getFileFilteringRespectGitIgnore: () => true,
|
getFileFilteringRespectGitIgnore: () => true,
|
||||||
getOutputFormat: () => 'text',
|
getOutputFormat: () => 'text',
|
||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
@@ -1030,6 +951,7 @@ describe('gemini.tsx main function kitty protocol', () => {
|
|||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
refreshAuth: vi.fn(),
|
refreshAuth: vi.fn(),
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
vi.mock('./utils/readStdin.js', () => ({
|
vi.mock('./utils/readStdin.js', () => ({
|
||||||
@@ -1191,6 +1113,7 @@ describe('gemini.tsx main function exit codes', () => {
|
|||||||
getOutputFormat: () => 'text',
|
getOutputFormat: () => 'text',
|
||||||
getExtensions: () => [],
|
getExtensions: () => [],
|
||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
} as unknown as Config);
|
} as unknown as Config);
|
||||||
vi.mocked(loadSettings).mockReturnValue({
|
vi.mocked(loadSettings).mockReturnValue({
|
||||||
@@ -1257,6 +1180,7 @@ describe('gemini.tsx main function exit codes', () => {
|
|||||||
getExtensions: () => [],
|
getExtensions: () => [],
|
||||||
getUsageStatisticsEnabled: () => false,
|
getUsageStatisticsEnabled: () => false,
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
|
getRemoteAdminSettings: () => undefined,
|
||||||
} as unknown as Config);
|
} as unknown as Config);
|
||||||
vi.mocked(loadSettings).mockReturnValue({
|
vi.mocked(loadSettings).mockReturnValue({
|
||||||
merged: { security: { auth: {} }, ui: {} },
|
merged: { security: { auth: {} }, ui: {} },
|
||||||
|
|||||||
+45
-39
@@ -375,6 +375,51 @@ export async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const partialConfig = await loadCliConfig(settings.merged, sessionId, argv, {
|
||||||
|
projectHooks: settings.workspace.settings.hooks,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Refresh auth to fetch remote admin settings from CCPA and before entering
|
||||||
|
// the sandbox because the sandbox will interfere with the Oauth2 web
|
||||||
|
// redirect.
|
||||||
|
if (
|
||||||
|
settings.merged.security?.auth?.selectedType &&
|
||||||
|
!settings.merged.security?.auth?.useExternal
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (partialConfig.isInteractive()) {
|
||||||
|
const err = validateAuthMethod(
|
||||||
|
settings.merged.security.auth.selectedType,
|
||||||
|
);
|
||||||
|
if (err) {
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
await partialConfig.refreshAuth(
|
||||||
|
settings.merged.security.auth.selectedType,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const authType = await validateNonInteractiveAuth(
|
||||||
|
settings.merged.security?.auth?.selectedType,
|
||||||
|
settings.merged.security?.auth?.useExternal,
|
||||||
|
partialConfig,
|
||||||
|
settings,
|
||||||
|
);
|
||||||
|
await partialConfig.refreshAuth(authType);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
debugLogger.error('Error authenticating:', err);
|
||||||
|
await runExitCleanup();
|
||||||
|
process.exit(ExitCodes.FATAL_AUTHENTICATION_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteAdminSettings = partialConfig.getRemoteAdminSettings();
|
||||||
|
// Set remote admin settings if returned from CCPA.
|
||||||
|
if (remoteAdminSettings) {
|
||||||
|
settings.setRemoteAdminSettings(remoteAdminSettings);
|
||||||
|
}
|
||||||
|
|
||||||
// hop into sandbox if we are outside and sandboxing is enabled
|
// hop into sandbox if we are outside and sandboxing is enabled
|
||||||
if (!process.env['SANDBOX']) {
|
if (!process.env['SANDBOX']) {
|
||||||
const memoryArgs = settings.merged.advanced?.autoConfigureMemory
|
const memoryArgs = settings.merged.advanced?.autoConfigureMemory
|
||||||
@@ -388,45 +433,6 @@ export async function main() {
|
|||||||
// another way to decouple refreshAuth from requiring a config.
|
// another way to decouple refreshAuth from requiring a config.
|
||||||
|
|
||||||
if (sandboxConfig) {
|
if (sandboxConfig) {
|
||||||
const partialConfig = await loadCliConfig(
|
|
||||||
settings.merged,
|
|
||||||
sessionId,
|
|
||||||
argv,
|
|
||||||
{ projectHooks: settings.workspace.settings.hooks },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
settings.merged.security?.auth?.selectedType &&
|
|
||||||
!settings.merged.security?.auth?.useExternal
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (partialConfig.isInteractive()) {
|
|
||||||
// Validate authentication here because the sandbox will interfere with the Oauth2 web redirect.
|
|
||||||
const err = validateAuthMethod(
|
|
||||||
settings.merged.security.auth.selectedType,
|
|
||||||
);
|
|
||||||
if (err) {
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
await partialConfig.refreshAuth(
|
|
||||||
settings.merged.security.auth.selectedType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const authType = await validateNonInteractiveAuth(
|
|
||||||
settings.merged.security?.auth?.selectedType,
|
|
||||||
settings.merged.security?.auth?.useExternal,
|
|
||||||
partialConfig,
|
|
||||||
settings,
|
|
||||||
);
|
|
||||||
await partialConfig.refreshAuth(authType);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
debugLogger.error('Error authenticating:', err);
|
|
||||||
await runExitCleanup();
|
|
||||||
process.exit(ExitCodes.FATAL_AUTHENTICATION_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let stdinData = '';
|
let stdinData = '';
|
||||||
if (!process.stdin.isTTY) {
|
if (!process.stdin.isTTY) {
|
||||||
stdinData = await readStdin();
|
stdinData = await readStdin();
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ describe('gemini.tsx main function cleanup', () => {
|
|||||||
getUsageStatisticsEnabled: vi.fn(() => false),
|
getUsageStatisticsEnabled: vi.fn(() => false),
|
||||||
setTerminalBackground: vi.fn(),
|
setTerminalBackground: vi.fn(),
|
||||||
refreshAuth: vi.fn(),
|
refreshAuth: vi.fn(),
|
||||||
|
getRemoteAdminSettings: vi.fn(() => undefined),
|
||||||
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
} as any); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user