/**
* build-components.ts
*
* Generates the @thesvg/svelte distribution from the monorepo source data.
* For each icon, reads the default SVG and emits a .svelte component file
* with the SVG content and $$restProps for attribute forwarding.
*
* Run with:
* bun run scripts/build-components.ts
* tsx scripts/build-components.ts
*
* Output layout:
* dist/
* {slug}.svelte Svelte component per icon
* {slug}.d.ts Type declarations per icon
* index.js ESM barrel (named re-exports)
* index.d.ts Type barrel
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
// ---------------------------------------------------------------------------
// Paths
// ---------------------------------------------------------------------------
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/** Root of the packages/svelte package */
const PKG_ROOT = resolve(__dirname, "..");
/** Root of the thesvg monorepo */
const REPO_ROOT = resolve(PKG_ROOT, "../..");
const ICONS_JSON = join(REPO_ROOT, "src/data/icons.json");
const ICONS_PUBLIC = join(REPO_ROOT, "public/icons");
const DIST = join(PKG_ROOT, "dist");
// ---------------------------------------------------------------------------
// Types mirrored from icons.json shape
// ---------------------------------------------------------------------------
interface RawIconVariants {
default?: string;
mono?: string;
light?: string;
dark?: string;
wordmark?: string;
wordmarkLight?: string;
wordmarkDark?: string;
color?: string;
[key: string]: string | undefined;
}
interface RawIcon {
slug: string;
title: string;
aliases: string[];
hex: string;
categories: string[];
variants: RawIconVariants;
license: string;
url: string;
guidelines?: string;
}
// ---------------------------------------------------------------------------
// SVG reading & parsing
// ---------------------------------------------------------------------------
/** Read an SVG file from the public directory. Returns empty string on miss. */
function readSvg(slug: string, variant: string): string {
const filePath = join(ICONS_PUBLIC, slug, `${variant}.svg`);
if (!existsSync(filePath)) return "";
return readFileSync(filePath, "utf8").trim();
}
/**
* Resolve the "primary" SVG for an icon.
* Preference order: default -> color -> mono -> light -> dark -> wordmark -> first available.
*/
function primarySvg(slug: string, variants: RawIconVariants): string {
const order = ["default", "color", "mono", "light", "dark", "wordmark"];
for (const v of order) {
if (v in variants) {
const content = readSvg(slug, v);
if (content) return content;
}
}
for (const v of Object.keys(variants)) {
const content = readSvg(slug, v);
if (content) return content;
}
return "";
}
// ---------------------------------------------------------------------------
// SVG parsing helpers
// ---------------------------------------------------------------------------
/**
* Extract the viewBox attribute from an SVG string.
* Returns "0 0 24 24" as a safe fallback.
*/
function extractViewBox(svgContent: string): string {
const match = svgContent.match(/viewBox=["']([^"']+)["']/);
return match ? match[1] : "0 0 24 24";
}
/**
* Extract the inner content of an SVG (everything between ).
*/
function extractSvgInner(svgContent: string): string {
return svgContent
.replace(/^