mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
Refactors the `resolveEnvVarsInObject` function in settings to explicitly handle primitive types (null, undefined, boolean, number) at the beginning of the function. This clarifies the logic for subsequent string, array, and object processing.
220 lines
5.9 KiB
TypeScript
220 lines
5.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { homedir } from 'os';
|
|
import { MCPServerConfig } from '@gemini-code/core';
|
|
import stripJsonComments from 'strip-json-comments';
|
|
import { DefaultLight } from '../ui/themes/default-light.js';
|
|
import { DefaultDark } from '../ui/themes/default.js';
|
|
|
|
export const SETTINGS_DIRECTORY_NAME = '.gemini';
|
|
export const USER_SETTINGS_DIR = path.join(homedir(), SETTINGS_DIRECTORY_NAME);
|
|
export const USER_SETTINGS_PATH = path.join(USER_SETTINGS_DIR, 'settings.json');
|
|
|
|
export enum SettingScope {
|
|
User = 'User',
|
|
Workspace = 'Workspace',
|
|
}
|
|
|
|
export interface AccessibilitySettings {
|
|
disableLoadingPhrases?: boolean;
|
|
}
|
|
|
|
export interface Settings {
|
|
theme?: string;
|
|
sandbox?: boolean | string;
|
|
coreTools?: string[];
|
|
toolDiscoveryCommand?: string;
|
|
toolCallCommand?: string;
|
|
mcpServerCommand?: string;
|
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
showMemoryUsage?: boolean;
|
|
contextFileName?: string;
|
|
accessibility?: AccessibilitySettings;
|
|
telemetry?: boolean;
|
|
|
|
// Git-aware file filtering settings
|
|
fileFiltering?: {
|
|
respectGitIgnore?: boolean;
|
|
allowBuildArtifacts?: boolean;
|
|
};
|
|
|
|
// Add other settings here.
|
|
}
|
|
|
|
export interface SettingsFile {
|
|
settings: Settings;
|
|
path: string;
|
|
}
|
|
export class LoadedSettings {
|
|
constructor(user: SettingsFile, workspace: SettingsFile) {
|
|
this.user = user;
|
|
this.workspace = workspace;
|
|
this._merged = this.computeMergedSettings();
|
|
}
|
|
|
|
readonly user: SettingsFile;
|
|
readonly workspace: SettingsFile;
|
|
|
|
private _merged: Settings;
|
|
|
|
get merged(): Settings {
|
|
return this._merged;
|
|
}
|
|
|
|
private computeMergedSettings(): Settings {
|
|
return {
|
|
...this.user.settings,
|
|
...this.workspace.settings,
|
|
};
|
|
}
|
|
|
|
forScope(scope: SettingScope): SettingsFile {
|
|
switch (scope) {
|
|
case SettingScope.User:
|
|
return this.user;
|
|
case SettingScope.Workspace:
|
|
return this.workspace;
|
|
default:
|
|
throw new Error(`Invalid scope: ${scope}`);
|
|
}
|
|
}
|
|
|
|
setValue(
|
|
scope: SettingScope,
|
|
key: keyof Settings,
|
|
value: string | Record<string, MCPServerConfig> | undefined,
|
|
): void {
|
|
const settingsFile = this.forScope(scope);
|
|
// @ts-expect-error - value can be string | Record<string, MCPServerConfig>
|
|
settingsFile.settings[key] = value;
|
|
this._merged = this.computeMergedSettings();
|
|
saveSettings(settingsFile);
|
|
}
|
|
}
|
|
|
|
function resolveEnvVarsInString(value: string): string {
|
|
const envVarRegex = /\$(?:(\w+)|{([^}]+)})/g; // Find $VAR_NAME or ${VAR_NAME}
|
|
return value.replace(envVarRegex, (match, varName1, varName2) => {
|
|
const varName = varName1 || varName2;
|
|
if (process && process.env && typeof process.env[varName] === 'string') {
|
|
return process.env[varName]!;
|
|
}
|
|
return match;
|
|
});
|
|
}
|
|
|
|
function resolveEnvVarsInObject<T>(obj: T): T {
|
|
if (
|
|
obj === null ||
|
|
obj === undefined ||
|
|
typeof obj === 'boolean' ||
|
|
typeof obj === 'number'
|
|
) {
|
|
return obj;
|
|
}
|
|
|
|
if (typeof obj === 'string') {
|
|
return resolveEnvVarsInString(obj) as unknown as T;
|
|
}
|
|
|
|
if (Array.isArray(obj)) {
|
|
return obj.map((item) => resolveEnvVarsInObject(item)) as unknown as T;
|
|
}
|
|
|
|
if (typeof obj === 'object') {
|
|
const newObj = { ...obj } as T;
|
|
for (const key in newObj) {
|
|
if (Object.prototype.hasOwnProperty.call(newObj, key)) {
|
|
newObj[key] = resolveEnvVarsInObject(newObj[key]);
|
|
}
|
|
}
|
|
return newObj;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* Loads settings from user and workspace directories.
|
|
* Project settings override user settings.
|
|
*/
|
|
export function loadSettings(workspaceDir: string): LoadedSettings {
|
|
let userSettings: Settings = {};
|
|
let workspaceSettings: Settings = {};
|
|
|
|
// Load user settings
|
|
try {
|
|
if (fs.existsSync(USER_SETTINGS_PATH)) {
|
|
const userContent = fs.readFileSync(USER_SETTINGS_PATH, 'utf-8');
|
|
const parsedUserSettings = JSON.parse(
|
|
stripJsonComments(userContent),
|
|
) as Settings;
|
|
userSettings = resolveEnvVarsInObject(parsedUserSettings);
|
|
// Support legacy theme names
|
|
if (userSettings.theme && userSettings.theme === 'VS') {
|
|
userSettings.theme = DefaultLight.name;
|
|
} else if (userSettings.theme && userSettings.theme === 'VS2015') {
|
|
userSettings.theme = DefaultDark.name;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error reading user settings file:', error);
|
|
}
|
|
|
|
const workspaceSettingsPath = path.join(
|
|
workspaceDir,
|
|
SETTINGS_DIRECTORY_NAME,
|
|
'settings.json',
|
|
);
|
|
|
|
// Load workspace settings
|
|
try {
|
|
if (fs.existsSync(workspaceSettingsPath)) {
|
|
const projectContent = fs.readFileSync(workspaceSettingsPath, 'utf-8');
|
|
const parsedWorkspaceSettings = JSON.parse(
|
|
stripJsonComments(projectContent),
|
|
) as Settings;
|
|
workspaceSettings = resolveEnvVarsInObject(parsedWorkspaceSettings);
|
|
if (workspaceSettings.theme && workspaceSettings.theme === 'VS') {
|
|
workspaceSettings.theme = DefaultLight.name;
|
|
} else if (
|
|
workspaceSettings.theme &&
|
|
workspaceSettings.theme === 'VS2015'
|
|
) {
|
|
workspaceSettings.theme = DefaultDark.name;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error reading workspace settings file:', error);
|
|
}
|
|
|
|
return new LoadedSettings(
|
|
{ path: USER_SETTINGS_PATH, settings: userSettings },
|
|
{ path: workspaceSettingsPath, settings: workspaceSettings },
|
|
);
|
|
}
|
|
|
|
export function saveSettings(settingsFile: SettingsFile): void {
|
|
try {
|
|
// Ensure the directory exists
|
|
const dirPath = path.dirname(settingsFile.path);
|
|
if (!fs.existsSync(dirPath)) {
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(
|
|
settingsFile.path,
|
|
JSON.stringify(settingsFile.settings, null, 2),
|
|
'utf-8',
|
|
);
|
|
} catch (error) {
|
|
console.error('Error saving user settings file:', error);
|
|
}
|
|
}
|