mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 18:01:20 +08:00
4e7fcf7f2a
Extends the studio-preview fix to the render path and the asset-discovery utilities, which share the same resolver and had the same defect. `rewriteAssetPath` takes an optional `assetExists` probe. A plain relative ref authored in a sub-composition (`_shared.css`, `clip.mp4`) is re-pointed at the composition's own directory when that sibling exists on disk; project-root refs with no sibling (the registry's `assets/logo.png` convention) stay as authored. Callers that can see the filesystem supply the probe, so the module stays free of node:fs. Also fixes a second defect in the inliner: `<head>` <link> hrefs and external script srcs are hoisted into the root document but never went through the rewrite at all, so even the documented `../` form escaped the project and 404'd at render time. Wired into the preview bundler, the producer compiler, the studio preview builder, the HEVC preview lint, the project lint's asset scans, publish proxy baking, and media-treatment source resolution.
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
rewriteAssetPath,
|
|
rewriteCssAssetUrls,
|
|
rewriteInlineStyleAssetUrls,
|
|
} from "./rewriteSubCompPaths.js";
|
|
|
|
describe("rewriteAssetPath", () => {
|
|
it("rewrites `../` against the sub-composition dir", () => {
|
|
expect(rewriteAssetPath("compositions/scene.html", "../icon.svg")).toBe("icon.svg");
|
|
});
|
|
|
|
it("leaves plain relative paths untouched", () => {
|
|
expect(rewriteAssetPath("compositions/scene.html", "assets/logo.png")).toBe("assets/logo.png");
|
|
});
|
|
|
|
it("leaves absolute URLs and data URIs untouched", () => {
|
|
expect(rewriteAssetPath("compositions/scene.html", "https://x/y")).toBe("https://x/y");
|
|
expect(rewriteAssetPath("compositions/scene.html", "data:image/png;base64,AA")).toBe(
|
|
"data:image/png;base64,AA",
|
|
);
|
|
expect(rewriteAssetPath("compositions/scene.html", "#hash")).toBe("#hash");
|
|
});
|
|
|
|
// Regression guard for a Windows-only bug: the rewriter used to import
|
|
// `path` (native) and emit `:\fonts\brand.woff2` — native `join` used
|
|
// backslashes, and `resolve("/", x).slice(1)` chopped the `D` off a
|
|
// `D:\…` absolute path. URLs must be POSIX regardless of host OS.
|
|
it("never emits backslashes on any platform", () => {
|
|
const out = rewriteAssetPath("compositions/nested/scene.html", "../../fonts/brand.woff2");
|
|
expect(out).toBe("fonts/brand.woff2");
|
|
expect(out).not.toMatch(/\\/);
|
|
expect(out).not.toMatch(/^:/);
|
|
});
|
|
|
|
it("CSS url(...) rewrites also stay POSIX under nesting", () => {
|
|
const css = `@font-face { src: url("../../fonts/brand.woff2") format("woff2"); }`;
|
|
const out = rewriteCssAssetUrls(css, "compositions/nested/scene.html");
|
|
expect(out).toContain(`url("fonts/brand.woff2")`);
|
|
expect(out).not.toMatch(/\\/);
|
|
expect(out).not.toMatch(/:\\/);
|
|
});
|
|
|
|
it("rewrites CSS urls inside inline style attributes", () => {
|
|
const elements = [{ style: `background-image: url("../cover.png")` }];
|
|
|
|
rewriteInlineStyleAssetUrls(
|
|
elements,
|
|
"compositions/scene.html",
|
|
(el) => el.style,
|
|
(el, value) => {
|
|
el.style = value;
|
|
},
|
|
);
|
|
|
|
expect(elements[0]?.style).toBe(`background-image: url("cover.png")`);
|
|
});
|
|
|
|
// A sub-composition referencing a SIBLING file (`_shared.css`, no `../`) means
|
|
// a file in its own directory, but the inlined/preview document resolves it
|
|
// against the project root — so it 404s. `assetExists` lets a caller that can
|
|
// see the filesystem opt into browser semantics, while paths with no such
|
|
// sibling (the registry's project-root `assets/logo.png` convention) stay put.
|
|
describe("with an assetExists probe", () => {
|
|
const exists = (p: string) =>
|
|
[
|
|
"design/styleframes/_shared.css",
|
|
"design/styleframes/frame.png",
|
|
"assets/logo.png",
|
|
].includes(p);
|
|
|
|
it("resolves a sibling file against the sub-composition dir", () => {
|
|
expect(rewriteAssetPath("design/styleframes/frame-01.html", "_shared.css", exists)).toBe(
|
|
"design/styleframes/_shared.css",
|
|
);
|
|
});
|
|
|
|
it("keeps a query string and hash on the rewritten path", () => {
|
|
expect(rewriteAssetPath("design/styleframes/frame-01.html", "frame.png?v=2", exists)).toBe(
|
|
"design/styleframes/frame.png?v=2",
|
|
);
|
|
});
|
|
|
|
it("leaves project-root-relative paths alone when no sibling exists", () => {
|
|
expect(rewriteAssetPath("blocks/hero.html", "assets/logo.png", exists)).toBe(
|
|
"assets/logo.png",
|
|
);
|
|
});
|
|
|
|
it("still resolves `../` without consulting the probe", () => {
|
|
expect(rewriteAssetPath("compositions/scene.html", "../icon.svg", exists)).toBe("icon.svg");
|
|
});
|
|
|
|
it("is a no-op without the probe (unchanged default)", () => {
|
|
expect(rewriteAssetPath("design/styleframes/frame-01.html", "_shared.css")).toBe(
|
|
"_shared.css",
|
|
);
|
|
});
|
|
});
|
|
});
|