@olwiba/dx 0.0.23 → 0.0.24
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/README.md +44 -9
- package/dist/ascii-gif.js +7 -0
- package/dist/ascii-gif.js.map +1 -1
- package/dist/cli.js +479 -20
- package/dist/generate-assets.js.map +1 -1
- package/dist/generate-previews.d.ts +19 -1
- package/dist/generate-previews.js +52 -11
- package/dist/generate-previews.js.map +1 -1
- package/dist/skills.d.ts +3 -1
- package/dist/skills.js +5 -0
- package/dist/skills.js.map +1 -1
- package/package.json +10 -6
|
@@ -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, 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 * Optional path to a rendered component (SVG, PNG, JPG, or WebP) placed bottom-middle\n * in og-image.png. When set, the OG image uses a solid `color` background with a smaller\n * `icon` above the component instead of the default large-logo layout.\n */\n ogComponent?: 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 resolveIcon(icon: string): { mode: IconMode; inner: string; viewBox: number } {\n const mode = detectIconMode(icon)\n if (mode === \"svg\") {\n const { inner, viewBox } = getSvgInner(icon)\n return { mode, inner, viewBox }\n }\n return { mode, inner: getLucideInner(icon), viewBox: 24 }\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 OG_WIDTH = 1200\nconst OG_HEIGHT = 630\n\n/**\n * The wordmark band at the top, and the space left under it for the component.\n *\n * Shared by the SVG that draws the header and the compositing that places the\n * component, so the two cannot disagree about where one ends and the other\n * begins — the failure mode being a component that creeps up under the logo.\n */\nconst OG_HEADER = {\n top: 54,\n markSize: 96,\n /** Clear space between the wordmark's baseline and the component. */\n gapBelow: 30,\n} as const\n\n/**\n * A soft bloom behind the component, so it sits on the brand field rather than\n * on top of it. White at low opacity rather than a tint, so it reads as light\n * on any brand colour instead of as a second colour competing with it.\n */\nfunction buildOgGlowSvg(box: { left: number; top: number; width: number; height: number }): string {\n const spread = 40\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\">\n <defs>\n <filter id=\"glow\" x=\"-40%\" y=\"-40%\" width=\"180%\" height=\"180%\">\n <feGaussianBlur stdDeviation=\"46\"/>\n </filter>\n </defs>\n <rect x=\"${box.left - spread}\" y=\"${box.top - spread}\" width=\"${box.width + spread * 2}\" height=\"${box.height + spread * 2}\" rx=\"48\" fill=\"#ffffff\" opacity=\"0.22\" filter=\"url(#glow)\"/>\n</svg>`\n}\n\n/**\n * OG base image used when `ogComponent` is set: solid brand-`color` background with a\n * small logo mark + name wordmark centered as a group near the top. The component itself\n * is composited on top afterward, filling the space beneath.\n */\nfunction buildOgSvgWithSmallLogo(\n mode: IconMode,\n inner: string,\n viewBox: number,\n color: string,\n name: string,\n): string {\n const markSize = OG_HEADER.markSize\n const gap = 28\n const fontSize = 56\n const groupY = OG_HEADER.top\n const scale = markSize / viewBox\n\n // No text-measurement API in raw SVG generation, so estimate wordmark width from\n // character count to center the mark+text group as a whole.\n const textWidth = Math.round(name.length * fontSize * 0.58)\n const totalWidth = markSize + gap + textWidth\n const groupX = Math.round((1200 - totalWidth) / 2)\n\n const logoMarkup =\n mode === \"lucide\"\n ? (() => {\n const sw = (2.5 / scale).toFixed(4)\n return `<g transform=\"translate(${groupX} ${groupY}) scale(${scale})\" stroke=\"white\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>`\n })()\n : `<g transform=\"translate(${groupX} ${groupY}) scale(${scale})\">\n ${inner}\n </g>`\n\n const textX = groupX + markSize + gap\n const textY = groupY + markSize / 2\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\">\n <rect width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\" fill=\"${color}\"/>\n ${logoMarkup}\n <text x=\"${textX}\" y=\"${textY}\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif\" font-size=\"${fontSize}\" font-weight=\"700\" fill=\"white\" dominant-baseline=\"middle\" letter-spacing=\"-1\">${escapeXml(name)}</text>\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, ogComponent } = 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 written: string[] = []\n\n const { mode, inner, viewBox } = resolveIcon(icon)\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 = ogComponent\n ? buildOgSvgWithSmallLogo(mode, inner, viewBox, color, name)\n : mode === \"svg\"\n ? buildOgSvgCustom(inner, viewBox, color)\n : buildOgSvgLucide(inner, name, color)\n\n let ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer()\n\n if (ogComponent) {\n // Scaled to width and allowed to run off the bottom edge, rather than\n // shrunk to fit a box. A screenshot that fits entirely inside the frame\n // reads as a picture of an app; one that continues past the edge reads as\n // the app itself, and the detail stays legible at the size a link preview\n // is actually seen.\n //\n // The previous fixed 880x300 box assumed a wide strip — a composer, a\n // toolbar — and shrank anything squarer to fit its height: a 1.87:1\n // dashboard landed at 561x300, adrift in the middle of a 1200px canvas.\n const sideMargin = 72\n const maxWidth = OG_WIDTH - sideMargin * 2\n const headerBottom = OG_HEADER.top + OG_HEADER.markSize + OG_HEADER.gapBelow\n\n const componentBuf = readFileSync(resolve(process.cwd(), ogComponent))\n const resized = await sharpFn(componentBuf, { density: 300 })\n .resize({ width: maxWidth, withoutEnlargement: true })\n .png()\n .toBuffer()\n const { width: compWidth = maxWidth, height: compHeight = 0 } =\n await sharpFn(resized).metadata()\n\n const left = Math.round((OG_WIDTH - compWidth) / 2)\n // A component short enough to fit sits centred in the space under the\n // header; a tall one starts there and bleeds off the bottom. Without this\n // branch a wide strip would cling to the header with a gulf beneath it.\n const fits = headerBottom + compHeight <= OG_HEIGHT\n const top = fits\n ? Math.round(headerBottom + (OG_HEIGHT - headerBottom - compHeight) / 2)\n : headerBottom\n\n ogPng = await sharpFn(ogPng)\n .composite([\n // Glow first: it has to sit under the component, and sharp applies\n // composites in array order.\n {\n input: Buffer.from(\n buildOgGlowSvg({ left, top, width: compWidth, height: compHeight }),\n ),\n left: 0,\n top: 0,\n },\n { input: resized, left, top },\n ])\n .png()\n .toBuffer()\n }\n\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"]}
|
|
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\"\nimport type sharp from \"sharp\"\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 * Optional path to a rendered component (SVG, PNG, JPG, or WebP) placed bottom-middle\n * in og-image.png. When set, the OG image uses a solid `color` background with a smaller\n * `icon` above the component instead of the default large-logo layout.\n */\n ogComponent?: 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 resolveIcon(icon: string): { mode: IconMode; inner: string; viewBox: number } {\n const mode = detectIconMode(icon)\n if (mode === \"svg\") {\n const { inner, viewBox } = getSvgInner(icon)\n return { mode, inner, viewBox }\n }\n return { mode, inner: getLucideInner(icon), viewBox: 24 }\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 OG_WIDTH = 1200\nconst OG_HEIGHT = 630\n\n/**\n * The wordmark band at the top, and the space left under it for the component.\n *\n * Shared by the SVG that draws the header and the compositing that places the\n * component, so the two cannot disagree about where one ends and the other\n * begins — the failure mode being a component that creeps up under the logo.\n */\nconst OG_HEADER = {\n top: 54,\n markSize: 96,\n /** Clear space between the wordmark's baseline and the component. */\n gapBelow: 30,\n} as const\n\n/**\n * A soft bloom behind the component, so it sits on the brand field rather than\n * on top of it. White at low opacity rather than a tint, so it reads as light\n * on any brand colour instead of as a second colour competing with it.\n */\nfunction buildOgGlowSvg(box: { left: number; top: number; width: number; height: number }): string {\n const spread = 40\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\">\n <defs>\n <filter id=\"glow\" x=\"-40%\" y=\"-40%\" width=\"180%\" height=\"180%\">\n <feGaussianBlur stdDeviation=\"46\"/>\n </filter>\n </defs>\n <rect x=\"${box.left - spread}\" y=\"${box.top - spread}\" width=\"${box.width + spread * 2}\" height=\"${box.height + spread * 2}\" rx=\"48\" fill=\"#ffffff\" opacity=\"0.22\" filter=\"url(#glow)\"/>\n</svg>`\n}\n\n/**\n * OG base image used when `ogComponent` is set: solid brand-`color` background with a\n * small logo mark + name wordmark centered as a group near the top. The component itself\n * is composited on top afterward, filling the space beneath.\n */\nfunction buildOgSvgWithSmallLogo(\n mode: IconMode,\n inner: string,\n viewBox: number,\n color: string,\n name: string,\n): string {\n const markSize = OG_HEADER.markSize\n const gap = 28\n const fontSize = 56\n const groupY = OG_HEADER.top\n const scale = markSize / viewBox\n\n // No text-measurement API in raw SVG generation, so estimate wordmark width from\n // character count to center the mark+text group as a whole.\n const textWidth = Math.round(name.length * fontSize * 0.58)\n const totalWidth = markSize + gap + textWidth\n const groupX = Math.round((1200 - totalWidth) / 2)\n\n const logoMarkup =\n mode === \"lucide\"\n ? (() => {\n const sw = (2.5 / scale).toFixed(4)\n return `<g transform=\"translate(${groupX} ${groupY}) scale(${scale})\" stroke=\"white\" fill=\"none\" stroke-width=\"${sw}\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n ${inner}\n </g>`\n })()\n : `<g transform=\"translate(${groupX} ${groupY}) scale(${scale})\">\n ${inner}\n </g>`\n\n const textX = groupX + markSize + gap\n const textY = groupY + markSize / 2\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\">\n <rect width=\"${OG_WIDTH}\" height=\"${OG_HEIGHT}\" fill=\"${color}\"/>\n ${logoMarkup}\n <text x=\"${textX}\" y=\"${textY}\" font-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif\" font-size=\"${fontSize}\" font-weight=\"700\" fill=\"white\" dominant-baseline=\"middle\" letter-spacing=\"-1\">${escapeXml(name)}</text>\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, ogComponent } = config\n\n let sharpFn: typeof 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 written: string[] = []\n\n const { mode, inner, viewBox } = resolveIcon(icon)\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 = ogComponent\n ? buildOgSvgWithSmallLogo(mode, inner, viewBox, color, name)\n : mode === \"svg\"\n ? buildOgSvgCustom(inner, viewBox, color)\n : buildOgSvgLucide(inner, name, color)\n\n let ogPng = await sharpFn(Buffer.from(ogSvg)).png().toBuffer()\n\n if (ogComponent) {\n // Scaled to width and allowed to run off the bottom edge, rather than\n // shrunk to fit a box. A screenshot that fits entirely inside the frame\n // reads as a picture of an app; one that continues past the edge reads as\n // the app itself, and the detail stays legible at the size a link preview\n // is actually seen.\n //\n // The previous fixed 880x300 box assumed a wide strip — a composer, a\n // toolbar — and shrank anything squarer to fit its height: a 1.87:1\n // dashboard landed at 561x300, adrift in the middle of a 1200px canvas.\n const sideMargin = 72\n const maxWidth = OG_WIDTH - sideMargin * 2\n const headerBottom = OG_HEADER.top + OG_HEADER.markSize + OG_HEADER.gapBelow\n\n const componentBuf = readFileSync(resolve(process.cwd(), ogComponent))\n const resized = await sharpFn(componentBuf, { density: 300 })\n .resize({ width: maxWidth, withoutEnlargement: true })\n .png()\n .toBuffer()\n const { width: compWidth = maxWidth, height: compHeight = 0 } =\n await sharpFn(resized).metadata()\n\n const left = Math.round((OG_WIDTH - compWidth) / 2)\n // A component short enough to fit sits centred in the space under the\n // header; a tall one starts there and bleeds off the bottom. Without this\n // branch a wide strip would cling to the header with a gulf beneath it.\n const fits = headerBottom + compHeight <= OG_HEIGHT\n const top = fits\n ? Math.round(headerBottom + (OG_HEIGHT - headerBottom - compHeight) / 2)\n : headerBottom\n\n ogPng = await sharpFn(ogPng)\n .composite([\n // Glow first: it has to sit under the component, and sharp applies\n // composites in array order.\n {\n input: Buffer.from(\n buildOgGlowSvg({ left, top, width: compWidth, height: compHeight }),\n ),\n left: 0,\n top: 0,\n },\n { input: resized, left, top },\n ])\n .png()\n .toBuffer()\n }\n\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"]}
|
|
@@ -17,6 +17,20 @@ interface GeneratePreviewsConfig {
|
|
|
17
17
|
width?: number;
|
|
18
18
|
height?: number;
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Cookies to set before navigating, for pages that only render when signed in.
|
|
22
|
+
*
|
|
23
|
+
* Without these, capturing an authenticated page silently photographs its
|
|
24
|
+
* signed-out state — a sign-in form or an empty shell — and the result looks
|
|
25
|
+
* like a working screenshot. Use a fixture account; never a real user's
|
|
26
|
+
* session, since whatever it can see ends up in an image on disk.
|
|
27
|
+
*/
|
|
28
|
+
cookies?: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
value: string;
|
|
31
|
+
domain?: string;
|
|
32
|
+
path?: string;
|
|
33
|
+
}>;
|
|
20
34
|
}
|
|
21
35
|
interface ManifestEntry {
|
|
22
36
|
name: string;
|
|
@@ -25,6 +39,10 @@ interface ManifestEntry {
|
|
|
25
39
|
width: number;
|
|
26
40
|
height: number;
|
|
27
41
|
}
|
|
42
|
+
declare function resolvePreviewOutputPath(outputDir: string, componentName: string, theme: 'light' | 'dark', includeTheme: boolean): {
|
|
43
|
+
filename: string;
|
|
44
|
+
filepath: string;
|
|
45
|
+
};
|
|
28
46
|
declare function generatePreviews(config: GeneratePreviewsConfig): Promise<void>;
|
|
29
47
|
|
|
30
|
-
export { type GeneratePreviewsConfig, type ManifestEntry, type PreviewEntry, generatePreviews };
|
|
48
|
+
export { type GeneratePreviewsConfig, type ManifestEntry, type PreviewEntry, generatePreviews, resolvePreviewOutputPath };
|
|
@@ -3,6 +3,19 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
5
|
// src/generate-previews.ts
|
|
6
|
+
function resolvePreviewOutputPath(outputDir, componentName, theme, includeTheme) {
|
|
7
|
+
if (!componentName || componentName === "." || componentName === ".." || path.isAbsolute(componentName) || componentName.includes("/") || componentName.includes("\\")) {
|
|
8
|
+
throw new Error(`Invalid preview component name: ${componentName}`);
|
|
9
|
+
}
|
|
10
|
+
const filename = includeTheme ? `${componentName}-${theme}.png` : `${componentName}.png`;
|
|
11
|
+
const resolvedOutputDir = path.resolve(outputDir);
|
|
12
|
+
const filepath = path.resolve(resolvedOutputDir, filename);
|
|
13
|
+
const relativePath = path.relative(resolvedOutputDir, filepath);
|
|
14
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) {
|
|
15
|
+
throw new Error(`Invalid preview component name: ${componentName}`);
|
|
16
|
+
}
|
|
17
|
+
return { filename, filepath };
|
|
18
|
+
}
|
|
6
19
|
function findChromePath() {
|
|
7
20
|
const candidates = process.platform === "win32" ? [
|
|
8
21
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
@@ -31,19 +44,22 @@ async function generatePreviews(config) {
|
|
|
31
44
|
manifestPath,
|
|
32
45
|
executablePath,
|
|
33
46
|
padding = 0,
|
|
34
|
-
viewport
|
|
47
|
+
viewport,
|
|
48
|
+
cookies
|
|
35
49
|
} = config;
|
|
36
50
|
const vw = viewport?.width ?? 1280;
|
|
37
51
|
const vh = viewport?.height ?? 800;
|
|
52
|
+
for (const component of components) {
|
|
53
|
+
for (const theme of themes) {
|
|
54
|
+
resolvePreviewOutputPath(outputDir, component.name, theme, themes.length > 1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
38
57
|
try {
|
|
39
58
|
const res = await fetch(baseUrl);
|
|
40
|
-
if (!res.ok) throw new Error();
|
|
41
|
-
} catch {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
Start it first, then re-run iso:generate.
|
|
45
|
-
`);
|
|
46
|
-
process.exit(1);
|
|
59
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const detail = error instanceof Error ? `: ${error.message}` : "";
|
|
62
|
+
throw new Error(`Preview dev server is unreachable at ${baseUrl}${detail}`);
|
|
47
63
|
}
|
|
48
64
|
const chromePath = executablePath ?? findChromePath();
|
|
49
65
|
console.log(`Using browser: ${chromePath}`);
|
|
@@ -54,6 +70,7 @@ Start it first, then re-run iso:generate.
|
|
|
54
70
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
55
71
|
});
|
|
56
72
|
const manifest = [];
|
|
73
|
+
const captureFailures = [];
|
|
57
74
|
try {
|
|
58
75
|
for (const component of components) {
|
|
59
76
|
const urlPath = component.urlPath ?? `/docs/components/${component.name}`;
|
|
@@ -82,6 +99,16 @@ Start it first, then re-run iso:generate.
|
|
|
82
99
|
})(${JSON.stringify(theme)})`
|
|
83
100
|
);
|
|
84
101
|
await page.setViewport({ width: vw, height: vh });
|
|
102
|
+
if (cookies && cookies.length > 0) {
|
|
103
|
+
const { hostname } = new URL(baseUrl);
|
|
104
|
+
await page.setCookie(
|
|
105
|
+
...cookies.map((cookie) => ({
|
|
106
|
+
path: "/",
|
|
107
|
+
domain: hostname,
|
|
108
|
+
...cookie
|
|
109
|
+
}))
|
|
110
|
+
);
|
|
111
|
+
}
|
|
85
112
|
await page.goto(url, { waitUntil: "networkidle0" });
|
|
86
113
|
await page.evaluate(
|
|
87
114
|
`((scheme) => {
|
|
@@ -97,8 +124,12 @@ Start it first, then re-run iso:generate.
|
|
|
97
124
|
theme
|
|
98
125
|
);
|
|
99
126
|
await new Promise((r) => setTimeout(r, 300));
|
|
100
|
-
const filename
|
|
101
|
-
|
|
127
|
+
const { filename, filepath } = resolvePreviewOutputPath(
|
|
128
|
+
outputDir,
|
|
129
|
+
component.name,
|
|
130
|
+
theme,
|
|
131
|
+
themes.length > 1
|
|
132
|
+
);
|
|
102
133
|
if (captureSelector === null) {
|
|
103
134
|
await page.screenshot({ path: filepath, clip: { x: 0, y: 0, width: vw, height: vh } });
|
|
104
135
|
manifest.push({ name: component.name, theme, file: filename, width: vw, height: vh });
|
|
@@ -140,6 +171,10 @@ Start it first, then re-run iso:generate.
|
|
|
140
171
|
console.log(`\u2713 ${filename}`);
|
|
141
172
|
} catch (err) {
|
|
142
173
|
console.error(`\u2717 ${component.name} (${theme}):`, err);
|
|
174
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
175
|
+
captureFailures.push(
|
|
176
|
+
new Error(`Failed to capture ${component.name} (${theme}): ${detail}`)
|
|
177
|
+
);
|
|
143
178
|
} finally {
|
|
144
179
|
await page.close();
|
|
145
180
|
}
|
|
@@ -148,6 +183,12 @@ Start it first, then re-run iso:generate.
|
|
|
148
183
|
} finally {
|
|
149
184
|
await browser.close();
|
|
150
185
|
}
|
|
186
|
+
if (captureFailures.length > 0) {
|
|
187
|
+
throw new AggregateError(
|
|
188
|
+
captureFailures,
|
|
189
|
+
`Failed to generate ${captureFailures.length} preview capture(s)`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
151
192
|
const resolvedManifestPath = manifestPath ?? path.join(outputDir, "manifest.json");
|
|
152
193
|
fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });
|
|
153
194
|
fs.writeFileSync(resolvedManifestPath, JSON.stringify(manifest, null, 2));
|
|
@@ -155,6 +196,6 @@ Start it first, then re-run iso:generate.
|
|
|
155
196
|
Generated ${manifest.length} previews \u2192 ${outputDir}`);
|
|
156
197
|
}
|
|
157
198
|
|
|
158
|
-
export { generatePreviews };
|
|
199
|
+
export { generatePreviews, resolvePreviewOutputPath };
|
|
159
200
|
//# sourceMappingURL=generate-previews.js.map
|
|
160
201
|
//# sourceMappingURL=generate-previews.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate-previews.ts"],"names":[],"mappings":";;;;;AA+BA,SAAS,cAAA,GAAyB;AAChC,EAAA,MAAM,UAAA,GACJ,OAAA,CAAQ,QAAA,KAAa,OAAA,GACjB;AAAA,IACE,4DAAA;AAAA,IACA,kEAAA;AAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,yCAAA,CAAA;AAAA,IAC3B,mEAAA;AAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,0CAAA;AAAA,GAC7B,GACA,OAAA,CAAQ,QAAA,KAAa,QAAA,GACnB;AAAA,IACE,8DAAA;AAAA,IACA;AAAA,GACF,GACA,CAAC,wBAAA,EAA0B,2BAAA,EAA6B,mBAAmB,CAAA;AAEnF,EAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC1B,IAAA,IAAI,CAAA,IAAK,EAAA,CAAG,UAAA,CAAW,CAAC,GAAG,OAAO,CAAA;AAAA,EACpC;AACA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;AAEA,eAAsB,iBAAiB,MAAA,EAA+C;AACpF,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA,GAAW,wCAAA;AAAA,IACX,MAAA,GAAS,CAAC,OAAA,EAAS,MAAM,CAAA;AAAA,IACzB,YAAA;AAAA,IACA,cAAA;AAAA,IACA,OAAA,GAAU,CAAA;AAAA,IACV;AAAA,GACF,GAAI,MAAA;AAEJ,EAAA,MAAM,EAAA,GAAK,UAAU,KAAA,IAAS,IAAA;AAC9B,EAAA,MAAM,EAAA,GAAK,UAAU,MAAA,IAAU,GAAA;AAE/B,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,OAAO,CAAA;AAC/B,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,EAAM;AAAA,EAC/B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAA,CAAQ,KAAA,CAAM;AAAA,0BAAA,EAA+B,OAAO,CAAA;AAAA;AAAA,CAAgD,CAAA;AACpG,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAkB,cAAA,EAAe;AACpD,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAkB,UAAU,CAAA,CAAE,CAAA;AAE1C,EAAA,EAAA,CAAG,SAAA,CAAU,SAAA,EAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAE3C,EAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,MAAA,CAAO;AAAA,IACrC,cAAA,EAAgB,UAAA;AAAA,IAChB,QAAA,EAAU,IAAA;AAAA,IACV,IAAA,EAAM,CAAC,cAAA,EAAgB,0BAA0B;AAAA,GAClD,CAAA;AAED,EAAA,MAAM,WAA4B,EAAC;AAEnC,EAAA,IAAI;AACF,IAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,MAAA,MAAM,OAAA,GAAU,SAAA,CAAU,OAAA,IAAW,CAAA,iBAAA,EAAoB,UAAU,IAAI,CAAA,CAAA;AACvE,MAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA;AAChC,MAAA,MAAM,eAAA,GACJ,SAAA,CAAU,QAAA,KAAa,KAAA,CAAA,GAAY,UAAU,QAAA,GAAW,QAAA;AAE1D,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,OAAA,EAAQ;AACnC,QAAA,IAAI;AAEF,UAAA,MAAM,KAAK,oBAAA,CAAqB;AAAA,YAC9B,EAAE,IAAA,EAAM,sBAAA,EAAwB,KAAA,EAAO,KAAA;AAAM,WAC9C,CAAA;AAGD,UAAA,MAAM,IAAA,CAAK,qBAAA;AAAA,YACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAA,EAaK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA;AAAA,WAC5B;AAEA,UAAA,MAAM,KAAK,WAAA,CAAY,EAAE,OAAO,EAAA,EAAI,MAAA,EAAQ,IAAI,CAAA;AAChD,UAAA,MAAM,KAAK,IAAA,CAAK,GAAA,EAAK,EAAE,SAAA,EAAW,gBAAgB,CAAA;AAElD,UAAA,MAAM,IAAA,CAAK,QAAA;AAAA,YACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAA,EAKK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA;AAAA,WAC5B;AAEA,UAAA,MAAM,IAAA,CAAK,eAAA;AAAA,YACT,CAAA,uFAAA,CAAA;AAAA,YACA,EAAE,SAAS,GAAA,EAAK;AAAA,YAChB;AAAA,WACF;AACA,UAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,MAAM,UAAA,CAAW,CAAA,EAAG,GAAG,CAAC,CAAA;AAE3C,UAAA,MAAM,QAAA,GACJ,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,CAAA,EAAG,SAAA,CAAU,IAAI,CAAA,CAAA,EAAI,KAAK,CAAA,IAAA,CAAA,GAAS,CAAA,EAAG,SAAA,CAAU,IAAI,CAAA,IAAA,CAAA;AAC1E,UAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,SAAA,EAAW,QAAQ,CAAA;AAE9C,UAAA,IAAI,oBAAoB,IAAA,EAAM;AAC5B,YAAA,MAAM,KAAK,UAAA,CAAW,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,EAAA,IAAM,CAAA;AACrF,YAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,CAAA;AAAA,UACtF,CAAA,MAAO;AACL,YAAA,MAAM,EAAA,GAAK,MAAM,IAAA,CAAK,eAAA,CAAgB,iBAAiB,EAAE,OAAA,EAAS,KAAQ,CAAA;AAC1E,YAAA,IAAI,CAAC,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,eAAe,CAAA,CAAE,CAAA;AAEjE,YAAA,MAAM,IAAA,CAAK,QAAA;AAAA,cACT,CAAA,uBAAA,EAA0B,IAAA,CAAK,SAAA,CAAU,eAAe,CAAC,CAAA,uBAAA;AAAA,aAC3D;AACA,YAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,MAAM,UAAA,CAAW,CAAA,EAAG,GAAG,CAAC,CAAA;AAE3C,YAAA,MAAM,GAAA,GAAM,MAAM,EAAA,CAAG,WAAA,EAAY;AACjC,YAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,eAAe,CAAA,CAAE,CAAA;AAE9E,YAAA,IAAI,UAAU,CAAA,EAAG;AACf,cAAA,MAAM,IAAA,GAAO;AAAA,gBACX,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,IAAI,OAAO,CAAA;AAAA,gBAC9B,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,IAAI,OAAO,CAAA;AAAA,gBAC9B,KAAA,EAAO,GAAA,CAAI,KAAA,GAAQ,OAAA,GAAU,CAAA;AAAA,gBAC7B,MAAA,EAAQ,GAAA,CAAI,MAAA,GAAS,OAAA,GAAU;AAAA,eACjC;AACA,cAAA,MAAM,KAAK,UAAA,CAAW,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,CAAA;AAC9C,cAAA,QAAA,CAAS,IAAA,CAAK;AAAA,gBACZ,MAAM,SAAA,CAAU,IAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,IAAA,EAAM,QAAA;AAAA,gBACN,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAAA,gBAC5B,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAM;AAAA,eAC/B,CAAA;AAAA,YACH,CAAA,MAAO;AACL,cAAA,MAAM,EAAA,CAAG,UAAA,CAAW,EAAE,IAAA,EAAM,UAAU,CAAA;AACtC,cAAA,QAAA,CAAS,IAAA,CAAK;AAAA,gBACZ,MAAM,SAAA,CAAU,IAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,IAAA,EAAM,QAAA;AAAA,gBACN,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,KAAK,CAAA;AAAA,gBAC3B,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM;AAAA,eAC9B,CAAA;AAAA,YACH;AAAA,UACF;AAEA,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,OAAA,EAAK,QAAQ,CAAA,CAAE,CAAA;AAAA,QAC7B,SAAS,GAAA,EAAK;AACZ,UAAA,OAAA,CAAQ,MAAM,CAAA,OAAA,EAAK,SAAA,CAAU,IAAI,CAAA,EAAA,EAAK,KAAK,MAAM,GAAG,CAAA;AAAA,QACtD,CAAA,SAAE;AACA,UAAA,MAAM,KAAK,KAAA,EAAM;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA,SAAE;AACA,IAAA,MAAM,QAAQ,KAAA,EAAM;AAAA,EACtB;AAEA,EAAA,MAAM,oBAAA,GAAuB,YAAA,IAAgB,IAAA,CAAK,IAAA,CAAK,WAAW,eAAe,CAAA;AACjF,EAAA,EAAA,CAAG,SAAA,CAAU,KAAK,OAAA,CAAQ,oBAAoB,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AACpE,EAAA,EAAA,CAAG,cAAc,oBAAA,EAAsB,IAAA,CAAK,UAAU,QAAA,EAAU,IAAA,EAAM,CAAC,CAAC,CAAA;AAExE,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,UAAA,EAAe,QAAA,CAAS,MAAM,CAAA,iBAAA,EAAe,SAAS,CAAA,CAAE,CAAA;AACtE","file":"generate-previews.js","sourcesContent":["import puppeteer from 'puppeteer-core';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PreviewEntry {\n name: string;\n urlPath?: string;\n /** Override config.selector for this entry (e.g. null for full-page captures). */\n selector?: string | null;\n}\n\nexport interface GeneratePreviewsConfig {\n baseUrl: string;\n outputDir: string;\n components: PreviewEntry[];\n selector?: string | null;\n themes?: ('light' | 'dark')[];\n manifestPath?: string;\n executablePath?: string;\n padding?: number;\n viewport?: { width?: number; height?: number };\n}\n\nexport interface ManifestEntry {\n name: string;\n theme: 'light' | 'dark';\n file: string;\n width: number;\n height: number;\n}\n\nfunction findChromePath(): string {\n const candidates =\n process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n 'C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n `${process.env.ProgramFiles}\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe`,\n ]\n : process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',\n ]\n : ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'];\n\n for (const p of candidates) {\n if (p && fs.existsSync(p)) return p;\n }\n throw new Error(\n 'No Chrome/Edge executable found. Pass executablePath in config or install Chrome.',\n );\n}\n\nexport async function generatePreviews(config: GeneratePreviewsConfig): Promise<void> {\n const {\n baseUrl,\n outputDir,\n components,\n selector = '[data-slot=\"component-preview-canvas\"]',\n themes = ['light', 'dark'],\n manifestPath,\n executablePath,\n padding = 0,\n viewport,\n } = config;\n\n const vw = viewport?.width ?? 1280;\n const vh = viewport?.height ?? 800;\n\n try {\n const res = await fetch(baseUrl);\n if (!res.ok) throw new Error();\n } catch {\n console.error(`\\nDev server not running at ${baseUrl}.\\nStart it first, then re-run iso:generate.\\n`);\n process.exit(1);\n }\n\n const chromePath = executablePath ?? findChromePath();\n console.log(`Using browser: ${chromePath}`);\n\n fs.mkdirSync(outputDir, { recursive: true });\n\n const browser = await puppeteer.launch({\n executablePath: chromePath,\n headless: true,\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n\n const manifest: ManifestEntry[] = [];\n\n try {\n for (const component of components) {\n const urlPath = component.urlPath ?? `/docs/components/${component.name}`;\n const url = `${baseUrl}${urlPath}`;\n const captureSelector =\n component.selector !== undefined ? component.selector : selector;\n\n for (const theme of themes) {\n const page = await browser.newPage();\n try {\n // next-themes (Fumadocs RootProvider) defaults to system — match OS unless we override early.\n await page.emulateMediaFeatures([\n { name: 'prefers-color-scheme', value: theme },\n ]);\n\n // Run before page scripts (next-themes / ModeSwitcher) so light captures stay light.\n await page.evaluateOnNewDocument(\n `((scheme) => {\n const isDark = scheme === 'dark';\n const apply = () => {\n const root = document.documentElement;\n root.classList.toggle('dark', isDark);\n root.style.colorScheme = scheme;\n };\n try { localStorage.setItem('theme', scheme); } catch {}\n apply();\n new MutationObserver(apply).observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n })(${JSON.stringify(theme)})`,\n );\n\n await page.setViewport({ width: vw, height: vh });\n await page.goto(url, { waitUntil: 'networkidle0' });\n\n await page.evaluate(\n `((scheme) => {\n const isDark = scheme === 'dark';\n document.documentElement.classList.toggle('dark', isDark);\n document.documentElement.style.colorScheme = scheme;\n try { localStorage.setItem('theme', scheme); } catch {}\n })(${JSON.stringify(theme)})`,\n );\n\n await page.waitForFunction(\n `(scheme) => document.documentElement.classList.contains('dark') === (scheme === 'dark')`,\n { timeout: 5000 },\n theme,\n );\n await new Promise((r) => setTimeout(r, 300));\n\n const filename =\n themes.length > 1 ? `${component.name}-${theme}.png` : `${component.name}.png`;\n const filepath = path.join(outputDir, filename);\n\n if (captureSelector === null) {\n await page.screenshot({ path: filepath, clip: { x: 0, y: 0, width: vw, height: vh } });\n manifest.push({ name: component.name, theme, file: filename, width: vw, height: vh });\n } else {\n const el = await page.waitForSelector(captureSelector, { timeout: 10_000 });\n if (!el) throw new Error(`Selector not found: ${captureSelector}`);\n\n await page.evaluate(\n `document.querySelector(${JSON.stringify(captureSelector)}).style.minHeight = '0'`,\n );\n await new Promise((r) => setTimeout(r, 100));\n\n const box = await el.boundingBox();\n if (!box) throw new Error(`Could not get bounding box for: ${captureSelector}`);\n\n if (padding > 0) {\n const clip = {\n x: Math.max(0, box.x - padding),\n y: Math.max(0, box.y - padding),\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n await page.screenshot({ path: filepath, clip });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(clip.width),\n height: Math.round(clip.height),\n });\n } else {\n await el.screenshot({ path: filepath });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(box.width),\n height: Math.round(box.height),\n });\n }\n }\n\n console.log(`✓ ${filename}`);\n } catch (err) {\n console.error(`✗ ${component.name} (${theme}):`, err);\n } finally {\n await page.close();\n }\n }\n }\n } finally {\n await browser.close();\n }\n\n const resolvedManifestPath = manifestPath ?? path.join(outputDir, 'manifest.json');\n fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });\n fs.writeFileSync(resolvedManifestPath, JSON.stringify(manifest, null, 2));\n\n console.log(`\\nGenerated ${manifest.length} previews → ${outputDir}`);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/generate-previews.ts"],"names":[],"mappings":"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","file":"generate-previews.js","sourcesContent":["import puppeteer from 'puppeteer-core';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PreviewEntry {\n name: string;\n urlPath?: string;\n /** Override config.selector for this entry (e.g. null for full-page captures). */\n selector?: string | null;\n}\n\nexport interface GeneratePreviewsConfig {\n baseUrl: string;\n outputDir: string;\n components: PreviewEntry[];\n selector?: string | null;\n themes?: ('light' | 'dark')[];\n manifestPath?: string;\n executablePath?: string;\n padding?: number;\n viewport?: { width?: number; height?: number };\n /**\n * Cookies to set before navigating, for pages that only render when signed in.\n *\n * Without these, capturing an authenticated page silently photographs its\n * signed-out state — a sign-in form or an empty shell — and the result looks\n * like a working screenshot. Use a fixture account; never a real user's\n * session, since whatever it can see ends up in an image on disk.\n */\n cookies?: Array<{ name: string; value: string; domain?: string; path?: string }>;\n}\n\nexport interface ManifestEntry {\n name: string;\n theme: 'light' | 'dark';\n file: string;\n width: number;\n height: number;\n}\n\nexport function resolvePreviewOutputPath(\n outputDir: string,\n componentName: string,\n theme: 'light' | 'dark',\n includeTheme: boolean,\n): { filename: string; filepath: string } {\n if (\n !componentName ||\n componentName === '.' ||\n componentName === '..' ||\n path.isAbsolute(componentName) ||\n componentName.includes('/') ||\n componentName.includes('\\\\')\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n const filename = includeTheme\n ? `${componentName}-${theme}.png`\n : `${componentName}.png`;\n const resolvedOutputDir = path.resolve(outputDir);\n const filepath = path.resolve(resolvedOutputDir, filename);\n const relativePath = path.relative(resolvedOutputDir, filepath);\n\n if (\n relativePath === '..' ||\n relativePath.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativePath)\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n return { filename, filepath };\n}\n\nfunction findChromePath(): string {\n const candidates =\n process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n 'C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n `${process.env.ProgramFiles}\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe`,\n ]\n : process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',\n ]\n : ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'];\n\n for (const p of candidates) {\n if (p && fs.existsSync(p)) return p;\n }\n throw new Error(\n 'No Chrome/Edge executable found. Pass executablePath in config or install Chrome.',\n );\n}\n\nexport async function generatePreviews(config: GeneratePreviewsConfig): Promise<void> {\n const {\n baseUrl,\n outputDir,\n components,\n selector = '[data-slot=\"component-preview-canvas\"]',\n themes = ['light', 'dark'],\n manifestPath,\n executablePath,\n padding = 0,\n viewport,\n cookies,\n } = config;\n\n const vw = viewport?.width ?? 1280;\n const vh = viewport?.height ?? 800;\n\n for (const component of components) {\n for (const theme of themes) {\n resolvePreviewOutputPath(outputDir, component.name, theme, themes.length > 1);\n }\n }\n\n try {\n const res = await fetch(baseUrl);\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());\n } catch (error) {\n const detail = error instanceof Error ? `: ${error.message}` : '';\n throw new Error(`Preview dev server is unreachable at ${baseUrl}${detail}`);\n }\n\n const chromePath = executablePath ?? findChromePath();\n console.log(`Using browser: ${chromePath}`);\n\n fs.mkdirSync(outputDir, { recursive: true });\n\n const browser = await puppeteer.launch({\n executablePath: chromePath,\n headless: true,\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n\n const manifest: ManifestEntry[] = [];\n const captureFailures: Error[] = [];\n\n try {\n for (const component of components) {\n const urlPath = component.urlPath ?? `/docs/components/${component.name}`;\n const url = `${baseUrl}${urlPath}`;\n const captureSelector =\n component.selector !== undefined ? component.selector : selector;\n\n for (const theme of themes) {\n const page = await browser.newPage();\n try {\n // next-themes (Fumadocs RootProvider) defaults to system — match OS unless we override early.\n await page.emulateMediaFeatures([\n { name: 'prefers-color-scheme', value: theme },\n ]);\n\n // Run before page scripts (next-themes / ModeSwitcher) so light captures stay light.\n await page.evaluateOnNewDocument(\n `((scheme) => {\n const isDark = scheme === 'dark';\n const apply = () => {\n const root = document.documentElement;\n root.classList.toggle('dark', isDark);\n root.style.colorScheme = scheme;\n };\n try { localStorage.setItem('theme', scheme); } catch {}\n apply();\n new MutationObserver(apply).observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n })(${JSON.stringify(theme)})`,\n );\n\n await page.setViewport({ width: vw, height: vh });\n\n if (cookies && cookies.length > 0) {\n const { hostname } = new URL(baseUrl);\n await page.setCookie(\n ...cookies.map((cookie) => ({\n path: '/',\n domain: hostname,\n ...cookie,\n })),\n );\n }\n\n await page.goto(url, { waitUntil: 'networkidle0' });\n\n await page.evaluate(\n `((scheme) => {\n const isDark = scheme === 'dark';\n document.documentElement.classList.toggle('dark', isDark);\n document.documentElement.style.colorScheme = scheme;\n try { localStorage.setItem('theme', scheme); } catch {}\n })(${JSON.stringify(theme)})`,\n );\n\n await page.waitForFunction(\n `(scheme) => document.documentElement.classList.contains('dark') === (scheme === 'dark')`,\n { timeout: 5000 },\n theme,\n );\n await new Promise((r) => setTimeout(r, 300));\n\n const { filename, filepath } = resolvePreviewOutputPath(\n outputDir,\n component.name,\n theme,\n themes.length > 1,\n );\n\n if (captureSelector === null) {\n await page.screenshot({ path: filepath, clip: { x: 0, y: 0, width: vw, height: vh } });\n manifest.push({ name: component.name, theme, file: filename, width: vw, height: vh });\n } else {\n const el = await page.waitForSelector(captureSelector, { timeout: 10_000 });\n if (!el) throw new Error(`Selector not found: ${captureSelector}`);\n\n await page.evaluate(\n `document.querySelector(${JSON.stringify(captureSelector)}).style.minHeight = '0'`,\n );\n await new Promise((r) => setTimeout(r, 100));\n\n const box = await el.boundingBox();\n if (!box) throw new Error(`Could not get bounding box for: ${captureSelector}`);\n\n if (padding > 0) {\n const clip = {\n x: Math.max(0, box.x - padding),\n y: Math.max(0, box.y - padding),\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n await page.screenshot({ path: filepath, clip });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(clip.width),\n height: Math.round(clip.height),\n });\n } else {\n await el.screenshot({ path: filepath });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(box.width),\n height: Math.round(box.height),\n });\n }\n }\n\n console.log(`✓ ${filename}`);\n } catch (err) {\n console.error(`✗ ${component.name} (${theme}):`, err);\n const detail = err instanceof Error ? err.message : String(err);\n captureFailures.push(\n new Error(`Failed to capture ${component.name} (${theme}): ${detail}`),\n );\n } finally {\n await page.close();\n }\n }\n }\n } finally {\n await browser.close();\n }\n\n if (captureFailures.length > 0) {\n throw new AggregateError(\n captureFailures,\n `Failed to generate ${captureFailures.length} preview capture(s)`,\n );\n }\n\n const resolvedManifestPath = manifestPath ?? path.join(outputDir, 'manifest.json');\n fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });\n fs.writeFileSync(resolvedManifestPath, JSON.stringify(manifest, null, 2));\n\n console.log(`\\nGenerated ${manifest.length} previews → ${outputDir}`);\n}\n"]}
|
package/dist/skills.d.ts
CHANGED
|
@@ -12,5 +12,7 @@ interface SkillsManifest {
|
|
|
12
12
|
version: string;
|
|
13
13
|
skills: ManifestSkill[];
|
|
14
14
|
}
|
|
15
|
+
/** Returns true when a manifest slug is a safe, single directory name. */
|
|
16
|
+
declare function isSafeSkillSlug(slug: string): boolean;
|
|
15
17
|
|
|
16
|
-
export type
|
|
18
|
+
export { type ManifestSkill, type SkillsManifest, isSafeSkillSlug };
|
package/dist/skills.js
CHANGED
package/dist/skills.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"skills.js"}
|
|
1
|
+
{"version":3,"sources":["../src/skills.ts"],"names":[],"mappings":";AAiBO,SAAS,gBAAgB,IAAA,EAAuB;AACrD,EAAA,OAAO,SAAS,GAAA,IAAO,IAAA,KAAS,IAAA,IAAQ,mBAAA,CAAoB,KAAK,IAAI,CAAA;AACvE","file":"skills.js","sourcesContent":["export interface ManifestSkill {\n slug: string\n name: string\n description: string\n category?: string\n providers?: string[]\n examples?: string[]\n tip?: string | null\n contentUrl: string\n}\n\nexport interface SkillsManifest {\n version: string\n skills: ManifestSkill[]\n}\n\n/** Returns true when a manifest slug is a safe, single directory name. */\nexport function isSafeSkillSlug(slug: string): boolean {\n return slug !== \".\" && slug !== \"..\" && /^[A-Za-z0-9._-]+$/.test(slug)\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.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -47,14 +47,15 @@
|
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsup",
|
|
49
49
|
"dev": "tsup --watch",
|
|
50
|
+
"test": "bun test",
|
|
50
51
|
"tsc": "tsc --noEmit"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@types/node": "^22.5.4",
|
|
54
|
-
"eslint": "^9.
|
|
55
|
+
"eslint": "^9.39.5",
|
|
55
56
|
"lucide-static": "^0.488.0",
|
|
56
|
-
"puppeteer-core": "^
|
|
57
|
-
"sharp": "^0.
|
|
57
|
+
"puppeteer-core": "^25.9.0",
|
|
58
|
+
"sharp": "^0.35.0",
|
|
58
59
|
"tsup": "^8.4.0",
|
|
59
60
|
"typescript": "^5.7.2"
|
|
60
61
|
},
|
|
@@ -64,8 +65,8 @@
|
|
|
64
65
|
"eslint": ">=9.0.0",
|
|
65
66
|
"eslint-plugin-react-hooks": ">=5.0.0",
|
|
66
67
|
"lucide-static": ">=0.400.0",
|
|
67
|
-
"puppeteer-core": ">=
|
|
68
|
-
"sharp": ">=0.
|
|
68
|
+
"puppeteer-core": ">=25.9.0",
|
|
69
|
+
"sharp": ">=0.35.0"
|
|
69
70
|
},
|
|
70
71
|
"peerDependenciesMeta": {
|
|
71
72
|
"@typescript-eslint/eslint-plugin": {
|
|
@@ -95,6 +96,9 @@
|
|
|
95
96
|
"bugs": {
|
|
96
97
|
"url": "https://github.com/Olwiba/olwibaDX/issues"
|
|
97
98
|
},
|
|
99
|
+
"engines": {
|
|
100
|
+
"node": ">=22.12.0"
|
|
101
|
+
},
|
|
98
102
|
"keywords": [
|
|
99
103
|
"olwiba",
|
|
100
104
|
"dx",
|