@olwiba/dx 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdirSync, writeFileSync, readFileSync } from 'fs';
3
- import path, { join } from 'path';
3
+ import path, { join, resolve } from 'path';
4
4
  import { createRequire } from 'module';
5
5
  import { createInterface } from 'readline/promises';
6
6
  import { stdout, stdin } from 'process';
@@ -1680,13 +1680,16 @@ var generate_assets_exports = {};
1680
1680
  __export(generate_assets_exports, {
1681
1681
  generateAssets: () => generateAssets
1682
1682
  });
1683
+ function detectIconMode(icon) {
1684
+ return icon.endsWith(".svg") || icon.includes("/") || icon.includes("\\") ? "svg" : "lucide";
1685
+ }
1683
1686
  function toKebabCase(name) {
1684
1687
  return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
1685
1688
  }
1686
1689
  function escapeXml(str) {
1687
1690
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1688
1691
  }
1689
- function getLucideIconInner(iconName) {
1692
+ function getLucideInner(iconName) {
1690
1693
  const kebab = toKebabCase(iconName);
1691
1694
  const req = createRequire(import.meta.url);
1692
1695
  let iconPath;
@@ -1700,7 +1703,15 @@ function getLucideIconInner(iconName) {
1700
1703
  const svg = readFileSync(iconPath, "utf-8");
1701
1704
  return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
1702
1705
  }
1703
- function buildIconSvg(inner, color, size) {
1706
+ function getSvgInner(svgPath) {
1707
+ const abs = resolve(process.cwd(), svgPath);
1708
+ const svg = readFileSync(abs, "utf-8");
1709
+ const vbMatch = svg.match(/viewBox="0 0 (\d+\.?\d*) \d/);
1710
+ const viewBox = vbMatch ? parseFloat(vbMatch[1]) : 24;
1711
+ const inner = svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").trim();
1712
+ return { inner, viewBox };
1713
+ }
1714
+ function buildIconSvgLucide(inner, color, size) {
1704
1715
  const pad = Math.round(size * 0.18);
1705
1716
  const area = size - pad * 2;
1706
1717
  const scale = area / 24;
@@ -1713,7 +1724,19 @@ function buildIconSvg(inner, color, size) {
1713
1724
  </g>
1714
1725
  </svg>`;
1715
1726
  }
1716
- function buildOgSvg(inner, name, color) {
1727
+ function buildIconSvgCustom(inner, viewBox, color, size) {
1728
+ const pad = Math.round(size * 0.08);
1729
+ const area = size - pad * 2;
1730
+ const scale = area / viewBox;
1731
+ const rx = Math.round(size * 0.16);
1732
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
1733
+ <rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
1734
+ <g transform="translate(${pad} ${pad}) scale(${scale})">
1735
+ ${inner}
1736
+ </g>
1737
+ </svg>`;
1738
+ }
1739
+ function buildOgSvgLucide(inner, name, color) {
1717
1740
  const iconPx = 180;
1718
1741
  const iconX = Math.round((1200 - iconPx) / 2);
1719
1742
  const iconY = 160;
@@ -1734,6 +1757,18 @@ function buildOgSvg(inner, name, color) {
1734
1757
  <text x="600" y="470" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif" font-size="72" font-weight="700" fill="white" text-anchor="middle" letter-spacing="-1">${escapeXml(name)}</text>
1735
1758
  </svg>`;
1736
1759
  }
1760
+ function buildOgSvgCustom(inner, viewBox, color) {
1761
+ const s = 470;
1762
+ const x = Math.round((1200 - s) / 2);
1763
+ const y = Math.round((630 - s) / 2);
1764
+ const scale = s / viewBox;
1765
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
1766
+ <rect width="1200" height="630" fill="${color}"/>
1767
+ <g transform="translate(${x} ${y}) scale(${scale})">
1768
+ ${inner}
1769
+ </g>
1770
+ </svg>`;
1771
+ }
1737
1772
  async function generateAssets(config) {
1738
1773
  const { name, icon, color, outputDir } = config;
1739
1774
  let sharpFn;
@@ -1747,23 +1782,31 @@ async function generateAssets(config) {
1747
1782
  mkdirSync(outputDir, { recursive: true });
1748
1783
  const faviconDir = join(outputDir, "favicon");
1749
1784
  mkdirSync(faviconDir, { recursive: true });
1750
- const inner = getLucideIconInner(icon);
1785
+ const mode = detectIconMode(icon);
1751
1786
  const written = [];
1787
+ let inner;
1788
+ let viewBox = 24;
1789
+ if (mode === "svg") {
1790
+ const parsed = getSvgInner(icon);
1791
+ inner = parsed.inner;
1792
+ viewBox = parsed.viewBox;
1793
+ } else {
1794
+ inner = getLucideInner(icon);
1795
+ }
1796
+ const buildIcon = (size) => mode === "svg" ? buildIconSvgCustom(inner, viewBox, color, size) : buildIconSvgLucide(inner, color, size);
1752
1797
  for (const size of FAVICON_SIZES) {
1753
- const svg = buildIconSvg(inner, color, size);
1754
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1798
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
1755
1799
  const dest = join(faviconDir, `favicon-${size}.png`);
1756
1800
  writeFileSync(dest, png);
1757
1801
  written.push(dest);
1758
1802
  }
1759
1803
  for (const [filename, size] of NAMED_ICONS) {
1760
- const svg = buildIconSvg(inner, color, size);
1761
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1804
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
1762
1805
  const dest = join(outputDir, filename);
1763
1806
  writeFileSync(dest, png);
1764
1807
  written.push(dest);
1765
1808
  }
1766
- const ogSvg = buildOgSvg(inner, name, color);
1809
+ const ogSvg = mode === "svg" ? buildOgSvgCustom(inner, viewBox, color) : buildOgSvgLucide(inner, name, color);
1767
1810
  const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
1768
1811
  const ogDest = join(outputDir, "og-image.png");
1769
1812
  writeFileSync(ogDest, ogPng);
@@ -1772,16 +1815,11 @@ async function generateAssets(config) {
1772
1815
  name,
1773
1816
  short_name: name,
1774
1817
  theme_color: color,
1775
- background_color: "#09090b",
1818
+ background_color: mode === "svg" ? color : "#09090b",
1776
1819
  display: "standalone",
1777
1820
  icons: [
1778
1821
  { src: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
1779
- {
1780
- src: "/android-chrome-512x512.png",
1781
- sizes: "512x512",
1782
- type: "image/png",
1783
- purpose: "maskable"
1784
- }
1822
+ { src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
1785
1823
  ]
1786
1824
  };
1787
1825
  const manifestDest = join(outputDir, "manifest.json");
@@ -1795,7 +1833,7 @@ async function generateAssets(config) {
1795
1833
  var FAVICON_SIZES, NAMED_ICONS;
1796
1834
  var init_generate_assets = __esm({
1797
1835
  "src/generate-assets.ts"() {
1798
- FAVICON_SIZES = [16, 32, 48, 64];
1836
+ FAVICON_SIZES = [16, 32, 48, 64, 192, 512];
1799
1837
  NAMED_ICONS = [
1800
1838
  ["apple-touch-icon.png", 180],
1801
1839
  ["android-chrome-192x192.png", 192],
@@ -1,5 +1,6 @@
1
1
  interface GenerateAssetsConfig {
2
2
  name: string;
3
+ /** Lucide icon name (e.g. "Zap") OR path to an SVG file (e.g. "./logo.svg") */
3
4
  icon: string;
4
5
  color: string;
5
6
  outputDir: string;
@@ -1,15 +1,18 @@
1
1
  import { mkdirSync, writeFileSync, readFileSync } from 'fs';
2
2
  import { createRequire } from 'module';
3
- import { join } from 'path';
3
+ import { join, resolve } from 'path';
4
4
 
5
5
  // src/generate-assets.ts
6
+ function detectIconMode(icon) {
7
+ return icon.endsWith(".svg") || icon.includes("/") || icon.includes("\\") ? "svg" : "lucide";
8
+ }
6
9
  function toKebabCase(name) {
7
10
  return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
8
11
  }
9
12
  function escapeXml(str) {
10
13
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
11
14
  }
12
- function getLucideIconInner(iconName) {
15
+ function getLucideInner(iconName) {
13
16
  const kebab = toKebabCase(iconName);
14
17
  const req = createRequire(import.meta.url);
15
18
  let iconPath;
@@ -23,7 +26,15 @@ function getLucideIconInner(iconName) {
23
26
  const svg = readFileSync(iconPath, "utf-8");
24
27
  return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
25
28
  }
26
- function buildIconSvg(inner, color, size) {
29
+ function getSvgInner(svgPath) {
30
+ const abs = resolve(process.cwd(), svgPath);
31
+ const svg = readFileSync(abs, "utf-8");
32
+ const vbMatch = svg.match(/viewBox="0 0 (\d+\.?\d*) \d/);
33
+ const viewBox = vbMatch ? parseFloat(vbMatch[1]) : 24;
34
+ const inner = svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").trim();
35
+ return { inner, viewBox };
36
+ }
37
+ function buildIconSvgLucide(inner, color, size) {
27
38
  const pad = Math.round(size * 0.18);
28
39
  const area = size - pad * 2;
29
40
  const scale = area / 24;
@@ -36,7 +47,19 @@ function buildIconSvg(inner, color, size) {
36
47
  </g>
37
48
  </svg>`;
38
49
  }
39
- function buildOgSvg(inner, name, color) {
50
+ function buildIconSvgCustom(inner, viewBox, color, size) {
51
+ const pad = Math.round(size * 0.08);
52
+ const area = size - pad * 2;
53
+ const scale = area / viewBox;
54
+ const rx = Math.round(size * 0.16);
55
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
56
+ <rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
57
+ <g transform="translate(${pad} ${pad}) scale(${scale})">
58
+ ${inner}
59
+ </g>
60
+ </svg>`;
61
+ }
62
+ function buildOgSvgLucide(inner, name, color) {
40
63
  const iconPx = 180;
41
64
  const iconX = Math.round((1200 - iconPx) / 2);
42
65
  const iconY = 160;
@@ -57,7 +80,19 @@ function buildOgSvg(inner, name, color) {
57
80
  <text x="600" y="470" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif" font-size="72" font-weight="700" fill="white" text-anchor="middle" letter-spacing="-1">${escapeXml(name)}</text>
58
81
  </svg>`;
59
82
  }
60
- var FAVICON_SIZES = [16, 32, 48, 64];
83
+ function buildOgSvgCustom(inner, viewBox, color) {
84
+ const s = 470;
85
+ const x = Math.round((1200 - s) / 2);
86
+ const y = Math.round((630 - s) / 2);
87
+ const scale = s / viewBox;
88
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
89
+ <rect width="1200" height="630" fill="${color}"/>
90
+ <g transform="translate(${x} ${y}) scale(${scale})">
91
+ ${inner}
92
+ </g>
93
+ </svg>`;
94
+ }
95
+ var FAVICON_SIZES = [16, 32, 48, 64, 192, 512];
61
96
  var NAMED_ICONS = [
62
97
  ["apple-touch-icon.png", 180],
63
98
  ["android-chrome-192x192.png", 192],
@@ -76,23 +111,31 @@ async function generateAssets(config) {
76
111
  mkdirSync(outputDir, { recursive: true });
77
112
  const faviconDir = join(outputDir, "favicon");
78
113
  mkdirSync(faviconDir, { recursive: true });
79
- const inner = getLucideIconInner(icon);
114
+ const mode = detectIconMode(icon);
80
115
  const written = [];
116
+ let inner;
117
+ let viewBox = 24;
118
+ if (mode === "svg") {
119
+ const parsed = getSvgInner(icon);
120
+ inner = parsed.inner;
121
+ viewBox = parsed.viewBox;
122
+ } else {
123
+ inner = getLucideInner(icon);
124
+ }
125
+ const buildIcon = (size) => mode === "svg" ? buildIconSvgCustom(inner, viewBox, color, size) : buildIconSvgLucide(inner, color, size);
81
126
  for (const size of FAVICON_SIZES) {
82
- const svg = buildIconSvg(inner, color, size);
83
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
127
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
84
128
  const dest = join(faviconDir, `favicon-${size}.png`);
85
129
  writeFileSync(dest, png);
86
130
  written.push(dest);
87
131
  }
88
132
  for (const [filename, size] of NAMED_ICONS) {
89
- const svg = buildIconSvg(inner, color, size);
90
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
133
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
91
134
  const dest = join(outputDir, filename);
92
135
  writeFileSync(dest, png);
93
136
  written.push(dest);
94
137
  }
95
- const ogSvg = buildOgSvg(inner, name, color);
138
+ const ogSvg = mode === "svg" ? buildOgSvgCustom(inner, viewBox, color) : buildOgSvgLucide(inner, name, color);
96
139
  const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
97
140
  const ogDest = join(outputDir, "og-image.png");
98
141
  writeFileSync(ogDest, ogPng);
@@ -101,16 +144,11 @@ async function generateAssets(config) {
101
144
  name,
102
145
  short_name: name,
103
146
  theme_color: color,
104
- background_color: "#09090b",
147
+ background_color: mode === "svg" ? color : "#09090b",
105
148
  display: "standalone",
106
149
  icons: [
107
150
  { src: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
108
- {
109
- src: "/android-chrome-512x512.png",
110
- sizes: "512x512",
111
- type: "image/png",
112
- purpose: "maskable"
113
- }
151
+ { src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
114
152
  ]
115
153
  };
116
154
  const manifestDest = join(outputDir, "manifest.json");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/generate-assets.ts"],"names":[],"mappings":"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","file":"generate-assets.js","sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { createRequire } from \"node:module\"\nimport { join } from \"node:path\"\n\nexport interface GenerateAssetsConfig {\n name: string\n icon: string\n color: string\n outputDir: string\n}\n\nexport interface GenerateAssetsResult {\n files: string[]\n}\n\nfunction toKebabCase(name: string): string {\n return name\n .replace(/([A-Z])/g, (_, ch: string) => `-${ch.toLowerCase()}`)\n .replace(/^-/, \"\")\n .toLowerCase()\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\")\n}\n\nfunction getLucideIconInner(iconName: string): string {\n const kebab = toKebabCase(iconName)\n const req = createRequire(import.meta.url)\n let iconPath: string\n try {\n iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`)\n } catch {\n throw new Error(\n `Unknown Lucide icon: \"${iconName}\" (tried \"${kebab}.svg\"). ` +\n `Browse icons at https://lucide.dev/icons`,\n )\n }\n const svg = readFileSync(iconPath, \"utf-8\")\n // Strip outer <svg> wrapper; clear inherited stroke/fill so parent <g> controls them\n return svg\n .replace(/<svg[^>]*>/, \"\")\n .replace(/<\\/svg>/, \"\")\n .replace(/\\sstroke=\"currentColor\"/g, \"\")\n .replace(/\\sfill=\"none\"/g, \"\")\n .trim()\n}\n\nfunction buildIconSvg(inner: string, color: string, size: number): string {\n const pad = Math.round(size * 0.18)\n const area = size - pad * 2\n const scale = area / 24\n const rx = Math.round(size * 0.16)\n // Inverse-scale stroke so it renders at ~2px regardless of size\n const sw = (2 / scale).toFixed(4)\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\">\n <rect width=\"${size}\" height=\"${size}\" rx=\"${rx}\" fill=\"${color}\"/>\n <g transform=\"translate(${pad} ${pad}) scale(${scale})\" stroke=\"white\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>\n</svg>`\n}\n\nfunction buildOgSvg(inner: string, name: string, color: string): string {\n const iconPx = 180\n const iconX = Math.round((1200 - iconPx) / 2) // 510\n const iconY = 160\n const scale = iconPx / 24\n // ~2.5px strokes at final 180px icon size\n const sw = (2.5 / scale).toFixed(4)\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\">\n <defs>\n <radialGradient id=\"rg\" cx=\"50%\" cy=\"40%\" r=\"65%\">\n <stop offset=\"0%\" stop-color=\"${color}\" stop-opacity=\"0.22\"/>\n <stop offset=\"100%\" stop-color=\"#000000\" stop-opacity=\"0\"/>\n </radialGradient>\n </defs>\n <rect width=\"1200\" height=\"630\" fill=\"#09090b\"/>\n <rect width=\"1200\" height=\"630\" fill=\"url(#rg)\"/>\n <g transform=\"translate(${iconX} ${iconY}) scale(${scale})\" stroke=\"${color}\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>\n <text x=\"600\" y=\"470\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif\" font-size=\"72\" font-weight=\"700\" fill=\"white\" text-anchor=\"middle\" letter-spacing=\"-1\">${escapeXml(name)}</text>\n</svg>`\n}\n\nconst FAVICON_SIZES = [16, 32, 48, 64] as const\n\nconst NAMED_ICONS: Array<[string, number]> = [\n [\"apple-touch-icon.png\", 180],\n [\"android-chrome-192x192.png\", 192],\n [\"android-chrome-512x512.png\", 512],\n]\n\nexport async function generateAssets(\n config: GenerateAssetsConfig,\n): Promise<GenerateAssetsResult> {\n const { name, icon, color, outputDir } = config\n\n let sharpFn: typeof import(\"sharp\")\n try {\n sharpFn = (await import(\"sharp\")).default\n } catch {\n throw new Error(\n \"sharp is required for asset generation.\\n\" +\n \"Install it in your project: bun add sharp lucide-static\",\n )\n }\n\n mkdirSync(outputDir, { recursive: true })\n\n const faviconDir = join(outputDir, \"favicon\")\n mkdirSync(faviconDir, { recursive: true })\n\n const inner = getLucideIconInner(icon)\n const written: string[] = []\n\n // favicon/favicon-{size}.png\n for (const size of FAVICON_SIZES) {\n const svg = buildIconSvg(inner, color, size)\n const png = await sharpFn(Buffer.from(svg)).png().toBuffer()\n const dest = join(faviconDir, `favicon-${size}.png`)\n writeFileSync(dest, png)\n written.push(dest)\n }\n\n // apple-touch-icon, android-chrome-*\n for (const [filename, size] of NAMED_ICONS) {\n const svg = buildIconSvg(inner, color, size)\n const png = await sharpFn(Buffer.from(svg)).png().toBuffer()\n const dest = join(outputDir, filename)\n writeFileSync(dest, png)\n written.push(dest)\n }\n\n // og-image.png\n const ogSvg = buildOgSvg(inner, name, color)\n const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer()\n const ogDest = join(outputDir, \"og-image.png\")\n writeFileSync(ogDest, ogPng)\n written.push(ogDest)\n\n // manifest.json\n const manifest = {\n name,\n short_name: name,\n theme_color: color,\n background_color: \"#09090b\",\n display: \"standalone\",\n icons: [\n { src: \"/android-chrome-192x192.png\", sizes: \"192x192\", type: \"image/png\" },\n {\n src: \"/android-chrome-512x512.png\",\n sizes: \"512x512\",\n type: \"image/png\",\n purpose: \"maskable\",\n },\n ],\n }\n const manifestDest = join(outputDir, \"manifest.json\")\n writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + \"\\n\")\n written.push(manifestDest)\n\n // robots.txt\n const robotsDest = join(outputDir, \"robots.txt\")\n writeFileSync(robotsDest, \"User-agent: *\\nAllow: /\\n\")\n written.push(robotsDest)\n\n return { files: written }\n}\n"]}
1
+ {"version":3,"sources":["../src/generate-assets.ts"],"names":[],"mappings":";;;;;AAkBA,SAAS,eAAe,IAAA,EAAwB;AAC9C,EAAA,OAAO,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,IAAI,CAAA,GAAI,KAAA,GAAQ,QAAA;AACtF;AAEA,SAAS,YAAY,IAAA,EAAsB;AACzC,EAAA,OAAO,KACJ,OAAA,CAAQ,UAAA,EAAY,CAAC,CAAA,EAAG,OAAe,CAAA,CAAA,EAAI,EAAA,CAAG,WAAA,EAAa,EAAE,CAAA,CAC7D,OAAA,CAAQ,IAAA,EAAM,EAAE,EAChB,WAAA,EAAY;AACjB;AAEA,SAAS,UAAU,GAAA,EAAqB;AACtC,EAAA,OAAO,IACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA,CACrB,OAAA,CAAQ,MAAM,MAAM,CAAA,CACpB,QAAQ,IAAA,EAAM,MAAM,EACpB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,OAAA,CAAQ,MAAM,QAAQ,CAAA;AAC3B;AAEA,SAAS,eAAe,QAAA,EAA0B;AAChD,EAAA,MAAM,KAAA,GAAQ,YAAY,QAAQ,CAAA;AAClC,EAAA,MAAM,GAAA,GAAM,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA;AACzC,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI;AACF,IAAA,QAAA,GAAW,GAAA,CAAI,OAAA,CAAQ,CAAA,oBAAA,EAAuB,KAAK,CAAA,IAAA,CAAM,CAAA;AAAA,EAC3D,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,sBAAA,EAAyB,QAAQ,CAAA,UAAA,EAAa,KAAK,CAAA,gDAAA;AAAA,KAErD;AAAA,EACF;AACA,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,QAAA,EAAU,OAAO,CAAA;AAC1C,EAAA,OAAO,IACJ,OAAA,CAAQ,YAAA,EAAc,EAAE,CAAA,CACxB,QAAQ,SAAA,EAAW,EAAE,CAAA,CACrB,OAAA,CAAQ,4BAA4B,EAAE,CAAA,CACtC,QAAQ,gBAAA,EAAkB,EAAE,EAC5B,IAAA,EAAK;AACV;AAEA,SAAS,YAAY,OAAA,EAAqD;AACxE,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,OAAO,CAAA;AAC1C,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,EAAK,OAAO,CAAA;AACrC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,KAAA,CAAM,6BAA6B,CAAA;AACvD,EAAA,MAAM,UAAU,OAAA,GAAU,UAAA,CAAW,OAAA,CAAQ,CAAC,CAAC,CAAA,GAAI,EAAA;AACnD,EAAA,MAAM,KAAA,GAAQ,GAAA,CACX,OAAA,CAAQ,YAAA,EAAc,EAAE,EACxB,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAA,CACrB,IAAA,EAAK;AACR,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;AAEA,SAAS,kBAAA,CAAmB,KAAA,EAAe,KAAA,EAAe,IAAA,EAAsB;AAC9E,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,IAAI,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,GAAM,CAAA;AAC1B,EAAA,MAAM,QAAQ,IAAA,GAAO,EAAA;AACrB,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,IAAI,CAAA;AACjC,EAAA,MAAM,EAAA,GAAA,CAAM,CAAA,GAAI,KAAA,EAAO,OAAA,CAAQ,CAAC,CAAA;AAChC,EAAA,OAAO,CAAA,+CAAA,EAAkD,IAAI,CAAA,UAAA,EAAa,IAAI,CAAA;AAAA,eAAA,EAC/D,IAAI,CAAA,UAAA,EAAa,IAAI,CAAA,MAAA,EAAS,EAAE,WAAW,KAAK,CAAA;AAAA,0BAAA,EACrC,GAAG,CAAA,CAAA,EAAI,GAAG,CAAA,QAAA,EAAW,KAAK,+CAA+C,EAAE,CAAA;AAAA,IAAA,EACjG,KAAK;AAAA;AAAA,MAAA,CAAA;AAGX;AAEA,SAAS,kBAAA,CAAmB,KAAA,EAAe,OAAA,EAAiB,KAAA,EAAe,IAAA,EAAsB;AAC/F,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,IAAI,CAAA;AAClC,EAAA,MAAM,IAAA,GAAO,OAAO,GAAA,GAAM,CAAA;AAC1B,EAAA,MAAM,QAAQ,IAAA,GAAO,OAAA;AACrB,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,IAAI,CAAA;AACjC,EAAA,OAAO,CAAA,+CAAA,EAAkD,IAAI,CAAA,UAAA,EAAa,IAAI,CAAA;AAAA,eAAA,EAC/D,IAAI,CAAA,UAAA,EAAa,IAAI,CAAA,MAAA,EAAS,EAAE,WAAW,KAAK,CAAA;AAAA,0BAAA,EACrC,GAAG,CAAA,CAAA,EAAI,GAAG,CAAA,QAAA,EAAW,KAAK,CAAA;AAAA,IAAA,EAChD,KAAK;AAAA;AAAA,MAAA,CAAA;AAGX;AAEA,SAAS,gBAAA,CAAiB,KAAA,EAAe,IAAA,EAAc,KAAA,EAAuB;AAC5E,EAAA,MAAM,MAAA,GAAS,GAAA;AACf,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAA,CAAO,IAAA,GAAO,UAAU,CAAC,CAAA;AAC5C,EAAA,MAAM,KAAA,GAAQ,GAAA;AACd,EAAA,MAAM,QAAQ,MAAA,GAAS,EAAA;AACvB,EAAA,MAAM,EAAA,GAAA,CAAM,GAAA,GAAM,KAAA,EAAO,OAAA,CAAQ,CAAC,CAAA;AAClC,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA,oCAAA,EAG6B,KAAK,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAAA,EAMf,KAAK,IAAI,KAAK,CAAA,QAAA,EAAW,KAAK,CAAA,WAAA,EAAc,KAAK,+BAA+B,EAAE,CAAA;AAAA,IAAA,EACxG,KAAK;AAAA;AAAA,yMAAA,EAEgM,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,MAAA,CAAA;AAE1N;AAEA,SAAS,gBAAA,CAAiB,KAAA,EAAe,OAAA,EAAiB,KAAA,EAAuB;AAC/E,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAA,CAAO,IAAA,GAAO,KAAK,CAAC,CAAA;AACnC,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,KAAK,CAAC,CAAA;AAClC,EAAA,MAAM,QAAQ,CAAA,GAAI,OAAA;AAClB,EAAA,OAAO,CAAA;AAAA,wCAAA,EACiC,KAAK,CAAA;AAAA,0BAAA,EACnB,CAAC,CAAA,CAAA,EAAI,CAAC,CAAA,QAAA,EAAW,KAAK,CAAA;AAAA,IAAA,EAC5C,KAAK;AAAA;AAAA,MAAA,CAAA;AAGX;AAEA,IAAM,gBAAgB,CAAC,EAAA,EAAI,IAAI,EAAA,EAAI,EAAA,EAAI,KAAK,GAAG,CAAA;AAE/C,IAAM,WAAA,GAAuC;AAAA,EAC3C,CAAC,wBAAwB,GAAG,CAAA;AAAA,EAC5B,CAAC,8BAA8B,GAAG,CAAA;AAAA,EAClC,CAAC,8BAA8B,GAAG;AACpC,CAAA;AAEA,eAAsB,eACpB,MAAA,EAC+B;AAC/B,EAAA,MAAM,EAAE,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,WAAU,GAAI,MAAA;AAEzC,EAAA,IAAI,OAAA;AACJ,EAAA,IAAI;AACF,IAAA,OAAA,GAAA,CAAW,MAAM,OAAO,OAAO,CAAA,EAAG,OAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAEA,EAAA,SAAA,CAAU,SAAA,EAAW,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACxC,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,EAAW,SAAS,CAAA;AAC5C,EAAA,SAAA,CAAU,UAAA,EAAY,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAEzC,EAAA,MAAM,IAAA,GAAO,eAAe,IAAI,CAAA;AAChC,EAAA,MAAM,UAAoB,EAAC;AAE3B,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,OAAA,GAAU,EAAA;AAEd,EAAA,IAAI,SAAS,KAAA,EAAO;AAClB,IAAA,MAAM,MAAA,GAAS,YAAY,IAAI,CAAA;AAC/B,IAAA,KAAA,GAAQ,MAAA,CAAO,KAAA;AACf,IAAA,OAAA,GAAU,MAAA,CAAO,OAAA;AAAA,EACnB,CAAA,MAAO;AACL,IAAA,KAAA,GAAQ,eAAe,IAAI,CAAA;AAAA,EAC7B;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,IAAA,KACjB,IAAA,KAAS,QACL,kBAAA,CAAmB,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,IAAI,CAAA,GAC9C,kBAAA,CAAmB,KAAA,EAAO,OAAO,IAAI,CAAA;AAE3C,EAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAC,CAAA,CAAE,GAAA,EAAI,CAAE,QAAA,EAAS;AACvE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,UAAA,EAAY,CAAA,QAAA,EAAW,IAAI,CAAA,IAAA,CAAM,CAAA;AACnD,IAAA,aAAA,CAAc,MAAM,GAAG,CAAA;AACvB,IAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,EACnB;AAEA,EAAA,KAAA,MAAW,CAAC,QAAA,EAAU,IAAI,CAAA,IAAK,WAAA,EAAa;AAC1C,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAC,CAAA,CAAE,GAAA,EAAI,CAAE,QAAA,EAAS;AACvE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA;AACrC,IAAA,aAAA,CAAc,MAAM,GAAG,CAAA;AACvB,IAAA,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,EACnB;AAEA,EAAA,MAAM,KAAA,GACJ,IAAA,KAAS,KAAA,GACL,gBAAA,CAAiB,KAAA,EAAO,OAAA,EAAS,KAAK,CAAA,GACtC,gBAAA,CAAiB,KAAA,EAAO,IAAA,EAAM,KAAK,CAAA;AAEzC,EAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,MAAA,CAAO,IAAA,CAAK,KAAK,CAAC,CAAA,CAAE,GAAA,EAAI,CAAE,QAAA,EAAS;AAC/D,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,EAAW,cAAc,CAAA;AAC7C,EAAA,aAAA,CAAc,QAAQ,KAAK,CAAA;AAC3B,EAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAEnB,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,IAAA;AAAA,IACA,UAAA,EAAY,IAAA;AAAA,IACZ,WAAA,EAAa,KAAA;AAAA,IACb,gBAAA,EAAkB,IAAA,KAAS,KAAA,GAAQ,KAAA,GAAQ,SAAA;AAAA,IAC3C,OAAA,EAAS,YAAA;AAAA,IACT,KAAA,EAAO;AAAA,MACL,EAAE,GAAA,EAAK,6BAAA,EAA+B,KAAA,EAAO,SAAA,EAAW,MAAM,WAAA,EAAY;AAAA,MAC1E,EAAE,KAAK,6BAAA,EAA+B,KAAA,EAAO,WAAW,IAAA,EAAM,WAAA,EAAa,SAAS,UAAA;AAAW;AACjG,GACF;AACA,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,SAAA,EAAW,eAAe,CAAA;AACpD,EAAA,aAAA,CAAc,cAAc,IAAA,CAAK,SAAA,CAAU,UAAU,IAAA,EAAM,CAAC,IAAI,IAAI,CAAA;AACpE,EAAA,OAAA,CAAQ,KAAK,YAAY,CAAA;AAEzB,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,EAAW,YAAY,CAAA;AAC/C,EAAA,aAAA,CAAc,YAAY,2BAA2B,CAAA;AACrD,EAAA,OAAA,CAAQ,KAAK,UAAU,CAAA;AAEvB,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B","file":"generate-assets.js","sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { createRequire } from \"node:module\"\nimport { join, resolve } from \"node:path\"\n\nexport interface GenerateAssetsConfig {\n name: string\n /** Lucide icon name (e.g. \"Zap\") OR path to an SVG file (e.g. \"./logo.svg\") */\n icon: string\n color: string\n outputDir: string\n}\n\nexport interface GenerateAssetsResult {\n files: string[]\n}\n\ntype IconMode = \"lucide\" | \"svg\"\n\nfunction detectIconMode(icon: string): IconMode {\n return icon.endsWith(\".svg\") || icon.includes(\"/\") || icon.includes(\"\\\\\") ? \"svg\" : \"lucide\"\n}\n\nfunction toKebabCase(name: string): string {\n return name\n .replace(/([A-Z])/g, (_, ch: string) => `-${ch.toLowerCase()}`)\n .replace(/^-/, \"\")\n .toLowerCase()\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\")\n}\n\nfunction getLucideInner(iconName: string): string {\n const kebab = toKebabCase(iconName)\n const req = createRequire(import.meta.url)\n let iconPath: string\n try {\n iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`)\n } catch {\n throw new Error(\n `Unknown Lucide icon: \"${iconName}\" (tried \"${kebab}.svg\"). ` +\n `Browse icons at https://lucide.dev/icons`,\n )\n }\n const svg = readFileSync(iconPath, \"utf-8\")\n return svg\n .replace(/<svg[^>]*>/, \"\")\n .replace(/<\\/svg>/, \"\")\n .replace(/\\sstroke=\"currentColor\"/g, \"\")\n .replace(/\\sfill=\"none\"/g, \"\")\n .trim()\n}\n\nfunction getSvgInner(svgPath: string): { inner: string; viewBox: number } {\n const abs = resolve(process.cwd(), svgPath)\n const svg = readFileSync(abs, \"utf-8\")\n const vbMatch = svg.match(/viewBox=\"0 0 (\\d+\\.?\\d*) \\d/)\n const viewBox = vbMatch ? parseFloat(vbMatch[1]) : 24\n const inner = svg\n .replace(/<svg[^>]*>/, \"\")\n .replace(/<\\/svg>/, \"\")\n .trim()\n return { inner, viewBox }\n}\n\nfunction buildIconSvgLucide(inner: string, color: string, size: number): string {\n const pad = Math.round(size * 0.18)\n const area = size - pad * 2\n const scale = area / 24\n const rx = Math.round(size * 0.16)\n const sw = (2 / scale).toFixed(4)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\">\n <rect width=\"${size}\" height=\"${size}\" rx=\"${rx}\" fill=\"${color}\"/>\n <g transform=\"translate(${pad} ${pad}) scale(${scale})\" stroke=\"white\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>\n</svg>`\n}\n\nfunction buildIconSvgCustom(inner: string, viewBox: number, color: string, size: number): string {\n const pad = Math.round(size * 0.08)\n const area = size - pad * 2\n const scale = area / viewBox\n const rx = Math.round(size * 0.16)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\">\n <rect width=\"${size}\" height=\"${size}\" rx=\"${rx}\" fill=\"${color}\"/>\n <g transform=\"translate(${pad} ${pad}) scale(${scale})\">\n ${inner}\n </g>\n</svg>`\n}\n\nfunction buildOgSvgLucide(inner: string, name: string, color: string): string {\n const iconPx = 180\n const iconX = Math.round((1200 - iconPx) / 2)\n const iconY = 160\n const scale = iconPx / 24\n const sw = (2.5 / scale).toFixed(4)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\">\n <defs>\n <radialGradient id=\"rg\" cx=\"50%\" cy=\"40%\" r=\"65%\">\n <stop offset=\"0%\" stop-color=\"${color}\" stop-opacity=\"0.22\"/>\n <stop offset=\"100%\" stop-color=\"#000000\" stop-opacity=\"0\"/>\n </radialGradient>\n </defs>\n <rect width=\"1200\" height=\"630\" fill=\"#09090b\"/>\n <rect width=\"1200\" height=\"630\" fill=\"url(#rg)\"/>\n <g transform=\"translate(${iconX} ${iconY}) scale(${scale})\" stroke=\"${color}\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>\n <text x=\"600\" y=\"470\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif\" font-size=\"72\" font-weight=\"700\" fill=\"white\" text-anchor=\"middle\" letter-spacing=\"-1\">${escapeXml(name)}</text>\n</svg>`\n}\n\nfunction buildOgSvgCustom(inner: string, viewBox: number, color: string): string {\n const s = 470\n const x = Math.round((1200 - s) / 2)\n const y = Math.round((630 - s) / 2)\n const scale = s / viewBox\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"630\">\n <rect width=\"1200\" height=\"630\" fill=\"${color}\"/>\n <g transform=\"translate(${x} ${y}) scale(${scale})\">\n ${inner}\n </g>\n</svg>`\n}\n\nconst FAVICON_SIZES = [16, 32, 48, 64, 192, 512] as const\n\nconst NAMED_ICONS: Array<[string, number]> = [\n [\"apple-touch-icon.png\", 180],\n [\"android-chrome-192x192.png\", 192],\n [\"android-chrome-512x512.png\", 512],\n]\n\nexport async function generateAssets(\n config: GenerateAssetsConfig,\n): Promise<GenerateAssetsResult> {\n const { name, icon, color, outputDir } = config\n\n let sharpFn: typeof import(\"sharp\")\n try {\n sharpFn = (await import(\"sharp\")).default\n } catch {\n throw new Error(\n \"sharp is required for asset generation.\\n\" +\n \"Install it in your project: bun add sharp lucide-static\",\n )\n }\n\n mkdirSync(outputDir, { recursive: true })\n const faviconDir = join(outputDir, \"favicon\")\n mkdirSync(faviconDir, { recursive: true })\n\n const mode = detectIconMode(icon)\n const written: string[] = []\n\n let inner: string\n let viewBox = 24\n\n if (mode === \"svg\") {\n const parsed = getSvgInner(icon)\n inner = parsed.inner\n viewBox = parsed.viewBox\n } else {\n inner = getLucideInner(icon)\n }\n\n const buildIcon = (size: number) =>\n mode === \"svg\"\n ? buildIconSvgCustom(inner, viewBox, color, size)\n : buildIconSvgLucide(inner, color, size)\n\n for (const size of FAVICON_SIZES) {\n const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer()\n const dest = join(faviconDir, `favicon-${size}.png`)\n writeFileSync(dest, png)\n written.push(dest)\n }\n\n for (const [filename, size] of NAMED_ICONS) {\n const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer()\n const dest = join(outputDir, filename)\n writeFileSync(dest, png)\n written.push(dest)\n }\n\n const ogSvg =\n mode === \"svg\"\n ? buildOgSvgCustom(inner, viewBox, color)\n : buildOgSvgLucide(inner, name, color)\n\n const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer()\n const ogDest = join(outputDir, \"og-image.png\")\n writeFileSync(ogDest, ogPng)\n written.push(ogDest)\n\n const manifest = {\n name,\n short_name: name,\n theme_color: color,\n background_color: mode === \"svg\" ? color : \"#09090b\",\n display: \"standalone\",\n icons: [\n { src: \"/android-chrome-192x192.png\", sizes: \"192x192\", type: \"image/png\" },\n { src: \"/android-chrome-512x512.png\", sizes: \"512x512\", type: \"image/png\", purpose: \"maskable\" },\n ],\n }\n const manifestDest = join(outputDir, \"manifest.json\")\n writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + \"\\n\")\n written.push(manifestDest)\n\n const robotsDest = join(outputDir, \"robots.txt\")\n writeFileSync(robotsDest, \"User-agent: *\\nAllow: /\\n\")\n written.push(robotsDest)\n\n return { files: written }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olwiba/dx",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -52,6 +52,7 @@
52
52
  "devDependencies": {
53
53
  "@types/node": "^22.5.4",
54
54
  "eslint": "^9.0.0",
55
+ "lucide-static": "^0.488.0",
55
56
  "puppeteer-core": "^24.0.0",
56
57
  "sharp": "^0.33.0",
57
58
  "tsup": "^8.4.0",