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('')
&& code.includes('