Files
n8n/scripts/generate-lucide-icon-data.mjs
2026-06-16 08:55:15 +00:00

139 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Generates lucideIconData.ts with search metadata (keywords + categories) for Lucide icons.
* SVG bodies are NOT included — they are loaded via generated chunks at runtime by lucideIconsPlugin
* (packages/frontend/@n8n/design-system/src/icons/lucide/vite.ts).
*
* Usage: node scripts/generate-lucide-icon-data.mjs
*
* Output: packages/frontend/@n8n/design-system/src/components/N8nIconPicker/lucideIconData.ts
*/
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, '..');
const COMPONENTS_ROOT = resolve(
ROOT,
'packages/frontend/@n8n/design-system/src/components',
);
const LUCIDE_JSON_PATH = resolve(ROOT, 'node_modules/@iconify/json/json/lucide.json');
const OUTPUT_PATH = resolve(COMPONENTS_ROOT, 'N8nIconPicker/lucideIconData.ts');
const CACHE_PATH = resolve(ROOT, 'scripts/.lucide-tags-cache.json');
// Lucide GitHub raw URL for per-icon metadata
const LUCIDE_GITHUB_BASE =
'https://raw.githubusercontent.com/lucide-icons/lucide/main/icons';
async function fetchIconMeta(iconName) {
const url = `${LUCIDE_GITHUB_BASE}/${iconName}.json`;
try {
const res = await fetch(url);
if (!res.ok) return { tags: [], categories: [] };
const data = await res.json();
return {
tags: data.tags ?? [],
categories: data.categories ?? [],
};
} catch {
return { tags: [], categories: [] };
}
}
async function main() {
console.log('Reading @iconify/json lucide data...');
const lucideJson = JSON.parse(readFileSync(LUCIDE_JSON_PATH, 'utf-8'));
const icons = lucideJson.icons;
const iconNames = Object.keys(icons).sort();
console.log(`Found ${iconNames.length} Lucide icons`);
// Load or initialize cache
let cache = {};
if (existsSync(CACHE_PATH)) {
try {
cache = JSON.parse(readFileSync(CACHE_PATH, 'utf-8'));
console.log(`Loaded ${Object.keys(cache).length} cached icon metadata entries`);
} catch {
cache = {};
}
}
// Fetch metadata for uncached icons
const uncachedNames = iconNames.filter((name) => !cache[name]);
if (uncachedNames.length > 0) {
console.log(`Fetching metadata for ${uncachedNames.length} icons from Lucide GitHub...`);
const BATCH_SIZE = 50;
for (let i = 0; i < uncachedNames.length; i += BATCH_SIZE) {
const batch = uncachedNames.slice(i, i + BATCH_SIZE);
const results = await Promise.all(batch.map(fetchIconMeta));
for (let j = 0; j < batch.length; j++) {
cache[batch[j]] = results[j];
}
const progress = Math.min(i + BATCH_SIZE, uncachedNames.length);
process.stdout.write(`\r Fetched ${progress}/${uncachedNames.length}`);
}
console.log('\nSaving cache...');
writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2));
}
// Build the output data
const allCategories = new Set();
const entries = [];
for (const name of iconNames) {
const meta = cache[name] ?? { tags: [], categories: [] };
const body = icons[name]?.body;
if (!body) continue;
// Keywords: icon name parts + tags
const nameParts = name.split('-').filter((p) => p.length > 0);
const keywords = [...new Set([...nameParts, ...meta.tags])];
const categories = meta.categories ?? [];
categories.forEach((c) => allCategories.add(c));
entries.push({ name, keywords, categories });
}
const sortedCategories = [...allCategories].sort();
console.log(`Generating TypeScript file with ${entries.length} icons and ${sortedCategories.length} categories...`);
// Generate TypeScript output — metadata only, no SVG bodies
let output = `// AUTO-GENERATED by scripts/generate-lucide-icon-data.mjs — DO NOT EDIT
// Source: Lucide GitHub (tags/categories). SVG bodies are loaded via generated chunks at runtime.
// Icons: ${entries.length} | Categories: ${sortedCategories.length}
export interface LucideIconMeta {
\t/** Searchable keywords: icon name parts + Lucide tags */
\tkeywords: string[];
\t/** Lucide categories this icon belongs to */
\tcategories: string[];
}
export const lucideIcons: Record<string, LucideIconMeta> = {\n`;
for (const entry of entries) {
const kw = JSON.stringify(entry.keywords);
const cats = JSON.stringify(entry.categories);
output += `\t'${entry.name}': { keywords: ${kw}, categories: ${cats} },\n`;
}
output += `};\n\n`;
output += `/** All unique Lucide icon categories, sorted alphabetically */\nexport const lucideCategories: string[] = ${JSON.stringify(sortedCategories, null, '\t')};\n`;
writeFileSync(OUTPUT_PATH, output);
const sizeKB = Math.round(Buffer.byteLength(output) / 1024);
console.log(`\nDone! Written to: ${OUTPUT_PATH}`);
console.log(`File size: ${sizeKB} KB`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});