feat(cli): hyperframes lambda render --variables / --variables-file / --strict-variables

Mirror the local hyperframes render variables UX on the Lambda CLI:

- --variables '<json>'       inline JSON object of variable values
- --variables-file <path>    path to a JSON file with variable values
- --strict-variables          fail on type/declared-mismatch (warn by default)

Resolution + validation logic is hoisted to packages/cli/src/utils/variables.ts
so both surfaces share one parser. The new reportVariableIssues helper formats
the warning block + handles --strict-variables exit, deduping the per-CLI
issue-handling block.

Variables flow into SerializableDistributedRenderConfig.variables and reach
every chunk worker via the path PR 9.1 + 9.2 wired up (plan() →
meta/encoder.json → renderChunk() → window.__hfVariables). Pre-validation
against the composition's data-composition-variables declaration runs only
when the project's index.html is on disk — --site-id pointing at a
pre-uploaded site that was packaged elsewhere skips the check, matching how
the local CLI treats unreadable index files.

The render.ts re-exports of parseVariablesArg / resolveVariablesArg /
validateVariablesAgainstProject are dropped; the matching tests move to
packages/cli/src/utils/variables.test.ts where the implementations now live.

Docs: docs/packages/cli.mdx adds a section on --variables / --variables-file /
--strict-variables for lambda render, including the 256 KiB Step Functions
execution-input cap and a pointer to the upcoming templates-on-lambda guide
(PR 9.5).

Phase 9 PR 9.3 of the distributed rendering plan.
This commit is contained in:
James
2026-05-19 19:54:30 -04:00
committed by James Russo
parent 87fdd556c4
commit cb948d5fcf
7 changed files with 479 additions and 305 deletions
+30
View File
@@ -23,6 +23,14 @@ export const examples: Example[] = [
"Render and stream progress until done",
"hyperframes lambda render ./my-project --width 1920 --height 1080 --wait",
],
[
"Render with composition variables (personalised template)",
'hyperframes lambda render ./my-template --site-id abc1234deadbeef0 --width 1920 --height 1080 --variables \'{"title":"Hello Alice","accent":"#ff0000"}\'',
],
[
"Render with variables from a JSON file",
"hyperframes lambda render ./my-template --site-id abc1234deadbeef0 --width 1920 --height 1080 --variables-file ./alice.json",
],
["Check progress for a started render", "hyperframes lambda progress hf-render-abcd1234"],
[
"Pre-upload a project so multiple renders share the upload",
@@ -114,6 +122,25 @@ export default defineCommand({
type: "string",
description: "Final output S3 key (default: renders/<exec>/output.<ext>)",
},
// Variables — mirrors the local `hyperframes render` UX. Inline JSON or
// file path, plus --strict-variables for type-checked validation against
// the composition's `data-composition-variables` declaration.
variables: {
type: "string",
description:
'JSON object of variable values for the composition. Example: --variables \'{"title":"Hello"}\'. Values flow into window.__hfVariables on the Lambda chunk workers.',
},
"variables-file": {
type: "string",
description:
"Path to a JSON file with variable values (alternative to --variables). The file must contain a single JSON object.",
},
"strict-variables": {
type: "boolean",
description:
"Fail the render command if any --variables key is undeclared or has a wrong type vs the composition's data-composition-variables. Without this flag, mismatches are warnings.",
default: false,
},
wait: { type: "boolean", description: "Block until the render finishes" },
"wait-interval-ms": {
type: "string",
@@ -242,6 +269,9 @@ export default defineCommand({
maxParallelChunks: parsePositiveInt(args["max-parallel-chunks"], "--max-parallel-chunks"),
executionName: args["execution-name"] as string | undefined,
outputKey: args["output-key"] as string | undefined,
variables: args.variables as string | undefined,
variablesFile: args["variables-file"] as string | undefined,
strictVariables: Boolean(args["strict-variables"]),
json: Boolean(args.json),
wait: Boolean(args.wait),
waitIntervalMs: parsePositiveInt(args["wait-interval-ms"], "--wait-interval-ms") ?? 5000,