#!/usr/bin/env bun import { mkdir, writeFile } from "fs/promises"; import { basename, join, resolve, relative } from "path"; import { loadHtmlParser, normalizeUrl, parseArgs } from "./cli.js"; import { MAX_ASSET_BYTES, MAX_PALETTE_ENTRIES, MAX_STYLESHEET_BYTES, MAX_STYLESHEETS, USER_AGENT, VERSION, } from "./constants.js"; import { extensionFor, extractColorsFromCss, extractFontsFromCss, fetchPage, fetchWithTimeout, looksLikeLogo, safeResolve, sanitizeFilename, toHex, } from "./extract.js"; import type { AssetCandidate, ColorHit, ColorKind, DownloadedAsset, FontHit, } from "./types.js"; interface WrittenFile { path: string; type: string; bytes: number; } async function main(): Promise { const options = parseArgs(process.argv.slice(2)); const target = normalizeUrl(options.url!); const outDir = resolve(options.output); const parse = await loadHtmlParser(); const { html, finalUrl, contentType } = await fetchPage(target.toString(), options.timeout); const root = parse(html); const base = safeResolve(root.querySelector("base")?.getAttribute("href"), finalUrl) ?? finalUrl; /* ---- metadata ---- */ const metaOf = (selector: string): string | undefined => root.querySelector(selector)?.getAttribute("content")?.trim(); const pageTitle = root.querySelector("title")?.text?.trim(); const siteName = metaOf('meta[property="og:site_name"]'); const ogTitle = metaOf('meta[property="og:title"]'); const description = metaOf('meta[name="description"]') ?? metaOf('meta[property="og:description"]'); const brandName = siteName ?? (ogTitle ? ogTitle.split(/[|–—-]/)[0].trim() : undefined) ?? (pageTitle ? pageTitle.split(/[|–—-]/)[0].trim() : undefined) ?? new URL(finalUrl).hostname.replace(/^www\./, ""); /* ---- colors ---- */ const colors: ColorHit[] = []; for (const node of root.querySelectorAll('meta[name="theme-color"]')) { const value = node.getAttribute("content")?.trim(); const hex = value ? toHex(value) : null; if (hex) { const media = node.getAttribute("media"); colors.push({ hex, raw: value!, name: "theme-color", kind: "meta", foundIn: media ? `` : '', }); } } for (const node of root.querySelectorAll('meta[name="msapplication-TileColor"]')) { const value = node.getAttribute("content")?.trim(); const hex = value ? toHex(value) : null; if (hex) colors.push({ hex, raw: value!, name: "msapplication-TileColor", kind: "meta", foundIn: "" }); } const fonts: FontHit[] = []; const inlineStyles = root.querySelectorAll("style"); inlineStyles.forEach((node, index) => { const css = node.rawText ?? node.text ?? ""; colors.push(...extractColorsFromCss(css, `inline