download-simple-icons.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. #!/usr/bin/env node
  2. import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
  3. import { existsSync } from "node:fs";
  4. import path from "node:path";
  5. import vm from "node:vm";
  6. const root = path.resolve(new URL("..", import.meta.url).pathname);
  7. const catalogPath = path.join(root, "platformCatalog.js");
  8. const assetsManifestPath = path.join(root, "platformAssets.js");
  9. const outputDir = path.join(root, "assets", "platforms");
  10. const cdnBase = "https://cdn.simpleicons.org";
  11. function loadCatalog(source) {
  12. const sandbox = { window: {}, globalThis: {} };
  13. sandbox.globalThis = sandbox.window;
  14. vm.runInNewContext(source, sandbox, { filename: catalogPath });
  15. return sandbox.window.BindVaultPlatformCatalog || [];
  16. }
  17. function iconFile(entry) {
  18. return entry.file || `${entry.simpleIcon || entry.key}.svg`;
  19. }
  20. async function downloadIcon(entry, options) {
  21. const slug = entry.simpleIcon || entry.key;
  22. const target = path.join(outputDir, iconFile(entry));
  23. if (!options.force && existsSync(target)) {
  24. return { key: entry.key, status: "skipped", file: path.relative(root, target) };
  25. }
  26. const response = await fetch(`${cdnBase}/${encodeURIComponent(slug)}`);
  27. if (!response.ok) {
  28. throw new Error(`${entry.key}: ${response.status} ${response.statusText}`);
  29. }
  30. const svg = await response.text();
  31. if (!svg.trim().startsWith("<svg")) {
  32. throw new Error(`${entry.key}: response is not SVG`);
  33. }
  34. await writeFile(target, `${svg.trim()}\n`, "utf8");
  35. return { key: entry.key, status: "downloaded", file: path.relative(root, target) };
  36. }
  37. async function main() {
  38. const args = new Set(process.argv.slice(2));
  39. const options = { force: args.has("--force"), strict: args.has("--strict") };
  40. const catalog = loadCatalog(await readFile(catalogPath, "utf8"));
  41. const entries = catalog.filter((entry) => entry.simpleIcon);
  42. await mkdir(outputDir, { recursive: true });
  43. const results = [];
  44. const failures = [];
  45. for (const entry of entries) {
  46. try {
  47. const result = await downloadIcon(entry, options);
  48. results.push(result);
  49. console.log(`${result.status.padEnd(10)} ${result.file}`);
  50. } catch (error) {
  51. failures.push(error);
  52. console.error(`failed ${entry.key}: ${error.message}`);
  53. }
  54. }
  55. const files = (await readdir(outputDir))
  56. .filter((file) => file.toLowerCase().endsWith(".svg"))
  57. .sort();
  58. const manifest = [
  59. "window.BindVaultPlatformAssets = {",
  60. ...files.map((file) => ` ${JSON.stringify(file)}: true,`),
  61. "};",
  62. "",
  63. ].join("\n");
  64. await writeFile(assetsManifestPath, manifest, "utf8");
  65. console.log(`\nSimple Icons: ${results.length} ok, ${failures.length} failed`);
  66. console.log(`Manifest: ${path.relative(root, assetsManifestPath)}`);
  67. if (failures.length && options.strict) process.exitCode = 1;
  68. }
  69. main().catch((error) => {
  70. console.error(error);
  71. process.exit(1);
  72. });