/** * 临时调研:Iconify 官方集合 vs 本地已收录,找出未收录且许可证合规的候选源 * 输出:未收录集合清单(id / 图标数 / license / 是否合规 / 收录价值初判) */ 'use strict'; const https = require('https'); const fs = require('fs'); const path = require('path'); const ROOT = process.cwd(); const SOURCES = require(path.join(ROOT, 'js', 'data', 'icon-sources.js')); // 本地已收录的 iconify prefix(npm: '@iconify-json/xxx' 的 xxx) const localPrefixes = new Set(); for (const s of SOURCES) { const m = String(s.npm || '').match(/@iconify-json\/([\w-]+)/); if (m) localPrefixes.add(m[1]); // 有些源 id 即 prefix localPrefixes.add(s.id); } function fetch(url, redirects) { return new Promise((resolve, reject) => { const req = https.get(url, { timeout: 60000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { const next = res.headers.location; if (next.startsWith('/')) return resolve(fetch('https://api.iconify.design' + next, (redirects || 0) + 1)); return resolve(fetch(next, (redirects || 0) + 1)); } if (res.statusCode !== 200) { reject(new Error('HTTP ' + res.statusCode)); res.resume(); return; } const chunks = []; res.on('data', (c) => chunks.push(c)); res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); req.on('error', reject); req.setTimeout(60000, () => { req.destroy(new Error('timeout')); }); }); } (async () => { console.log('拉取 Iconify 官方集合列表…'); const raw = await fetch('https://api.iconify.design/collections'); const json = JSON.parse(raw); const all = Object.entries(json.collections || json); console.log('顶层键:', Object.keys(json).slice(0, 10).join(', ')); // 许可证过滤:排除 BY-SA / BY-NC / BY-ND / GPL / -NC-(兼容空格分隔形式) const badRe = /by[\s-]?sa|by[\s-]?nc|by[\s-]?nd|gpl|nc-|cc[\s-]?by[\s-]?nc/i; const missing = []; for (const [prefix, info] of all) { if (localPrefixes.has(prefix)) continue; const license = info.license ? info.license.title : 'unknown'; const bad = badRe.test(license); missing.push({ prefix, total: info.total, license, bad, author: info.author && info.author.name }); } // 未收录 + 合规(或 license unknown 单独列出) const good = missing.filter(m => !m.bad); const bad = missing.filter(m => m.bad); good.sort((a, b) => b.total - a.total); bad.sort((a, b) => b.total - a.total); console.log(`\n=== 官方集合总数: ${all.length} | 本地已收录 iconify prefix: ${localPrefixes.size} ===`); console.log(`\n=== 未收录 + 许可证合规(${good.length} 个)===\n`); for (const m of good) { console.log(` ${m.prefix.padEnd(28)} ${String(m.total).padStart(6)} ${m.license.padEnd(28)} ${m.author || ''}`); } console.log(`\n=== 未收录 + 许可证不合规(${bad.length} 个,仅供参考)===\n`); for (const m of bad.slice(0, 60)) { console.log(` ${m.prefix.padEnd(28)} ${String(m.total).padStart(6)} ${m.license.padEnd(28)} ${m.author || ''}`); } })().catch((e) => { console.error('❌', e.message); process.exit(1); });