From 4578f213f16bb64fe8588e96dcaf85144c2a04b9 Mon Sep 17 00:00:00 2001 From: Dustin Persek Date: Wed, 1 Jul 2026 07:26:55 -0400 Subject: [PATCH 1/2] fix(cli): forward batch render output options Batch renders now carry parsed GIF loop and source video frame-format options into each row render, matching single-render behavior. --- packages/cli/src/commands/render.test.ts | 56 +++++++++++++++++++++++- packages/cli/src/commands/render.ts | 2 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/render.test.ts b/packages/cli/src/commands/render.test.ts index f6e0c089df..f74f530c44 100644 --- a/packages/cli/src/commands/render.test.ts +++ b/packages/cli/src/commands/render.test.ts @@ -1,5 +1,8 @@ // fallow-ignore-file code-duplication import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; const producerState = vi.hoisted(() => ({ createdJobs: [] as Array>, @@ -59,8 +62,13 @@ vi.mock("../browser/preflight.js", () => ({ runEnvironmentChecks: vi.fn(async () => preflightState.result), })); +vi.mock("../browser/manager.js", () => ({ + ensureBrowser: vi.fn(async () => ({ executablePath: "/mock/chrome", source: "cache" })), +})); + describe("renderLocal browser GPU config", () => { const savedEnv = new Map(); + let tempDirs: string[] = []; // Pre-resolve once. The first dynamic `import("./render.js")` in this file // takes >5 s on Windows runners (cold module load) — long enough to blow // vitest's default 5 s timeout in whichever test happens to be first. When @@ -68,13 +76,28 @@ describe("renderLocal browser GPU config", () => { // the next test's `beforeEach` clears `producerState.createdJobs`, shifting // index 0 and corrupting unrelated assertions. Importing once in // `beforeAll` keeps every test fast and isolated. + let renderCommand: (typeof import("./render.js"))["default"]; let renderLocal: typeof import("./render.js").renderLocal; let resolveBrowserGpuForCli: typeof import("./render.js").resolveBrowserGpuForCli; beforeAll(async () => { - ({ renderLocal, resolveBrowserGpuForCli } = await import("./render.js")); + const renderModule = await import("./render.js"); + renderCommand = renderModule.default; + renderLocal = renderModule.renderLocal; + resolveBrowserGpuForCli = renderModule.resolveBrowserGpuForCli; }); + function createRenderProject(): string { + const dir = mkdtempSync(join(tmpdir(), "hf-render-command-")); + tempDirs.push(dir); + writeFileSync( + join(dir, "index.html"), + '
', + "utf8", + ); + return dir; + } + function setEnv(key: string, value: string) { if (!savedEnv.has(key)) savedEnv.set(key, process.env[key]); process.env[key] = value; @@ -103,6 +126,10 @@ describe("renderLocal browser GPU config", () => { vi.clearAllMocks(); vi.useRealTimers(); vi.restoreAllMocks(); + for (const dir of tempDirs) { + rmSync(dir, { recursive: true, force: true }); + } + tempDirs = []; }); it("passes an explicit software override for --no-browser-gpu even when env requests hardware", async () => { @@ -252,6 +279,33 @@ describe("renderLocal browser GPU config", () => { expect(producerState.createdJobs[0]?.videoFrameFormat).toBe("png"); }); + it("forwards gif loop and video frame format to batch row renders", async () => { + const projectDir = createRenderProject(); + const rowsPath = join(projectDir, "rows.json"); + writeFileSync(rowsPath, "[{}]", "utf8"); + + if (!renderCommand.run) throw new Error("render command missing run handler"); + await renderCommand.run({ + args: { + dir: projectDir, + batch: rowsPath, + output: join(projectDir, "renders", "{index}.gif"), + fps: "15", + quality: "standard", + format: "gif", + "gif-loop": "3", + "video-frame-format": "png", + quiet: true, + }, + } as never); + + expect(producerState.createdJobs[0]).toMatchObject({ + format: "gif", + gifLoop: 3, + videoFrameFormat: "png", + }); + }); + it("forwards debug mode to createRenderJob", async () => { await renderLocal("/tmp/project", "/tmp/out.mp4", { fps: { num: 30, den: 1 }, diff --git a/packages/cli/src/commands/render.ts b/packages/cli/src/commands/render.ts index 238d6fd3d0..46d159abed 100644 --- a/packages/cli/src/commands/render.ts +++ b/packages/cli/src/commands/render.ts @@ -804,6 +804,7 @@ export default defineCommand({ quality, authoringSkill, format, + gifLoop, workers, gpu: useGpu, browserGpuMode, @@ -811,6 +812,7 @@ export default defineCommand({ crf, vp9CpuUsed, videoBitrate, + videoFrameFormat, quiet: batchQuiet, browserPath, entryFile, From e9780effd9469e265a591fe0f6ff35de94d6d6fc Mon Sep 17 00:00:00 2001 From: Dustin Persek Date: Wed, 1 Jul 2026 10:48:50 -0400 Subject: [PATCH 2/2] test(cli): harden render test cold import --- packages/cli/src/commands/render.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/render.test.ts b/packages/cli/src/commands/render.test.ts index f74f530c44..2ce040baf4 100644 --- a/packages/cli/src/commands/render.test.ts +++ b/packages/cli/src/commands/render.test.ts @@ -85,7 +85,7 @@ describe("renderLocal browser GPU config", () => { renderCommand = renderModule.default; renderLocal = renderModule.renderLocal; resolveBrowserGpuForCli = renderModule.resolveBrowserGpuForCli; - }); + }, 30_000); function createRenderProject(): string { const dir = mkdtempSync(join(tmpdir(), "hf-render-command-"));