mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 22:52:08 +08:00
- Add data-classify tools for data inventory extraction and code generation - Include consolidated Chinese documentation (README.md) - Update generated file path references This temporary directory will be removed after V2 refactor is complete.
240 lines
7.8 KiB
JavaScript
240 lines
7.8 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
class SimpleMappingGenerator {
|
||
constructor() {
|
||
this.dataDir = path.resolve(__dirname, '../data')
|
||
this.targetDir = path.resolve(__dirname, '../../../../src/main/data/migration/v2/migrators/mappings')
|
||
this.classificationFile = path.join(this.dataDir, 'classification.json')
|
||
}
|
||
|
||
generate() {
|
||
console.log('开始生成简化的映射关系代码...')
|
||
|
||
// 读取分类数据
|
||
const classification = this.loadClassification()
|
||
|
||
// 提取preferences相关数据
|
||
const preferencesData = this.extractPreferencesData(classification)
|
||
|
||
// 创建目标目录
|
||
this.ensureTargetDirectory()
|
||
|
||
// 生成映射关系文件
|
||
this.generateMappings(preferencesData)
|
||
|
||
console.log('映射关系生成完成!')
|
||
this.printSummary(preferencesData)
|
||
}
|
||
|
||
loadClassification() {
|
||
if (!fs.existsSync(this.classificationFile)) {
|
||
throw new Error(`分类文件不存在: ${this.classificationFile}`)
|
||
}
|
||
|
||
const content = fs.readFileSync(this.classificationFile, 'utf8')
|
||
return JSON.parse(content)
|
||
}
|
||
|
||
extractPreferencesData(classification) {
|
||
const allPreferencesData = []
|
||
const sources = ['electronStore', 'redux', 'localStorage']
|
||
|
||
// 递归提取项目,包括children (保持现有逻辑)
|
||
const extractItems = (items, source, category, parentKey = '') => {
|
||
if (!Array.isArray(items)) return
|
||
|
||
items.forEach((item) => {
|
||
// 处理有children的项目
|
||
if (item.children && Array.isArray(item.children)) {
|
||
console.log(`处理children项: ${source}/${category}/${item.originalKey}`)
|
||
extractItems(item.children, source, category, `${parentKey}${item.originalKey}.`)
|
||
return
|
||
}
|
||
|
||
// 处理普通项目
|
||
if (item.category === 'preferences' && item.status === 'classified' && item.targetKey) {
|
||
allPreferencesData.push({
|
||
...item,
|
||
source,
|
||
sourceCategory: category,
|
||
originalKey: parentKey + item.originalKey, // 包含父级路径
|
||
fullPath: `${source}/${category}/${parentKey}${item.originalKey}`
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
sources.forEach((source) => {
|
||
if (classification.classifications[source]) {
|
||
Object.keys(classification.classifications[source]).forEach((category) => {
|
||
const items = classification.classifications[source][category]
|
||
extractItems(items, source, category)
|
||
})
|
||
}
|
||
})
|
||
|
||
console.log(`提取到 ${allPreferencesData.length} 个preferences项(包含children)`)
|
||
|
||
// 处理重复的targetKey,优先使用redux数据
|
||
const targetKeyGroups = {}
|
||
allPreferencesData.forEach((item) => {
|
||
if (!targetKeyGroups[item.targetKey]) {
|
||
targetKeyGroups[item.targetKey] = []
|
||
}
|
||
targetKeyGroups[item.targetKey].push(item)
|
||
})
|
||
|
||
// 去重:按redux > localStorage > electronStore优先级选择
|
||
const sourcePriority = { redux: 3, localStorage: 2, electronStore: 1 }
|
||
const deduplicatedData = []
|
||
|
||
Object.keys(targetKeyGroups).forEach((targetKey) => {
|
||
const items = targetKeyGroups[targetKey]
|
||
if (items.length > 1) {
|
||
console.log(`发现重复targetKey: ${targetKey},共${items.length}项`)
|
||
items.forEach((item) => console.log(` - ${item.fullPath}`))
|
||
|
||
// 按优先级排序,选择最高优先级的项
|
||
items.sort((a, b) => sourcePriority[b.source] - sourcePriority[a.source])
|
||
const selected = items[0]
|
||
console.log(` 选择: ${selected.fullPath}`)
|
||
deduplicatedData.push(selected)
|
||
} else {
|
||
deduplicatedData.push(items[0])
|
||
}
|
||
})
|
||
|
||
console.log(`去重后剩余 ${deduplicatedData.length} 个preferences项`)
|
||
|
||
// 按数据源分组
|
||
const preferencesData = {
|
||
electronStore: [],
|
||
redux: [],
|
||
localStorage: [],
|
||
all: deduplicatedData
|
||
}
|
||
|
||
deduplicatedData.forEach((item) => {
|
||
if (preferencesData[item.source]) {
|
||
preferencesData[item.source].push(item)
|
||
}
|
||
})
|
||
|
||
return preferencesData
|
||
}
|
||
|
||
ensureTargetDirectory() {
|
||
if (!fs.existsSync(this.targetDir)) {
|
||
fs.mkdirSync(this.targetDir, { recursive: true })
|
||
}
|
||
}
|
||
|
||
generateMappings(preferencesData) {
|
||
// 生成ElectronStore映射 - 简单结构,不需要sourceCategory
|
||
const electronStoreMappings = preferencesData.electronStore.map((item) => ({
|
||
originalKey: item.originalKey,
|
||
targetKey: item.targetKey
|
||
}))
|
||
|
||
// 生成Redux映射 - 按category分组
|
||
const reduxMappings = {}
|
||
preferencesData.redux.forEach((item) => {
|
||
if (!reduxMappings[item.sourceCategory]) {
|
||
reduxMappings[item.sourceCategory] = []
|
||
}
|
||
reduxMappings[item.sourceCategory].push({
|
||
originalKey: item.originalKey, // 可能包含嵌套路径,如"codeEditor.enabled"
|
||
targetKey: item.targetKey
|
||
})
|
||
})
|
||
|
||
// 生成映射关系文件内容
|
||
const content = `/**
|
||
* Auto-generated preference mappings from classification.json
|
||
* Generated at: ${new Date().toISOString()}
|
||
*
|
||
* This file contains pure mapping relationships without default values.
|
||
* Default values are managed in packages/shared/data/preferences.ts
|
||
*
|
||
* === AUTO-GENERATED CONTENT START ===
|
||
*/
|
||
|
||
/**
|
||
* ElectronStore映射关系 - 简单一层结构
|
||
*
|
||
* ElectronStore没有嵌套,originalKey直接对应configManager.get(key)
|
||
*/
|
||
export const ELECTRON_STORE_MAPPINGS = ${JSON.stringify(electronStoreMappings, null, 2)} as const
|
||
|
||
/**
|
||
* Redux Store映射关系 - 按category分组,支持嵌套路径
|
||
*
|
||
* Redux Store可能有children结构,originalKey可能包含嵌套路径:
|
||
* - 直接字段: "theme" -> reduxData.settings.theme
|
||
* - 嵌套字段: "codeEditor.enabled" -> reduxData.settings.codeEditor.enabled
|
||
* - 多层嵌套: "exportMenuOptions.docx" -> reduxData.settings.exportMenuOptions.docx
|
||
*/
|
||
export const REDUX_STORE_MAPPINGS = ${JSON.stringify(reduxMappings, null, 2)} as const
|
||
|
||
// === AUTO-GENERATED CONTENT END ===
|
||
|
||
/**
|
||
* 映射统计:
|
||
* - ElectronStore项: ${electronStoreMappings.length}
|
||
* - Redux Store项: ${preferencesData.redux.length}
|
||
* - Redux分类: ${Object.keys(reduxMappings).join(', ')}
|
||
* - 总配置项: ${preferencesData.all.length}
|
||
*
|
||
* 使用说明:
|
||
* 1. ElectronStore读取: configManager.get(mapping.originalKey)
|
||
* 2. Redux读取: 需要解析嵌套路径 reduxData[category][originalKey路径]
|
||
* 3. 默认值: 从defaultPreferences.default[mapping.targetKey]获取
|
||
*/`
|
||
|
||
// 写入文件
|
||
const targetFile = path.join(this.targetDir, 'PreferencesMappings.ts')
|
||
fs.writeFileSync(targetFile, content, 'utf8')
|
||
|
||
console.log(`映射关系文件已生成: ${targetFile}`)
|
||
}
|
||
|
||
printSummary(preferencesData) {
|
||
console.log(`\n生成摘要:`)
|
||
console.log(`- 输出文件: PreferencesMappings.ts`)
|
||
console.log(`- ElectronStore映射: ${preferencesData.electronStore.length}`)
|
||
console.log(`- Redux Store映射: ${preferencesData.redux.length}`)
|
||
console.log(`- 总配置项: ${preferencesData.all.length}`)
|
||
|
||
// 显示Redux分类
|
||
const reduxCategories = [...new Set(preferencesData.redux.map((item) => item.sourceCategory))]
|
||
console.log(`- Redux分类: ${reduxCategories.join(', ')}`)
|
||
|
||
// 显示一些嵌套路径的例子
|
||
const nestedKeys = preferencesData.redux
|
||
.filter((item) => item.originalKey.includes('.'))
|
||
.slice(0, 5)
|
||
.map((item) => item.originalKey)
|
||
|
||
if (nestedKeys.length > 0) {
|
||
console.log(`\n嵌套路径示例:`)
|
||
nestedKeys.forEach((key) => console.log(` - ${key}`))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 主执行逻辑
|
||
if (require.main === module) {
|
||
try {
|
||
const generator = new SimpleMappingGenerator()
|
||
generator.generate()
|
||
} catch (error) {
|
||
console.error('生成失败:', error.message)
|
||
process.exit(1)
|
||
}
|
||
}
|
||
|
||
module.exports = SimpleMappingGenerator
|