@olwiba/dx 0.0.17 → 0.0.19

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';
@@ -1368,6 +1368,23 @@ async function generateAsciiGif(options) {
1368
1368
  const layout = composeAsciiText(font, options.text);
1369
1369
  const bounds = getLayoutBounds(layout);
1370
1370
  const accentColumns = getAsciiAccentColumns(font, options.text, options.accent ?? "");
1371
+ let colAccentMap;
1372
+ let accentColorList;
1373
+ let sparklingAccents;
1374
+ if (options.accents && options.accents.length > 0) {
1375
+ colAccentMap = /* @__PURE__ */ new Map();
1376
+ sparklingAccents = /* @__PURE__ */ new Set();
1377
+ for (let i = 0; i < options.accents.length; i++) {
1378
+ const cols = getAsciiAccentColumns(font, options.text, options.accents[i].text);
1379
+ for (const col of cols) colAccentMap.set(col, i);
1380
+ if (options.accents[i].sparkle) sparklingAccents.add(i);
1381
+ }
1382
+ accentColorList = options.accents.map((a) => a.color);
1383
+ } else {
1384
+ colAccentMap = new Map([...accentColumns].map((col) => [col, 0]));
1385
+ accentColorList = [options.accentColor ?? options.color ?? "#38bdf8"];
1386
+ sparklingAccents = /* @__PURE__ */ new Set();
1387
+ }
1371
1388
  const width = Math.ceil(bounds.cols * ASCII_CHAR_WIDTH * scale) + padding * 2;
1372
1389
  const height = bounds.rows * ASCII_CHAR_HEIGHT * scale + padding * 2;
1373
1390
  const frameCount = Math.max(1, Math.round(fps * duration));
@@ -1410,11 +1427,11 @@ async function generateAsciiGif(options) {
1410
1427
  writeFileSync(options.outputPath, bytes2);
1411
1428
  return;
1412
1429
  }
1413
- const palette = createPalette({
1430
+ const palette = createMultiAccentPalette({
1414
1431
  backgroundColor: options.backgroundColor ?? "#0a0a0a",
1415
1432
  blendColor: options.blendColor ?? options.backgroundColor ?? "#0a0a0a",
1416
1433
  color: options.color ?? "#e5e5e5",
1417
- accentColor: options.accentColor ?? options.color ?? "#38bdf8",
1434
+ accentColors: accentColorList,
1418
1435
  levels,
1419
1436
  transparent: options.transparent ?? false
1420
1437
  });
@@ -1422,7 +1439,8 @@ async function generateAsciiGif(options) {
1422
1439
  frames.push(
1423
1440
  renderIndexedAsciiFrame({
1424
1441
  layout,
1425
- accentColumns,
1442
+ colAccentMap,
1443
+ sparklingAccents,
1426
1444
  phase: frame / frameCount,
1427
1445
  width,
1428
1446
  height,
@@ -1449,9 +1467,11 @@ function renderIndexedAsciiFrame(options) {
1449
1467
  const charWidth = Math.round(ASCII_CHAR_WIDTH * options.scale);
1450
1468
  const charHeight = Math.round(ASCII_CHAR_HEIGHT * options.scale);
1451
1469
  for (const cell of options.layout.cells) {
1452
- const intensity = getAsciiCellIntensity(cell, { phase: options.phase });
1470
+ const accentIdx = options.colAccentMap.get(cell.col);
1471
+ const isGlitter = accentIdx !== void 0 && options.sparklingAccents.has(accentIdx) && cell.ch === "\u2588";
1472
+ const intensity = isGlitter ? getGlitterIntensity(cell, options.phase * Math.PI * 2) : getAsciiCellIntensity(cell, { phase: options.phase });
1453
1473
  const level = Math.max(0, Math.min(options.levels - 1, Math.round(intensity * (options.levels - 1))));
1454
- const offset = options.accentColumns.has(cell.col) ? 1 + options.levels : 1;
1474
+ const offset = accentIdx !== void 0 ? 1 + (accentIdx + 1) * options.levels : 1;
1455
1475
  const colorIndex = offset + level;
1456
1476
  const x0 = options.padding + Math.round((cell.col - options.bounds.minCol) * ASCII_CHAR_WIDTH * options.scale);
1457
1477
  const y0 = options.padding + (cell.row - options.bounds.minRow) * charHeight;
@@ -1489,6 +1509,13 @@ function renderIndexedAsciiFrameWithRowColors(options) {
1489
1509
  }
1490
1510
  return pixels;
1491
1511
  }
1512
+ function getGlitterIntensity(cell, loop) {
1513
+ const seed = (cell.col * 7.13 + cell.row * 13.37) % (Math.PI * 2);
1514
+ const glitter = (Math.sin(loop * 3.5 + seed) + 1) / 2;
1515
+ const flow = Math.sin(cell.col * 0.08 + loop) * 0.055 + Math.sin(cell.row * 0.22 - loop * 0.75) * 0.04;
1516
+ const sparkBoost = Math.pow(glitter, 3) * 0.42;
1517
+ return Math.max(0.55, Math.min(1, 0.68 + flow + sparkBoost));
1518
+ }
1492
1519
  function createRowColorPalette(options) {
1493
1520
  const bg = parseColor(options.backgroundColor);
1494
1521
  const blend = parseColor(options.blendColor);
@@ -1529,21 +1556,23 @@ function resolveFont(font) {
1529
1556
  if (font in asciiFonts) return asciiFonts[font];
1530
1557
  return readFileSync(font, "utf-8");
1531
1558
  }
1532
- function createPalette(options) {
1559
+ function createMultiAccentPalette(options) {
1533
1560
  const bg = parseColor(options.backgroundColor);
1534
1561
  const blend = parseColor(options.blendColor);
1535
1562
  const base = parseColor(options.color);
1536
- const accent = parseColor(options.accentColor);
1537
1563
  const table = [bg.r, bg.g, bg.b];
1538
1564
  for (let level = 0; level < options.levels; level++) {
1539
1565
  const alpha = level / (options.levels - 1);
1540
1566
  const mixed = mix(blend, base, alpha);
1541
1567
  table.push(mixed.r, mixed.g, mixed.b);
1542
1568
  }
1543
- for (let level = 0; level < options.levels; level++) {
1544
- const alpha = level / (options.levels - 1);
1545
- const mixed = mix(blend, accent, alpha);
1546
- table.push(mixed.r, mixed.g, mixed.b);
1569
+ for (const accentHex of options.accentColors) {
1570
+ const accent = parseColor(accentHex);
1571
+ for (let level = 0; level < options.levels; level++) {
1572
+ const alpha = level / (options.levels - 1);
1573
+ const mixed = mix(blend, accent, alpha);
1574
+ table.push(mixed.r, mixed.g, mixed.b);
1575
+ }
1547
1576
  }
1548
1577
  while (table.length < 256 * 3) table.push(0, 0, 0);
1549
1578
  return { table: Uint8Array.from(table.slice(0, 256 * 3)) };
@@ -1680,13 +1709,16 @@ var generate_assets_exports = {};
1680
1709
  __export(generate_assets_exports, {
1681
1710
  generateAssets: () => generateAssets
1682
1711
  });
1712
+ function detectIconMode(icon) {
1713
+ return icon.endsWith(".svg") || icon.includes("/") || icon.includes("\\") ? "svg" : "lucide";
1714
+ }
1683
1715
  function toKebabCase(name) {
1684
1716
  return name.replace(/([A-Z])/g, (_, ch) => `-${ch.toLowerCase()}`).replace(/^-/, "").toLowerCase();
1685
1717
  }
1686
1718
  function escapeXml(str) {
1687
1719
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1688
1720
  }
1689
- function getLucideIconInner(iconName) {
1721
+ function getLucideInner(iconName) {
1690
1722
  const kebab = toKebabCase(iconName);
1691
1723
  const req = createRequire(import.meta.url);
1692
1724
  let iconPath;
@@ -1700,7 +1732,15 @@ function getLucideIconInner(iconName) {
1700
1732
  const svg = readFileSync(iconPath, "utf-8");
1701
1733
  return svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").replace(/\sstroke="currentColor"/g, "").replace(/\sfill="none"/g, "").trim();
1702
1734
  }
1703
- function buildIconSvg(inner, color, size) {
1735
+ function getSvgInner(svgPath) {
1736
+ const abs = resolve(process.cwd(), svgPath);
1737
+ const svg = readFileSync(abs, "utf-8");
1738
+ const vbMatch = svg.match(/viewBox="0 0 (\d+\.?\d*) \d/);
1739
+ const viewBox = vbMatch ? parseFloat(vbMatch[1]) : 24;
1740
+ const inner = svg.replace(/<svg[^>]*>/, "").replace(/<\/svg>/, "").trim();
1741
+ return { inner, viewBox };
1742
+ }
1743
+ function buildIconSvgLucide(inner, color, size) {
1704
1744
  const pad = Math.round(size * 0.18);
1705
1745
  const area = size - pad * 2;
1706
1746
  const scale = area / 24;
@@ -1713,7 +1753,19 @@ function buildIconSvg(inner, color, size) {
1713
1753
  </g>
1714
1754
  </svg>`;
1715
1755
  }
1716
- function buildOgSvg(inner, name, color) {
1756
+ function buildIconSvgCustom(inner, viewBox, color, size) {
1757
+ const pad = Math.round(size * 0.08);
1758
+ const area = size - pad * 2;
1759
+ const scale = area / viewBox;
1760
+ const rx = Math.round(size * 0.16);
1761
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}">
1762
+ <rect width="${size}" height="${size}" rx="${rx}" fill="${color}"/>
1763
+ <g transform="translate(${pad} ${pad}) scale(${scale})">
1764
+ ${inner}
1765
+ </g>
1766
+ </svg>`;
1767
+ }
1768
+ function buildOgSvgLucide(inner, name, color) {
1717
1769
  const iconPx = 180;
1718
1770
  const iconX = Math.round((1200 - iconPx) / 2);
1719
1771
  const iconY = 160;
@@ -1734,6 +1786,18 @@ function buildOgSvg(inner, name, color) {
1734
1786
  <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
1787
  </svg>`;
1736
1788
  }
1789
+ function buildOgSvgCustom(inner, viewBox, color) {
1790
+ const s = 470;
1791
+ const x = Math.round((1200 - s) / 2);
1792
+ const y = Math.round((630 - s) / 2);
1793
+ const scale = s / viewBox;
1794
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630">
1795
+ <rect width="1200" height="630" fill="${color}"/>
1796
+ <g transform="translate(${x} ${y}) scale(${scale})">
1797
+ ${inner}
1798
+ </g>
1799
+ </svg>`;
1800
+ }
1737
1801
  async function generateAssets(config) {
1738
1802
  const { name, icon, color, outputDir } = config;
1739
1803
  let sharpFn;
@@ -1747,23 +1811,31 @@ async function generateAssets(config) {
1747
1811
  mkdirSync(outputDir, { recursive: true });
1748
1812
  const faviconDir = join(outputDir, "favicon");
1749
1813
  mkdirSync(faviconDir, { recursive: true });
1750
- const inner = getLucideIconInner(icon);
1814
+ const mode = detectIconMode(icon);
1751
1815
  const written = [];
1816
+ let inner;
1817
+ let viewBox = 24;
1818
+ if (mode === "svg") {
1819
+ const parsed = getSvgInner(icon);
1820
+ inner = parsed.inner;
1821
+ viewBox = parsed.viewBox;
1822
+ } else {
1823
+ inner = getLucideInner(icon);
1824
+ }
1825
+ const buildIcon = (size) => mode === "svg" ? buildIconSvgCustom(inner, viewBox, color, size) : buildIconSvgLucide(inner, color, size);
1752
1826
  for (const size of FAVICON_SIZES) {
1753
- const svg = buildIconSvg(inner, color, size);
1754
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1827
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
1755
1828
  const dest = join(faviconDir, `favicon-${size}.png`);
1756
1829
  writeFileSync(dest, png);
1757
1830
  written.push(dest);
1758
1831
  }
1759
1832
  for (const [filename, size] of NAMED_ICONS) {
1760
- const svg = buildIconSvg(inner, color, size);
1761
- const png = await sharpFn(Buffer.from(svg)).png().toBuffer();
1833
+ const png = await sharpFn(Buffer.from(buildIcon(size))).png().toBuffer();
1762
1834
  const dest = join(outputDir, filename);
1763
1835
  writeFileSync(dest, png);
1764
1836
  written.push(dest);
1765
1837
  }
1766
- const ogSvg = buildOgSvg(inner, name, color);
1838
+ const ogSvg = mode === "svg" ? buildOgSvgCustom(inner, viewBox, color) : buildOgSvgLucide(inner, name, color);
1767
1839
  const ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer();
1768
1840
  const ogDest = join(outputDir, "og-image.png");
1769
1841
  writeFileSync(ogDest, ogPng);
@@ -1772,16 +1844,11 @@ async function generateAssets(config) {
1772
1844
  name,
1773
1845
  short_name: name,
1774
1846
  theme_color: color,
1775
- background_color: "#09090b",
1847
+ background_color: mode === "svg" ? color : "#09090b",
1776
1848
  display: "standalone",
1777
1849
  icons: [
1778
1850
  { 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
- }
1851
+ { src: "/android-chrome-512x512.png", sizes: "512x512", type: "image/png", purpose: "maskable" }
1785
1852
  ]
1786
1853
  };
1787
1854
  const manifestDest = join(outputDir, "manifest.json");
@@ -1795,7 +1862,7 @@ async function generateAssets(config) {
1795
1862
  var FAVICON_SIZES, NAMED_ICONS;
1796
1863
  var init_generate_assets = __esm({
1797
1864
  "src/generate-assets.ts"() {
1798
- FAVICON_SIZES = [16, 32, 48, 64];
1865
+ FAVICON_SIZES = [16, 32, 48, 64, 192, 512];
1799
1866
  NAMED_ICONS = [
1800
1867
  ["apple-touch-icon.png", 180],
1801
1868
  ["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":"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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, \"&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/dist/index.d.ts CHANGED
@@ -19,10 +19,23 @@ type DevPlugin = {
19
19
  apply: "serve";
20
20
  configureServer: (server: unknown) => void;
21
21
  };
22
+ declare function hexToAnsi24(hex: string): string | null;
22
23
  declare function printBanner(input: string | BannerSegment[] | BannerOptions): Promise<void>;
23
24
  declare function createTsupBannerHook(project: string | BannerOptions | LegacyBannerOptions): () => Promise<void>;
24
25
  declare function createDevBannerPlugin(project: string | BannerOptions | LegacyBannerOptions): DevPlugin;
25
26
 
27
+ /**
28
+ * Probe for the first free port from `start` up. Vite's own auto-increment
29
+ * doesn't kick in reliably under the TanStack Start plugin, so dev servers
30
+ * resolve the port themselves before passing it to `server.port`.
31
+ */
32
+ declare function findFreePort(start: number, attempts?: number): Promise<number>;
33
+ /**
34
+ * Resolve the dev server port for a preferred base port, logging when the
35
+ * preferred port is taken and a fallback is used.
36
+ */
37
+ declare function resolveDevPort(preferred: number, attempts?: number): Promise<number>;
38
+
26
39
  type ProjectBrandAccent = {
27
40
  hex: string;
28
41
  lightOklch?: string;
@@ -49,4 +62,4 @@ declare function createProjectDevBannerPlugin(project: ProjectConfig): {
49
62
  };
50
63
  declare function createProjectTsupBannerHook(project: ProjectConfig): () => Promise<void>;
51
64
 
52
- export { type BannerSegment, type ProjectBannerSegment, type ProjectBrandAccent, type ProjectConfig, createDevBannerPlugin, createProjectDevBannerPlugin, createProjectTsupBannerHook, createTsupBannerHook, printBanner };
65
+ export { type BannerSegment, type ProjectBannerSegment, type ProjectBrandAccent, type ProjectConfig, createDevBannerPlugin, createProjectDevBannerPlugin, createProjectTsupBannerHook, createTsupBannerHook, findFreePort, hexToAnsi24, printBanner, resolveDevPort };
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { createServer } from 'net';
2
+
1
3
  var __defProp = Object.defineProperty;
2
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
5
  var __esm = (fn, res) => function __init() {
@@ -1428,6 +1430,23 @@ function createDevBannerPlugin(project) {
1428
1430
  }
1429
1431
  };
1430
1432
  }
1433
+ async function findFreePort(start, attempts = 20) {
1434
+ for (let port = start; port < start + attempts; port++) {
1435
+ const free = await new Promise((resolve) => {
1436
+ const probe = createServer().once("error", () => resolve(false)).once("listening", () => probe.close(() => resolve(true)));
1437
+ probe.listen(port);
1438
+ });
1439
+ if (free) return port;
1440
+ }
1441
+ throw new Error(`No free port found in range ${start}-${start + attempts - 1}`);
1442
+ }
1443
+ async function resolveDevPort(preferred, attempts = 20) {
1444
+ const port = await findFreePort(preferred, attempts);
1445
+ if (port !== preferred) {
1446
+ console.log(`Port ${preferred} is in use \u2014 dev server will use ${port} instead.`);
1447
+ }
1448
+ return port;
1449
+ }
1431
1450
 
1432
1451
  // src/project-brand.ts
1433
1452
  function resolveProjectBannerSegments(segments, accentHex) {
@@ -1449,6 +1468,6 @@ function createProjectTsupBannerHook(project) {
1449
1468
  return createTsupBannerHook(getProjectBanner(project));
1450
1469
  }
1451
1470
 
1452
- export { createDevBannerPlugin, createProjectDevBannerPlugin, createProjectTsupBannerHook, createTsupBannerHook, printBanner };
1471
+ export { createDevBannerPlugin, createProjectDevBannerPlugin, createProjectTsupBannerHook, createTsupBannerHook, findFreePort, hexToAnsi24, printBanner, resolveDevPort };
1453
1472
  //# sourceMappingURL=index.js.map
1454
1473
  //# sourceMappingURL=index.js.map