@olwiba/dx 0.0.16 → 0.0.17

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,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { mkdirSync, writeFileSync, readFileSync } from 'fs';
3
3
  import path, { join } from 'path';
4
+ import { createRequire } from 'module';
4
5
  import { createInterface } from 'readline/promises';
5
6
  import { stdout, stdin } from 'process';
6
7
 
@@ -1673,15 +1674,146 @@ var init_ascii_gif = __esm({
1673
1674
  init_ascii();
1674
1675
  }
1675
1676
  });
1677
+
1678
+ // src/generate-assets.ts
1679
+ var generate_assets_exports = {};
1680
+ __export(generate_assets_exports, {
1681
+ generateAssets: () => generateAssets
1682
+ });
1683
+ function toKebabCase(name) {
1684
+ return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
1685
+ }
1686
+ function escapeXml(str) {
1687
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1688
+ }
1689
+ function getLucideIconInner(iconName) {
1690
+ const kebab = toKebabCase(iconName);
1691
+ const req = createRequire(import.meta.url);
1692
+ let iconPath;
1693
+ try {
1694
+ iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`);
1695
+ } catch {
1696
+ throw new Error(
1697
+ `Unknown Lucide icon: "${iconName}" (tried "${kebab}.svg"). Browse icons at https://lucide.dev/icons`
1698
+ );
1699
+ }
1700
+ const svg = readFileSync(iconPath, "utf-8");
1701
+ return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
1702
+ }
1703
+ function buildIconSvg(inner, color, size) {
1704
+ const pad = Math.round(size * 0.18);
1705
+ const area = size - pad * 2;
1706
+ const scale = area / 24;
1707
+ const rx = Math.round(size * 0.16);
1708
+ const sw = (2 / scale).toFixed(4);
1709
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
1710
+ <rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
1711
+ <g transform="translate(${pad} ${pad}) scale(${scale})" stroke="white" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
1712
+ ${inner}
1713
+ </g>
1714
+ </svg>`;
1715
+ }
1716
+ function buildOgSvg(inner, name, color) {
1717
+ const iconPx = 180;
1718
+ const iconX = Math.round((1200 - iconPx) / 2);
1719
+ const iconY = 160;
1720
+ const scale = iconPx / 24;
1721
+ const sw = (2.5 / scale).toFixed(4);
1722
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
1723
+ <defs>
1724
+ <radialGradient id="rg" cx="50%" cy="40%" r="65%">
1725
+ <stop offset="0%" stop-color="${color}" stop-opacity="0.22"/>
1726
+ <stop offset="100%" stop-color="#000000" stop-opacity="0"/>
1727
+ </radialGradient>
1728
+ </defs>
1729
+ <rect width="1200" height="630" fill="#09090b"/>
1730
+ <rect width="1200" height="630" fill="url(#rg)"/>
1731
+ <g transform="translate(${iconX} ${iconY}) scale(${scale})" stroke="${color}" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
1732
+ ${inner}
1733
+ </g>
1734
+ <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
+ </svg>`;
1736
+ }
1737
+ async function generateAssets(config) {
1738
+ const { name, icon, color, outputDir } = config;
1739
+ let sharpFn;
1740
+ try {
1741
+ sharpFn = (await import('sharp')).default;
1742
+ } catch {
1743
+ throw new Error(
1744
+ "sharp is required for asset generation.\nInstall it in your project: bun add sharp lucide-static"
1745
+ );
1746
+ }
1747
+ mkdirSync(outputDir, { recursive: true });
1748
+ const faviconDir = join(outputDir, "favicon");
1749
+ mkdirSync(faviconDir, { recursive: true });
1750
+ const inner = getLucideIconInner(icon);
1751
+ const written = [];
1752
+ for (const size of FAVICON_SIZES) {
1753
+ const svg = buildIconSvg(inner, color, size);
1754
+ const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1755
+ const dest = join(faviconDir, `favicon-${size}.png`);
1756
+ writeFileSync(dest, png);
1757
+ written.push(dest);
1758
+ }
1759
+ for (const [filename, size] of NAMED_ICONS) {
1760
+ const svg = buildIconSvg(inner, color, size);
1761
+ const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1762
+ const dest = join(outputDir, filename);
1763
+ writeFileSync(dest, png);
1764
+ written.push(dest);
1765
+ }
1766
+ const ogSvg = buildOgSvg(inner, name, color);
1767
+ const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
1768
+ const ogDest = join(outputDir, "og-image.png");
1769
+ writeFileSync(ogDest, ogPng);
1770
+ written.push(ogDest);
1771
+ const manifest = {
1772
+ name,
1773
+ short_name: name,
1774
+ theme_color: color,
1775
+ background_color: "#09090b",
1776
+ display: "standalone",
1777
+ icons: [
1778
+ { 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
+ }
1785
+ ]
1786
+ };
1787
+ const manifestDest = join(outputDir, "manifest.json");
1788
+ writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + "\n");
1789
+ written.push(manifestDest);
1790
+ const robotsDest = join(outputDir, "robots.txt");
1791
+ writeFileSync(robotsDest, "User-agent: *\nAllow: /\n");
1792
+ written.push(robotsDest);
1793
+ return { files: written };
1794
+ }
1795
+ var FAVICON_SIZES, NAMED_ICONS;
1796
+ var init_generate_assets = __esm({
1797
+ "src/generate-assets.ts"() {
1798
+ FAVICON_SIZES = [16, 32, 48, 64];
1799
+ NAMED_ICONS = [
1800
+ ["apple-touch-icon.png", 180],
1801
+ ["android-chrome-192x192.png", 192],
1802
+ ["android-chrome-512x512.png", 512]
1803
+ ];
1804
+ }
1805
+ });
1676
1806
  var DEFAULT_SOURCE = "https://olwiba.com/skills/manifest.json";
