mirror of
https://github.com/hanzoai/icons.git
synced 2026-09-24 16:59:52 +00:00
304 lines
9.7 KiB
TypeScript
304 lines
9.7 KiB
TypeScript
#!/usr/bin/env npx tsx
|
|
import { execSync } from 'node:child_process';
|
|
import { promises as fs } from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
/**
|
|
* 特性配置文件同步器
|
|
* 将 web 版本的配置文件转换为 React Native 版本
|
|
*/
|
|
|
|
interface ConfigFile {
|
|
name: string;
|
|
sourceFile: string;
|
|
targetFile: string;
|
|
}
|
|
|
|
class FeatureConfigSyncer {
|
|
private projectRoot: string;
|
|
private configFiles: ConfigFile[];
|
|
|
|
constructor() {
|
|
this.projectRoot = path.resolve(__dirname, '../../');
|
|
this.configFiles = [
|
|
{
|
|
name: 'modelConfig.ts',
|
|
sourceFile: path.join(this.projectRoot, 'src/features/modelConfig.ts'),
|
|
targetFile: path.join(
|
|
this.projectRoot,
|
|
'packages/react-native/src/features/modelConfig.ts',
|
|
),
|
|
},
|
|
{
|
|
name: 'providerEnum.ts',
|
|
sourceFile: path.join(this.projectRoot, 'src/features/providerEnum.ts'),
|
|
targetFile: path.join(
|
|
this.projectRoot,
|
|
'packages/react-native/src/features/providerEnum.ts',
|
|
),
|
|
},
|
|
{
|
|
name: 'providerConfig.tsx',
|
|
sourceFile: path.join(this.projectRoot, 'src/features/providerConfig.tsx'),
|
|
targetFile: path.join(
|
|
this.projectRoot,
|
|
'packages/react-native/src/features/providerConfig.tsx',
|
|
),
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 转换配置文件内容
|
|
*/
|
|
private transformContent(content: string, fileName: string): string {
|
|
let transformed = content;
|
|
|
|
// 1. 转换导入路径:@/IconName → ../icons/IconName
|
|
transformed = transformed.replaceAll(/from '@\/([A-Z][\dA-Za-z]*)'/g, "from '../icons/$1'");
|
|
|
|
// 2. 转换类型名称
|
|
// ModelProvider → RNModelProvider (枚举定义)
|
|
transformed = transformed.replaceAll(
|
|
/export enum ModelProvider\s*{/g,
|
|
'export enum RNModelProvider {',
|
|
);
|
|
|
|
// ModelProviderKey → RNModelProviderKey (类型定义)
|
|
transformed = transformed.replaceAll(
|
|
/export type ModelProviderKey\s*=/g,
|
|
'export type RNModelProviderKey =',
|
|
);
|
|
|
|
// ModelMapping → RNModelMapping (接口和类型)
|
|
transformed = transformed.replaceAll(/\bModelMapping\b/g, 'RNModelMapping');
|
|
|
|
// ProviderMapping → RNProviderMapping (接口和类型)
|
|
transformed = transformed.replaceAll(/\bProviderMapping\b/g, 'RNProviderMapping');
|
|
|
|
// ModelProvider 引用 → RNModelProvider (在使用的地方)
|
|
// 处理 typeof ModelProvider 的情况
|
|
transformed = transformed.replaceAll('typeof ModelProvider', 'typeof RNModelProvider');
|
|
// 处理 ModelProvider. 的情况
|
|
transformed = transformed.replaceAll(/\bModelProvider\./g, 'RNModelProvider.');
|
|
// 处理导入的情况
|
|
transformed = transformed.replaceAll('{ ModelProvider }', '{ RNModelProvider }');
|
|
transformed = transformed.replaceAll("from './providerEnum'", "from './providerEnum'");
|
|
|
|
// 3. 转换类型导入和使用
|
|
// IconType → RNIconProps
|
|
transformed = transformed.replaceAll(/\bIconType\b/g, 'RNIconProps');
|
|
|
|
// IconAvatarProps → RNIconAvatarProps
|
|
transformed = transformed.replaceAll(/\bIconAvatarProps\b/g, 'RNIconAvatarProps');
|
|
|
|
// IconCombineProps → RNIconCombineProps
|
|
transformed = transformed.replaceAll(/\bIconCombineProps\b/g, 'RNIconCombineProps');
|
|
|
|
// 确保类型名称带有 RN 前缀
|
|
if (fileName === 'modelConfig.ts') {
|
|
// ModelIconType → RNModelIconType
|
|
transformed = transformed.replaceAll('type ModelIconType = ', 'type RNModelIconType = ');
|
|
transformed = transformed.replaceAll('Icon: ModelIconType;', 'Icon: RNModelIconType;');
|
|
}
|
|
|
|
if (fileName === 'providerConfig.tsx') {
|
|
// ProviderIconType → RNProviderIconType
|
|
transformed = transformed.replaceAll(
|
|
'type ProviderIconType = ',
|
|
'type RNProviderIconType = ',
|
|
);
|
|
transformed = transformed.replaceAll('Icon: ProviderIconType;', 'Icon: RNProviderIconType;');
|
|
}
|
|
|
|
// 4. 转换导出变量名
|
|
transformed = transformed.replaceAll(
|
|
'export const modelMappings:',
|
|
'export const rnModelMappings:',
|
|
);
|
|
transformed = transformed.replaceAll(
|
|
'export const providerMappings:',
|
|
'export const rnProviderMappings:',
|
|
);
|
|
|
|
// 5. 特殊导入处理
|
|
// 移除 @lobehub/ui 的 DivProps 导入
|
|
transformed = transformed.replaceAll(
|
|
/import\s*{\s*DivProps\s*}\s*from\s*'@lobehub\/ui';\s*\n/g,
|
|
'',
|
|
);
|
|
|
|
// 移除 DivProps 类型引用
|
|
transformed = transformed.replaceAll(/\bDivProps\s*&\s*/g, '');
|
|
|
|
// 调整类型导入路径
|
|
transformed = transformed.replaceAll("from '@/features/IconAvatar'", "from './IconAvatar'");
|
|
transformed = transformed.replaceAll("from '@/features/IconCombine'", "from './IconCombine'");
|
|
transformed = transformed.replaceAll("from '@/types'", "from './types'");
|
|
|
|
// 处理特殊的导入语句
|
|
if (fileName === 'modelConfig.ts') {
|
|
// 添加 React Native 特有的注释
|
|
transformed = transformed.replaceAll(
|
|
"import { FC } from 'react';",
|
|
"import { FC } from 'react';\n",
|
|
);
|
|
} else if (fileName === 'providerConfig.tsx') {
|
|
// 移除 memo 导入中的多余部分
|
|
transformed = transformed.replaceAll(
|
|
/import\s*{\s*FC,\s*memo\s*}\s*from\s*'react';/g,
|
|
"import { FC, memo } from 'react';",
|
|
);
|
|
}
|
|
|
|
// 6. 修复特定的导入语句格式
|
|
// 处理 Kwaipilot 的特殊导入 - 从 @/icons 改为直接导入
|
|
transformed = transformed.replaceAll(
|
|
"import { Kwaipilot } from '@/icons';",
|
|
"import Kwaipilot from '../icons/Kwaipilot';",
|
|
);
|
|
|
|
// 移除转换后多余的类型导入行
|
|
// 这些在步骤5的路径转换后会变成 './IconAvatar' 和 './IconCombine'
|
|
transformed = transformed.replaceAll(
|
|
"import type { RNIconAvatarProps } from './IconAvatar';\n",
|
|
'',
|
|
);
|
|
transformed = transformed.replaceAll(
|
|
"import type { RNIconCombineProps } from './IconCombine';\n",
|
|
'',
|
|
);
|
|
transformed = transformed.replaceAll("import type { RNIconProps } from './types';\n", '');
|
|
|
|
// 7. 统一添加类型导入
|
|
if (fileName === 'modelConfig.ts') {
|
|
// 在 Kwaipilot 导入后添加统一的类型导入
|
|
transformed = transformed.replaceAll(
|
|
"import Kwaipilot from '../icons/Kwaipilot';",
|
|
"import Kwaipilot from '../icons/Kwaipilot';\nimport type { RNIconAvatarProps, RNIconCombineProps, RNIconProps } from './types';",
|
|
);
|
|
} else if (fileName === 'providerConfig.tsx') {
|
|
// 在 Combine 导入之前添加统一的类型导入
|
|
transformed = transformed.replaceAll(
|
|
"import Combine from './ProviderCombine/Combine';",
|
|
"import type { RNIconAvatarProps, RNIconCombineProps, RNIconProps } from './types';\nimport Combine from './ProviderCombine/Combine';",
|
|
);
|
|
}
|
|
|
|
return transformed;
|
|
}
|
|
|
|
/**
|
|
* 同步单个配置文件
|
|
*/
|
|
private async syncFile(config: ConfigFile): Promise<boolean> {
|
|
try {
|
|
console.log(`📝 同步 ${config.name}...`);
|
|
|
|
// 读取源文件
|
|
const content = await fs.readFile(config.sourceFile, 'utf8');
|
|
|
|
// 转换内容
|
|
const transformed = this.transformContent(content, config.name);
|
|
|
|
// 写入目标文件
|
|
await fs.writeFile(config.targetFile, transformed, 'utf8');
|
|
|
|
console.log(`✅ ${config.name} 同步完成`);
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`❌ ${config.name} 同步失败:`, error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 格式化生成的文件
|
|
*/
|
|
private async formatFiles(): Promise<boolean> {
|
|
try {
|
|
console.log('\n🎨 格式化生成的文件...');
|
|
|
|
const targetFiles = this.configFiles.map((config) => config.targetFile);
|
|
|
|
// 使用 prettier 格式化
|
|
try {
|
|
execSync(`npx prettier --write ${targetFiles.join(' ')}`, {
|
|
cwd: this.projectRoot,
|
|
encoding: 'utf8',
|
|
stdio: 'pipe',
|
|
});
|
|
console.log('✅ Prettier 格式化完成');
|
|
} catch {
|
|
console.log('⚠️ Prettier 格式化失败,尝试使用 ESLint...');
|
|
}
|
|
|
|
// 使用 eslint 修复
|
|
try {
|
|
const featuresDir = path.join(this.projectRoot, 'packages/react-native/src/features');
|
|
execSync(`npx eslint "${featuresDir}/*.{ts,tsx}" --fix`, {
|
|
cwd: this.projectRoot,
|
|
encoding: 'utf8',
|
|
stdio: 'pipe',
|
|
});
|
|
console.log('✅ ESLint 修复完成');
|
|
} catch {
|
|
console.log('⚠️ ESLint 修复跳过');
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('❌ 格式化失败:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 同步所有配置文件
|
|
*/
|
|
async syncAll(options: { format?: boolean } = {}): Promise<boolean> {
|
|
console.log('\n🔄 开始同步特性配置文件...\n');
|
|
|
|
const results = await Promise.all(this.configFiles.map((config) => this.syncFile(config)));
|
|
|
|
const allSuccess = results.every(Boolean);
|
|
|
|
if (allSuccess) {
|
|
console.log('\n✅ 所有配置文件同步完成');
|
|
|
|
// 格式化生成的文件
|
|
if (options.format !== false) {
|
|
await this.formatFiles();
|
|
}
|
|
} else {
|
|
console.log('\n❌ 部分配置文件同步失败');
|
|
}
|
|
|
|
return allSuccess;
|
|
}
|
|
}
|
|
|
|
// 主函数
|
|
async function main() {
|
|
try {
|
|
const syncer = new FeatureConfigSyncer();
|
|
const success = await syncer.syncAll();
|
|
process.exit(success ? 0 : 1);
|
|
} catch (error) {
|
|
console.error('💥 执行失败:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// 如果直接运行此脚本
|
|
if (require.main === module) {
|
|
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
main().catch((error) => {
|
|
console.error('❌ 主函数执行失败:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
// 导出类供其他脚本使用
|
|
export default FeatureConfigSyncer;
|