mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-11 19:09:20 +00:00
Addresses R2 CHANGES_REQUESTED from Miga + Rames on PR #2529: 1. Sibling-surface gap (blocker): `hyperframes cloudrun render{,-batch}`, `hyperframes lambda render{,-batch}` all advertised the same tier-only aliases (`1080p` / `hd` / `4k` / `uhd`) but normalized them to `landscape` and never set `outputResolutionAspectAgnostic`. The distributed plumbing PR #2529 added received `undefined` from those callers, so portrait `1080p` still hit the original aspect-mismatch on Cloud Run / Lambda. Fix: introduce `resolveResolutionFlagPair` in `@hyperframes/parsers` (the single source of truth for the two-step normalize + aspect-agnostic detect) and route every distributed entrypoint through a shared `parseOutputResolutionFlag` CLI util so the alias signal now reaches `SerializableDistributedRenderConfig`. Studio Server keeps its canonical-only HTTP contract; that intent is now pinned in tests. 2. Preflight recompute (hardening): the earlier "downgrade aspect-mismatch" preflight cleared un-remapped mismatches, so IG 4:5 (non-preset aspect, no sibling) and portrait-4K comp + `--resolution 1080p` (remap + downsample) both slipped through to fail late in `resolveDeviceScaleFactor`. Now `checkRenderResolutionPreflight` computes the effective preset via `suggestMatchingPreset` (mirroring the compile stage's `adaptAspectAgnosticResolution`) and re-checks against that — only genuinely-fixable mismatches clear early. New tests pin both regressed input classes. 3. Docker forwarding boundary test (Miga's important #2): pinned `1080p` survives verbatim as `--resolution 1080p` in the Docker args so the in-container CLI can re-run `isAspectAgnosticResolutionAlias`. 4. Doc-nit (Miga): parsers/src/types.ts no longer references the nonexistent `resolveResolutionForComposition` — points at the actual remap helpers. Fallow: cloudrun.ts / lambda.ts share 390 lines of pre-existing structural symmetry (parallel AWS + GCP dispatchers), and lambda/render.ts + render-batch.ts declare parallel RenderArgs interfaces. Both re-flagged after threading the aspect-agnostic field through each surface; ignored with justification in .fallowrc.jsonc. lambda.ts's `run` and lambda/render.ts's `waitForCompletion` are pre-existing CRAP-score hotspots untouched by this PR — added under health.ignore. Co-Authored-By: Claude <noreply@anthropic.com> — Via
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
/**
|
|
* Boundary tests for the shared `parseOutputResolutionFlag` helper. Every
|
|
* distributed entrypoint (`hyperframes cloudrun render{,-batch}`,
|
|
* `hyperframes lambda render{,-batch}`) delegates to this one function —
|
|
* covering it here (rather than at each surface) makes the sibling-surface
|
|
* regression this PR fixes unreachable by construction: any future surface
|
|
* that calls `parseOutputResolutionFlag` inherits the correct alias
|
|
* threading. The wire-config-level tests at
|
|
* `../commands/cloudrun.test.ts` / `../commands/lambda/render.test.ts` /
|
|
* `../commands/lambda/render-batch.test.ts` still exercise the
|
|
* per-entrypoint composition so the plumbing stays end-to-end covered.
|
|
*/
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import { parseOutputResolutionFlag } from "./parseOutputResolution.js";
|
|
|
|
const CLOUDRUN = { surfaceLabel: "[cloudrun render]" } as const;
|
|
const LAMBDA = {
|
|
surfaceLabel: "[lambda render]",
|
|
aliasHint:
|
|
"1080p, 4k, uhd, hd, 1080p-portrait, portrait-1080p, 4k-portrait, 1080p-square, square-1080p, 4k-square",
|
|
} as const;
|
|
|
|
describe("parseOutputResolutionFlag", () => {
|
|
it.each([undefined, "", null])(
|
|
"returns undefined + false when the flag is omitted (raw=%s)",
|
|
(raw) => {
|
|
expect(parseOutputResolutionFlag(raw, CLOUDRUN)).toEqual({
|
|
outputResolution: undefined,
|
|
outputResolutionAspectAgnostic: false,
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each(["landscape", "portrait-4k", "square", "square-4k"])(
|
|
"normalizes canonical preset %s with aspect-agnostic=false",
|
|
(preset) => {
|
|
const { outputResolution, outputResolutionAspectAgnostic } = parseOutputResolutionFlag(
|
|
preset,
|
|
CLOUDRUN,
|
|
);
|
|
expect(outputResolution).toBe(preset);
|
|
expect(outputResolutionAspectAgnostic).toBe(false);
|
|
},
|
|
);
|
|
|
|
// The blocker path from Miga's R2 review: without this pair, a portrait
|
|
// composition with `--output-resolution 1080p` reaches the compile stage
|
|
// as the explicit `landscape` preset and rejects with the original
|
|
// aspect-mismatch instead of remapping to `portrait`.
|
|
it.each(["1080p", "hd", "4k", "uhd"])(
|
|
"flags aspect-agnostic tier alias %s so the compile stage can remap orientation",
|
|
(alias) => {
|
|
const { outputResolution, outputResolutionAspectAgnostic } = parseOutputResolutionFlag(
|
|
alias,
|
|
CLOUDRUN,
|
|
);
|
|
expect(outputResolutionAspectAgnostic).toBe(true);
|
|
expect(outputResolution).toBeDefined();
|
|
},
|
|
);
|
|
|
|
it.each(["1080p-portrait", "portrait-1080p", "1080p-square", "4k-portrait", "4k-square"])(
|
|
"does NOT flag orientation-suffixed alias %s as aspect-agnostic",
|
|
(alias) => {
|
|
// The user picked an orientation — respect it, don't silently swap.
|
|
const { outputResolutionAspectAgnostic } = parseOutputResolutionFlag(alias, CLOUDRUN);
|
|
expect(outputResolutionAspectAgnostic).toBe(false);
|
|
},
|
|
);
|
|
|
|
it("treats input case-insensitively (1080P, UHD, HD, 4K all pass)", () => {
|
|
for (const alias of ["1080P", "UHD", "HD", "4K"]) {
|
|
expect(parseOutputResolutionFlag(alias, CLOUDRUN).outputResolutionAspectAgnostic).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("throws with the caller-supplied surface label on unknown values", () => {
|
|
// The two surfaces MUST use the same underlying helper (see PR #2529 —
|
|
// divergent copies is exactly the cross-scaffold drift class this
|
|
// consolidation prevents), but each stakes its own label so debugging
|
|
// still points at the right verb.
|
|
expect(() => parseOutputResolutionFlag("8k", CLOUDRUN)).toThrow(/\[cloudrun render\]/);
|
|
expect(() => parseOutputResolutionFlag("8k", LAMBDA)).toThrow(/\[lambda render\]/);
|
|
});
|
|
|
|
it("appends the caller-supplied aliasHint to the error text (so the message stays surface-accurate)", () => {
|
|
// Lambda advertises the full orientation-suffixed alias list in help
|
|
// text; the error message must match that surface for the user's
|
|
// "did you mean?" search to land on real docs.
|
|
const err = getThrown(() => parseOutputResolutionFlag("8k", LAMBDA));
|
|
expect(err.message).toContain("1080p-portrait");
|
|
expect(err.message).toContain("4k-portrait");
|
|
});
|
|
});
|
|
|
|
function getThrown(fn: () => void): Error {
|
|
try {
|
|
fn();
|
|
} catch (e) {
|
|
if (e instanceof Error) return e;
|
|
throw new Error(`Non-Error thrown: ${String(e)}`);
|
|
}
|
|
throw new Error("Expected fn to throw, but it did not");
|
|
}
|