Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/cli/src/config/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ import { isWorkspaceTrusted } from './trustedFolders.js';
import {
type Settings,
type MemoryImportFormat,
SETTINGS_SCHEMA,
type MergeStrategy,
type SettingsSchema,
type SettingDefinition,
getSettingsSchema,
} from './settingsSchema.js';
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
import { customDeepMerge } from '../utils/deepMerge.js';

function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
let current: SettingDefinition | undefined = undefined;
let currentSchema: SettingsSchema | undefined = SETTINGS_SCHEMA;
let currentSchema: SettingsSchema | undefined = getSettingsSchema();

for (const key of path) {
if (!currentSchema || !currentSchema[key]) {
Expand Down
175 changes: 93 additions & 82 deletions packages/cli/src/config/settingsSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
*/

import { describe, it, expect } from 'vitest';
import type { Settings } from './settingsSchema.js';
import { SETTINGS_SCHEMA } from './settingsSchema.js';
import {
getSettingsSchema,
type SettingDefinition,
type Settings,
type SettingsSchema,
} from './settingsSchema.js';

describe('SettingsSchema', () => {
describe('SETTINGS_SCHEMA', () => {
describe('getSettingsSchema', () => {
it('should contain all expected top-level settings', () => {
const expectedSettings = [
const expectedSettings: Array<keyof Settings> = [
'mcpServers',
'general',
'ui',
Expand All @@ -27,14 +31,12 @@ describe('SettingsSchema', () => {
];

expectedSettings.forEach((setting) => {
expect(
SETTINGS_SCHEMA[setting as keyof typeof SETTINGS_SCHEMA],
).toBeDefined();
expect(getSettingsSchema()[setting as keyof Settings]).toBeDefined();
});
});

it('should have correct structure for each setting', () => {
Object.entries(SETTINGS_SCHEMA).forEach(([_key, definition]) => {
Object.entries(getSettingsSchema()).forEach(([_key, definition]) => {
expect(definition).toHaveProperty('type');
expect(definition).toHaveProperty('label');
expect(definition).toHaveProperty('category');
Expand All @@ -48,7 +50,7 @@ describe('SettingsSchema', () => {
});

it('should have correct nested setting structure', () => {
const nestedSettings = [
const nestedSettings: Array<keyof Settings> = [
'general',
'ui',
'ide',
Expand All @@ -62,11 +64,9 @@ describe('SettingsSchema', () => {
];

nestedSettings.forEach((setting) => {
const definition = SETTINGS_SCHEMA[
setting as keyof typeof SETTINGS_SCHEMA
] as (typeof SETTINGS_SCHEMA)[keyof typeof SETTINGS_SCHEMA] & {
properties: unknown;
};
const definition = getSettingsSchema()[
setting as keyof Settings
] as SettingDefinition;
expect(definition.type).toBe('object');
expect(definition.properties).toBeDefined();
expect(typeof definition.properties).toBe('object');
Expand All @@ -75,35 +75,36 @@ describe('SettingsSchema', () => {

it('should have accessibility nested properties', () => {
expect(
SETTINGS_SCHEMA.ui?.properties?.accessibility?.properties,
getSettingsSchema().ui?.properties?.accessibility?.properties,
).toBeDefined();
expect(
SETTINGS_SCHEMA.ui?.properties?.accessibility.properties
getSettingsSchema().ui?.properties?.accessibility.properties
?.disableLoadingPhrases.type,
).toBe('boolean');
});

it('should have checkpointing nested properties', () => {
expect(
SETTINGS_SCHEMA.general?.properties?.checkpointing.properties?.enabled,
getSettingsSchema().general?.properties?.checkpointing.properties
?.enabled,
).toBeDefined();
expect(
SETTINGS_SCHEMA.general?.properties?.checkpointing.properties?.enabled
.type,
getSettingsSchema().general?.properties?.checkpointing.properties
?.enabled.type,
).toBe('boolean');
});

it('should have fileFiltering nested properties', () => {
expect(
SETTINGS_SCHEMA.context.properties.fileFiltering.properties
getSettingsSchema().context.properties.fileFiltering.properties
?.respectGitIgnore,
).toBeDefined();
expect(
SETTINGS_SCHEMA.context.properties.fileFiltering.properties
getSettingsSchema().context.properties.fileFiltering.properties
?.respectGeminiIgnore,
).toBeDefined();
expect(
SETTINGS_SCHEMA.context.properties.fileFiltering.properties
getSettingsSchema().context.properties.fileFiltering.properties
?.enableRecursiveFileSearch,
).toBeDefined();
});
Expand All @@ -112,7 +113,7 @@ describe('SettingsSchema', () => {
const categories = new Set();

// Collect categories from top-level settings
Object.values(SETTINGS_SCHEMA).forEach((definition) => {
Object.values(getSettingsSchema()).forEach((definition) => {
categories.add(definition.category);
// Also collect from nested properties
const defWithProps = definition as typeof definition & {
Expand All @@ -137,74 +138,80 @@ describe('SettingsSchema', () => {
});

it('should have consistent default values for boolean settings', () => {
const checkBooleanDefaults = (schema: Record<string, unknown>) => {
Object.entries(schema).forEach(
([_key, definition]: [string, unknown]) => {
const def = definition as {
type?: string;
default?: unknown;
properties?: Record<string, unknown>;
};
if (def.type === 'boolean') {
// Boolean settings can have boolean or undefined defaults (for optional settings)
expect(['boolean', 'undefined']).toContain(typeof def.default);
}
if (def.properties) {
checkBooleanDefaults(def.properties);
}
},
);
const checkBooleanDefaults = (schema: SettingsSchema) => {
Object.entries(schema).forEach(([, definition]) => {
const def = definition as SettingDefinition;
if (def.type === 'boolean') {
// Boolean settings can have boolean or undefined defaults (for optional settings)
expect(['boolean', 'undefined']).toContain(typeof def.default);
}
if (def.properties) {
checkBooleanDefaults(def.properties);
}
});
};

checkBooleanDefaults(SETTINGS_SCHEMA as Record<string, unknown>);
checkBooleanDefaults(getSettingsSchema() as SettingsSchema);
});

it('should have showInDialog property configured', () => {
// Check that user-facing settings are marked for dialog display
expect(SETTINGS_SCHEMA.ui.properties.showMemoryUsage.showInDialog).toBe(
expect(
getSettingsSchema().ui.properties.showMemoryUsage.showInDialog,
).toBe(true);
expect(getSettingsSchema().general.properties.vimMode.showInDialog).toBe(
true,
);
expect(SETTINGS_SCHEMA.general.properties.vimMode.showInDialog).toBe(
expect(getSettingsSchema().ide.properties.enabled.showInDialog).toBe(
true,
);
expect(SETTINGS_SCHEMA.ide.properties.enabled.showInDialog).toBe(true);
expect(
SETTINGS_SCHEMA.general.properties.disableAutoUpdate.showInDialog,
getSettingsSchema().general.properties.disableAutoUpdate.showInDialog,
).toBe(true);
expect(SETTINGS_SCHEMA.ui.properties.hideWindowTitle.showInDialog).toBe(
expect(
getSettingsSchema().ui.properties.hideWindowTitle.showInDialog,
).toBe(true);
expect(getSettingsSchema().ui.properties.hideTips.showInDialog).toBe(
true,
);
expect(getSettingsSchema().ui.properties.hideBanner.showInDialog).toBe(
true,
);
expect(SETTINGS_SCHEMA.ui.properties.hideTips.showInDialog).toBe(true);
expect(SETTINGS_SCHEMA.ui.properties.hideBanner.showInDialog).toBe(true);
expect(
SETTINGS_SCHEMA.privacy.properties.usageStatisticsEnabled.showInDialog,
getSettingsSchema().privacy.properties.usageStatisticsEnabled
.showInDialog,
).toBe(false);

// Check that advanced settings are hidden from dialog
expect(SETTINGS_SCHEMA.security.properties.auth.showInDialog).toBe(false);
expect(SETTINGS_SCHEMA.tools.properties.core.showInDialog).toBe(false);
expect(SETTINGS_SCHEMA.mcpServers.showInDialog).toBe(false);
expect(SETTINGS_SCHEMA.telemetry.showInDialog).toBe(false);
expect(getSettingsSchema().security.properties.auth.showInDialog).toBe(
false,
);
expect(getSettingsSchema().tools.properties.core.showInDialog).toBe(
false,
);
expect(getSettingsSchema().mcpServers.showInDialog).toBe(false);
expect(getSettingsSchema().telemetry.showInDialog).toBe(false);

// Check that some settings are appropriately hidden
expect(SETTINGS_SCHEMA.ui.properties.theme.showInDialog).toBe(false); // Changed to false
expect(SETTINGS_SCHEMA.ui.properties.customThemes.showInDialog).toBe(
expect(getSettingsSchema().ui.properties.theme.showInDialog).toBe(false); // Changed to false
expect(getSettingsSchema().ui.properties.customThemes.showInDialog).toBe(
false,
); // Managed via theme editor
expect(
SETTINGS_SCHEMA.general.properties.checkpointing.showInDialog,
getSettingsSchema().general.properties.checkpointing.showInDialog,
).toBe(false); // Experimental feature
expect(SETTINGS_SCHEMA.ui.properties.accessibility.showInDialog).toBe(
expect(getSettingsSchema().ui.properties.accessibility.showInDialog).toBe(
false,
); // Changed to false
expect(
SETTINGS_SCHEMA.context.properties.fileFiltering.showInDialog,
getSettingsSchema().context.properties.fileFiltering.showInDialog,
).toBe(false); // Changed to false
expect(
SETTINGS_SCHEMA.general.properties.preferredEditor.showInDialog,
getSettingsSchema().general.properties.preferredEditor.showInDialog,
).toBe(false); // Changed to false
expect(
SETTINGS_SCHEMA.advanced.properties.autoConfigureMemory.showInDialog,
getSettingsSchema().advanced.properties.autoConfigureMemory
.showInDialog,
).toBe(false);
});

Expand All @@ -228,80 +235,84 @@ describe('SettingsSchema', () => {

it('should have includeDirectories setting in schema', () => {
expect(
SETTINGS_SCHEMA.context?.properties.includeDirectories,
getSettingsSchema().context?.properties.includeDirectories,
).toBeDefined();
expect(SETTINGS_SCHEMA.context?.properties.includeDirectories.type).toBe(
'array',
);
expect(
SETTINGS_SCHEMA.context?.properties.includeDirectories.category,
getSettingsSchema().context?.properties.includeDirectories.type,
).toBe('array');
expect(
getSettingsSchema().context?.properties.includeDirectories.category,
).toBe('Context');
expect(
SETTINGS_SCHEMA.context?.properties.includeDirectories.default,
getSettingsSchema().context?.properties.includeDirectories.default,
).toEqual([]);
});

it('should have loadMemoryFromIncludeDirectories setting in schema', () => {
expect(
SETTINGS_SCHEMA.context?.properties.loadMemoryFromIncludeDirectories,
getSettingsSchema().context?.properties
.loadMemoryFromIncludeDirectories,
).toBeDefined();
expect(
SETTINGS_SCHEMA.context?.properties.loadMemoryFromIncludeDirectories
getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories
.type,
).toBe('boolean');
expect(
SETTINGS_SCHEMA.context?.properties.loadMemoryFromIncludeDirectories
getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories
.category,
).toBe('Context');
expect(
SETTINGS_SCHEMA.context?.properties.loadMemoryFromIncludeDirectories
getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories
.default,
).toBe(false);
});

it('should have folderTrustFeature setting in schema', () => {
expect(
SETTINGS_SCHEMA.security.properties.folderTrust.properties.enabled,
getSettingsSchema().security.properties.folderTrust.properties.enabled,
).toBeDefined();
expect(
SETTINGS_SCHEMA.security.properties.folderTrust.properties.enabled.type,
getSettingsSchema().security.properties.folderTrust.properties.enabled
.type,
).toBe('boolean');
expect(
SETTINGS_SCHEMA.security.properties.folderTrust.properties.enabled
getSettingsSchema().security.properties.folderTrust.properties.enabled
.category,
).toBe('Security');
expect(
SETTINGS_SCHEMA.security.properties.folderTrust.properties.enabled
getSettingsSchema().security.properties.folderTrust.properties.enabled
.default,
).toBe(false);
expect(
SETTINGS_SCHEMA.security.properties.folderTrust.properties.enabled
getSettingsSchema().security.properties.folderTrust.properties.enabled
.showInDialog,
).toBe(true);
});

it('should have debugKeystrokeLogging setting in schema', () => {
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging,
getSettingsSchema().general.properties.debugKeystrokeLogging,
).toBeDefined();
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging.type,
getSettingsSchema().general.properties.debugKeystrokeLogging.type,
).toBe('boolean');
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging.category,
getSettingsSchema().general.properties.debugKeystrokeLogging.category,
).toBe('General');
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging.default,
getSettingsSchema().general.properties.debugKeystrokeLogging.default,
).toBe(false);
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging
getSettingsSchema().general.properties.debugKeystrokeLogging
.requiresRestart,
).toBe(false);
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging.showInDialog,
getSettingsSchema().general.properties.debugKeystrokeLogging
.showInDialog,
).toBe(true);
expect(
SETTINGS_SCHEMA.general.properties.debugKeystrokeLogging.description,
getSettingsSchema().general.properties.debugKeystrokeLogging
.description,
).toBe('Enable debug logging of keystrokes to the console.');
});
});
Expand Down
Loading
Loading