import * as cheerio from 'cheerio'; /** Thin HTML helpers shared by connectors (cheerio). */ export function load(html: string) { return cheerio.load(html); } export type CheerioRoot = ReturnType; export function text($el: { text(): string } | null | undefined): string | null { const t = $el?.text()?.replace(/\s+/g, ' ').trim(); return t ? t : null; } export function absUrl(base: string, href: string | null | undefined): string | null { if (!href) return null; try { return new URL(href, base).toString(); } catch { return null; } } /** Extract JSON-LD blocks of a given @type (e.g. Product, Offer) from HTML. */ export function jsonLd(html: string, type?: string): Record[] { const $ = cheerio.load(html); const out: Record[] = []; $('script[type="application/ld+json"]').each((_, el) => { const raw = $(el).contents().text(); try { const parsed = JSON.parse(raw) as unknown; const items = Array.isArray(parsed) ? parsed : (parsed && typeof parsed === 'object' && '@graph' in (parsed as object) ? ((parsed as { '@graph': unknown[] })['@graph'] ?? []) : [parsed]); for (const it of items) { if (it && typeof it === 'object') { const t = (it as { '@type'?: string | string[] })['@type']; const types = Array.isArray(t) ? t : t ? [t] : []; if (!type || types.includes(type)) out.push(it as Record); } } } catch { /* ignore malformed JSON-LD */ } }); return out; } /** Extract Next.js __NEXT_DATA__ payload when present. */ export function nextData(html: string): unknown | null { const m = html.match(/