
Cherry Studio Boot Config 详解schema 自动生成、运行时校验与 V1→V2 迁移管线【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio本文以 Cherry Studio 的docs/references/data/boot-config-schema-guide.md为骨架讲清两件事如何向自动生成的 BootConfig schema 中正确添加启动配置键命名规范、生成器、BootConfigService运行时行为、统一偏好 API 接入以及scripts/data-classify工具链如何把 V1 时代的遗留数据Redux / ElectronStore / Dexie / localStorage / 旧版 home 配置文件迁移到 V2 启动配置系统。读完后你能独立新增一个 BootConfig 键并跑通从分类定义、代码生成到迁移映射的全链路同时理解该机制在启动时序上的设计边界。一、BootConfig 的适用范围一张决策表Cherry Studio 的启动期配置分属两套系统BootConfig主进程最早期加载的极少量配置与Preference常规偏好设置。文档给出的核心原则是BootConfig 只服务于一个非常窄的配置集合新增键之前必须先过这张决策表问题若答案为“是”若答案为“否”必须在生命周期系统接管之前加载吗BootConfigPreference是否影响进程级行为Chromium flags、数据目录BootConfigPreference可以等到BeforeReady生命周期阶段再读吗PreferenceBootConfig可以在运行时修改而无需重启吗PreferenceBootConfig文档给出的经验法则是如果一个设置能等到生命周期的BeforeReady阶段它就属于 PreferenceBootConfig 只保留必须在生命周期系统启动前就可用的设置并保持最小化。从源码结构看这个“最早期”是有严格时序约束的。主进程入口 中import main/data/bootConfig是全文件第一条 import并配有注释 “BootConfig must load before any other import (configures userData path)”——因为用户数据目录的位置本身就由 BootConfig 决定。紧随其后的 preboot 调用顺序是resolveUserDataLocation() → requireSingleInstance() → configureChromiumFlags() → initCrashTelemetry()之后才进入备份恢复门、V2 迁移门最终application.bootstrap()启动生命周期。这也印证了“BootConfig 决定 userData 在哪里而不是相反”的注释见 BootConfigService 构造函数。二、键命名规范与 Preference 完全一致BootConfig 键沿用与 preferences 相同的命名约定格式namespace.key_name至少 2 段以点分隔字符集仅小写字母、数字、下划线正则模式/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)$/语义约定点表示层级下划线表示多词名。文档中的有效性对照表合法非法原因app.disable_hardware_accelerationdisableHardwareAcceleration缺少点分隔app.user_data_pathApp.userDataPath大写、驼峰chromium.gpu_compositinggpu单段注意temp.*前缀是一个特例命名空间它保留给主进程内部的瞬时运行时状态刻意排除在统一偏好 API 之外不在UnifiedPreferenceType中、无法通过usePreference访问、并在 PreferenceService 的 IPC 边界被拒绝。这一排除在类型层面是静态强制的bootConfigTypes.ts 中InternalBootConfigKey ExtractBootConfigKey, \temp.${string}PublicBootConfigKey再将其排除最终BootConfigPreferenceKeys这个映射类型只会为 public 键自动生成BootConfig.前缀。访问temp.*键的唯一途径是直接调用bootConfigService并通过onChange() 订阅变化。三、新增一个 BootConfig 键的四步流程Step 1进入生成器输入禁止手改 schema 文件目标文件 src/shared/data/bootConfig/bootConfigSchemas.ts 是完全自动生成的——文件头明确标注 “Auto-generated … DO NOT edit by hand”并给出重新生成的命令node scripts/data-classify/scripts/generate-boot-config.js。zod schema 是单一事实来源BootConfigSchema类型由它推导BootConfigService在运行时对文件加载值和set()值做校验。文档给出的最小形态示例export const bootConfigSchema z.object({ app.disable_hardware_acceleration: z.boolean(), app.user_data_path: z.record(z.string(), z.string()) }) export type BootConfigSchema z.infertypeof bootConfigSchema export const DefaultBootConfig: BootConfigSchema { app.disable_hardware_acceleration: false, app.user_data_path: {} }实际生成文件中还包含第三个键temp.user_data_relocation一个pending/failed两种状态对象联合的 nullable 类型用于跨启动传递 Electron userData 目录迁移任务它正体现了第二节所述的temp.*内部命名空间用法。修改入口分两类文档明确要求不能直接改生成物从受支持的 V1 来源迁移的键编辑 scripts/data-classify/data/classification.json无遗留来源的新键、或来自配置文件的键加入 generate-boot-config.js 顶部的MANUAL_BOOT_CONFIG_ITEMS复杂类型必须显式给出zodType表达式字符串。关于生成器的几个源码级细节值得注意生成器只接受四类分类来源electronStore、redux、localStorage、dexieSettings当同一个targetKey在多个来源出现时按redux(4) dexieSettings(3) localStorage(2) electronStore(1)的优先级去重并打印警告见extractBootConfigData()mapZodType()刻意不做typeof defaultValue之类的隐式回退——无法映射的类型会直接抛错中止生成防止错误的 schema 被静默写入defaultValue支持VALUE: xxx转义前缀用于输出原始 JS 字面量例如{}避免被当成字符串{}输出按targetKey字典序排序每个键上方带一行// source/category/originalKey溯源注释保证生成结果可 diff、可追溯。Step 2按需添加自定义类型文件src/shared/data/bootConfig/bootConfigTypes.ts简单类型boolean、string、number无需任何改动——类型直接从 schema 推导。需要联合字面量等复杂语义时与BootConfigKey并列定义即可export type BootConfigKey keyof BootConfigSchema // Custom types if needed export type GpuMode auto | disabled | software实际文件还包含InternalBootConfigKey、PublicBootConfigKey与BootConfigPreferenceKeys三个派生类型构成temp.*键隔离的静态机制见第二节。Step 3在早期启动代码中使用如需要仅针对必须在生命周期之前生效的设置。文件src/main/main.ts。文档给出的范式import { bootConfigService } from main/data/bootConfig // Apply before app.whenReady() if (bootConfigService.get(app.disable_hardware_acceleration)) { app.disableHardwareAcceleration() }当前仓库中这一机制的对应实现位于 preboot 阶段configureChromiumFlags()src/main/core/preboot/chromiumFlags.ts在模块求值阶段读取 BootConfig 并设置 Chromium flags而resolveUserDataLocation()则消费app.user_data_path决定 Electron 的 userData 目录。入口文件自身的注释也明确告诫“DO NOT add new code here”新服务应放入生命周期系统、不可移除的 preboot 步骤放入core/preboot/。Step 4从渲染进程 / 生命周期服务访问零接线无需额外 wiringBootConfigPreferenceKeys映射类型自动为每个 public 键添加BootConfig.前缀使其立即可通过统一偏好 API 使用// Renderer — 加入 schema 后立即可用 const [disableHardwareAcceleration, setDisableHardwareAcceleration] usePreference( BootConfig.app.disable_hardware_acceleration ) // Main process lifecycle service const disableHardwareAcceleration preferenceService.get(BootConfig.app.disable_hardware_acceleration)temp.*键是例外temp.前缀下的键是主进程内部瞬时状态刻意不出现在UnifiedPreferenceType中、无法经usePreference触达、并在 PreferenceService 的 IPC 边界被拒绝。只能通过bootConfigService直接访问任何阶段均可变更通知走bootConfigService.onChange()。usePreference的完整用法参见 Preference Usage Guide。四、BootConfigService运行时行为与文件布局文档“File Structure”一节的文件职责表结合当前仓库实际情况文件用途src/shared/data/bootConfig/bootConfigSchemas.tsZod value schema单一事实来源、推导的BootConfigSchema类型、默认值src/shared/data/bootConfig/bootConfigTypes.tsBootConfigKey、Public/InternalBootConfigKey、BootConfigPreferenceKeys映射类型src/main/data/bootConfig/BootConfigService.ts服务实现同步加载、防抖保存、校验、订阅src/main/data/bootConfig/types.tsBootConfigLoadError类型scripts/data-classify/data/classification.json迁移事实来源scripts/data-classify/scripts/generate-boot-config.jsSchema 生成器迁移管线src/main/data/migration/v2/migrators/BootConfigMigrator.ts迁移执行器src/main/data/migration/v2/migrators/mappings/BootConfigMappings.ts自动生成的迁移映射从 BootConfigService.ts 源码可以进一步确认文档所述行为的实现细节这些细节对理解“为什么这么设计”很有价值存储位置配置文件是~/.cherrystudio/boot-config.json常量BOOT_CONFIG_PATH定义于 src/main/core/paths/constants.ts。刻意放在~/.cherrystudio/而非 userData 下原因有二它要能决定 userData 去哪里不能反过来被appDataPath影响且它必须在initAppDataDir()改写 userData 路径之前就可读。constants.ts还被特意做成零业务依赖模块避免该服务引入重 import。加载构造函数中同步加载模块 import 时即完成文件不存在时用DefaultBootConfig这解释了最佳实践第 2 条——缺默认值的键在首启不可用JSON 解析失败记parse_error逐键 schema 校验失败记validation_error并把坏键回退默认值读失败记read_error三类错误结构见 types.ts。写入set()先经bootConfigSchema.shape[key].safeParse校验校验失败抛异常且不做任何状态变更——这是 Preference IPC 路由与 V1 迁移器两条不可信数据路径上的唯一强制点校验通过后才更新内存、置 dirty 并触发 350ms 防抖保存。落盘策略只写与默认值不同的键diff 写入若所有值都是默认值则直接删除文件让“全默认状态不留盘”实际写入采用临时文件writeFileSyncrenameSync的原子模式。持久化语义分层persist()严格写盘且传播失败迁移器、IPC handler 用flush()是 best-effort 包装关机、preboot 路径用失败只记日志防抖自动保存也是 best-effort——三者共享 dirty 标志以便失败后重试。五、V1 到 V2 的数据迁移管线本节覆盖的是把 V1Redux / ElectronStore / Dexie遗留数据迁入 V2 BootConfig 的迁移工具链不是日常新增键的常规路径。5.1 管线总览scripts/data-classify/目录承载代码生成管线classification.json 是唯一事实来源把每个遗留键分类到目标系统Preference、BootConfig、Cache 或 DataApi。工作流程classification.json中每个category: bootConfig的条目把一个遗留键映射到一个 boot config 键生成器读取这些分类产出两样东西src/shared/data/bootConfig/bootConfigSchemas.ts——zod schema、推导出的BootConfigSchema类型与默认值src/main/data/migration/v2/migrators/mappings/BootConfigMappings.ts——旧键到新键的映射表迁移时刻BootConfigMigrator从各遗留来源读值并写入bootConfigService。5.2 迁移来源来源访问器示例Redux StoreReduxStateReadercategory 点路径settings.disableHardwareAccelerationElectronStoreElectronStoreReader.get(key)直接按键查找Dexie settings键值表直接按键查找localStoragelocalStorage.getItem(key)直接按键查找旧版 home 配置文件LegacyHomeConfigReader~/.cherrystudio/config/config.json仅appDataPath字段5.3 配置文件来源的映射是手工维护的文档特别强调data-classify工具链的classification.json尚不建模配置文件来源因此在两处由一份小型手工清单补充分类驱动的管线Schema 键generate-boot-config.js 顶部的MANUAL_BOOT_CONFIG_ITEMS——这些条目与分类推导条目合并后走同一套排序/输出代码最终输出仍是完全自动生成的单文件无手工区段。每个手工条目需要显式zodType表达式字符串分类推导的简单类型会自动映射到 zod生成器遇到无法映射的条目会中止。当前仓库中该列表包含两条app.user_data_path来源configfile/legacy-home/appDataPath与temp.user_data_relocation来源preboot/transient/userDataRelocation。映射BootConfigMigrator.loadMigrationItems()内联的configFileMappings——一个ReadonlyArray{ originalKey: string; targetKey: BootConfigKey }其BootConfigKey类型标注就是重新生成的安全网如果 schema 中丢掉app.user_data_path这个数组字面量会在声明处编译失败见 BootConfigMigrator.ts 附近注释。新增一个配置文件来源的键的完整步骤往MANUAL_BOOT_CONFIG_ITEMS加条目 → 往BootConfigMigrator.loadMigrationItems()的configFileMappings加对应条目 → 运行npm run generate。5.4 添加一条迁移映射把遗留键迁到 boot config在 classification.json 中添加或更新条目{ originalKey: disableHardwareAcceleration, source: redux, category: bootConfig, status: classified, targetKey: app.disable_hardware_acceleration, targetType: boolean, defaultValue: false, reduxCategory: settings }重新生成映射cd scripts/data-classify npm run generate检查BootConfigMappings.ts中的生成结果。5.5 当前映射表遗留来源遗留键目标键ReduxsettingsdisableHardwareAccelerationapp.disable_hardware_acceleration配置文件~/.cherrystudio/config/config.jsonappDataPathapp.user_data_pathAppImage / Windows 便携版可执行文件路径的特判V1 的~/.cherrystudio/config/config.json把appDataPath存成以可执行路径为键的{ executablePath, dataPath }数组。AppImageLinux与 Windows 便携版构建使用的规范化可执行键与app.getPath(exe)不同因为这两类构建的原始 exe 路径在不同启动之间不稳定AppImagepath.dirname(process.env.APPIMAGE) /cherry-studio.appimageWindows 便携版process.env.PORTABLE_EXECUTABLE_DIR /cherry-studio-portable.exeresolveMigrationPaths()与运行时用户数据位置解析器都使用 src/main/core/preboot/userDataLocation.ts 中的getNormalizedExecutablePath()保证迁移时的写入键和运行时的查找键严格一致。这正是 bootConfigSchemas.ts 中app.user_data_pathJSDoc 所描述的“按可执行路径键控的 Record、同机多安装stable / dev / portable各自独立数据目录”设计的由来。另一个从迁移器文档可补充的细节配置文件来源的条目把defaultValue设为null是有意为之——其他来源在源无值时会回退DefaultBootConfig[targetKey]但对配置文件来源“v1 文件不存在”应表示“无东西可迁移”若写入 schema 默认值{}会制造一次虚假迁移。null默认值让该条目走共享的 null-skip 守卫被整体跳过见 BootConfigMigrator 说明文档。六、最佳实践文档原文四条逐条落地保持 BootConfig 最小化——绝大多数设置属于 Preference。BootConfig 只给必须在生命周期系统接管前加载的设置使用提供合理的默认值——BootConfigService首启文件缺失时直接使用默认值缺默认值意味着该键在首启不可用实现上即 loadSync() 中return { ...DefaultBootConfig }分支遵循命名规范——与 preferences 使用同一套namespace.key_name模式保持一致性进程级设置需要重启——在把 boot config 设置暴露给用户时于 UI 中说明这一点。七、延伸阅读Boot Config Overview——架构与加载时序含 Internaltemp.*namespace 专节Preference Schema Guide——新增非 boot 的 preference 键Preference Usage Guide——usePreferencehook 与服务端 APIV2 Migration Guide——完整迁移系统文档【免费下载链接】cherry-studioAI productivity studio with smart chat, autonomous agents, and 300 assistants. Unified access to frontier LLMs项目地址: https://gitcode.com/GitHub_Trending/ch/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考