mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(core,cli,engine,producer): add getVariables() helper and --variables render flag
Adds the parametrized-render primitive from hf#592 by reusing the existing
data-composition-variables schema as the source of declared defaults.
- Runtime helper window.__hyperframes.getVariables() (also exported from
@hyperframes/core) reads data-composition-variables defaults from the
document root and merges window.__hfVariables (CLI override) on top.
Returns Partial<T> for typed access; supports a generic for editor
ergonomics. Same code path runs in dev preview and at render time.
- CLI render --variables '<json>' / --variables-file <path> populates the
override. Mutually exclusive; fail-fast on conflicting flags, missing
file, unparseable JSON, or non-object payloads. parseVariablesArg is
exported as a pure function so validation paths stay unit-testable.
- Engine injects window.__hfVariables via evaluateOnNewDocument before
any page script runs, so the helper sees the merged values on its
first call. Empty payloads are skipped to avoid pointless init scripts.
- Producer threads variables through RenderConfig and into the engine's
CaptureOptions; Docker mode forwards --variables to the in-container
CLI invocation via dockerRunArgs.
Composition authors declare variables once on the root <html> element:
<html data-composition-variables='[
{"id":"title","type":"string","label":"Title","default":"Hello"}
]'>
and read them in any composition script:
const { title } = window.__hyperframes.getVariables();
A render with `--variables '{"title":"Q4 Report"}'` overrides the default
without modifying the composition source. Missing keys fall through to
the declared defaults, so dev preview and CLI renders without --variables
behave identically.
This is PR 1 of a 4-PR stack. Sub-comp per-instance scoping (carrying
host data-variable-values through the inlined sub-comp's getVariables()
call) lands in PR 2; schema validation and lint in PR 3; skill / scaffold
distribution in PR 4.
Tests: 9 new unit tests for getVariables() (jsdom), 11 new CLI tests
covering parseVariablesArg validation paths and Docker passthrough,
2 new dockerRunArgs assertions for the --variables flag. All existing
tests green (core 611, cli 208, engine 519).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { getVariables } from "./getVariables";
|
||||
|
||||
const VARIABLES_ATTR = "data-composition-variables";
|
||||
|
||||
function setDeclared(json: string | null) {
|
||||
if (json == null) {
|
||||
document.documentElement.removeAttribute(VARIABLES_ATTR);
|
||||
} else {
|
||||
document.documentElement.setAttribute(VARIABLES_ATTR, json);
|
||||
}
|
||||
}
|
||||
|
||||
function setOverrides(value: unknown) {
|
||||
(window as Window & { __hfVariables?: unknown }).__hfVariables = value;
|
||||
}
|
||||
|
||||
describe("getVariables", () => {
|
||||
beforeEach(() => {
|
||||
setDeclared(null);
|
||||
setOverrides(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setDeclared(null);
|
||||
setOverrides(undefined);
|
||||
});
|
||||
|
||||
it("returns {} when nothing is declared and no overrides", () => {
|
||||
expect(getVariables()).toEqual({});
|
||||
});
|
||||
|
||||
it("returns declared defaults when no overrides", () => {
|
||||
setDeclared(
|
||||
JSON.stringify([
|
||||
{ id: "title", type: "string", label: "Title", default: "Hello" },
|
||||
{ id: "count", type: "number", label: "Count", default: 3 },
|
||||
{ id: "active", type: "boolean", label: "Active", default: true },
|
||||
]),
|
||||
);
|
||||
expect(getVariables()).toEqual({ title: "Hello", count: 3, active: true });
|
||||
});
|
||||
|
||||
it("merges overrides over declared defaults (overrides win)", () => {
|
||||
setDeclared(
|
||||
JSON.stringify([
|
||||
{ id: "title", type: "string", label: "Title", default: "Hello" },
|
||||
{ id: "theme", type: "string", label: "Theme", default: "light" },
|
||||
]),
|
||||
);
|
||||
setOverrides({ title: "Custom Title" });
|
||||
expect(getVariables()).toEqual({ title: "Custom Title", theme: "light" });
|
||||
});
|
||||
|
||||
it("includes override keys not declared in the schema", () => {
|
||||
setDeclared(JSON.stringify([{ id: "title", type: "string", label: "Title", default: "x" }]));
|
||||
setOverrides({ extra: 42 });
|
||||
expect(getVariables()).toEqual({ title: "x", extra: 42 });
|
||||
});
|
||||
|
||||
it("returns {} when the declared JSON is invalid", () => {
|
||||
setDeclared("{not-json");
|
||||
expect(getVariables()).toEqual({});
|
||||
});
|
||||
|
||||
it("ignores declared entries without an id or default", () => {
|
||||
setDeclared(
|
||||
JSON.stringify([
|
||||
{ id: "ok", type: "string", label: "Ok", default: "yes" },
|
||||
{ type: "string", label: "no-id", default: "nope" },
|
||||
{ id: "no-default", type: "string", label: "No default" },
|
||||
"not-an-object",
|
||||
null,
|
||||
]),
|
||||
);
|
||||
expect(getVariables()).toEqual({ ok: "yes" });
|
||||
});
|
||||
|
||||
it("ignores non-array declared payloads", () => {
|
||||
setDeclared(JSON.stringify({ title: "Hello" }));
|
||||
expect(getVariables()).toEqual({});
|
||||
});
|
||||
|
||||
it("ignores non-object overrides (string, array, null)", () => {
|
||||
setDeclared(JSON.stringify([{ id: "title", type: "string", label: "Title", default: "x" }]));
|
||||
setOverrides("not-an-object");
|
||||
expect(getVariables()).toEqual({ title: "x" });
|
||||
setOverrides([1, 2, 3]);
|
||||
expect(getVariables()).toEqual({ title: "x" });
|
||||
setOverrides(null);
|
||||
expect(getVariables()).toEqual({ title: "x" });
|
||||
});
|
||||
|
||||
it("supports the typed generic for editor ergonomics", () => {
|
||||
setDeclared(
|
||||
JSON.stringify([{ id: "title", type: "string", label: "Title", default: "Hello" }]),
|
||||
);
|
||||
type Vars = { title: string; missing?: number };
|
||||
const vars = getVariables<Vars>();
|
||||
expect(vars.title).toBe("Hello");
|
||||
expect(vars.missing).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user