107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const React = require("react");
|
|
const ReactDOMServer = require("react-dom/server");
|
|
const sharp = require("sharp");
|
|
|
|
const LIBRARIES = {
|
|
fa6: "react-icons/fa6",
|
|
fi: "react-icons/fi",
|
|
hi2: "react-icons/hi2",
|
|
io5: "react-icons/io5",
|
|
lu: "react-icons/lu",
|
|
md: "react-icons/md",
|
|
ri: "react-icons/ri",
|
|
tb: "react-icons/tb",
|
|
};
|
|
|
|
function fail(message) {
|
|
process.stderr.write(`${message}\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const values = {};
|
|
for (let index = 0; index < argv.length; index += 2) {
|
|
if (!argv[index]?.startsWith("--") || argv[index + 1] === undefined) {
|
|
fail("参数必须按 --name value 成对提供");
|
|
}
|
|
values[argv[index].slice(2)] = argv[index + 1];
|
|
}
|
|
if (!values.spec || !values.output) {
|
|
fail("缺少 --spec 或 --output");
|
|
}
|
|
return values;
|
|
}
|
|
|
|
function color(value, label) {
|
|
if (typeof value !== "string" || !/^[0-9A-Fa-f]{6}$/.test(value)) {
|
|
throw new Error(`${label} 必须是不带 # 的 6 位十六进制颜色`);
|
|
}
|
|
return `#${value.toUpperCase()}`;
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const specPath = path.resolve(args.spec);
|
|
const outputPath = path.resolve(args.output);
|
|
if (path.extname(outputPath).toLowerCase() !== ".png") {
|
|
fail("output 必须使用 .png 扩展名");
|
|
}
|
|
let spec;
|
|
try {
|
|
spec = JSON.parse(fs.readFileSync(specPath, "utf8"));
|
|
} catch (error) {
|
|
fail(`读取 spec 失败:${error.message}`);
|
|
}
|
|
try {
|
|
const libraryPath = LIBRARIES[spec.library];
|
|
if (!libraryPath) {
|
|
throw new Error(`不支持的图标库:${spec.library}`);
|
|
}
|
|
if (typeof spec.name !== "string" || !/^[A-Za-z][A-Za-z0-9]*$/.test(spec.name)) {
|
|
throw new Error("name 必须是合法的 React Icons 导出名称");
|
|
}
|
|
const size = spec.size ?? 256;
|
|
if (!Number.isInteger(size) || size < 32 || size > 2048) {
|
|
throw new Error("size 必须是 32 到 2048 的整数");
|
|
}
|
|
const moduleExports = require(libraryPath);
|
|
const Icon = moduleExports[spec.name];
|
|
if (typeof Icon !== "function") {
|
|
throw new Error(`${spec.library} 中不存在图标 ${spec.name}`);
|
|
}
|
|
const foreground = color(spec.color || "111827", "color");
|
|
const markup = ReactDOMServer.renderToStaticMarkup(
|
|
React.createElement(Icon, {
|
|
color: foreground,
|
|
size,
|
|
title: typeof spec.title === "string" ? spec.title : undefined,
|
|
}),
|
|
);
|
|
let pipeline = sharp(Buffer.from(markup))
|
|
.resize(size, size, { fit: "contain" });
|
|
if (spec.background) {
|
|
pipeline = pipeline.flatten({ background: color(spec.background, "background") });
|
|
}
|
|
await pipeline.png().toFile(outputPath);
|
|
const metadata = await sharp(outputPath).metadata();
|
|
process.stdout.write(
|
|
`${JSON.stringify({
|
|
library: spec.library,
|
|
name: spec.name,
|
|
width: metadata.width,
|
|
height: metadata.height,
|
|
})}\n`,
|
|
);
|
|
} catch (error) {
|
|
fail(error instanceof Error ? error.message : String(error));
|
|
}
|
|
}
|
|
|
|
main();
|