Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149import { existsSync } from "node:fs";import { readFile, writeFile } from "node:fs/promises";import { createRequire } from "node:module";import { tmpdir } from "node:os";import path from "node:path";import { fileURLToPath, pathToFileURL } from "node:url";
import type { ReportData } from "./data";
const here = path.dirname(fileURLToPath(import.meta.url));const web = path.resolve(here, "../../../web");const modules = path.resolve(process.env.E2E_WEB_MODULES ?? path.join(web, "node_modules"));const fonts = path.join(web, "static");
interface Resolver { resolve: (id: string, importer: string | undefined, options: object) => Promise<unknown>;}
const bare = /^(?![./])(@[\w.-]+\/)?[\w.-]+(\/|$)/;
const [input, out] = process.argv.slice(2);if (!existsSync(path.join(modules, "vite"))) { console.error( `${modules} has no vite, run pnpm install in web/ or pass --web-modules=<a web node_modules>` ); process.exit(1);}
const require = createRequire(path.join(modules, "noop.js"));const load = (name: string, from = require) => import(pathToFileURL(from.resolve(name)).href);
// tailwind only searches up from web/src, so it misses packages installed elsewhere// take the esm entry, cjs gives tailwind a module object instead of the pluginconst entry = (condition: "style" | "import") => async (id: string) => { if (!bare.test(id)) return undefined; const manifest = path.join(modules, id, "package.json"); const pkg = JSON.parse(await readFile(manifest, "utf8")); const fallback = condition === "style" ? pkg.style : (pkg.module ?? pkg.main); return path.join(path.dirname(manifest), pkg.exports?.["."]?.[condition] ?? fallback);};
const renderHtml = async (data: ReportData) => { const { createServer } = await load("vite"); const { svelte } = await load("@sveltejs/vite-plugin-svelte"); const { default: icons } = await load("unplugin-icons/vite"); const server = await createServer({ configFile: false, root: web, logLevel: "error", // vite's default cache would land in web/node_modules cacheDir: path.join(tmpdir(), "e2e-report-vite"), appType: "custom", server: { middlewareMode: true, hmr: false, ws: false, watch: null }, optimizeDeps: { noDiscovery: true, include: [] }, ssr: { noExternal: true }, // the tsconfig extends .svelte-kit, which only exists after svelte-kit sync oxc: { tsconfig: false }, resolve: { alias: [ { find: /^\$lib\//, replacement: `${web}/src/lib/` }, { find: /^\$icon\//, replacement: "~icons/lucide/" } ] }, plugins: [ // web/ may have no node_modules, so bare imports fall back to ours { name: "web-modules", enforce: "pre", async resolveId( this: Resolver, id: string, importer: string | undefined, options: object ) { if (!bare.test(id)) return null; const own = await this.resolve(id, importer, { ...options, skipSelf: true }); const fallback = path.join(modules, "noop.js"); return own ?? this.resolve(id, fallback, { ...options, skipSelf: true }); } }, svelte({ configFile: false, compilerOptions: { css: "injected", runes: true, experimental: { async: true } } }), icons({ compiler: "svelte", collectionsNodeResolvePath: path.dirname(modules) }) ] }); try { const { render } = await server.ssrLoadModule("svelte/server"); const { default: Report } = await server.ssrLoadModule(path.join(here, "Report.svelte")); return await render(Report, { props: { data } }); } finally { await server.close(); }};
const buildCss = async (html: string) => { const node = await load( "@tailwindcss/node", createRequire(require.resolve("@tailwindcss/vite")) ); const compiler = await node.compile(`@import "${web}/src/app.css";`, { base: path.join(web, "src"), onDependency: () => {}, customCssResolver: entry("style"), customJsResolver: entry("import") }); const candidates = new Set<string>(); for (const [, list] of html.matchAll(/class="([^"]*)"/g)) for (const name of list.split(/\s+/)) if (name) candidates.add(name); return inlineFonts(compiler.build([...candidates]));};
// the report opens from disk so /fonts urls miss, and italics are cut for sizeconst inlineFonts = async (css: string) => { const faces = css.match(/@font-face\s*{[^}]*}/g) ?? []; for (const face of faces) { const url = face.match(/url\("?(\/fonts\/[^")]+)"?\)/)?.[1]; const file = url && path.join(fonts, url); if (/font-style:\s*italic/.test(face) || !file || !existsSync(file)) { css = css.replace(face, ""); continue; } const data = `data:font/woff2;base64,${(await readFile(file)).toString("base64")}`; css = css.replace(face, face.replace(url, data)); } return css;};
const data: ReportData = JSON.parse(await readFile(input, "utf8"));const { head, body } = await renderHtml(data);const css = await buildCss(body);const script = await readFile(path.join(here, "search.js"), "utf8");await writeFile( out, `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>e2e report</title><style>${css}</style>${head}</head><body>${body}<script>${script}</script></body></html>`);