Download packages/cli/src/config/settingsSchema.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 24.7 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/settingsSchema.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/config/settingsSchema.test.ts
-
curl -L -o settingsSchema.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/config/settingsSchema.test.ts
24.7 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect } from 'vitest'; | |
| import { | |
| getSettingsSchema, | |
| SETTINGS_SCHEMA_DEFINITIONS, | |
| type SettingCollectionDefinition, | |
| type SettingDefinition, | |
| type Settings, | |
| type SettingsSchema, | |
| } from './settingsSchema.js'; | |
| describe('SettingsSchema', () => { | |
| describe('getSettingsSchema', () => { | |
| it('should contain all expected top-level settings', () => { | |
| const expectedSettings: Array<keyof Settings> = [ | |
| 'mcpServers', | |
| 'general', | |
| 'ui', | |
| 'ide', | |
| 'privacy', | |
| 'telemetry', | |
| 'model', | |
| 'context', | |
| 'tools', | |
| 'mcp', | |
| 'security', | |
| 'advanced', | |
| ]; | |
| expectedSettings.forEach((setting) => { | |
| expect(getSettingsSchema()[setting]).toBeDefined(); | |
| }); | |
| }); | |
| it('should have correct structure for each setting', () => { | |
| Object.entries(getSettingsSchema()).forEach(([_key, definition]) => { | |
| expect(definition).toHaveProperty('type'); | |
| expect(definition).toHaveProperty('label'); | |
| expect(definition).toHaveProperty('category'); | |
| expect(definition).toHaveProperty('requiresRestart'); | |
| expect(definition).toHaveProperty('default'); | |
| expect(typeof definition.type).toBe('string'); | |
| expect(typeof definition.label).toBe('string'); | |
| expect(typeof definition.category).toBe('string'); | |
| expect(typeof definition.requiresRestart).toBe('boolean'); | |
| }); | |
| }); | |
| it('should have correct nested setting structure', () => { | |
| const nestedSettings: Array<keyof Settings> = [ | |
| 'general', | |
| 'ui', | |
| 'ide', | |
| 'privacy', | |
| 'model', | |
| 'context', | |
| 'tools', | |
| 'mcp', | |
| 'security', | |
| 'advanced', | |
| ]; | |
| nestedSettings.forEach((setting) => { | |
| const definition = getSettingsSchema()[setting] as SettingDefinition; | |
| expect(definition.type).toBe('object'); | |
| expect(definition.properties).toBeDefined(); | |
| expect(typeof definition.properties).toBe('object'); | |
| }); | |
| }); | |
| it('should have accessibility nested properties', () => { | |
| expect( | |
| getSettingsSchema().ui?.properties?.accessibility?.properties, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().ui?.properties?.accessibility.properties | |
| ?.enableLoadingPhrases.type, | |
| ).toBe('boolean'); | |
| }); | |
| it('should have loadingPhrases enum property', () => { | |
| const definition = getSettingsSchema().ui?.properties?.loadingPhrases; | |
| expect(definition).toBeDefined(); | |
| expect(definition?.type).toBe('enum'); | |
| expect(definition?.default).toBe('off'); | |
| expect(definition?.options?.map((o) => o.value)).toEqual([ | |
| 'tips', | |
| 'witty', | |
| 'all', | |
| 'off', | |
| ]); | |
| }); | |
| it('should have errorVerbosity enum property', () => { | |
| const definition = getSettingsSchema().ui?.properties?.errorVerbosity; | |
| expect(definition).toBeDefined(); | |
| expect(definition?.type).toBe('enum'); | |
| expect(definition?.default).toBe('low'); | |
| expect(definition?.options?.map((o) => o.value)).toEqual(['low', 'full']); | |
| }); | |
| it('should have checkpointing nested properties', () => { | |
| expect( | |
| getSettingsSchema().general?.properties?.checkpointing.properties | |
| ?.enabled, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().general?.properties?.checkpointing.properties | |
| ?.enabled.type, | |
| ).toBe('boolean'); | |
| }); | |
| it('should have plan nested properties', () => { | |
| expect( | |
| getSettingsSchema().general?.properties?.plan?.properties?.directory, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().general?.properties?.plan?.properties?.directory | |
| .type, | |
| ).toBe('string'); | |
| }); | |
| it('should have fileFiltering nested properties', () => { | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.respectGitIgnore, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.respectGeminiIgnore, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.enableRecursiveFileSearch, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.enableFileWatcher, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.customIgnoreFilePaths, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.properties | |
| ?.customIgnoreFilePaths.type, | |
| ).toBe('array'); | |
| }); | |
| it('should have unique categories', () => { | |
| const categories = new Set(); | |
| // Collect categories from top-level settings | |
| Object.values(getSettingsSchema()).forEach((definition) => { | |
| categories.add(definition.category); | |
| // Also collect from nested properties | |
| const defWithProps = definition as typeof definition & { | |
| properties?: Record<string, unknown>; | |
| }; | |
| if (defWithProps.properties) { | |
| Object.values(defWithProps.properties).forEach( | |
| (nestedDef: unknown) => { | |
| const nestedDefTyped = nestedDef as { category?: string }; | |
| if (nestedDefTyped.category) { | |
| categories.add(nestedDefTyped.category); | |
| } | |
| }, | |
| ); | |
| } | |
| }); | |
| expect(categories.size).toBeGreaterThan(0); | |
| expect(categories).toContain('General'); | |
| expect(categories).toContain('UI'); | |
| expect(categories).toContain('Advanced'); | |
| }); | |
| it('should have consistent default values for boolean settings', () => { | |
| const checkBooleanDefaults = (schema: SettingsSchema) => { | |
| Object.entries(schema).forEach(([, definition]) => { | |
| const def = definition; | |
| 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(getSettingsSchema() as SettingsSchema); | |
| }); | |
| it('should have showInDialog property configured', () => { | |
| // Check that user-facing settings are marked for dialog display | |
| expect( | |
| getSettingsSchema().ui.properties.showMemoryUsage.showInDialog, | |
| ).toBe(true); | |
| expect( | |
| getSettingsSchema().ui.properties.footer.properties | |
| .hideContextPercentage.showInDialog, | |
| ).toBe(true); | |
| expect(getSettingsSchema().general.properties.vimMode.showInDialog).toBe( | |
| true, | |
| ); | |
| expect(getSettingsSchema().ide.properties.enabled.showInDialog).toBe( | |
| true, | |
| ); | |
| expect( | |
| getSettingsSchema().general.properties.enableAutoUpdate.showInDialog, | |
| ).toBe(true); | |
| expect( | |
| getSettingsSchema().ui.properties.hideWindowTitle.showInDialog, | |
| ).toBe(true); | |
| expect(getSettingsSchema().ui.properties.hideTips.showInDialog).toBe( | |
| true, | |
| ); | |
| expect( | |
| getSettingsSchema().ui.properties.showShortcutsHint.showInDialog, | |
| ).toBe(true); | |
| expect(getSettingsSchema().ui.properties.hideBanner.showInDialog).toBe( | |
| true, | |
| ); | |
| expect( | |
| getSettingsSchema().privacy.properties.usageStatisticsEnabled | |
| .showInDialog, | |
| ).toBe(false); | |
| // Check that advanced settings are hidden from dialog | |
| 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(getSettingsSchema().ui.properties.theme.showInDialog).toBe(false); // Changed to false | |
| expect(getSettingsSchema().ui.properties.customThemes.showInDialog).toBe( | |
| false, | |
| ); // Managed via theme editor | |
| expect( | |
| getSettingsSchema().general.properties.checkpointing.showInDialog, | |
| ).toBe(false); // Experimental feature | |
| expect(getSettingsSchema().ui.properties.accessibility.showInDialog).toBe( | |
| false, | |
| ); // Changed to false | |
| expect( | |
| getSettingsSchema().context.properties.fileFiltering.showInDialog, | |
| ).toBe(false); // Changed to false | |
| expect( | |
| getSettingsSchema().general.properties.preferredEditor.showInDialog, | |
| ).toBe(false); // Changed to false | |
| expect( | |
| getSettingsSchema().advanced.properties.autoConfigureMemory | |
| .showInDialog, | |
| ).toBe(true); | |
| }); | |
| it('should infer Settings type correctly', () => { | |
| // This test ensures that the Settings type is properly inferred from the schema | |
| const settings: Settings = { | |
| ui: { | |
| theme: 'dark', | |
| }, | |
| context: { | |
| includeDirectories: ['/path/to/dir'], | |
| loadMemoryFromIncludeDirectories: true, | |
| }, | |
| }; | |
| // TypeScript should not complain about these properties | |
| expect(settings.ui?.theme).toBe('dark'); | |
| expect(settings.context?.includeDirectories).toEqual(['/path/to/dir']); | |
| expect(settings.context?.loadMemoryFromIncludeDirectories).toBe(true); | |
| }); | |
| it('should have includeDirectories setting in schema', () => { | |
| expect( | |
| getSettingsSchema().context?.properties.includeDirectories, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context?.properties.includeDirectories.type, | |
| ).toBe('array'); | |
| expect( | |
| getSettingsSchema().context?.properties.includeDirectories.category, | |
| ).toBe('Context'); | |
| expect( | |
| getSettingsSchema().context?.properties.includeDirectories.default, | |
| ).toEqual([]); | |
| }); | |
| it('should have loadMemoryFromIncludeDirectories setting in schema', () => { | |
| expect( | |
| getSettingsSchema().context?.properties | |
| .loadMemoryFromIncludeDirectories, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories | |
| .type, | |
| ).toBe('boolean'); | |
| expect( | |
| getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories | |
| .category, | |
| ).toBe('Context'); | |
| expect( | |
| getSettingsSchema().context?.properties.loadMemoryFromIncludeDirectories | |
| .default, | |
| ).toBe(false); | |
| }); | |
| it('should have Vertex AI routing settings in schema', () => { | |
| const vertexAi = | |
| getSettingsSchema().billing.properties.vertexAi.properties; | |
| expect(vertexAi.requestType).toBeDefined(); | |
| expect(vertexAi.requestType.type).toBe('enum'); | |
| expect( | |
| vertexAi.requestType.options?.map((option) => option.value), | |
| ).toEqual(['dedicated', 'shared']); | |
| expect(vertexAi.sharedRequestType).toBeDefined(); | |
| expect(vertexAi.sharedRequestType.type).toBe('enum'); | |
| expect( | |
| vertexAi.sharedRequestType.options?.map((option) => option.value), | |
| ).toEqual(['priority', 'flex']); | |
| }); | |
| it('should have folderTrustFeature setting in schema', () => { | |
| expect( | |
| getSettingsSchema().security.properties.folderTrust.properties.enabled, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().security.properties.folderTrust.properties.enabled | |
| .type, | |
| ).toBe('boolean'); | |
| expect( | |
| getSettingsSchema().security.properties.folderTrust.properties.enabled | |
| .category, | |
| ).toBe('Security'); | |
| expect( | |
| getSettingsSchema().security.properties.folderTrust.properties.enabled | |
| .default, | |
| ).toBe(true); | |
| expect( | |
| getSettingsSchema().security.properties.folderTrust.properties.enabled | |
| .showInDialog, | |
| ).toBe(true); | |
| }); | |
| it('should have debugKeystrokeLogging setting in schema', () => { | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging, | |
| ).toBeDefined(); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging.type, | |
| ).toBe('boolean'); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging.category, | |
| ).toBe('General'); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging.default, | |
| ).toBe(false); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging | |
| .requiresRestart, | |
| ).toBe(false); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging | |
| .showInDialog, | |
| ).toBe(true); | |
| expect( | |
| getSettingsSchema().general.properties.debugKeystrokeLogging | |
| .description, | |
| ).toBe('Enable debug logging of keystrokes to the console.'); | |
| }); | |
| it('should have showShortcutsHint setting in schema', () => { | |
| expect(getSettingsSchema().ui.properties.showShortcutsHint).toBeDefined(); | |
| expect(getSettingsSchema().ui.properties.showShortcutsHint.type).toBe( | |
| 'boolean', | |
| ); | |
| expect(getSettingsSchema().ui.properties.showShortcutsHint.category).toBe( | |
| 'UI', | |
| ); | |
| expect(getSettingsSchema().ui.properties.showShortcutsHint.default).toBe( | |
| true, | |
| ); | |
| expect( | |
| getSettingsSchema().ui.properties.showShortcutsHint.requiresRestart, | |
| ).toBe(false); | |
| expect( | |
| getSettingsSchema().ui.properties.showShortcutsHint.showInDialog, | |
| ).toBe(true); | |
| expect( | |
| getSettingsSchema().ui.properties.showShortcutsHint.description, | |
| ).toBe('Show the "? for shortcuts" hint above the input.'); | |
| }); | |
| it('should have enableNotifications setting in schema', () => { | |
| const setting = | |
| getSettingsSchema().general.properties.enableNotifications; | |
| expect(setting).toBeDefined(); | |
| expect(setting.type).toBe('boolean'); | |
| expect(setting.category).toBe('General'); | |
| expect(setting.default).toBe(false); | |
| expect(setting.requiresRestart).toBe(false); | |
| expect(setting.showInDialog).toBe(true); | |
| }); | |
| it('should have enableAgents setting in schema', () => { | |
| const setting = getSettingsSchema().experimental.properties.enableAgents; | |
| expect(setting).toBeDefined(); | |
| expect(setting.type).toBe('boolean'); | |
| expect(setting.category).toBe('Experimental'); | |
| expect(setting.default).toBe(true); | |
| expect(setting.requiresRestart).toBe(true); | |
| expect(setting.showInDialog).toBe(false); | |
| expect(setting.description).toBe('Enable local and remote subagents.'); | |
| }); | |
| it('should have skills setting enabled by default', () => { | |
| const setting = getSettingsSchema().skills.properties.enabled; | |
| expect(setting).toBeDefined(); | |
| expect(setting.type).toBe('boolean'); | |
| expect(setting.category).toBe('Advanced'); | |
| expect(setting.default).toBe(true); | |
| expect(setting.requiresRestart).toBe(true); | |
| expect(setting.showInDialog).toBe(true); | |
| expect(setting.description).toBe('Enable Agent Skills.'); | |
| }); | |
| it('should have plan setting in schema', () => { | |
| const setting = | |
| getSettingsSchema().general.properties.plan.properties.enabled; | |
| expect(setting).toBeDefined(); | |
| expect(setting.type).toBe('boolean'); | |
| expect(setting.category).toBe('General'); | |
| expect(setting.default).toBe(true); | |
| expect(setting.requiresRestart).toBe(true); | |
| expect(setting.showInDialog).toBe(true); | |
| expect(setting.description).toBe( | |
| 'Enable Plan Mode for read-only safety during planning.', | |
| ); | |
| }); | |
| it('should have hooksConfig.notifications setting in schema', () => { | |
| const setting = getSettingsSchema().hooksConfig?.properties.notifications; | |
| expect(setting).toBeDefined(); | |
| expect(setting.type).toBe('boolean'); | |
| expect(setting.category).toBe('Advanced'); | |
| expect(setting.default).toBe(true); | |
| expect(setting.showInDialog).toBe(true); | |
| }); | |
| it('should have name and description in hook definitions', () => { | |
| const hookDef = SETTINGS_SCHEMA_DEFINITIONS['HookDefinitionArray']; | |
| expect(hookDef).toBeDefined(); | |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | |
| const hookItemProperties = (hookDef as any).items.properties.hooks.items | |
| .properties; | |
| expect(hookItemProperties.name).toBeDefined(); | |
| expect(hookItemProperties.name.type).toBe('string'); | |
| expect(hookItemProperties.description).toBeDefined(); | |
| expect(hookItemProperties.description.type).toBe('string'); | |
| }); | |
| it('should have gemmaModelRouter setting in schema', () => { | |
| const gemmaModelRouter = | |
| getSettingsSchema().experimental.properties.gemmaModelRouter; | |
| expect(gemmaModelRouter).toBeDefined(); | |
| expect(gemmaModelRouter.type).toBe('object'); | |
| expect(gemmaModelRouter.category).toBe('Experimental'); | |
| expect(gemmaModelRouter.default).toEqual({}); | |
| expect(gemmaModelRouter.requiresRestart).toBe(true); | |
| expect(gemmaModelRouter.showInDialog).toBe(false); | |
| expect(gemmaModelRouter.description).toBe( | |
| 'Enable Gemma model router (experimental).', | |
| ); | |
| const enabled = gemmaModelRouter.properties.enabled; | |
| expect(enabled).toBeDefined(); | |
| expect(enabled.type).toBe('boolean'); | |
| expect(enabled.category).toBe('Experimental'); | |
| expect(enabled.default).toBe(false); | |
| expect(enabled.requiresRestart).toBe(true); | |
| expect(enabled.showInDialog).toBe(true); | |
| expect(enabled.description).toBe( | |
| 'Enable the Gemma Model Router (experimental). Requires a local endpoint serving Gemma via the Gemini API using LiteRT-LM shim.', | |
| ); | |
| const autoStartServer = gemmaModelRouter.properties.autoStartServer; | |
| expect(autoStartServer).toBeDefined(); | |
| expect(autoStartServer.type).toBe('boolean'); | |
| expect(autoStartServer.category).toBe('Experimental'); | |
| expect(autoStartServer.default).toBe(false); | |
| expect(autoStartServer.requiresRestart).toBe(true); | |
| expect(autoStartServer.showInDialog).toBe(true); | |
| expect(autoStartServer.description).toBe( | |
| 'Automatically start the LiteRT-LM server when Gemini CLI starts and the Gemma router is enabled.', | |
| ); | |
| const binaryPath = gemmaModelRouter.properties.binaryPath; | |
| expect(binaryPath).toBeDefined(); | |
| expect(binaryPath.type).toBe('string'); | |
| expect(binaryPath.category).toBe('Experimental'); | |
| expect(binaryPath.default).toBe(''); | |
| expect(binaryPath.requiresRestart).toBe(true); | |
| expect(binaryPath.showInDialog).toBe(false); | |
| expect(binaryPath.description).toBe( | |
| 'Custom path to the LiteRT-LM binary. Leave empty to use the default location (~/.gemini/bin/litert/).', | |
| ); | |
| const classifier = gemmaModelRouter.properties.classifier; | |
| expect(classifier).toBeDefined(); | |
| expect(classifier.type).toBe('object'); | |
| expect(classifier.category).toBe('Experimental'); | |
| expect(classifier.default).toEqual({}); | |
| expect(classifier.requiresRestart).toBe(true); | |
| expect(classifier.showInDialog).toBe(false); | |
| expect(classifier.description).toBe('Classifier configuration.'); | |
| const host = classifier.properties.host; | |
| expect(host).toBeDefined(); | |
| expect(host.type).toBe('string'); | |
| expect(host.category).toBe('Experimental'); | |
| expect(host.default).toBe('http://localhost:9379'); | |
| expect(host.requiresRestart).toBe(true); | |
| expect(host.showInDialog).toBe(false); | |
| expect(host.description).toBe('The host of the classifier.'); | |
| const model = classifier.properties.model; | |
| expect(model).toBeDefined(); | |
| expect(model.type).toBe('string'); | |
| expect(model.category).toBe('Experimental'); | |
| expect(model.default).toBe('gemma3-1b-gpu-custom'); | |
| expect(model.requiresRestart).toBe(true); | |
| expect(model.showInDialog).toBe(false); | |
| expect(model.description).toBe( | |
| 'The model to use for the classifier. Only tested on `gemma3-1b-gpu-custom`.', | |
| ); | |
| }); | |
| it('should have adk setting in schema', () => { | |
| const adk = getSettingsSchema().experimental.properties.adk; | |
| expect(adk).toBeDefined(); | |
| expect(adk.type).toBe('object'); | |
| expect(adk.category).toBe('Experimental'); | |
| expect(adk.default).toEqual({}); | |
| expect(adk.requiresRestart).toBe(true); | |
| expect(adk.showInDialog).toBe(false); | |
| expect(adk.description).toBe( | |
| 'Settings for the Agent Development Kit (ADK).', | |
| ); | |
| const agentSessionNoninteractiveEnabled = | |
| adk.properties.agentSessionNoninteractiveEnabled; | |
| expect(agentSessionNoninteractiveEnabled).toBeDefined(); | |
| expect(agentSessionNoninteractiveEnabled.type).toBe('boolean'); | |
| expect(agentSessionNoninteractiveEnabled.category).toBe('Experimental'); | |
| expect(agentSessionNoninteractiveEnabled.default).toBe(false); | |
| expect(agentSessionNoninteractiveEnabled.requiresRestart).toBe(true); | |
| expect(agentSessionNoninteractiveEnabled.showInDialog).toBe(false); | |
| expect(agentSessionNoninteractiveEnabled.description).toBe( | |
| 'Enable non-interactive agent sessions.', | |
| ); | |
| const agentSessionSubagentEnabled = | |
| adk.properties.agentSessionSubagentEnabled; | |
| expect(agentSessionSubagentEnabled).toBeDefined(); | |
| expect(agentSessionSubagentEnabled.type).toBe('boolean'); | |
| expect(agentSessionSubagentEnabled.category).toBe('Experimental'); | |
| expect(agentSessionSubagentEnabled.default).toBe(false); | |
| expect(agentSessionSubagentEnabled.requiresRestart).toBe(true); | |
| expect(agentSessionSubagentEnabled.showInDialog).toBe(false); | |
| expect(agentSessionSubagentEnabled.description).toBe( | |
| 'Route subagent invocations through the AgentSession protocol instead of legacy executors.', | |
| ); | |
| }); | |
| }); | |
| it('has JSON schema definitions for every referenced ref', () => { | |
| const schema = getSettingsSchema(); | |
| const referenced = new Set<string>(); | |
| const visitDefinition = (definition: SettingDefinition) => { | |
| if (definition.ref) { | |
| referenced.add(definition.ref); | |
| expect(SETTINGS_SCHEMA_DEFINITIONS).toHaveProperty(definition.ref); | |
| } | |
| if (definition.properties) { | |
| Object.values(definition.properties).forEach(visitDefinition); | |
| } | |
| if (definition.items) { | |
| visitCollection(definition.items); | |
| } | |
| if (definition.additionalProperties) { | |
| visitCollection(definition.additionalProperties); | |
| } | |
| }; | |
| const visitCollection = (collection: SettingCollectionDefinition) => { | |
| if (collection.ref) { | |
| referenced.add(collection.ref); | |
| expect(SETTINGS_SCHEMA_DEFINITIONS).toHaveProperty(collection.ref); | |
| return; | |
| } | |
| if (collection.properties) { | |
| Object.values(collection.properties).forEach(visitDefinition); | |
| } | |
| if (collection.type === 'array' && collection.properties) { | |
| Object.values(collection.properties).forEach(visitDefinition); | |
| } | |
| }; | |
| const visitJsonSchema = (jsonSchema: Record<string, unknown>) => { | |
| const ref = jsonSchema['ref']; | |
| if (typeof ref === 'string') { | |
| referenced.add(ref); | |
| } | |
| const properties = jsonSchema['properties']; | |
| if ( | |
| properties && | |
| typeof properties === 'object' && | |
| !Array.isArray(properties) | |
| ) { | |
| Object.values(properties as Record<string, unknown>).forEach((prop) => | |
| visitJsonSchema(prop as Record<string, unknown>), | |
| ); | |
| } | |
| const items = jsonSchema['items']; | |
| if (items && typeof items === 'object' && !Array.isArray(items)) { | |
| visitJsonSchema(items as Record<string, unknown>); | |
| } | |
| }; | |
| Object.values(schema).forEach(visitDefinition); | |
| // Also visit all definitions to find nested references | |
| Object.values(SETTINGS_SCHEMA_DEFINITIONS).forEach(visitJsonSchema); | |
| // Ensure definitions map doesn't accumulate stale entries. | |
| Object.keys(SETTINGS_SCHEMA_DEFINITIONS).forEach((key) => { | |
| if (!referenced.has(key)) { | |
| throw new Error( | |
| `Definition "${key}" is exported but never referenced in the schema`, | |
| ); | |
| } | |
| }); | |
| }); | |
| }); | |