(
tokens: DesignTokens,
outputDir: string,
catalogedAssets?: CatalogedAsset[],
faviconLinks?: Array<{ rel: string; href: string }>,
)
| 18 | } |
| 19 | |
| 20 | export async function downloadAssets( |
| 21 | tokens: DesignTokens, |
| 22 | outputDir: string, |
| 23 | catalogedAssets?: CatalogedAsset[], |
| 24 | faviconLinks?: Array<{ rel: string; href: string }>, |
| 25 | ): Promise<DownloadedAsset[]> { |
| 26 | const assetsDir = join(outputDir, "assets"); |
| 27 | mkdirSync(assetsDir, { recursive: true }); |
| 28 | |
| 29 | const assets: DownloadedAsset[] = []; |
| 30 | const downloadedUrls = new Set<string>(); |
| 31 | |
| 32 | mkdirSync(join(outputDir, "assets", "svgs"), { recursive: true }); |
| 33 | const usedSvgNames = new Set<string>(); |
| 34 | for (let i = 0; i < tokens.svgs.length && i < 30; i++) { |
| 35 | const svg = tokens.svgs[i]!; |
| 36 | if (!svg.outerHTML || svg.outerHTML.length < 50) continue; |
| 37 | const slug = svgContentHashSlug(svg.outerHTML, !!svg.isLogo); |
| 38 | let finalSlug = slug; |
| 39 | let suffix = 2; |
| 40 | while (usedSvgNames.has(finalSlug)) { |
| 41 | finalSlug = `${slug}-${suffix}`; |
| 42 | suffix++; |
| 43 | } |
| 44 | usedSvgNames.add(finalSlug); |
| 45 | const name = `${finalSlug}.svg`; |
| 46 | const localPath = `assets/svgs/${name}`; |
| 47 | try { |
| 48 | writeFileSync(join(outputDir, localPath), svg.outerHTML, "utf-8"); |
| 49 | assets.push({ url: "", localPath, type: "svg" }); |
| 50 | } catch { |
| 51 | /* skip */ |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | // 2. Favicon |
| 56 | for (const icon of faviconLinks || []) { |
| 57 | if (!icon.href) continue; |
| 58 | try { |
| 59 | const ext = extname(new URL(icon.href).pathname) || ".ico"; |
| 60 | const name = `favicon${ext}`; |
| 61 | const localPath = `assets/${name}`; |
| 62 | const buffer = await fetchBuffer(icon.href); |
| 63 | if (buffer) { |
| 64 | writeFileSync(join(outputDir, localPath), buffer); |
| 65 | assets.push({ url: icon.href, localPath, type: "favicon" }); |
| 66 | break; |
| 67 | } |
| 68 | } catch { |
| 69 | /* skip */ |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // 3. Images — use the catalog as the single source of truth (highest resolution, deduplicated) |
| 74 | // If the catalog is empty, asset download produces zero images — this is surfaced as a warning |
| 75 | // so the capture doesn't silently produce a half-empty dataset. |
| 76 | const imageUrls: { url: string; isPoster: boolean }[] = []; |
| 77 |
no test coverage detected