mirror of
https://github.com/hanzoai/icons.git
synced 2026-09-24 16:59:52 +00:00
* 🔧 chore: Init react-native package * ✨ feat: Support react native * 🐛 fix: adjust style & offset rn transform ease * 🐛 fix: gradient avatar icons & gradient compatibility * ✨ feat(RN): Support gradient avatar bg * ✨ feat(RN): dalle avatar icon * ✨ feat(RN): cogview & cogvideo icon * ✨ feat(RN): Dalle icon style * ✨ feat(RN): script update * ✨ feat(RN): support more feature icons * 💄 style: Update README * 📝 docs: Update README --------- Co-authored-by: canisminor1990 <[email protected]>
257 lines
7.3 KiB
TypeScript
257 lines
7.3 KiB
TypeScript
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { FileConverter } from './utils/file-converter';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const converter = new FileConverter();
|
|
|
|
interface IconInfo {
|
|
name: string;
|
|
rnPath: string;
|
|
webPath: string;
|
|
}
|
|
|
|
// 获取所有图标列表
|
|
function getAllIcons(): IconInfo[] {
|
|
// 计算项目根目录路径,无论脚本从哪里调用
|
|
const scriptDir = __dirname;
|
|
const projectRoot = join(scriptDir, '../../');
|
|
const srcDir = join(projectRoot, 'src');
|
|
|
|
const iconNames = readdirSync(srcDir).filter((name) =>
|
|
existsSync(join(srcDir, name, 'components')),
|
|
);
|
|
|
|
return iconNames.map((name) => ({
|
|
name,
|
|
rnPath: join(projectRoot, 'packages/react-native/src/icons', name),
|
|
webPath: join(srcDir, name),
|
|
}));
|
|
}
|
|
|
|
// 创建图标目录结构
|
|
function createIconDirectory(iconPath: string): void {
|
|
const componentsDir = join(iconPath, 'components');
|
|
if (!existsSync(componentsDir)) {
|
|
mkdirSync(componentsDir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
// 转换并复制style.ts文件
|
|
function copyStyleFile(webPath: string, rnPath: string): boolean {
|
|
const webStylePath = join(webPath, 'style.ts');
|
|
const rnStylePath = join(rnPath, 'style.ts');
|
|
|
|
if (existsSync(webStylePath)) {
|
|
const webContent = readFileSync(webStylePath, 'utf8');
|
|
const result = converter.convertStyleFile(webContent);
|
|
|
|
if (result.success && result.content) {
|
|
writeFileSync(rnStylePath, result.content);
|
|
return true;
|
|
} else {
|
|
console.error(`❌ style.ts 转换失败: ${result.error}`);
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 生成index.ts文件
|
|
function generateIndexFile(iconName: string, rnPath: string): void {
|
|
const scriptDir = __dirname;
|
|
const projectRoot = join(scriptDir, '../../');
|
|
const webIndexPath = join(projectRoot, `src/${iconName}/index.ts`);
|
|
const rnIndexPath = join(rnPath, 'index.ts');
|
|
|
|
if (existsSync(webIndexPath)) {
|
|
// 简单复制,RN版本的index.ts结构与Web版本相同
|
|
const content = readFileSync(webIndexPath, 'utf8');
|
|
writeFileSync(rnIndexPath, content);
|
|
}
|
|
}
|
|
|
|
// 判断是否为SVG组件
|
|
function isSvgComponent(content: string): boolean {
|
|
return content.includes('<svg') || content.includes('fill="currentColor"');
|
|
}
|
|
|
|
// 判断是否为重导出组件
|
|
function isReExportComponent(content: string): boolean {
|
|
return content.trim().startsWith('export { default }');
|
|
}
|
|
|
|
// 转换单个组件
|
|
function convertComponent(
|
|
webPath: string,
|
|
rnPath: string,
|
|
componentType: string,
|
|
iconName: string,
|
|
): boolean {
|
|
const webFilePath = join(webPath, 'components', `${componentType}.tsx`);
|
|
const rnFilePath = join(rnPath, 'components', `${componentType}.tsx`);
|
|
|
|
if (!existsSync(webFilePath)) {
|
|
return false; // 组件不存在,跳过
|
|
}
|
|
|
|
try {
|
|
const webContent = readFileSync(webFilePath, 'utf8');
|
|
|
|
// 处理重导出组件
|
|
if (isReExportComponent(webContent)) {
|
|
const result = converter.convertReExportFile(webContent);
|
|
if (result.success && result.content) {
|
|
writeFileSync(rnFilePath, result.content);
|
|
return true;
|
|
} else {
|
|
console.error(`❌ ${iconName}/${componentType} (重导出): ${result.error}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
let result;
|
|
// 智能判断组件类型
|
|
if (isSvgComponent(webContent)) {
|
|
result = converter.convertSvgComponentFile(webContent, componentType as any, iconName);
|
|
} else {
|
|
result = converter.convertNonSvgComponentFile(webContent, componentType as any);
|
|
}
|
|
|
|
if (result.success && result.content) {
|
|
writeFileSync(rnFilePath, result.content);
|
|
return true;
|
|
} else {
|
|
console.error(`❌ ${iconName}/${componentType}: ${result.error}`);
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.error(`❌ ${iconName}/${componentType}: ${error}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 获取图标的所有组件
|
|
function getIconComponents(webPath: string): string[] {
|
|
const componentsDir = join(webPath, 'components');
|
|
if (!existsSync(componentsDir)) {
|
|
return [];
|
|
}
|
|
|
|
return readdirSync(componentsDir)
|
|
.filter((file) => file.endsWith('.tsx'))
|
|
.map((file) => file.replace('.tsx', ''));
|
|
}
|
|
|
|
// 转换单个图标
|
|
function convertIcon(iconInfo: IconInfo): boolean {
|
|
console.log(`🔄 转换 ${iconInfo.name}...`);
|
|
|
|
// 创建目录
|
|
createIconDirectory(iconInfo.rnPath);
|
|
|
|
// 获取所有组件
|
|
const components = getIconComponents(iconInfo.webPath);
|
|
let successCount = 0;
|
|
|
|
for (const component of components) {
|
|
if (convertComponent(iconInfo.webPath, iconInfo.rnPath, component, iconInfo.name)) {
|
|
successCount++;
|
|
}
|
|
}
|
|
|
|
// 复制style.ts
|
|
if (copyStyleFile(iconInfo.webPath, iconInfo.rnPath)) {
|
|
successCount++;
|
|
}
|
|
|
|
// 生成index.ts
|
|
generateIndexFile(iconInfo.name, iconInfo.rnPath);
|
|
successCount++;
|
|
|
|
console.log(`✅ ${iconInfo.name} 完成 (${successCount}个文件)`);
|
|
return true;
|
|
}
|
|
|
|
// 主函数
|
|
async function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0 || args[0] === '--test') {
|
|
// 批量转换所有图标或测试模式
|
|
const isTestMode = args[0] === '--test';
|
|
|
|
console.log(isTestMode ? '🧪 测试模式:转换前5个图标...\n' : '🚀 开始批量转换所有图标...\n');
|
|
|
|
const icons = getAllIcons();
|
|
const targetIcons = isTestMode ? icons.slice(0, 5) : icons;
|
|
|
|
console.log(`📊 ${isTestMode ? '测试' : '发现'} ${targetIcons.length} 个图标\n`);
|
|
|
|
let successCount = 0;
|
|
for (const icon of targetIcons) {
|
|
if (convertIcon(icon)) {
|
|
successCount++;
|
|
}
|
|
}
|
|
|
|
console.log(`\n🎉 ${isTestMode ? '测试' : '批量转换'}完成!`);
|
|
console.log(`✅ 成功: ${successCount}/${targetIcons.length}`);
|
|
} else {
|
|
// 转换指定图标(支持多个)
|
|
const targetIcons = args;
|
|
console.log(`🎯 转换指定图标: ${targetIcons.join(', ')}\n`);
|
|
|
|
const allIcons = getAllIcons();
|
|
const foundIcons: IconInfo[] = [];
|
|
const notFoundIcons: string[] = [];
|
|
|
|
// 查找所有指定的图标
|
|
for (const targetIcon of targetIcons) {
|
|
const icon = allIcons.find((i) => i.name === targetIcon);
|
|
if (icon) {
|
|
foundIcons.push(icon);
|
|
} else {
|
|
notFoundIcons.push(targetIcon);
|
|
}
|
|
}
|
|
|
|
// 检查是否有找不到的图标
|
|
if (notFoundIcons.length > 0) {
|
|
console.error(`❌ 找不到以下图标: ${notFoundIcons.join(', ')}`);
|
|
console.log(
|
|
`💡 可用图标示例: ${allIcons
|
|
.slice(0, 5)
|
|
.map((i) => i.name)
|
|
.join(', ')}...`,
|
|
);
|
|
throw new Error(`找不到图标: ${notFoundIcons.join(', ')}`);
|
|
}
|
|
|
|
// 转换找到的图标
|
|
let successCount = 0;
|
|
for (const icon of foundIcons) {
|
|
if (convertIcon(icon)) {
|
|
successCount++;
|
|
}
|
|
}
|
|
|
|
console.log(`\n🎉 转换完成!`);
|
|
console.log(`✅ 成功: ${successCount}/${foundIcons.length}`);
|
|
|
|
if (successCount < foundIcons.length) {
|
|
throw new Error(`部分图标转换失败: ${foundIcons.length - successCount} 个失败`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// eslint-disable-next-line unicorn/prefer-top-level-await
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
throw error;
|
|
});
|