@olwiba/dx 0.0.16 → 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 +201 -2
- package/dist/generate-assets.d.ts +13 -0
- package/dist/generate-assets.js +165 -0
- package/dist/generate-assets.js.map +1 -0
- package/package.json +16 -2
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
+
import { createRequire } from 'module';
|
|
4
5
|
import { createInterface } from 'readline/promises';
|
|
5
6
|
import { stdout, stdin } from 'process';
|
|
6
7
|
|
|
@@ -1673,15 +1674,184 @@ 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 detectIconMode(icon) {
|
|
1684
|
+
return icon.endsWith(".svg") || icon.includes("/") || icon.includes("\\") ? "svg" : "lucide";
|
|
1685
|
+
}
|
|
1686
|
+
function toKebabCase(name) {
|
|
1687
|
+
return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
|
|
1688
|
+
}
|
|
1689
|
+
function escapeXml(str) {
|
|
1690
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1691
|
+
}
|
|
1692
|
+
function getLucideInner(iconName) {
|
|
1693
|
+
const kebab = toKebabCase(iconName);
|
|
1694
|
+
const req = createRequire(import.meta.url);
|
|
1695
|
+
let iconPath;
|
|
1696
|
+
try {
|
|
1697
|
+
iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`);
|
|
1698
|
+
} catch {
|
|
1699
|
+
throw new Error(
|
|
1700
|
+
`Unknown Lucide icon: "${iconName}" (tried "${kebab}.svg"). Browse icons at https://lucide.dev/icons`
|
|
1701
|
+
);
|
|
1702
|
+
}
|
|
1703
|
+
const svg = readFileSync(iconPath, "utf-8");
|
|
1704
|
+
return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
|
|
1705
|
+
}
|
|
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) {
|
|
1715
|
+
const pad = Math.round(size * 0.18);
|
|
1716
|
+
const area = size - pad * 2;
|
|
1717
|
+
const scale = area / 24;
|
|
1718
|
+
const rx = Math.round(size * 0.16);
|
|
1719
|
+
const sw = (2 / scale).toFixed(4);
|
|
1720
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
|
|
1721
|
+
<rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
|
|
1722
|
+
<g transform="translate(${pad} ${pad}) scale(${scale})" stroke="white" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
|
|
1723
|
+
${inner}
|
|
1724
|
+
</g>
|
|
1725
|
+
</svg>`;
|
|
1726
|
+
}
|
|
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) {
|
|
1740
|
+
const iconPx = 180;
|
|
1741
|
+
const iconX = Math.round((1200 - iconPx) / 2);
|
|
1742
|
+
const iconY = 160;
|
|
1743
|
+
const scale = iconPx / 24;
|
|
1744
|
+
const sw = (2.5 / scale).toFixed(4);
|
|
1745
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
|
|
1746
|
+
<defs>
|
|
1747
|
+
<radialGradient id="rg" cx="50%" cy="40%" r="65%">
|
|
1748
|
+
<stop offset="0%" stop-color="${color}" stop-opacity="0.22"/>
|
|
1749
|
+
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
|
|
1750
|
+
</radialGradient>
|
|
1751
|
+
</defs>
|
|
1752
|
+
<rect width="1200" height="630" fill="#09090b"/>
|
|
1753
|
+
<rect width="1200" height="630" fill="url(#rg)"/>
|
|
1754
|
+
<g transform="translate(${iconX} ${iconY}) scale(${scale})" stroke="${color}" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
|
|
1755
|
+
${inner}
|
|
1756
|
+
</g>
|
|
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>
|
|
1758
|
+
</svg>`;
|
|
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
|
+
}
|
|
1772
|
+
async function generateAssets(config) {
|
|
1773
|
+
const { name, icon, color, outputDir } = config;
|
|
1774
|
+
let sharpFn;
|
|
1775
|
+
try {
|
|
1776
|
+
sharpFn = (await import('sharp')).default;
|
|
1777
|
+
} catch {
|
|
1778
|
+
throw new Error(
|
|
1779
|
+
"sharp is required for asset generation.\nInstall it in your project: bun add sharp lucide-static"
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
mkdirSync(outputDir, { recursive: true });
|
|
1783
|
+
const faviconDir = join(outputDir, "favicon");
|
|
1784
|
+
mkdirSync(faviconDir, { recursive: true });
|
|
1785
|
+
const mode = detectIconMode(icon);
|
|
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);
|
|
1797
|
+
for (const size of FAVICON_SIZES) {
|
|
1798
|
+
const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
|
|
1799
|
+
const dest = join(faviconDir, `favicon-${size}.png`);
|
|
1800
|
+
writeFileSync(dest, png);
|
|
1801
|
+
written.push(dest);
|
|
1802
|
+
}
|
|
1803
|
+
for (const [filename, size] of NAMED_ICONS) {
|
|
1804
|
+
const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
|
|
1805
|
+
const dest = join(outputDir, filename);
|
|
1806
|
+
writeFileSync(dest, png);
|
|
1807
|
+
written.push(dest);
|
|
1808
|
+
}
|
|
1809
|
+
const ogSvg = mode === "svg" ? buildOgSvgCustom(inner, viewBox, color) : buildOgSvgLucide(inner, name, color);
|
|
1810
|
+
const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
|
|
1811
|
+
const ogDest = join(outputDir, "og-image.png");
|
|
1812
|
+
writeFileSync(ogDest, ogPng);
|
|
1813
|
+
written.push(ogDest);
|
|
1814
|
+
const manifest = {
|
|
1815
|
+
name,
|
|
1816
|
+
short_name: name,
|
|
1817
|
+
theme_color: color,
|
|
1818
|
+
background_color: mode === "svg" ? color : "#09090b",
|
|
1819
|
+
display: "standalone",
|
|
1820
|
+
icons: [
|
|
1821
|
+
{ src: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
|
|
1822
|
+
{ src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
|
|
1823
|
+
]
|
|
1824
|
+
};
|
|
1825
|
+
const manifestDest = join(outputDir, "manifest.json");
|
|
1826
|
+
writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + "\n");
|
|
1827
|
+
written.push(manifestDest);
|
|
1828
|
+
const robotsDest = join(outputDir, "robots.txt");
|
|
1829
|
+
writeFileSync(robotsDest, "User-agent: *\nAllow: /\n");
|
|
1830
|
+
written.push(robotsDest);
|
|
1831
|
+
return { files: written };
|
|
1832
|
+
}
|
|
1833
|
+
var FAVICON_SIZES, NAMED_ICONS;
|
|
1834
|
+
var init_generate_assets = __esm({
|
|
1835
|
+
"src/generate-assets.ts"() {
|
|
1836
|
+
FAVICON_SIZES = [16, 32, 48, 64, 192, 512];
|
|
1837
|
+
NAMED_ICONS = [
|
|
1838
|
+
["apple-touch-icon.png", 180],
|
|
1839
|
+
["android-chrome-192x192.png", 192],
|
|
1840
|
+
["android-chrome-512x512.png", 512]
|
|
1841
|
+
];
|
|
1842
|
+
}
|
|
1843
|
+
});
|
|
1676
1844
|
var DEFAULT_SOURCE = "https://olwiba.com/skills/manifest.json";
|
|
1677
1845
|
var [command, subcommand] = process.argv.slice(2);
|
|
1678
1846
|
if (command === "skills" && subcommand === "install") {
|
|
1679
1847
|
await runSkillsInstall();
|
|
1680
1848
|
} else if (command === "ascii-gif") {
|
|
1681
1849
|
await runAsciiGif();
|
|
1850
|
+
} else if (command === "generate-assets") {
|
|
1851
|
+
await runGenerateAssets();
|
|
1682
1852
|
} else {
|
|
1683
1853
|
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"
|
|
1854
|
+
"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
1855
|
);
|
|
1686
1856
|
}
|
|
1687
1857
|
async function runSkillsInstall() {
|
|
@@ -1838,6 +2008,35 @@ function parseFlags(args) {
|
|
|
1838
2008
|
}
|
|
1839
2009
|
return flags;
|
|
1840
2010
|
}
|
|
2011
|
+
async function runGenerateAssets() {
|
|
2012
|
+
const { generateAssets: generateAssets2 } = await Promise.resolve().then(() => (init_generate_assets(), generate_assets_exports));
|
|
2013
|
+
const flags = parseFlags(process.argv.slice(3));
|
|
2014
|
+
const name = flags.name;
|
|
2015
|
+
const icon = flags.icon;
|
|
2016
|
+
const color = flags.color;
|
|
2017
|
+
const outputDir = flags.out ?? flags.output ?? "public";
|
|
2018
|
+
if (!name || !icon || !color) {
|
|
2019
|
+
process.stderr.write(
|
|
2020
|
+
"Usage: dx generate-assets --name <app> --icon <lucide-icon> --color <#hex> [--out <dir>]\n"
|
|
2021
|
+
);
|
|
2022
|
+
process.exitCode = 1;
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
process.stdout.write(`Generating assets for "${name}"\u2026
|
|
2026
|
+
`);
|
|
2027
|
+
try {
|
|
2028
|
+
const result = await generateAssets2({ name, icon, color, outputDir });
|
|
2029
|
+
process.stdout.write(`Generated ${result.files.length} files in ${outputDir}/
|
|
2030
|
+
`);
|
|
2031
|
+
for (const f of result.files) process.stdout.write(` ${f}
|
|
2032
|
+
`);
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2035
|
+
process.stderr.write(`Error: ${message}
|
|
2036
|
+
`);
|
|
2037
|
+
process.exitCode = 1;
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
1841
2040
|
function parseNumberFlag(value) {
|
|
1842
2041
|
if (value == null) return void 0;
|
|
1843
2042
|
const parsed = Number(value);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface GenerateAssetsConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
/** Lucide icon name (e.g. "Zap") OR path to an SVG file (e.g. "./logo.svg") */
|
|
4
|
+
icon: string;
|
|
5
|
+
color: string;
|
|
6
|
+
outputDir: string;
|
|
7
|
+
}
|
|
8
|
+
interface GenerateAssetsResult {
|
|
9
|
+
files: string[];
|
|
10
|
+
}
|
|
11
|
+
declare function generateAssets(config: GenerateAssetsConfig): Promise<GenerateAssetsResult>;
|
|
12
|
+
|
|
13
|
+
export { type GenerateAssetsConfig, type GenerateAssetsResult, generateAssets };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/generate-assets.ts
|
|
6
|
+
function detectIconMode(icon) {
|
|
7
|
+
return icon.endsWith(".svg") || icon.includes("/") || icon.includes("\\") ? "svg" : "lucide";
|
|
8
|
+
}
|
|
9
|
+
function toKebabCase(name) {
|
|
10
|
+
return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
function escapeXml(str) {
|
|
13
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
14
|
+
}
|
|
15
|
+
function getLucideInner(iconName) {
|
|
16
|
+
const kebab = toKebabCase(iconName);
|
|
17
|
+
const req = createRequire(import.meta.url);
|
|
18
|
+
let iconPath;
|
|
19
|
+
try {
|
|
20
|
+
iconPath = req.resolve(`lucide-static/icons/${kebab}.svg`);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Unknown Lucide icon: "${iconName}" (tried "${kebab}.svg"). Browse icons at https://lucide.dev/icons`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const svg = readFileSync(iconPath, "utf-8");
|
|
27
|
+
return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
|
|
28
|
+
}
|
|
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) {
|
|
38
|
+
const pad = Math.round(size * 0.18);
|
|
39
|
+
const area = size - pad * 2;
|
|
40
|
+
const scale = area / 24;
|
|
41
|
+
const rx = Math.round(size * 0.16);
|
|
42
|
+
const sw = (2 / scale).toFixed(4);
|
|
43
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
|
|
44
|
+
<rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
|
|
45
|
+
<g transform="translate(${pad} ${pad}) scale(${scale})" stroke="white" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
|
|
46
|
+
${inner}
|
|
47
|
+
</g>
|
|
48
|
+
</svg>`;
|
|
49
|
+
}
|
|
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) {
|
|
63
|
+
const iconPx = 180;
|
|
64
|
+
const iconX = Math.round((1200 - iconPx) / 2);
|
|
65
|
+
const iconY = 160;
|
|
66
|
+
const scale = iconPx / 24;
|
|
67
|
+
const sw = (2.5 / scale).toFixed(4);
|
|
68
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
|
|
69
|
+
<defs>
|
|
70
|
+
<radialGradient id="rg" cx="50%" cy="40%" r="65%">
|
|
71
|
+
<stop offset="0%" stop-color="${color}" stop-opacity="0.22"/>
|
|
72
|
+
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
|
|
73
|
+
</radialGradient>
|
|
74
|
+
</defs>
|
|
75
|
+
<rect width="1200" height="630" fill="#09090b"/>
|
|
76
|
+
<rect width="1200" height="630" fill="url(#rg)"/>
|
|
77
|
+
<g transform="translate(${iconX} ${iconY}) scale(${scale})" stroke="${color}" fill="none" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round">
|
|
78
|
+
${inner}
|
|
79
|
+
</g>
|
|
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>
|
|
81
|
+
</svg>`;
|
|
82
|
+
}
|
|
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];
|
|
96
|
+
var NAMED_ICONS = [
|
|
97
|
+
["apple-touch-icon.png", 180],
|
|
98
|
+
["android-chrome-192x192.png", 192],
|
|
99
|
+
["android-chrome-512x512.png", 512]
|
|
100
|
+
];
|
|
101
|
+
async function generateAssets(config) {
|
|
102
|
+
const { name, icon, color, outputDir } = config;
|
|
103
|
+
let sharpFn;
|
|
104
|
+
try {
|
|
105
|
+
sharpFn = (await import('sharp')).default;
|
|
106
|
+
} catch {
|
|
107
|
+
throw new Error(
|
|
108
|
+
"sharp is required for asset generation.\nInstall it in your project: bun add sharp lucide-static"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
mkdirSync(outputDir, { recursive: true });
|
|
112
|
+
const faviconDir = join(outputDir, "favicon");
|
|
113
|
+
mkdirSync(faviconDir, { recursive: true });
|
|
114
|
+
const mode = detectIconMode(icon);
|
|
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);
|
|
126
|
+
for (const size of FAVICON_SIZES) {
|
|
127
|
+
const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
|
|
128
|
+
const dest = join(faviconDir, `favicon-${size}.png`);
|
|
129
|
+
writeFileSync(dest, png);
|
|
130
|
+
written.push(dest);
|
|
131
|
+
}
|
|
132
|
+
for (const [filename, size] of NAMED_ICONS) {
|
|
133
|
+
const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
|
|
134
|
+
const dest = join(outputDir, filename);
|
|
135
|
+
writeFileSync(dest, png);
|
|
136
|
+
written.push(dest);
|
|
137
|
+
}
|
|
138
|
+
const ogSvg = mode === "svg" ? buildOgSvgCustom(inner, viewBox, color) : buildOgSvgLucide(inner, name, color);
|
|
139
|
+
const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
|
|
140
|
+
const ogDest = join(outputDir, "og-image.png");
|
|
141
|
+
writeFileSync(ogDest, ogPng);
|
|
142
|
+
written.push(ogDest);
|
|
143
|
+
const manifest = {
|
|
144
|
+
name,
|
|
145
|
+
short_name: name,
|
|
146
|
+
theme_color: color,
|
|
147
|
+
background_color: mode === "svg" ? color : "#09090b",
|
|
148
|
+
display: "standalone",
|
|
149
|
+
icons: [
|
|
150
|
+
{ src: "/android-chrome-192x192.png", sizes: "192x192", type: "image/png" },
|
|
151
|
+
{ src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
const manifestDest = join(outputDir, "manifest.json");
|
|
155
|
+
writeFileSync(manifestDest, JSON.stringify(manifest, null, 2) + "\n");
|
|
156
|
+
written.push(manifestDest);
|
|
157
|
+
const robotsDest = join(outputDir, "robots.txt");
|
|
158
|
+
writeFileSync(robotsDest, "User-agent: *\nAllow: /\n");
|
|
159
|
+
written.push(robotsDest);
|
|
160
|
+
return { files: written };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export { generateAssets };
|
|
164
|
+
//# sourceMappingURL=generate-assets.js.map
|
|
165
|
+
//# 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, 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, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\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.
|
|
3
|
+
"version": "0.0.18",
|
|
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": [
|
|
@@ -48,7 +52,9 @@
|
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@types/node": "^22.5.4",
|
|
50
54
|
"eslint": "^9.0.0",
|
|
55
|
+
"lucide-static": "^0.488.0",
|
|
51
56
|
"puppeteer-core": "^24.0.0",
|
|
57
|
+
"sharp": "^0.33.0",
|
|
52
58
|
"tsup": "^8.4.0",
|
|
53
59
|
"typescript": "^5.7.2"
|
|
54
60
|
},
|
|
@@ -57,7 +63,9 @@
|
|
|
57
63
|
"puppeteer-core": ">=22.0.0",
|
|
58
64
|
"@typescript-eslint/eslint-plugin": ">=8.0.0",
|
|
59
65
|
"@typescript-eslint/parser": ">=8.0.0",
|
|
60
|
-
"eslint-plugin-react-hooks": ">=5.0.0"
|
|
66
|
+
"eslint-plugin-react-hooks": ">=5.0.0",
|
|
67
|
+
"sharp": ">=0.32.0",
|
|
68
|
+
"lucide-static": ">=0.400.0"
|
|
61
69
|
},
|
|
62
70
|
"peerDependenciesMeta": {
|
|
63
71
|
"@typescript-eslint/eslint-plugin": {
|
|
@@ -71,6 +79,12 @@
|
|
|
71
79
|
},
|
|
72
80
|
"puppeteer-core": {
|
|
73
81
|
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"sharp": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"lucide-static": {
|
|
87
|
+
"optional": true
|
|
74
88
|
}
|
|
75
89
|
},
|
|
76
90
|
"homepage": "https://github.com/Olwiba/olwibaDX#readme",
|