import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import { parseHTML } from "linkedom"; import { rewriteAssetPaths, rewriteCssAssetUrls, rewriteInlineStyleAssetUrls, } from "@hyperframes/core"; import { stripEmbeddedRuntimeScripts } from "@hyperframes/core/compiler"; /** * Detect whether `html` is a full document (has ``, ``, or * ``-wrapped fragment. * Anchored to start-of-string (ignoring leading whitespace) so stray * occurrences inside script/template content don't false-positive. */ function isFullHtmlDocument(html: string): boolean { return /^\s*(?:])/i.test(html); } /** * Rewrite relative asset paths in a parsed DOM tree. Shared across all * three dispatch branches (template, full-doc, fragment) to avoid drift. */ function rewriteRelativePaths(root: ParentNode, compPath: string): void { rewriteAssetPaths( root.querySelectorAll("[src], [href]"), compPath, (el: Element, attr: string) => el.getAttribute(attr), (el: Element, attr: string, value: string) => el.setAttribute(attr, value), ); rewriteInlineStyleAssetUrls( root.querySelectorAll("[style]"), compPath, (el: Element) => el.getAttribute("style"), (el: Element, value: string) => el.setAttribute("style", value), ); for (const styleEl of root.querySelectorAll("style")) { styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath); } } /** * Escape a CSS identifier whose first character is a digit so it is a valid * selector. A CSS ident cannot start with a digit, so it must be written as an * escaped code point: `01-foo` → `\30 1-foo` (leading `0` → `\30 `, rest kept). * * Only the leading digit needs escaping (per CSS Syntax Level 3 §4.3.11): once * the parser consumes the `\ ` escape, the rest of the ident continues * normally, so `123-scene` → `\31 23-scene` is valid (the `23-scene` tail is * consumed as identifier continuation). The trailing space terminates the hex * escape so a following hex digit isn't folded into the code point. */ function escapeLeadingDigitIdent(id: string): string { return `\\${id.charCodeAt(0).toString(16)} ${id.slice(1)}`; } const REGEXP_SPECIALS = /[.*+?^${}()|[\]\\]/g; /** * Fix `#` selectors in the tree's `