feat(cli): split cloud render --resolution into --aspect-ratio + --resolution (#1143)

Aligns the `hyperframes cloud render` CLI with the v3 API's decomposed
shape (ef#38182). Replaces the flat 6-value `--resolution` flag with two
independent flags:

- `--resolution`: tier ∈ {1080p, 4k}; default 1080p; 4k bills at 1.5x
- `--aspect-ratio`: ratio ∈ {16:9, 9:16, 1:1}; default 16:9

Regenerates `packages/cli/src/cloud/_gen/{types,client}.ts` from the
updated `experiment-framework/openapi/external-api.json`. Threads
`aspectRatio` through `SubmitOptions` and `buildRenderBody` so it lands
in the request body as `aspect_ratio`.

Old flag values (`landscape`, `portrait-4k`, etc.) now reject at the CLI
layer via `parseEnumFlag`, matching the API surface's rejection. The
six legacy combinations map to the same effective output in the new
shape — see the migration table in ef#38182's PR body.

Deferred (will follow in a separate PR): 720p, 4:5, 5:4, and `auto`.
These need producer-side capability + controller-side composition-dim
inference; out of scope for an API/CLI shape refactor.
This commit is contained in:
James Russo
2026-05-31 20:41:25 -04:00
committed by GitHub
parent 5e28738566
commit 8e0b26dab6
2 changed files with 43 additions and 29 deletions
+13 -10
View File
@@ -58,14 +58,8 @@ import { isAbsolute, resolve as resolvePath } from "node:path";
const VALID_QUALITY = ["draft", "standard", "high"] as const;
const VALID_FORMAT = ["mp4", "webm", "mov"] as const;
const VALID_RESOLUTION = [
"landscape",
"portrait",
"landscape-4k",
"portrait-4k",
"square",
"square-4k",
] as const;
const VALID_RESOLUTION = ["1080p", "4k"] as const;
const VALID_ASPECT_RATIO = ["16:9", "9:16", "1:1"] as const;
const FORMAT_EXT: Record<string, string> = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
@@ -96,8 +90,11 @@ export default defineCommand({
format: { type: "string", description: "mp4 | webm | mov (default: mp4)" },
resolution: {
type: "string",
description:
"Resolution preset: landscape | portrait | landscape-4k | portrait-4k | square | square-4k",
description: "Resolution tier: 1080p | 4k (default: 1080p; 4k is billed at 1.5x)",
},
"aspect-ratio": {
type: "string",
description: "Aspect ratio: 16:9 | 9:16 | 1:1 (default: 16:9)",
},
composition: {
type: "string",
@@ -185,6 +182,9 @@ export default defineCommand({
const resolution = parseEnumFlag(args.resolution, VALID_RESOLUTION, {
flag: "--resolution",
});
const aspectRatio = parseEnumFlag(args["aspect-ratio"], VALID_ASPECT_RATIO, {
flag: "--aspect-ratio",
});
const pollIntervalMs = parsePollIntervalMs(args["poll-interval"]);
const maxWaitMs = parseMaxWaitMs(args["max-wait"]);
validateIdempotencyKey(args["idempotency-key"]);
@@ -214,6 +214,7 @@ export default defineCommand({
quality,
format,
resolution,
aspectRatio,
composition: args.composition,
variables,
title: args.title,
@@ -443,6 +444,7 @@ interface SubmitOptions {
quality: "draft" | "standard" | "high" | undefined;
format: "mp4" | "webm" | "mov" | undefined;
resolution: CreateHyperframesRenderRequest["resolution"] | undefined;
aspectRatio: CreateHyperframesRenderRequest["aspect_ratio"] | undefined;
composition: string | undefined;
variables: Record<string, unknown> | undefined;
title: string | undefined;
@@ -470,6 +472,7 @@ function buildRenderBody(opts: SubmitOptions): CreateHyperframesRenderRequest {
if (opts.quality !== undefined) body.quality = opts.quality;
if (opts.format !== undefined) body.format = opts.format;
if (opts.resolution !== undefined) body.resolution = opts.resolution;
if (opts.aspectRatio !== undefined) body.aspect_ratio = opts.aspectRatio;
if (opts.composition !== undefined) body.composition = opts.composition;
if (opts.variables !== undefined) body.variables = opts.variables;
if (opts.title !== undefined) body.title = opts.title;