import * as fs from 'node:fs/promises'; import * as os from 'node:os'; import * as path from 'node:path'; export const UIVERSE_BASE_URL = 'https://uiverse.io'; const ROUTE_DATA_KEY = 'routes/$username.$friendlyId'; const CODE_DATA_KEY = 'routes/resource.post.code.$id'; const EXPORT_TARGET_BUTTON_LABELS = ['React', 'Vue', 'Svelte', 'Lit']; function trimPathSegment(value) { return String(value || '').trim().replace(/^\/+|\/+$/g, ''); } export function parseComponentInput(input) { const raw = String(input || '').trim(); if (!raw) { throw new Error('Missing component input. Pass a full Uiverse URL or an author/slug identifier.'); } let pathname = raw; if (/^https?:\/\//i.test(raw)) { const url = new URL(raw); if (url.hostname !== 'uiverse.io' && url.hostname !== 'www.uiverse.io') { throw new Error(`Unsupported non-Uiverse URL: ${raw}`); } pathname = url.pathname; } const cleaned = trimPathSegment(pathname); const segments = cleaned.split('/').filter(Boolean); if (segments.length !== 2) { throw new Error(`Could not parse author/slug from input: ${raw}`); } const [username, slug] = segments; if (!username || !slug) { throw new Error(`Invalid component identifier: ${raw}. Expected author/slug.`); } return { raw, username, slug, url: `${UIVERSE_BASE_URL}/${username}/${slug}`, }; } async function fetchJsonInBrowser(page, url) { const raw = await page.evaluate(`(async () => { const url = ${JSON.stringify(url)}; const response = await fetch(url, { credentials: 'include', headers: { accept: 'application/json, text/plain, */*', }, }); const text = await response.text(); return JSON.stringify({ ok: response.ok, status: response.status, statusText: response.statusText, text, url, }); })()`); const result = JSON.parse(raw); if (!result?.ok) { throw new Error(`Request failed: ${result?.status} ${result?.statusText} (${result?.url || url})`); } try { return JSON.parse(result.text); } catch { throw new Error(`Response was not valid JSON: ${url}`); } } export async function getPostDetails(page, input) { const normalized = parseComponentInput(input); await page.goto(normalized.url); const raw = await page.evaluate(`(async () => { const key = ${JSON.stringify(ROUTE_DATA_KEY)}; const loaderData = window.__remixContext?.state?.loaderData || {}; const routeData = loaderData[key]; return JSON.stringify({ routeData: routeData || null, keys: Object.keys(loaderData) }); })()`); const parsed = JSON.parse(raw); let routeData = parsed?.routeData; if (!routeData?.post?.id) { const routeUrl = `${normalized.url}?_data=${encodeURIComponent(ROUTE_DATA_KEY)}`; routeData = await fetchJsonInBrowser(page, routeUrl); } if (!routeData?.post?.id) { throw new Error(`Could not resolve post.id from the component page: ${normalized.url}`); } return { ...normalized, post: routeData.post, routeData, }; } export async function getRawCode(page, postId) { const codeUrl = `${UIVERSE_BASE_URL}/resource/post/code/${postId}?v=1&_data=${encodeURIComponent(CODE_DATA_KEY)}`; const payload = await fetchJsonInBrowser(page, codeUrl); if (typeof payload?.html !== 'string' || typeof payload?.css !== 'string') { throw new Error(`Unexpected code payload shape: ${codeUrl}`); } return payload; } export function inferLanguage(target, post) { if (target === 'react') return 'tsx'; if (target === 'vue') return 'vue'; if (target === 'html') return post?.isTailwind ? 'html+tailwind' : 'html'; if (target === 'css') return 'css'; return 'text'; } export function getCodeLength(code) { return String(code || '').length; } function normalizeExportTarget(target) { return String(target || '').trim().toLowerCase() === 'vue' ? 'Vue' : 'React'; } export async function extractExportCode(page, target = 'react') { const targetLabel = normalizeExportTarget(target); const raw = await page.evaluate(`(async () => { const targetLabel = ${JSON.stringify(targetLabel)}; const exportButtonLabel = 'Export'; const exportTargetButtonLabels = ${JSON.stringify(EXPORT_TARGET_BUTTON_LABELS)}; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const triggerClick = (element) => { if (!element) return; element.focus?.(); const pointer = { bubbles: true, cancelable: true, composed: true, view: window }; const mouse = { bubbles: true, cancelable: true, composed: true, view: window, button: 0, buttons: 1 }; element.dispatchEvent(new PointerEvent('pointerdown', pointer)); element.dispatchEvent(new MouseEvent('mousedown', mouse)); element.dispatchEvent(new PointerEvent('pointerup', pointer)); element.dispatchEvent(new MouseEvent('mouseup', mouse)); element.dispatchEvent(new MouseEvent('click', mouse)); }; const isCompleteExportCode = (code) => { if (!code) return false; if (targetLabel === 'Vue') { return code.includes('') && code.includes(''); } return code.includes('export default') && (code.includes('styled-components') || code.includes('StyledWrapper') || code.includes('styled.')); }; const readCode = () => { const dialog = document.querySelector('[role="dialog"]'); if (!dialog) return null; const heading = dialog.querySelector('h1,h2,h3,h4,h5,h6'); if (heading && (heading.textContent || '').trim() !== targetLabel) return null; const textarea = dialog.querySelector('textarea'); if (textarea && textarea.value) return textarea.value; return null; }; const exportButton = [...document.querySelectorAll('button')].find((element) => (element.textContent || '').trim() === exportButtonLabel); const currentTargetButton = [...document.querySelectorAll('button')].find((element) => { const text = (element.textContent || '').trim(); return exportTargetButtonLabels.includes(text); }); const existing = readCode(); if (!existing && (!exportButton || !currentTargetButton)) { return JSON.stringify({ ok: false, error: 'Could not find the export controls on the page.' }); } if (!existing) { const currentLabel = (currentTargetButton.textContent || '').trim(); if (currentLabel === targetLabel) { triggerClick(exportButton); } else { triggerClick(currentTargetButton); let menuItem = null; for (let index = 0; index < 20; index += 1) { menuItem = [...document.querySelectorAll('[role="menuitem"]')].find((element) => (element.textContent || '').trim() === targetLabel); if (menuItem) break; await sleep(100); } if (!menuItem) { return JSON.stringify({ ok: false, error: 'Could not find target in export menu: ' + targetLabel }); } triggerClick(menuItem); } } let longest = existing || ''; let longestLooksComplete = isCompleteExportCode(longest); let stableCount = 0; for (let index = 0; index < 40; index += 1) { await sleep(200); const code = readCode(); if (!code) continue; if (code.length > longest.length) { longest = code; longestLooksComplete = isCompleteExportCode(code); stableCount = 0; continue; } if (code === longest) { if (longestLooksComplete) { stableCount += 1; if (stableCount >= 2) { return JSON.stringify({ ok: true, code: longest, length: longest.length }); } } else { stableCount = 0; } } } const dialog = document.querySelector('[role="dialog"]'); if (longest && longestLooksComplete) { return JSON.stringify({ ok: true, code: longest, length: longest.length, fallback: true }); } return JSON.stringify({ ok: false, error: dialog ? (targetLabel + ' dialog appeared, but the exported code never reached a stable complete state.') : (targetLabel + ' export dialog did not appear after clicking the export controls.'), dialogFound: Boolean(dialog), dialogText: dialog ? (dialog.innerText || '').slice(0, 200) : null, longestLength: longest.length, }); })()`); const data = JSON.parse(raw); if (!data?.ok || typeof data.code !== 'string') { throw new Error(data?.error || `Failed to extract ${targetLabel} export code.`); } return data.code; } export function parseHtmlRootSignature(html) { const source = String(html || '').trim(); const match = source.match(/^<([a-zA-Z0-9-]+)([^>]*)>/); if (!match) { return { tag: null, id: null, classes: [] }; } const [, tag, attrs] = match; const idMatch = attrs.match(/\sid=["']([^"']+)["']/i); const classMatch = attrs.match(/\sclass=["']([^"']+)["']/i); const classes = classMatch ? classMatch[1].split(/\s+/).filter(Boolean) : []; return { tag: tag.toLowerCase(), id: idMatch ? idMatch[1] : null, classes, }; } export function getPreviewFallbackTags(signature) { const tag = String(signature?.tag || '').toLowerCase(); if (!tag) return ['label', 'button', 'a', 'div']; if (tag === 'input') return ['input', 'label', 'button', 'a', 'div']; return [tag]; } export async function locatePreviewElement(page, html) { const signature = parseHtmlRootSignature(html); const raw = await page.evaluate(`(async () => { const sig = ${JSON.stringify(signature)}; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const fallbackTags = ${JSON.stringify(getPreviewFallbackTags(signature))}; const getSearchRoots = () => { const roots = [document]; const seen = new Set([document]); for (let index = 0; index < roots.length; index += 1) { const root = roots[index]; const nodes = root.querySelectorAll ? Array.from(root.querySelectorAll('*')) : []; for (const node of nodes) { const shadowRoot = node.shadowRoot; if (shadowRoot && !seen.has(shadowRoot)) { seen.add(shadowRoot); roots.push(shadowRoot); } } } return roots; }; const queryAcrossRoots = (selector, limit = 200) => { const results = []; const seen = new Set(); for (const root of getSearchRoots()) { const matches = root.querySelectorAll ? Array.from(root.querySelectorAll(selector)) : []; for (const match of matches) { if (seen.has(match)) continue; seen.add(match); results.push(match); if (results.length >= limit) return results; } } return results; }; const isVisible = (element) => { if (!element || !(element instanceof Element)) return false; if (element.closest('[role="dialog"]')) return false; const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false; const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.height > 0; }; const scoreCandidate = (element, source) => { const rect = element.getBoundingClientRect(); const style = window.getComputedStyle(element); const centerX = rect.x + rect.width / 2; const centerY = rect.y + rect.height / 2; const area = rect.width * rect.height; let score = 0; if (sig.tag && element.tagName.toLowerCase() === sig.tag) score += 30; if (sig.id && element.id === sig.id) score += 120; if (sig.classes.length && sig.classes.every((className) => element.classList.contains(className))) score += 120; if (sig.tag === 'input' && source === 'shadow-host') score += 220; if (sig.tag === 'input' && element.tagName.toLowerCase() === 'label') score += 80; if (element.id && element.id.includes('shadow-root')) score += 80; if (centerX <= viewportWidth * 0.65) score += 40; if (centerY <= viewportHeight * 0.6) score += 40; if (area <= viewportWidth * viewportHeight * 0.2) score += 30; if (area <= viewportWidth * viewportHeight * 0.05) score += 20; if (style.position === 'fixed') score -= 180; if (rect.height <= 24 && rect.width >= viewportWidth * 0.8) score -= 120; return { source, tag: element.tagName.toLowerCase(), className: element.className || '', id: element.id || '', score, rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height, }, }; }; const candidates = []; const seen = new Set(); const collect = (element, source) => { if (!isVisible(element)) return; if (seen.has(element)) return; seen.add(element); candidates.push(scoreCandidate(element, source)); }; if (sig.id) { for (const element of queryAcrossRoots('#' + CSS.escape(sig.id), 20)) { collect(element, 'id'); } } if (sig.classes.length) { const classSelector = '.' + sig.classes.map((className) => CSS.escape(className)).join('.'); for (const element of queryAcrossRoots(classSelector, 20)) { collect(element, 'classes'); } } if (sig.tag === 'input') { for (const element of queryAcrossRoots('#shadow-root-div-ready,[id*="shadow-root"]', 20)) { collect(element, 'shadow-host'); } } for (const tagName of fallbackTags) { const tagNodes = queryAcrossRoots(tagName, 200); for (const node of tagNodes.slice(0, 200)) { collect(node, 'tag:' + tagName); } } candidates.sort((left, right) => { if (right.score !== left.score) return right.score - left.score; if (left.rect.y !== right.rect.y) return left.rect.y - right.rect.y; if (left.rect.x !== right.rect.x) return left.rect.x - right.rect.x; return (left.rect.width * left.rect.height) - (right.rect.width * right.rect.height); }); return JSON.stringify({ signature: sig, best: candidates[0] || null, candidates: candidates.slice(0, 5) }); })()`); const result = JSON.parse(raw); if (!result?.best?.rect?.width || !result?.best?.rect?.height) { throw new Error(`Could not locate a Uiverse preview element. Candidate data: ${JSON.stringify(result)}`); } return result; } export function getDefaultOutputPath({ username, slug, suffix, extension }) { const safeUsername = trimPathSegment(username).replace(/[^a-zA-Z0-9-_]/g, '-'); const safeSlug = trimPathSegment(slug).replace(/[^a-zA-Z0-9-_]/g, '-'); return path.join(os.tmpdir(), `opencli-uiverse-${safeUsername}-${safeSlug}-${suffix}.${extension}`); } export async function saveBase64File(base64, outputPath) { const resolved = path.resolve(outputPath); await fs.mkdir(path.dirname(resolved), { recursive: true }); await fs.writeFile(resolved, Buffer.from(base64, 'base64')); return resolved; }