fix(cli): report compile diagnostics in the check report

The bundler emits three warnings -- an un-inlinable color-grading LUT, a
StaticGuard contract breach, and a skipped sub-composition -- as bare
console.warn calls. They never reached the structured check report, so
--json consumers could not see them at all and a terminal reader had to
catch them scrolling past above the report they were reading.

bundleToSingleHtml now takes an optional diagnostics sink, following the
onMissingComposition hook inlineSubCompositions already uses. Without a
sink the calls console.warn exactly as before, with byte-identical text
and a byte-identical bundle; a test pins both. check passes one and
renders a Compile section carrying the same CheckFinding shape lint and
runtime already use.

Two deliberate limits. Compile counts stay out of the aggregate totals,
so --strict fails nothing it passes today: this changes what you can see,
not what passes. And the section carries a reached flag, because an empty
findings list is otherwise ambiguous -- runCheckPipeline short-circuits
before bundling whenever lint has errors, and clean and never-ran must
not print the same. It reports not reached (fix lint errors first)
instead, and is also false when the browser check throws after bundling.

Verified on a lint-clean probe: the LUT warning appears in the Compile
section and in the --json envelope. Removing data-width from the same
probe makes lint fail and the section correctly reads not reached.

Left as follow-ups: sub_composition_skipped is arguably an error for a
render, since you ship a bundle silently missing a scene; and
static_guard_contract fires only when lint has errors, so it is
structurally unreachable through check and surfaces for studio, preview
and render callers only.
This commit is contained in:
Miguel Ángel
2026-08-09 20:14:16 +00:00
parent adb13ce125
commit ebcee69dcc
10 changed files with 348 additions and 20 deletions
+107 -1
View File
@@ -32,6 +32,7 @@ import {
} from "../utils/checkPipeline.js";
import { resolveCompositionViewportFromHtml } from "../utils/compositionViewport.js";
import { consumeCommandResult } from "../utils/commandResult.js";
import type { BundleDiagnostic } from "@hyperframes/core/compiler";
import type { ProjectLintResult } from "../utils/lintProject.js";
import type {
LayoutIssue,
@@ -264,6 +265,7 @@ function dependencies(
runtime?: CheckFinding[];
writeSnapshot?: CheckDependencies["writeSnapshot"];
captureFindingCrops?: CheckDependencies["captureFindingCrops"];
compile?: BundleDiagnostic[];
} = {},
): { deps: CheckDependencies; runBrowserCheck: ReturnType<typeof vi.fn> } {
const runBrowserCheck = vi.fn(
@@ -273,7 +275,11 @@ function dependencies(
motion: MotionSpecResolution,
): Promise<CheckBrowserResult> => {
const result = await runAuditGrid(driver, checkOptions, motion);
return { ...result, runtimeFindings: options.runtime ?? [] };
return {
...result,
runtimeFindings: options.runtime ?? [],
compileDiagnostics: options.compile ?? [],
};
},
);
const deps: CheckDependencies = {
@@ -306,6 +312,16 @@ async function runScenario(
return { report, deps, browser: runBrowserCheck };
}
function lutDiagnostic(): BundleDiagnostic {
return {
code: "color_grading_lut_not_inlined",
severity: "warning",
message:
'[HyperFrames] Could not inline color grading LUT "assets/luts/gone.cube". The rendered bundle may not be self-contained.',
source: "assets/luts/gone.cube",
};
}
function runtimeError(): CheckFinding {
return {
code: "console_error",
@@ -1526,3 +1542,93 @@ describe("dense motion-overlap re-sampling", () => {
expect(report.layout.findings.some((f) => f.code === "content_overlap")).toBe(true);
});
});
describe("compile diagnostics section", () => {
afterEach(() => {
consumeCommandResult();
vi.restoreAllMocks();
});
it("surfaces bundler diagnostics as findings and marks the section reached", async () => {
const { report } = await runScenario(fakeDriver(), {}, { compile: [lutDiagnostic()] });
expect(report.compile.reached).toBe(true);
expect(report.compile.findings).toHaveLength(1);
expect(report.compile.findings[0]).toMatchObject({
code: "color_grading_lut_not_inlined",
severity: "warning",
sourceFile: "assets/luts/gone.cube",
});
expect(report.compile.warningCount).toBe(1);
expect(report.compile.errorCount).toBe(0);
});
it("keeps compile warnings out of the pass/fail gate, even under --strict", async () => {
const { report } = await runScenario(
fakeDriver(),
{ strict: true },
{ compile: [lutDiagnostic()] },
);
expect(report.ok).toBe(true);
expect(checkExitCode(report)).toBe(0);
});
it("reports an empty but reached compile section for a clean project", async () => {
const { report } = await runScenario(fakeDriver());
expect(report.compile.reached).toBe(true);
expect(report.compile.findings).toEqual([]);
expect(report.compile.ok).toBe(true);
});
it("marks compile not-reached when a lint error short-circuits before bundling", async () => {
const { report, browser } = await runScenario(
fakeDriver(),
{},
{ lint: lintWith("error", "missing_composition_id", "boom"), compile: [lutDiagnostic()] },
);
expect(browser).not.toHaveBeenCalled();
expect(report.compile.reached).toBe(false);
expect(report.compile.findings).toEqual([]);
});
it("carries the compile section into the --json envelope", async () => {
const { report } = await runScenario(fakeDriver(), {}, { compile: [lutDiagnostic()] });
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
const command = createCheckCommand({
resolveProject: () => PROJECT,
runPipeline: vi.fn(async () => report),
withMeta: (value) => ({ ...value, _meta: { version: "test" } }),
});
await runCommand(command, { rawArgs: ["--json"] });
const output = log.mock.calls[0]?.[0];
if (typeof output !== "string") throw new Error("expected JSON output");
const envelope = JSON.parse(output) as CheckReport;
expect(envelope.compile).toMatchObject({
ok: true,
reached: true,
warningCount: 1,
findings: [{ code: "color_grading_lut_not_inlined", severity: "warning" }],
});
});
it("prints the compile section in the human report", async () => {
const { report } = await runScenario(fakeDriver(), {}, { compile: [lutDiagnostic()] });
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
const command = createCheckCommand({
resolveProject: () => PROJECT,
runPipeline: vi.fn(async () => report),
withMeta: (value) => value,
});
await runCommand(command, { rawArgs: [] });
const printed = log.mock.calls.map((call) => String(call[0] ?? "")).join("\n");
expect(printed).toContain("Compile");
expect(printed).toContain("color_grading_lut_not_inlined");
});
});
+19
View File
@@ -405,6 +405,7 @@ function nonNegativeNumber(value: unknown, fallback: number): number {
function printHumanReport(report: CheckReport): void {
printSection("Lint", report.lint);
printCompileSection(report);
printSection("Runtime", report.runtime);
printLayoutSection("Layout", report.layout);
printSection("Motion", report.motion);
@@ -415,6 +416,24 @@ function printHumanReport(report: CheckReport): void {
console.log(`${report.ok ? c.success("◇") : c.error("◇")} ${label}`);
}
/** Compile-time bundler diagnostics. `reached: false` means the pipeline
* short-circuited before bundling (any lint error does this), so "0 findings"
* would be a lie — say "not reached" instead. */
function printCompileSection(report: CheckReport): void {
console.log();
console.log(c.bold("Compile"));
if (!report.compile.reached) {
console.log(` ${c.dim("◇")} not reached (fix lint errors first)`);
return;
}
if (report.compile.findings.length === 0) {
console.log(` ${c.success("◇")} 0 errors, 0 warnings`);
return;
}
for (const finding of report.compile.findings) printFinding(finding);
printCounts(report.compile);
}
function printSection(title: string, section: CheckSection): void {
console.log();
console.log(c.bold(title));
@@ -1,3 +1,4 @@
import type { BundleDiagnosticSink } from "@hyperframes/core/compiler";
import { normalizeErrorMessage } from "./errorMessage.js";
import { c } from "../ui/colors.js";
@@ -20,9 +21,12 @@ export async function bundleWithLocalizedFonts(
// Injectable for tests. Production callers omit it and get the producer
// font-localization pass (see localizeWithProducer).
localizeFonts: (html: string) => Promise<string> = localizeWithProducer,
// Optional compile-diagnostic sink, threaded straight through to the bundler.
// Omit it and the bundler keeps console.warn-ing exactly as before.
onDiagnostic?: BundleDiagnosticSink,
): Promise<string> {
const { bundleToSingleHtml } = await import("@hyperframes/core/compiler");
const html = await bundleToSingleHtml(projectDir);
const html = await bundleToSingleHtml(projectDir, onDiagnostic ? { onDiagnostic } : undefined);
return localizeFonts(html);
}
+5 -1
View File
@@ -138,7 +138,11 @@ it("carries raw browser geometry through the page driver and pipeline", async ()
runAuditGrid,
);
expect(mocks.bundleWithLocalizedFonts).toHaveBeenCalledWith(PROJECT.dir);
expect(mocks.bundleWithLocalizedFonts).toHaveBeenCalledWith(
PROJECT.dir,
undefined,
expect.any(Function),
);
expect(result.layoutIssues).toEqual([
expect.objectContaining({
code: "frame_out_of_frame",
+9 -1
View File
@@ -1,6 +1,7 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import type { Page } from "puppeteer-core";
import type { BundleDiagnostic } from "@hyperframes/core/compiler";
import {
AUDIT_SEEK_OPTIONS,
DENSE_GEOMETRY_SEEK_OPTIONS,
@@ -150,7 +151,13 @@ export async function runBrowserCheck(
runGrid: RunAuditGrid,
): Promise<CheckBrowserResult> {
const { bundleWithLocalizedFonts } = await import("./bundleWithLocalizedFonts.js");
const html = await bundleWithLocalizedFonts(project.dir);
// Compile diagnostics are captured here rather than console.warn-ed, so they
// land in the check report. captureFindingCrops re-bundles the same project
// and deliberately does NOT collect them again (they would be duplicates).
const compileDiagnostics: BundleDiagnostic[] = [];
const html = await bundleWithLocalizedFonts(project.dir, undefined, (diagnostic) =>
compileDiagnostics.push(diagnostic),
);
await preResolveHostileMediaProxies(project.dir, html, options.autoProxy);
const server = await serveStaticProjectHtml(
project.dir,
@@ -195,6 +202,7 @@ export async function runBrowserCheck(
...result,
timings: { ...result.timings, launchSettleMs },
runtimeFindings: drafts.map((draft) => runtimeFinding(draft, rootAnchor)),
compileDiagnostics,
};
} finally {
await chromeBrowser?.close().catch(() => undefined);
+28 -1
View File
@@ -21,6 +21,7 @@ import {
type MotionFrame,
} from "./motionAudit.js";
import { findMotionSpec, readMotionSpec } from "./motionSpec.js";
import type { BundleDiagnostic } from "@hyperframes/core/compiler";
import { normalizeErrorMessage } from "./errorMessage.js";
import {
parseColorRGBA,
@@ -1097,6 +1098,8 @@ export async function runAuditGrid(
contrastPassed: contrast.passed,
screenshots: collected.screenshots,
timings: { launchSettleMs: 0, seekLoopMs, contrastMs: collected.contrastMs },
// The grid never bundles; runBrowserCheck owns bundling and overwrites this.
compileDiagnostics: [],
};
}
@@ -1132,17 +1135,22 @@ export async function runCheckPipeline(
}
let browser: CheckBrowserResult;
// Bundling happens inside runBrowserCheck, so a throw from there (a Chrome
// launch failure, a bundler error) leaves the compile pass unobserved rather
// than clean. Track that instead of letting an empty section read as a pass.
let bundlingReached = true;
try {
browser = await dependencies.runBrowserCheck(project, options, motion);
} catch (error) {
browser = emptyBrowserResult();
browser.runtimeFindings.push(runtimeFailure(error));
bundlingReached = false;
}
const snapshotFiles = options.snapshots
? await writeContrastSnapshots(dependencies, project.dir, browser)
: [];
const report = buildReport(options, lint, browser, motion, [], snapshotFiles);
const report = buildReport(options, lint, browser, motion, [], snapshotFiles, bundlingReached);
return options.snapshots
? await withFindingCrops(dependencies, project, options, report)
: report;
@@ -1322,7 +1330,12 @@ function buildReport(
motion: MotionSpecResolution,
extraMotionFindings: CheckFinding[],
snapshotFiles: string[],
bundlingReached = false,
): CheckReport {
const compile = {
...section(browser.compileDiagnostics.map(compileFinding)),
reached: bundlingReached,
};
const layout = shapeLayoutSection(browser.layoutIssues, browser, options);
const shapedMotion = shapeLayoutFindings(browser.motionIssues, options);
const motionFindings: CheckFinding[] = [...shapedMotion.findings, ...extraMotionFindings];
@@ -1345,6 +1358,7 @@ function buildReport(
ok: errorCount === 0 && (!options.strict || warningCount === 0),
strict: options.strict,
lint,
compile,
runtime,
layout,
motion: {
@@ -1468,9 +1482,22 @@ function emptyBrowserResult(): CheckBrowserResult {
contrastPassed: 0,
screenshots: [],
timings: { launchSettleMs: 0, seekLoopMs: 0, contrastMs: 0 },
compileDiagnostics: [],
};
}
/** A compile diagnostic as a report finding. The bundler has no source
* positions at these sites, so it anchors at the composition root like the
* lint and motion-spec findings do. */
function compileFinding(diagnostic: BundleDiagnostic): CheckFinding {
return findingAtRoot(
diagnostic.code,
diagnostic.severity,
diagnostic.message,
diagnostic.source ?? "index.html",
);
}
function runtimeFailure(error: unknown, code = "check_runtime_failure"): CheckFinding {
return findingAtRoot(code, "error", normalizeErrorMessage(error), "index.html");
}
+17
View File
@@ -1,3 +1,4 @@
import type { BundleDiagnostic } from "@hyperframes/core/compiler";
import type { ProjectLintResult } from "./lintProject.js";
import type { LayoutIssue, LayoutOverflow, LayoutRect } from "./layoutAudit.js";
import type { Canvas, MotionFrame } from "./motionAudit.js";
@@ -242,6 +243,10 @@ export interface CheckBrowserResult {
contrastPassed: number;
screenshots: CheckScreenshot[];
timings: CheckTimings;
/** Compile-time diagnostics collected while bundling the project for the
* browser pass. Empty whenever bundling never ran (lint-error short-circuit,
* a browser-launch failure) — absence is "not observed", not "clean". */
compileDiagnostics: BundleDiagnostic[];
}
/** The seek-grid audit loop, injected into checkBrowser so it never imports checkPipeline back. */
@@ -263,6 +268,18 @@ export interface CheckReport {
ok: boolean;
strict: boolean;
lint: CheckSection & { filesScanned: number };
/**
* Compile-time bundler diagnostics, surfaced as findings so agents and
* `--json` consumers see them instead of losing them to stdout.
*
* Deliberately NOT folded into the report's aggregate error/warning counts:
* every code here is a warning today, and rolling them into the aggregate
* would make `--strict` start failing projects it passes now. Visibility
* first; gating is a separate decision. `reached` says whether bundling
* actually ran — `runCheckPipeline` short-circuits before the bundler when
* lint has any error, so `reached: false` means "unknown", not "none".
*/
compile: CheckSection & { reached: boolean };
runtime: CheckSection;
layout: CheckSection<AnchoredLayoutIssue> & {
duration: number;
+89 -1
View File
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { parseHTML } from "linkedom";
import { describe, it, expect, vi } from "vitest";
import { bundleToSingleHtml } from "./htmlBundler";
import { bundleToSingleHtml, type BundleDiagnostic } from "./htmlBundler";
import { getHyperframeRuntimeScript } from "../generated/runtime-inline";
function makeTempProject(files: Record<string, string>): string {
@@ -1388,3 +1388,91 @@ describe("bundleToSingleHtml", () => {
}
});
});
// ---------------------------------------------------------------------------
// Compile diagnostics: the sink that lets `hyperframes check` report what used
// to vanish into console.warn.
// ---------------------------------------------------------------------------
/** One project that trips all three diagnostic sites at once:
* - no `window.__timelines` registration -> StaticGuard contract breach
* - `data-composition-src` at a file that does not exist -> skipped sub-comp
* - `data-color-grading` LUT at a file that does not exist -> un-inlinable LUT
*/
function makeAllDiagnosticsProject(): string {
return makeTempProject({
"index.html": `<!doctype html>
<html><body>
<div data-composition-id="root" data-width="320" data-height="180">
<div data-composition-src="scenes/gone.html"></div>
<video id="clip" src="clip.mp4" data-color-grading='{"lut":{"src":"assets/luts/gone.cube"}}'></video>
</div>
</body></html>`,
});
}
describe("bundleToSingleHtml compile diagnostics", () => {
it("routes every compile-time diagnostic to the sink instead of console.warn", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
try {
const dir = makeAllDiagnosticsProject();
const diagnostics: BundleDiagnostic[] = [];
await bundleToSingleHtml(dir, { onDiagnostic: (d) => diagnostics.push(d) });
expect(diagnostics.map((d) => d.code).sort()).toEqual([
"color_grading_lut_not_inlined",
"static_guard_contract",
"sub_composition_skipped",
]);
expect(diagnostics.every((d) => d.severity === "warning")).toBe(true);
// A sink takes ownership: nothing leaks to the console behind its back.
expect(warnSpy).not.toHaveBeenCalled();
} finally {
warnSpy.mockRestore();
}
});
it("carries the message and the source each diagnostic already knows", async () => {
const dir = makeAllDiagnosticsProject();
const diagnostics: BundleDiagnostic[] = [];
await bundleToSingleHtml(dir, { onDiagnostic: (d) => diagnostics.push(d) });
const byCode = new Map(diagnostics.map((d) => [d.code, d]));
expect(byCode.get("sub_composition_skipped")?.source).toBe("scenes/gone.html");
expect(byCode.get("sub_composition_skipped")?.message).toContain(
'Skipping sub-composition "scenes/gone.html"',
);
expect(byCode.get("color_grading_lut_not_inlined")?.source).toBe("assets/luts/gone.cube");
expect(byCode.get("color_grading_lut_not_inlined")?.message).toContain(
'Could not inline color grading LUT "assets/luts/gone.cube"',
);
expect(byCode.get("static_guard_contract")?.source).toBe("index.html");
expect(byCode.get("static_guard_contract")?.message).toContain("[StaticGuard]");
});
it("still console.warns, with byte-identical output, when no sink is supplied", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
try {
const dir = makeAllDiagnosticsProject();
const diagnostics: BundleDiagnostic[] = [];
const withSink = await bundleToSingleHtml(dir, {
onDiagnostic: (d) => diagnostics.push(d),
});
expect(warnSpy).not.toHaveBeenCalled();
const withoutSink = await bundleToSingleHtml(dir);
// The sink changes reporting, never the bundle.
expect(withoutSink).toBe(withSink);
// Every diagnostic reaches console.warn with exactly its message, one
// argument, same as before the sink existed.
expect(warnSpy.mock.calls.map((call) => call[0])).toEqual(diagnostics.map((d) => d.message));
expect(warnSpy.mock.calls.every((call) => call.length === 1)).toBe(true);
} finally {
warnSpy.mockRestore();
}
});
});
+67 -14
View File
@@ -320,16 +320,23 @@ function isExternalSvgFragmentUse(el: Element, attr: string, urlValue: string):
return pathBeforeFragment.toLowerCase().endsWith(".svg");
}
function warnColorGradingLutNotInlined(lutSrc: string): void {
function warnColorGradingLutNotInlined(lutSrc: string, emit: BundleDiagnosticSink): void {
const trimmed = lutSrc.trim();
if (!isRelativeUrl(trimmed)) return;
console.warn(
`[HyperFrames] Could not inline color grading LUT "${trimmed}". The rendered bundle may not be self-contained.`,
);
emit({
code: "color_grading_lut_not_inlined",
severity: "warning",
message: `[HyperFrames] Could not inline color grading LUT "${trimmed}". The rendered bundle may not be self-contained.`,
source: trimmed,
});
}
// fallow-ignore-next-line complexity
function rewriteColorGradingLutWithInlinedAssets(value: string, projectDir: string): string {
function rewriteColorGradingLutWithInlinedAssets(
value: string,
projectDir: string,
emit: BundleDiagnosticSink,
): string {
if (!value.trim().startsWith("{")) return value;
let parsed: unknown;
try {
@@ -343,7 +350,7 @@ function rewriteColorGradingLutWithInlinedAssets(value: string, projectDir: stri
if (typeof lut === "string") {
const inlined = maybeInlineRelativeAssetUrl(lut, projectDir);
if (!inlined) {
warnColorGradingLutNotInlined(lut);
warnColorGradingLutNotInlined(lut, emit);
return value;
}
Reflect.set(parsed, "lut", inlined);
@@ -354,7 +361,7 @@ function rewriteColorGradingLutWithInlinedAssets(value: string, projectDir: stri
if (typeof lutSrc !== "string") return value;
const inlined = maybeInlineRelativeAssetUrl(lutSrc, projectDir);
if (!inlined) {
warnColorGradingLutNotInlined(lutSrc);
warnColorGradingLutNotInlined(lutSrc, emit);
return value;
}
Reflect.set(lut, "src", inlined);
@@ -673,9 +680,46 @@ function stripJsCommentsParserSafe(source: string): string {
}
}
/**
* A compile-time diagnostic raised while bundling — the class of problem that
* used to reach only `console.warn` and was therefore invisible to any
* programmatic consumer (`hyperframes check --json`, studio, CI logs).
*
* Severity is `"warning"` for every code emitted today: the bundler is
* deliberately tolerant here (it skips the broken piece and keeps going) and
* nothing about this envelope changes that. Escalating any of these to an
* error is a separate, gating decision.
*/
export interface BundleDiagnostic {
/** Stable machine-readable identifier — safe to match on in tooling. */
code: "static_guard_contract" | "color_grading_lut_not_inlined" | "sub_composition_skipped";
severity: "warning";
/**
* Human-readable text. Byte-identical to the `console.warn` line this
* replaces, so terminal output is unchanged when no sink is supplied.
*/
message: string;
/**
* What the diagnostic is about, when the emitter knows: the entry file, the
* sub-composition `src`, or the LUT URL. Not a line/column — none of these
* sites carry source positions, and inventing one would be a lie.
*/
source?: string;
}
export type BundleDiagnosticSink = (diagnostic: BundleDiagnostic) => void;
export interface BundleOptions {
/** Project-relative HTML entry to bundle. Defaults to `index.html`. */
entryFile?: string;
/**
* Collect compile-time diagnostics instead of logging them. Follows the same
* shape as `inlineSubCompositions`'s `onMissingComposition`: optional, and
* when omitted every diagnostic still goes to `console.warn` with exactly the
* text it has today. Producer, studio, vite preview and render all rely on
* that default.
*/
onDiagnostic?: BundleDiagnosticSink;
/** Optional media duration prober (e.g., ffprobe). If omitted, media durations are not resolved. */
probeMediaDuration?: MediaDurationProber;
/**
@@ -798,14 +842,20 @@ export async function bundleToSingleHtml(
return isSafePath(projectDir, resolved) ? resolved : null;
};
const emit: BundleDiagnosticSink =
options?.onDiagnostic ?? ((diagnostic) => console.warn(diagnostic.message));
const rawHtml = readFileSync(indexPath, "utf-8");
const compiled = await compileHtml(rawHtml, sourceDir, options?.probeMediaDuration);
const staticGuard = await validateHyperframeHtmlContract(compiled);
if (!staticGuard.isValid) {
console.warn(
`[StaticGuard] Invalid HyperFrame contract: ${staticGuard.missingKeys.join("; ")}`,
);
emit({
code: "static_guard_contract",
severity: "warning",
message: `[StaticGuard] Invalid HyperFrame contract: ${staticGuard.missingKeys.join("; ")}`,
source: entryFile,
});
}
const withInterceptor = injectInterceptor(compiled, options?.runtime ?? "inline");
@@ -911,9 +961,12 @@ export async function bundleToSingleHtml(
buildScopeSelector: (compId: string) => cssAttributeSelector("data-composition-id", compId),
scriptErrorLabel: "[HyperFrames] composition script error:",
onMissingComposition: (srcPath: string, reason?: string) => {
console.warn(
`[Bundler] Skipping sub-composition "${srcPath}": ${reason ?? "the file could not be found"}.`,
);
emit({
code: "sub_composition_skipped",
severity: "warning",
message: `[Bundler] Skipping sub-composition "${srcPath}": ${reason ?? "the file could not be found"}.`,
source: srcPath,
});
},
});
const compStyleChunks: string[] = [...subCompResult.styles];
@@ -1119,7 +1172,7 @@ export async function bundleToSingleHtml(
if (value) {
el.setAttribute(
HF_COLOR_GRADING_ATTR,
rewriteColorGradingLutWithInlinedAssets(value, projectDir),
rewriteColorGradingLutWithInlinedAssets(value, projectDir, emit),
);
}
}
+2
View File
@@ -32,6 +32,8 @@ export {
assignBundledRuntimeCompositionIds,
type BundledHostCompositionIdentity,
bundleToSingleHtml,
type BundleDiagnostic,
type BundleDiagnosticSink,
type BundleOptions,
prepareFlattenedInnerRoot,
FLATTENED_INNER_ROOT_STRIP_ATTRS,