Download src/server/services/settingsService.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 6.45 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/server/services/settingsService.ts
- Command line
-
hf download hf://chenbhao/codev/src/server/services/settingsService.ts
-
curl -L -o settingsService.ts https://huggingface.co/chenbhao/codev/resolve/main/src/server/services/settingsService.ts
6.45 kB
| /** | |
| * Settings Service — 读写用户级和项目级设置文件 | |
| * | |
| * 设置文件为 JSON 格式: | |
| * - 用户级: ~/.claude/settings.json | |
| * - 项目级: {projectRoot}/.claude/settings.json | |
| * | |
| * 合并策略:Object.assign({}, userSettings, projectSettings) | |
| */ | |
| import * as fs from 'fs/promises' | |
| import { randomBytes } from 'node:crypto' | |
| import * as path from 'path' | |
| import * as os from 'os' | |
| import { ApiError } from '../middleware/errorHandler.js' | |
| import { normalizeJsonObject, readRecoverableJsonFile } from './recoverableJsonFile.js' | |
| import { ensurePersistentStorageUpgraded } from './persistentStorageMigrations.js' | |
| import { resetSettingsCache } from '../../utils/settings/settingsCache.js' | |
| const VALID_PERMISSION_MODES = [ | |
| 'default', | |
| 'acceptEdits', | |
| 'plan', | |
| 'bypassPermissions', | |
| 'dontAsk', | |
| ] as const | |
| export type PermissionMode = (typeof VALID_PERMISSION_MODES)[number] | |
| export class SettingsService { | |
| private static writeLocks = new Map<string, Promise<void>>() | |
| private projectRoot?: string | |
| constructor(projectRoot?: string) { | |
| this.projectRoot = projectRoot | |
| } | |
| /** 配置目录,支持通过环境变量覆盖(便于测试) */ | |
| private getConfigDir(): string { | |
| return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude') | |
| } | |
| /** 用户级设置文件路径 */ | |
| private getUserSettingsPath(): string { | |
| return path.join(this.getConfigDir(), 'settings.json') | |
| } | |
| /** 项目级设置文件路径 */ | |
| private getProjectSettingsPath(projectRoot?: string): string { | |
| const root = projectRoot || this.projectRoot | |
| if (!root) { | |
| throw ApiError.badRequest('Project root is required for project settings') | |
| } | |
| return path.join(root, '.claude', 'settings.json') | |
| } | |
| // --------------------------------------------------------------------------- | |
| // 读取 | |
| // --------------------------------------------------------------------------- | |
| /** 安全读取 JSON 文件,文件不存在时返回空对象 */ | |
| private async readJsonFile(filePath: string): Promise<Record<string, unknown>> { | |
| await ensurePersistentStorageUpgraded() | |
| return readRecoverableJsonFile({ | |
| filePath, | |
| label: 'settings', | |
| defaultValue: {}, | |
| normalize: normalizeJsonObject, | |
| }) | |
| } | |
| /** 获取合并后的设置(user + project) */ | |
| async getSettings(projectRoot?: string): Promise<Record<string, unknown>> { | |
| const user = await this.getUserSettings() | |
| try { | |
| const project = await this.getProjectSettings(projectRoot) | |
| return Object.assign({}, user, project) | |
| } catch { | |
| // project root 未指定时,仅返回 user settings | |
| return user | |
| } | |
| } | |
| /** 获取用户级设置 */ | |
| async getUserSettings(): Promise<Record<string, unknown>> { | |
| return this.readJsonFile(this.getUserSettingsPath()) | |
| } | |
| /** 获取项目级设置 */ | |
| async getProjectSettings(projectRoot?: string): Promise<Record<string, unknown>> { | |
| return this.readJsonFile(this.getProjectSettingsPath(projectRoot)) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // 写入(原子写入:先写临时文件,再 rename) | |
| // --------------------------------------------------------------------------- | |
| /** 原子写入 JSON 文件 */ | |
| private async withWriteLock<T>( | |
| filePath: string, | |
| task: () => Promise<T>, | |
| ): Promise<T> { | |
| const previousWrite = SettingsService.writeLocks.get(filePath) ?? Promise.resolve() | |
| const nextWrite = previousWrite | |
| .catch(() => {}) | |
| .then(task) | |
| SettingsService.writeLocks.set(filePath, nextWrite) | |
| try { | |
| return await nextWrite | |
| } finally { | |
| if (SettingsService.writeLocks.get(filePath) === nextWrite) { | |
| SettingsService.writeLocks.delete(filePath) | |
| } | |
| } | |
| } | |
| private async writeJsonFile( | |
| filePath: string, | |
| data: Record<string, unknown>, | |
| ): Promise<void> { | |
| const dir = path.dirname(filePath) | |
| const contents = JSON.stringify(data, null, 2) + '\n' | |
| let lastError: unknown | |
| for (let attempt = 0; attempt < 2; attempt++) { | |
| const tmpFile = `${filePath}.tmp.${process.pid}.${Date.now()}.${randomBytes(6).toString('hex')}` | |
| try { | |
| await fs.mkdir(dir, { recursive: true }) | |
| await fs.writeFile(tmpFile, contents, 'utf-8') | |
| await fs.rename(tmpFile, filePath) | |
| resetSettingsCache() | |
| return | |
| } catch (err) { | |
| lastError = err | |
| await fs.unlink(tmpFile).catch(() => {}) | |
| if ( | |
| (err as NodeJS.ErrnoException).code !== 'ENOENT' || | |
| attempt === 1 | |
| ) { | |
| break | |
| } | |
| } | |
| } | |
| throw ApiError.internal( | |
| `Failed to write settings to ${filePath}: ${lastError}`, | |
| ) | |
| } | |
| /** 更新用户级设置(浅合并) */ | |
| async updateUserSettings(settings: Record<string, unknown>): Promise<void> { | |
| const filePath = this.getUserSettingsPath() | |
| await this.withWriteLock(filePath, async () => { | |
| const current = await this.readJsonFile(filePath) | |
| const merged = Object.assign({}, current, settings) | |
| await this.writeJsonFile(filePath, merged) | |
| }) | |
| } | |
| /** 更新项目级设置(浅合并) */ | |
| async updateProjectSettings( | |
| settings: Record<string, unknown>, | |
| projectRoot?: string, | |
| ): Promise<void> { | |
| const filePath = this.getProjectSettingsPath(projectRoot) | |
| await this.withWriteLock(filePath, async () => { | |
| const current = await this.readJsonFile(filePath) | |
| const merged = Object.assign({}, current, settings) | |
| await this.writeJsonFile(filePath, merged) | |
| }) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // 权限模式 | |
| // --------------------------------------------------------------------------- | |
| /** 获取当前权限模式 */ | |
| async getPermissionMode(): Promise<string> { | |
| const settings = await this.getUserSettings() | |
| const mode = settings.defaultMode | |
| return typeof mode === 'string' && VALID_PERMISSION_MODES.includes(mode as PermissionMode) | |
| ? mode | |
| : 'default' | |
| } | |
| /** 设置权限模式 */ | |
| async setPermissionMode(mode: string): Promise<void> { | |
| if (!VALID_PERMISSION_MODES.includes(mode as PermissionMode)) { | |
| throw ApiError.badRequest( | |
| `Invalid permission mode: "${mode}". Valid modes: ${VALID_PERMISSION_MODES.join(', ')}`, | |
| ) | |
| } | |
| await this.updateUserSettings({ defaultMode: mode }) | |
| } | |
| } | |