2025-07-21 16:47:04 +08:00
|
|
|
|
const fs = require("fs");
|
|
|
|
|
|
const path = require("path");
|
|
|
|
|
|
const AdmZip = require("adm-zip");
|
|
|
|
|
|
|
|
|
|
|
|
// 清理所有临时文件
|
|
|
|
|
|
function cleanupTempDir() {
|
|
|
|
|
|
const tempDir = path.join(".cool", "temp");
|
|
|
|
|
|
if (fs.existsSync(tempDir)) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
|
|
|
|
} catch (error) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.warn(`❌ 清理临时目录失败: ${ tempDir }`, error);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确保临时目录存在
|
|
|
|
|
|
function ensureTempDir() {
|
|
|
|
|
|
const tempDir = path.join(".cool", "temp");
|
|
|
|
|
|
if (!fs.existsSync(tempDir)) {
|
|
|
|
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建icons目录和子目录
|
|
|
|
|
|
function ensureDistDir(folderName = "") {
|
|
|
|
|
|
const iconsPath = folderName ? path.join("icons", folderName) : "icons";
|
|
|
|
|
|
if (!fs.existsSync(iconsPath)) {
|
|
|
|
|
|
fs.mkdirSync(iconsPath, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取zip文件列表
|
|
|
|
|
|
function getZipFiles() {
|
|
|
|
|
|
const iconsDir = path.join(".cool", "icons");
|
|
|
|
|
|
if (!fs.existsSync(iconsDir)) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.error(`❌ 目录不存在: ${ iconsDir }`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fs.readdirSync(iconsDir).filter((item) => {
|
|
|
|
|
|
const filePath = path.join(iconsDir, item);
|
|
|
|
|
|
const stats = fs.statSync(filePath);
|
|
|
|
|
|
return stats.isFile() && item.endsWith(".zip");
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解压zip文件到临时目录
|
|
|
|
|
|
function extractZipFile(zipPath, folderName) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const zip = new AdmZip(zipPath);
|
|
|
|
|
|
const tempDir = path.join(".cool", "temp", folderName);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保临时目录存在
|
|
|
|
|
|
if (!fs.existsSync(tempDir)) {
|
|
|
|
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解压到临时目录
|
|
|
|
|
|
zip.extractAllTo(tempDir, true);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有额外的顶层文件夹
|
|
|
|
|
|
const extractedItems = fs.readdirSync(tempDir);
|
|
|
|
|
|
|
|
|
|
|
|
// 如果只有一个项目且是文件夹,则可能是额外的包装文件夹
|
|
|
|
|
|
if (extractedItems.length === 1) {
|
|
|
|
|
|
const singleItem = extractedItems[0];
|
|
|
|
|
|
const singleItemPath = path.join(tempDir, singleItem);
|
|
|
|
|
|
const stats = fs.statSync(singleItemPath);
|
|
|
|
|
|
|
|
|
|
|
|
if (stats.isDirectory()) {
|
|
|
|
|
|
// 检查这个文件夹是否包含我们需要的文件
|
|
|
|
|
|
const innerItems = fs.readdirSync(singleItemPath);
|
|
|
|
|
|
const hasIconFiles = innerItems.some(
|
|
|
|
|
|
(item) =>
|
|
|
|
|
|
item.endsWith(".ttf") || item.endsWith(".json") || item.endsWith(".css")
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (hasIconFiles) {
|
|
|
|
|
|
return singleItemPath;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return tempDir;
|
|
|
|
|
|
} catch (error) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.error(`❌ 解压失败: ${ zipPath }`, error);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将TTF文件转换为base64
|
|
|
|
|
|
function ttfToBase64(ttfPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const ttfBuffer = fs.readFileSync(ttfPath);
|
|
|
|
|
|
return ttfBuffer.toString("base64");
|
|
|
|
|
|
} catch (error) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.error(`❌ 读取TTF文件失败: ${ ttfPath }`, error);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成TypeScript文件
|
2025-11-03 09:49:18 +08:00
|
|
|
|
function generateTypeScript(originalFolderName, camelCaseName, iconData, iconPrefix) {
|
|
|
|
|
|
const tsContent = `export const ${ camelCaseName } = {\n${ iconData
|
|
|
|
|
|
.map((item) => `\t"${ iconPrefix }${ item.name }": "${ item.unicode }"`)
|
|
|
|
|
|
.join(",\n") }\n};\n`;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
const outputPath = path.join("icons", originalFolderName, "index.ts");
|
2025-07-21 16:47:04 +08:00
|
|
|
|
fs.writeFileSync(outputPath, tsContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成SCSS文件
|
2025-08-26 14:15:15 +08:00
|
|
|
|
function generateSCSS(originalFolderName, base64Data) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
const scssContent = `@font-face {\n\tfont-family: "${ toCamelCase(originalFolderName) }";\n\tsrc: url("data:font/ttf;base64,${ base64Data }") format("woff");\n}\n`;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
const outputPath = path.join("icons", originalFolderName, "index.scss");
|
2025-07-21 16:47:04 +08:00
|
|
|
|
fs.writeFileSync(outputPath, scssContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从CSS文件提取图标数据(用于remixicon等)
|
|
|
|
|
|
function extractIconsFromCSS(cssPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const cssContent = fs.readFileSync(cssPath, "utf8");
|
|
|
|
|
|
const iconData = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配CSS中的图标规则,例如:.ri-home-line:before { content: "\ee2b"; }
|
|
|
|
|
|
const regex = /\.ri-([^:]+):before\s*{\s*content:\s*"\\([^"]+)"/g;
|
|
|
|
|
|
let match;
|
|
|
|
|
|
|
|
|
|
|
|
while ((match = regex.exec(cssContent)) !== null) {
|
|
|
|
|
|
const iconName = match[1];
|
|
|
|
|
|
const unicode = match[2];
|
|
|
|
|
|
|
|
|
|
|
|
iconData.push({
|
|
|
|
|
|
name: iconName,
|
|
|
|
|
|
unicode: unicode
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return iconData;
|
|
|
|
|
|
} catch (error) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.error(`❌ 读取CSS文件失败: ${ cssPath }`, error);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取和处理图标数据
|
|
|
|
|
|
function processIconData(jsonPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const jsonData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
|
|
|
|
|
return jsonData.glyphs.map((item) => ({
|
|
|
|
|
|
name: item.font_class,
|
|
|
|
|
|
unicode: item.unicode
|
|
|
|
|
|
}));
|
2025-11-03 09:49:18 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`❌ 读取JSON文件失败: ${ jsonPath }`, error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取iconfont图标前缀
|
|
|
|
|
|
function getIconPrefix(jsonPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const jsonData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
|
|
|
|
|
return jsonData.css_prefix_text;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`❌ 读取JSON文件失败: ${jsonPath}`, error);
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
// 将连字符转换为驼峰命名的函数
|
|
|
|
|
|
function toCamelCase(str) {
|
|
|
|
|
|
return str.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 处理单个zip文件
|
|
|
|
|
|
function processZipFile(zipFileName) {
|
2025-08-26 14:15:15 +08:00
|
|
|
|
const originalFolderName = path.basename(zipFileName, ".zip");
|
|
|
|
|
|
const folderName = toCamelCase(originalFolderName); // 转换为驼峰命名用于变量名
|
2025-07-21 16:47:04 +08:00
|
|
|
|
const zipPath = path.join(".cool", "icons", zipFileName);
|
|
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
// 解压zip文件 (使用原始文件夹名称)
|
|
|
|
|
|
const tempDir = extractZipFile(zipPath, originalFolderName);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
if (!tempDir) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 图标库名称
|
|
|
|
|
|
const ptName = ["iconfont", "remixicon"];
|
|
|
|
|
|
|
|
|
|
|
|
// 获取文件路径
|
|
|
|
|
|
const getFilePath = (ext) => {
|
|
|
|
|
|
let filePath = null;
|
|
|
|
|
|
for (const name of ptName) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
const tempPath = path.join(tempDir, `${ name }.${ ext }`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
if (fs.existsSync(tempPath)) {
|
|
|
|
|
|
filePath = tempPath;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return filePath;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 在解压后的目录中查找文件
|
|
|
|
|
|
const jsonPath = getFilePath("json");
|
|
|
|
|
|
const cssPath = getFilePath("css");
|
|
|
|
|
|
const ttfPath = getFilePath("ttf");
|
|
|
|
|
|
|
|
|
|
|
|
if (!ttfPath) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.warn(`⚠️跳过 ${ folderName }: 缺少 TTF 文件`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let iconData = [];
|
|
|
|
|
|
|
2025-11-03 09:49:18 +08:00
|
|
|
|
let iconPrefix = "";
|
|
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 优先使用JSON文件
|
|
|
|
|
|
if (jsonPath) {
|
|
|
|
|
|
iconData = processIconData(jsonPath);
|
2025-11-03 09:49:18 +08:00
|
|
|
|
if (originalFolderName !== "iconfont") {
|
|
|
|
|
|
iconPrefix = getIconPrefix(jsonPath);
|
|
|
|
|
|
}
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 如果没有则尝试CSS文件
|
|
|
|
|
|
else if (cssPath) {
|
|
|
|
|
|
iconData = extractIconsFromCSS(cssPath);
|
|
|
|
|
|
} else {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.warn(`⚠️ 跳过 ${ folderName }: 缺少 ${ jsonPath } 或 ${ cssPath }`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (iconData.length === 0) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.warn(`⚠️ ${ folderName }: 没有找到图标数据`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.log(`✅ ${ zipFileName } 找到 ${ iconData.length } 个图标`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 转换TTF为base64
|
|
|
|
|
|
const base64Data = ttfToBase64(ttfPath);
|
|
|
|
|
|
if (!base64Data) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
console.error(`❌ ${ folderName }: TTF转换失败`);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
// 为该文件夹创建icons子目录 (使用原始文件夹名称)
|
|
|
|
|
|
ensureDistDir(originalFolderName);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
// 生成TypeScript文件 (使用驼峰命名作为变量名,原始名称作为路径)
|
2025-11-03 09:49:18 +08:00
|
|
|
|
generateTypeScript(originalFolderName, folderName, iconData, iconPrefix);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
// 生成SCSS文件 (使用原始名称作为路径和字体名称)
|
|
|
|
|
|
generateSCSS(originalFolderName, base64Data);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-08-26 14:15:15 +08:00
|
|
|
|
return { originalName: originalFolderName, camelName: folderName };
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成主index.ts文件
|
2025-08-26 14:15:15 +08:00
|
|
|
|
function generateIndexTS(actualFolders) {
|
|
|
|
|
|
const imports = actualFolders
|
|
|
|
|
|
.map((folder) => {
|
|
|
|
|
|
const camelName = toCamelCase(folder);
|
2025-11-03 09:49:18 +08:00
|
|
|
|
return `import { ${ camelName } } from "./${ folder }";`;
|
2025-08-26 14:15:15 +08:00
|
|
|
|
})
|
2025-07-21 16:47:04 +08:00
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
2025-11-03 09:49:18 +08:00
|
|
|
|
const exports = `export const icons = {\n${ actualFolders
|
|
|
|
|
|
.map((folder) => `\t${ toCamelCase(folder) }`)
|
|
|
|
|
|
.join(",\n") }\n};\n`;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-11-03 09:49:18 +08:00
|
|
|
|
const content = `${ imports }\n\n${ exports }`;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
fs.writeFileSync("icons/index.ts", content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成主index.scss文件
|
2025-08-26 14:15:15 +08:00
|
|
|
|
function generateIndexSCSS(actualFolders) {
|
2025-11-03 09:49:18 +08:00
|
|
|
|
const imports = actualFolders.map((folder) => `@import "./${ folder }/index.scss";`).join("\n");
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
|
|
|
|
|
fs.writeFileSync("icons/index.scss", imports + "\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 扫描icons目录下的实际文件夹
|
|
|
|
|
|
function getActualIconFolders() {
|
|
|
|
|
|
const iconsDir = "icons";
|
|
|
|
|
|
if (!fs.existsSync(iconsDir)) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return fs.readdirSync(iconsDir).filter((item) => {
|
|
|
|
|
|
const itemPath = path.join(iconsDir, item);
|
|
|
|
|
|
const stats = fs.statSync(itemPath);
|
|
|
|
|
|
return stats.isDirectory();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 主函数
|
|
|
|
|
|
function main() {
|
|
|
|
|
|
console.log("🚀 开始处理字体文件...\n");
|
|
|
|
|
|
|
|
|
|
|
|
// 确保临时目录存在
|
|
|
|
|
|
ensureTempDir();
|
|
|
|
|
|
|
|
|
|
|
|
// 确保icons目录存在
|
|
|
|
|
|
ensureDistDir();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取所有zip文件
|
|
|
|
|
|
const zipFiles = getZipFiles();
|
|
|
|
|
|
|
|
|
|
|
|
// 处理每个zip文件
|
|
|
|
|
|
const processedFolders = [];
|
|
|
|
|
|
for (const zipFile of zipFiles) {
|
|
|
|
|
|
const result = processZipFile(zipFile);
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
processedFolders.push(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 扫描icons目录下的实际文件夹
|
|
|
|
|
|
const actualFolders = getActualIconFolders();
|
|
|
|
|
|
|
|
|
|
|
|
if (actualFolders.length > 0) {
|
|
|
|
|
|
// 生成主index文件
|
|
|
|
|
|
generateIndexTS(actualFolders);
|
|
|
|
|
|
generateIndexSCSS(actualFolders);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (processedFolders.length > 0) {
|
2025-09-11 11:27:30 +08:00
|
|
|
|
const folderNames = processedFolders.map((f) =>
|
|
|
|
|
|
typeof f === "string" ? f : f.originalName
|
|
|
|
|
|
);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
console.log(
|
2025-11-03 09:49:18 +08:00
|
|
|
|
`\n🎉 成功处理了 ${ processedFolders.length } 个字体包: ${ folderNames.join(", ") }`
|
2025-07-21 16:47:04 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("❌ 脚本执行出错:", error);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
cleanupTempDir();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 运行脚本
|
|
|
|
|
|
if (require.main === module) {
|
|
|
|
|
|
main();
|
|
|
|
|
|
}
|