@hyperframes/parsers 0.8.64 → 0.8.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -55,13 +55,18 @@ interface ParsableDocumentLike {
|
|
|
55
55
|
innerHTML?: string | null;
|
|
56
56
|
} | null;
|
|
57
57
|
}
|
|
58
|
+
/** Resolve authored content without invoking linkedom's synthetic body getter. */
|
|
59
|
+
declare function resolveSubCompositionContent(html: string, document: ParsableDocumentLike): {
|
|
60
|
+
contentHtml: string;
|
|
61
|
+
hasTemplate: boolean;
|
|
62
|
+
};
|
|
58
63
|
/**
|
|
59
64
|
* Check whether `html` (the raw file contents resolved for a
|
|
60
65
|
* `data-composition-src` reference) is non-empty and parses to a document
|
|
61
66
|
* that actually contains renderable content.
|
|
62
67
|
*
|
|
63
68
|
* Mirrors the content-detection steps in `inlineSubCompositions` exactly
|
|
64
|
-
* (resolve → parse →
|
|
69
|
+
* (resolve → parse → resolve template, body, or fragment content → parse that →
|
|
65
70
|
* confirm a `[data-composition-id]` root exists in it), so a file that
|
|
66
71
|
* passes this check is guaranteed to produce non-empty output from the
|
|
67
72
|
* inliner, and a file that fails it is guaranteed to hit one of the
|
|
@@ -76,4 +81,4 @@ interface ParsableDocumentLike {
|
|
|
76
81
|
*/
|
|
77
82
|
declare function checkSubCompositionUsability(html: string | null | undefined, parseHtml: (html: string) => ParsableDocumentLike): SubCompositionValidity;
|
|
78
83
|
|
|
79
|
-
export { type ParsableDocumentLike, type SubCompositionValidity, type SubCompositionValidityReason, checkSubCompositionUsability };
|
|
84
|
+
export { type ParsableDocumentLike, type SubCompositionValidity, type SubCompositionValidityReason, checkSubCompositionUsability, resolveSubCompositionContent };
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/subCompositionValidity.ts
|
|
2
|
+
function resolveSubCompositionContent(html, document) {
|
|
3
|
+
const template = document.querySelector("template");
|
|
4
|
+
if (template) return { contentHtml: template.innerHTML ?? "", hasTemplate: true };
|
|
5
|
+
const body = document.querySelector("body");
|
|
6
|
+
if (body) return { contentHtml: body.innerHTML ?? "", hasTemplate: false };
|
|
7
|
+
const contentHtml = document.querySelector("html") ? "" : html;
|
|
8
|
+
return { contentHtml, hasTemplate: false };
|
|
9
|
+
}
|
|
2
10
|
function checkSubCompositionUsability(html, parseHtml) {
|
|
3
11
|
if (html == null || !html.trim()) {
|
|
4
12
|
return {
|
|
@@ -15,8 +23,7 @@ function checkSubCompositionUsability(html, parseHtml) {
|
|
|
15
23
|
detail: "the file's contents could not be parsed as HTML"
|
|
16
24
|
};
|
|
17
25
|
}
|
|
18
|
-
const
|
|
19
|
-
const contentHtml = contentRoot ? contentRoot.innerHTML || "" : compDoc.body?.innerHTML || "";
|
|
26
|
+
const { contentHtml } = resolveSubCompositionContent(html, compDoc);
|
|
20
27
|
if (!contentHtml.trim()) {
|
|
21
28
|
return {
|
|
22
29
|
ok: false,
|
|
@@ -29,19 +36,20 @@ function checkSubCompositionUsability(html, parseHtml) {
|
|
|
29
36
|
return {
|
|
30
37
|
ok: false,
|
|
31
38
|
reason: "unparsable",
|
|
32
|
-
detail: "the file's
|
|
39
|
+
detail: "the file's resolved contents could not be parsed as HTML"
|
|
33
40
|
};
|
|
34
41
|
}
|
|
35
42
|
if (!contentDoc.querySelector("[data-composition-id]")) {
|
|
36
43
|
return {
|
|
37
44
|
ok: false,
|
|
38
45
|
reason: "no-composition-root",
|
|
39
|
-
detail: "the file's
|
|
46
|
+
detail: "the file's resolved content has no element with a data-composition-id attribute"
|
|
40
47
|
};
|
|
41
48
|
}
|
|
42
49
|
return { ok: true };
|
|
43
50
|
}
|
|
44
51
|
export {
|
|
45
|
-
checkSubCompositionUsability
|
|
52
|
+
checkSubCompositionUsability,
|
|
53
|
+
resolveSubCompositionContent
|
|
46
54
|
};
|
|
47
55
|
//# sourceMappingURL=subCompositionValidity.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/subCompositionValidity.ts"],"sourcesContent":["/**\n * Shared \"is this sub-composition file usable?\" check.\n *\n * `data-composition-src` files are authored by AI agents far more often than\n * by humans clicking a UI. The dominant real-world failure is a scene worker\n * that dies mid-write (or a step that references a scene before writing it),\n * leaving an empty or partial `compositions/scene-*.html` on disk. Historically\n * this surfaced in three different ways depending on which code path touched\n * the file first:\n *\n * 1. A raw crash inside linkedom's `Document.head` getter — destructuring\n * `firstElementChild` off a `null` `documentElement` — when the file is\n * empty or contains no parseable markup.\n * 2. An actionable-but-late `Error` thrown deep inside the render compiler\n * (see git history: #1364), which aborted the whole render.\n * 3. A silent skip (see git history: #1678) that drops the scene from the\n * output with only a `console.warn`, producing a materially broken\n * video (missing scene, no error surfaced anywhere) with no clear\n * signal to the caller.\n *\n * This module gives every consumer (lint, render pre-flight, the tolerant\n * inliner) a single, shared definition of \"usable\" so they can never\n * disagree about whether a given file would render something. It lives in\n * `@hyperframes/parsers` (rather than `@hyperframes/core`, where it\n * originated) because `@hyperframes/lint` needs it too, and `lint` cannot\n * depend on `core` — `core` already depends on `lint` — so this shared,\n * dependency-free check lives in the common ancestor package both `core`\n * and `lint` already depend on.\n *\n * `inlineSubCompositions.ts` (in `@hyperframes/core`) intentionally stays\n * tolerant (skip + continue) for the preview/studio bundling path, where\n * partial content while iterating is expected. `lint` and the render\n * pre-flight check (`packages/producer/src/services/htmlCompiler.ts`) use\n * this helper to fail loudly and name the exact offending file, because a\n * render that silently drops a scene is strictly worse than a render that\n * refuses to start.\n */\n\nexport type SubCompositionValidityReason =\n | \"empty\"\n | \"unparsable\"\n | \"no-content\"\n | \"no-composition-root\";\n\nexport interface SubCompositionValidity {\n ok: boolean;\n /** Present when `ok` is false. */\n reason?: SubCompositionValidityReason;\n /** Human-readable detail suitable for direct inclusion in an error message. */\n detail?: string;\n}\n\n/** Minimal shape both linkedom's `Document` and `happy-dom`'s satisfy. */\nexport interface ParsableDocumentLike {\n documentElement: { outerHTML?: string } | null;\n body?: { innerHTML?: string | null } | null;\n querySelector(selector: string): { innerHTML?: string | null } | null;\n}\n\n/**\n * Check whether `html` (the raw file contents resolved for a\n * `data-composition-src` reference) is non-empty and parses to a document\n * that actually contains renderable content.\n *\n * Mirrors the content-detection steps in `inlineSubCompositions` exactly\n * (resolve → parse →
|
|
1
|
+
{"version":3,"sources":["../src/subCompositionValidity.ts"],"sourcesContent":["/**\n * Shared \"is this sub-composition file usable?\" check.\n *\n * `data-composition-src` files are authored by AI agents far more often than\n * by humans clicking a UI. The dominant real-world failure is a scene worker\n * that dies mid-write (or a step that references a scene before writing it),\n * leaving an empty or partial `compositions/scene-*.html` on disk. Historically\n * this surfaced in three different ways depending on which code path touched\n * the file first:\n *\n * 1. A raw crash inside linkedom's `Document.head` getter — destructuring\n * `firstElementChild` off a `null` `documentElement` — when the file is\n * empty or contains no parseable markup.\n * 2. An actionable-but-late `Error` thrown deep inside the render compiler\n * (see git history: #1364), which aborted the whole render.\n * 3. A silent skip (see git history: #1678) that drops the scene from the\n * output with only a `console.warn`, producing a materially broken\n * video (missing scene, no error surfaced anywhere) with no clear\n * signal to the caller.\n *\n * This module gives every consumer (lint, render pre-flight, the tolerant\n * inliner) a single, shared definition of \"usable\" so they can never\n * disagree about whether a given file would render something. It lives in\n * `@hyperframes/parsers` (rather than `@hyperframes/core`, where it\n * originated) because `@hyperframes/lint` needs it too, and `lint` cannot\n * depend on `core` — `core` already depends on `lint` — so this shared,\n * dependency-free check lives in the common ancestor package both `core`\n * and `lint` already depend on.\n *\n * `inlineSubCompositions.ts` (in `@hyperframes/core`) intentionally stays\n * tolerant (skip + continue) for the preview/studio bundling path, where\n * partial content while iterating is expected. `lint` and the render\n * pre-flight check (`packages/producer/src/services/htmlCompiler.ts`) use\n * this helper to fail loudly and name the exact offending file, because a\n * render that silently drops a scene is strictly worse than a render that\n * refuses to start.\n */\n\nexport type SubCompositionValidityReason =\n | \"empty\"\n | \"unparsable\"\n | \"no-content\"\n | \"no-composition-root\";\n\nexport interface SubCompositionValidity {\n ok: boolean;\n /** Present when `ok` is false. */\n reason?: SubCompositionValidityReason;\n /** Human-readable detail suitable for direct inclusion in an error message. */\n detail?: string;\n}\n\n/** Minimal shape both linkedom's `Document` and `happy-dom`'s satisfy. */\nexport interface ParsableDocumentLike {\n documentElement: { outerHTML?: string } | null;\n body?: { innerHTML?: string | null } | null;\n querySelector(selector: string): { innerHTML?: string | null } | null;\n}\n\n/** Resolve authored content without invoking linkedom's synthetic body getter. */\nexport function resolveSubCompositionContent(html: string, document: ParsableDocumentLike) {\n const template = document.querySelector(\"template\");\n if (template) return { contentHtml: template.innerHTML ?? \"\", hasTemplate: true };\n\n const body = document.querySelector(\"body\");\n if (body) return { contentHtml: body.innerHTML ?? \"\", hasTemplate: false };\n\n // Browsers create a body for bare fragments; linkedom keeps their top-level nodes.\n const contentHtml = document.querySelector(\"html\") ? \"\" : html;\n return { contentHtml, hasTemplate: false };\n}\n\n/**\n * Check whether `html` (the raw file contents resolved for a\n * `data-composition-src` reference) is non-empty and parses to a document\n * that actually contains renderable content.\n *\n * Mirrors the content-detection steps in `inlineSubCompositions` exactly\n * (resolve → parse → resolve template, body, or fragment content → parse that →\n * confirm a `[data-composition-id]` root exists in it), so a file that\n * passes this check is guaranteed to produce non-empty output from the\n * inliner, and a file that fails it is guaranteed to hit one of the\n * inliner's `onMissingComposition` branches.\n *\n * @param html Raw file contents, or `null`/`undefined` if the file could not\n * be read (e.g. missing from disk). Callers should distinguish \"missing\"\n * from \"empty\" in their own error message using a separate existence\n * check — this function only inspects content.\n * @param parseHtml Parse an HTML string into a document. Pass linkedom's\n * `parseHTML(html).document` or the core bundler's `parseHTMLContent`.\n */\nexport function checkSubCompositionUsability(\n html: string | null | undefined,\n parseHtml: (html: string) => ParsableDocumentLike,\n): SubCompositionValidity {\n if (html == null || !html.trim()) {\n return {\n ok: false,\n reason: \"empty\",\n detail: \"the file is empty (0 bytes or whitespace-only)\",\n };\n }\n\n const compDoc = parseHtml(html);\n if (!compDoc.documentElement) {\n return {\n ok: false,\n reason: \"unparsable\",\n detail: \"the file's contents could not be parsed as HTML\",\n };\n }\n\n const { contentHtml } = resolveSubCompositionContent(html, compDoc);\n if (!contentHtml.trim()) {\n return {\n ok: false,\n reason: \"no-content\",\n detail: \"the file has no <template> or <body> content to render\",\n };\n }\n\n const contentDoc = parseHtml(contentHtml);\n if (!contentDoc.documentElement) {\n return {\n ok: false,\n reason: \"unparsable\",\n detail: \"the file's resolved contents could not be parsed as HTML\",\n };\n }\n\n // The content must contain an actual composition root — the element the\n // inliner looks for (`contentDoc.querySelector(\"[data-composition-id]\")`)\n // to know what to inject into the host. Well-formed but marker-free HTML\n // (e.g. an AI-authored placeholder like `<body><p>TODO</p></body>`) parses\n // fine and has non-empty content, but has nothing for the inliner to find.\n if (!contentDoc.querySelector(\"[data-composition-id]\")) {\n return {\n ok: false,\n reason: \"no-composition-root\",\n detail: \"the file's resolved content has no element with a data-composition-id attribute\",\n };\n }\n\n return { ok: true };\n}\n"],"mappings":";AA4DO,SAAS,6BAA6B,MAAc,UAAgC;AACzF,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,MAAI,SAAU,QAAO,EAAE,aAAa,SAAS,aAAa,IAAI,aAAa,KAAK;AAEhF,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,MAAI,KAAM,QAAO,EAAE,aAAa,KAAK,aAAa,IAAI,aAAa,MAAM;AAGzE,QAAM,cAAc,SAAS,cAAc,MAAM,IAAI,KAAK;AAC1D,SAAO,EAAE,aAAa,aAAa,MAAM;AAC3C;AAqBO,SAAS,6BACd,MACA,WACwB;AACxB,MAAI,QAAQ,QAAQ,CAAC,KAAK,KAAK,GAAG;AAChC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,CAAC,QAAQ,iBAAiB;AAC5B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,EAAE,YAAY,IAAI,6BAA6B,MAAM,OAAO;AAClE,MAAI,CAAC,YAAY,KAAK,GAAG;AACvB,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,UAAU,WAAW;AACxC,MAAI,CAAC,WAAW,iBAAiB;AAC/B,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAOA,MAAI,CAAC,WAAW,cAAc,uBAAuB,GAAG;AACtD,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/parsers",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.66",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"tsx": "^4.21.0",
|
|
115
115
|
"typescript": "^5.0.0",
|
|
116
116
|
"vitest": "^4.1.11",
|
|
117
|
-
"@hyperframes/core": "0.8.
|
|
117
|
+
"@hyperframes/core": "0.8.66"
|
|
118
118
|
},
|
|
119
119
|
"scripts": {
|
|
120
120
|
"build": "tsup",
|