fix(cli): expose render debug mode

Adds the render --debug CLI flag, forwards it through Docker/local render paths, and captures full producer debug artifacts from pipeline start.
This commit is contained in:
Miguel Ángel
2026-06-23 13:04:48 -04:00
committed by GitHub
parent 4961e4e477
commit 7133c396eb
5 changed files with 57 additions and 8 deletions
+15
View File
@@ -252,6 +252,21 @@ describe("renderLocal browser GPU config", () => {
expect(producerState.createdJobs[0]?.videoFrameFormat).toBe("png");
});
it("forwards debug mode to createRenderJob", async () => {
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: { num: 30, den: 1 },
quality: "standard",
format: "mp4",
gpu: false,
browserGpuMode: "software",
hdrMode: "auto",
quiet: true,
debug: true,
});
expect(producerState.createdJobs[0]?.debug).toBe(true);
});
it("omits variables from createRenderJob when not provided", async () => {
await renderLocal("/tmp/project", "/tmp/out.mp4", {
fps: { num: 30, den: 1 },
+14 -1
View File
@@ -241,6 +241,12 @@ export default defineCommand({
description: "Suppress verbose output",
default: false,
},
debug: {
type: "boolean",
description:
"Write full render diagnostics and keep intermediate artifacts under the producer .debug directory.",
default: false,
},
strict: {
type: "boolean",
description: "Fail render on lint errors",
@@ -548,6 +554,7 @@ export default defineCommand({
const browserGpuArg = args["browser-gpu"];
const browserGpuMode = resolveBrowserGpuForCli(useDocker, browserGpuArg);
const quiet = args.quiet ?? false;
const debug = args.debug ?? false;
const batchJson = args.json ?? false;
const effectiveQuiet = quiet || (batchPath != null && batchJson);
const strictAll = args["strict-all"] ?? false;
@@ -763,6 +770,7 @@ export default defineCommand({
pageNavigationTimeoutMs,
protocolTimeout,
playerReadyTimeout,
debug,
exitAfterComplete: false,
throwOnError: true,
skipFeedback: true,
@@ -815,6 +823,7 @@ export default defineCommand({
videoBitrate,
videoFrameFormat,
quiet,
debug,
variables,
entryFile,
outputResolution,
@@ -840,6 +849,7 @@ export default defineCommand({
videoFrameFormat,
quiet,
browserPath,
debug,
variables,
entryFile,
outputResolution,
@@ -876,6 +886,7 @@ interface RenderOptions {
videoBitrate?: string;
videoFrameFormat?: VideoFrameFormat;
quiet: boolean;
debug?: boolean;
browserPath?: string;
variables?: Record<string, unknown>;
entryFile?: string;
@@ -1124,6 +1135,7 @@ async function renderDocker(
entryFile: options.entryFile,
outputResolution: options.outputResolution,
pageSideCompositing: options.pageSideCompositing,
debug: options.debug,
pageNavigationTimeoutMs: options.pageNavigationTimeoutMs,
},
});
@@ -1206,7 +1218,7 @@ export async function renderLocal(
const startTime = Date.now();
const logger = createRenderTelemetryLogger(
producer.createConsoleLogger?.("info") ?? createNoopProducerLogger(),
producer.createConsoleLogger?.(options.debug ? "debug" : "info") ?? createNoopProducerLogger(),
);
const job = producer.createRenderJob({
@@ -1233,6 +1245,7 @@ export async function renderLocal(
variables: options.variables,
entryFile: options.entryFile,
outputResolution: options.outputResolution,
debug: options.debug,
});
const onProgress = options.quiet
@@ -171,6 +171,7 @@ describe("buildDockerRunArgs", () => {
videoBitrate: undefined,
videoFrameFormat: "png",
quiet: true,
debug: true,
entryFile: "compositions/intro.html",
},
});
@@ -188,6 +189,7 @@ describe("buildDockerRunArgs", () => {
expect(args).toContain("--video-frame-format");
expect(args).toContain("png");
expect(args).toContain("--quiet");
expect(args).toContain("--debug");
expect(args).toContain("--gpu");
expect(args).toContain("--no-browser-gpu");
expect(args).toContain("--hdr");
@@ -352,6 +354,18 @@ describe("buildDockerRunArgs", () => {
expect(args).toContain("--no-page-side-compositing");
});
it("keeps Docker debug artifacts under the mounted output directory", () => {
const args = buildDockerRunArgs({
...FIXED_INPUT,
options: { ...BASE, debug: true },
});
const envIdx = args.indexOf("PRODUCER_RENDERS_DIR=/output/renders");
const imageIdx = args.indexOf(FIXED_INPUT.imageTag);
expect(envIdx).toBeGreaterThan(-1);
expect(envIdx).toBeLessThan(imageIdx);
expect(args).toContain("--debug");
});
it("omits --no-page-side-compositing when pageSideCompositing is not explicitly false", () => {
const args = buildDockerRunArgs({ ...FIXED_INPUT, options: BASE });
expect(args).not.toContain("--no-page-side-compositing");
+6
View File
@@ -50,6 +50,7 @@ export interface DockerRenderOptions {
videoBitrate?: string;
videoFrameFormat?: "auto" | "jpg" | "png";
quiet: boolean;
debug?: boolean;
variables?: Record<string, unknown>;
entryFile?: string;
/** Output resolution preset (e.g. "landscape-4k"). Forwarded as `--resolution`. */
@@ -109,6 +110,10 @@ export function buildDockerRunArgs(input: DockerRunArgsInput): string[] {
`${projectDir}:/project:ro`,
"-v",
`${outputDir}:/output`,
// Keep debug artifacts on the mounted host output path. The producer roots
// `.debug` at dirname(PRODUCER_RENDERS_DIR), so `/output/renders` maps to
// `/output/.debug/<job id>` instead of a disposable container path.
...(options.debug ? ["-e", "PRODUCER_RENDERS_DIR=/output/renders"] : []),
imageTag,
"/project",
"--output",
@@ -128,6 +133,7 @@ export function buildDockerRunArgs(input: DockerRunArgsInput): string[] {
? ["--video-frame-format", options.videoFrameFormat]
: []),
...(options.quiet ? ["--quiet"] : []),
...(options.debug ? ["--debug"] : []),
...(options.gpu ? ["--gpu"] : []),
...(options.browserGpu ? [] : ["--no-browser-gpu"]),
...(options.hdrMode === "force-hdr" ? ["--hdr"] : []),
@@ -914,6 +914,14 @@ export async function executeRenderJob(
assertNotAborted();
assertConfiguredFfmpegBinariesExist();
if (!existsSync(workDir)) mkdirSync(workDir, { recursive: true });
if (job.config.debug) {
const logPath = join(workDir, "render.log");
restoreLogger = installDebugLogger(logPath, log);
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
}
log.info("[Render] Pipeline started", {
platform: process.platform,
arch: process.arch,
@@ -939,13 +947,6 @@ export async function executeRenderJob(
requestedWorkers: job.config.workers ?? "auto",
});
if (!existsSync(workDir)) mkdirSync(workDir, { recursive: true });
if (job.config.debug) {
const logPath = join(workDir, "render.log");
restoreLogger = installDebugLogger(logPath, log);
}
const entryFile = job.config.entryFile || "index.html";
let htmlPath = join(projectDir, entryFile);
if (!existsSync(htmlPath)) {