1677
1807
  var [command, subcommand] = process.argv.slice(2);
1678
1808
  if (command === "skills" && subcommand === "install") {
1679
1809
  await runSkillsInstall();
1680
1810
  } else if (command === "ascii-gif") {
1681
1811
  await runAsciiGif();
1812
+ } else if (command === "generate-assets") {
1813
+ await runGenerateAssets();
1682
1814
  } else {
1683
1815
  process.stdout.write(
1684
- "Usage:\n dx skills install [--source <url>] [--target claude|amp] [--all] [--name a,b,c]\n dx ascii-gif --text <text> --out <file.gif>\n"
1816
+ "Usage:\n dx skills install [--source <url>] [--target claude|amp] [--all] [--name a,b,c]\n dx ascii-gif --text <text> --out <file.gif>\n dx generate-assets --name <app> --icon <lucide-icon> --color <#hex> [--out <dir>]\n"
1685
1817
  );
1686
1818
  }
1687
1819
  async function runSkillsInstall() {
@@ -1838,6 +1970,35 @@ function parseFlags(args) {
1838
1970
  }
1839
1971
  return flags;
1840
1972
  }
1973
+ async function runGenerateAssets() {
1974
+ const { generateAssets: generateAssets2 } = await Promise.resolve().then(() => (init_generate_assets(), generate_assets_exports));
1975
+ const flags = parseFlags(process.argv.slice(3));
1976
+ const name = flags.name;
1977
+ const icon = flags.icon;
1978
+ const color = flags.color;
1979
+ const outputDir = flags.out ?? flags.output ?? "public";
1980
+ if (!name || !icon || !color) {
1981
+ process.stderr.write(
1982
+ "Usage: dx generate-assets --name <app> --icon <lucide-icon> --color <#hex> [--out <dir>]\n"
1983
+ );
1984
+ process.exitCode = 1;
1985
+ return;
1986
+ }
1987
+ process.stdout.write(`Generating assets for "${name}"\u2026
1988
+ `);
1989
+ try {
1990
+ const result = await generateAssets2({ name, icon, color, outputDir });
1991
+ process.stdout.write(`Generated ${result.files.length} files in ${outputDir}/
1992
+ `);
1993
+ for (const f of result.files) process.stdout.write(` ${f}
1994
+ `);
1995
+ } catch (err) {
1996
+ const message = err instanceof Error ? err.message : String(err);
1997
+ process.stderr.write(`Error: ${message}
1998
+ `);
1999
+ process.exitCode = 1;
2000
+ }
2001
+ }
1841
2002
  function parseNumberFlag(value) {
1842
2003
  if (value == null) return void 0;
1843
2004
  const parsed = Number(value);
@@ -0,0 +1,12 @@
1
+ interface GenerateAssetsConfig {
2
+ name: string;
3
+ icon: string;
4
+ color: string;
5
+ outputDir: string;
6
+ }
7
+ interface GenerateAssetsResult {
8
+ files: string[];
9
+ }
10
+ declare function generateAssets(config: GenerateAssetsConfig): Promise<GenerateAssetsResult>;
11
+
12
+ export { type GenerateAssetsConfig, type GenerateAssetsResult, generateAssets };
@@ -0,0 +1,127 @@
1
+ import { mkdirSync, writeFileSync, readFileSync } from 'fs';
2
+ import { createRequire } from 'module';
3
+ import { join } from 'path';
4
+
5
+ // src/generate-assets.ts
6
+ function toKebabCase(name) {
7
+ return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
8
+ }
9
+ function escapeXml(str) {
10
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
11
+ }
12
+ function getLucideIconInner(iconName) {
13
+ const kebab = toKebabCase(iconName);
14
+ const req = createRequire(import.meta.url);
15
+ let iconPath;
16
+ try {
17
+ iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`);
18
+ } catch {
19
+ throw new Error(
20
+ `Unknown Lucide icon: "${iconName}" (tried "${kebab}.svg"). Browse icons at https://lucide.dev/icons`
21
+ );
22
+ }
23
+ const svg = readFileSync(iconPath, "utf-8");
24
+ return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
25
+ }
26
+ function buildIconSvg(inner, color, size) {
27
+ const pad = Math.round(size * 0.18);
28
+ const area = size - pad * 2;
29
+ const scale = area / 24;
30
+ const rx = Math.round(size * 0.16);
31
+ const sw = (2 / scale).toFixed(4);
32
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
33
+ <rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
34
+ <g transform="translate(${pad} ${pad}) scale(${scale})" stroke="white" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
35
+ ${inner}
36
+ </g>
37
+ </svg>`;
38
+ }
39
+ function buildOgSvg(inner, name, color) {
40
+ const iconPx = 180;
41
+ const iconX = Math.round((1200 - iconPx) / 2);
42
+ const iconY = 160;
43
+ const scale = iconPx / 24;
44
+ const sw = (2.5 / scale).toFixed(4);
45
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
46
+ <defs>
47
+ <radialGradient id="rg" cx="50%" cy="40%" r="65%">
48
+ <stop offset="0%" stop-color="${color}" stop-opacity="0.22"/>
49
+ <stop offset="100%" stop-color="#000000" stop-opacity="0"/>
50
+ </radialGradient>
51
+ </defs>
52
+ <rect width="1200" height="630" fill="#09090b"/>
53
+ <rect width="1200" height="630" fill="url(#rg)"/>
54
+ <g transform="translate(${iconX} ${iconY}) scale(${scale})" stroke="${color}" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
55
+ ${inner}
56
+ </g>
57
+ <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
+ </svg>`;
59
+ }
60
+ var FAVICON_SIZES = [16, 32, 48, 64];
61
+ var NAMED_ICONS = [
62
+ ["apple-touch-icon.png", 180],
63
+ ["android-chrome-192x192.png", 192],
64
+ ["android-chrome-512x512.png", 512]
65
+ ];
66
+ async function generateAssets(config) {
67
+ const { name, icon, color, outputDir } = config;
68
+ let sharpFn;
69
+ try {
70
+ sharpFn = (await import('sharp')).default;
71
+ } catch {
72
+ throw new Error(
73
+ "sharp is required for asset generation.\nInstall it in your project: bun add sharp lucide-static"
74
+ );
75
+ }
76
+ mkdirSync(outputDir, { recursive: true });
77
+ const faviconDir = join(outputDir, "favicon");
78
+ mkdirSync(faviconDir, { recursive: true });
79
+ const inner = getLucideIconInner(icon);
80
+ const written = [];
81
+ for (const size of FAVICON_SIZES) {
82
+ const svg = buildIconSvg(inner, color, size);
83
+ const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
84
+ const dest = join(faviconDir, `favicon-${size}.png`);
85
+ writeFileSync(dest, png);
86
+ written.push(dest);
87
+ }
88
+ for (const [filename, size] of NAMED_ICONS) {
89
+ const svg = buildIconSvg(inner, color, size);
90
+ const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
91
+ const dest = join(outputDir, filename);
92
+ writeFileSync(dest, png);
93
+ written.push(dest);
94
+ }
95
+ const ogSvg = buildOgSvg(inner, name, color);
96
+ const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
97
+ const ogDest = join(outputDir, "og-image.png");
98
+ writeFileSync(ogDest, ogPng);
99
+ written.push(ogDest);
100
+ const manifest = {
101
+ name,
102
+ short_name: name,
103
+ theme_color: color,
104
+ background_color: "#09090b",
105
+ display: "standalone",
106
+ icons: [
107
+ { 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
+ }
114
+ ]
115
+ };
116
+ const manifestDest = join(outputDir, "manifest.json");
117
+ writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + "\n");
118
+ written.push(manifestDest);
119
+ const robotsDest = join(outputDir, "robots.txt");
120
+ writeFileSync(robotsDest, "User-agent: *\nAllow: /\n");
121
+ written.push(robotsDest);
122
+ return { files: written };
123
+ }
124
+
125
+ export { generateAssets };
126
+ //# sourceMappingURL=generate-assets.js.map
127
+ //# sourceMappingURL=generate-assets.js.map
@@ -0,0 +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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olwiba/dx",
3
- "version": "0.0.16",
3
+ "version": "0.0.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -35,6 +35,10 @@
35
35
  "./ascii-gif": {
36
36
  "import": "./dist/ascii-gif.js",
37
37
  "types": "./dist/ascii-gif.d.ts"
38
+ },
39
+ "./generate-assets": {
40
+ "import": "./dist/generate-assets.js",
41
+ "types": "./dist/generate-assets.d.ts"
38
42
  }
39
43
  },
40
44
  "files": [
@@ -49,6 +53,7 @@
49
53
  "@types/node": "^22.5.4",
50
54
  "eslint": "^9.0.0",
51
55
  "puppeteer-core": "^24.0.0",
56
+ "sharp": "^0.33.0",
52
57
  "tsup": "^8.4.0",
53
58
  "typescript": "^5.7.2"
54
59
  },
@@ -57,7 +62,9 @@
57
62
  "puppeteer-core": ">=22.0.0",
58
63
  "@typescript-eslint/eslint-plugin": ">=8.0.0",
59
64
  "@typescript-eslint/parser": ">=8.0.0",
60
- "eslint-plugin-react-hooks": ">=5.0.0"
65
+ "eslint-plugin-react-hooks": ">=5.0.0",
66
+ "sharp": ">=0.32.0",
67
+ "lucide-static": ">=0.400.0"
61
68
  },
62
69
  "peerDependenciesMeta": {
63
70
  "@typescript-eslint/eslint-plugin": {
@@ -71,6 +78,12 @@
71
78
  },
72
79
  "puppeteer-core": {
73
80
  "optional": true
81
+ },
82
+ "sharp": {
83
+ "optional": true
84
+ },
85
+ "lucide-static": {
86
+ "optional": true
74
87
  }
75
88
  },
76
89
  "homepage": "https://github.com/Olwiba/olwibaDX#readme",