diff --git a/packages/cli/src/commands/render.test.ts b/packages/cli/src/commands/render.test.ts index 6f35d40a6..f6e0c089d 100644 --- a/packages/cli/src/commands/render.test.ts +++ b/packages/cli/src/commands/render.test.ts @@ -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 }, diff --git a/packages/cli/src/commands/render.ts b/packages/cli/src/commands/render.ts index 333704f48..bf65c3c64 100644 --- a/packages/cli/src/commands/render.ts +++ b/packages/cli/src/commands/render.ts @@ -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; 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 diff --git a/packages/cli/src/utils/dockerRunArgs.test.ts b/packages/cli/src/utils/dockerRunArgs.test.ts index 810c090ce..78c138842 100644 --- a/packages/cli/src/utils/dockerRunArgs.test.ts +++ b/packages/cli/src/utils/dockerRunArgs.test.ts @@ -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"); diff --git a/packages/cli/src/utils/dockerRunArgs.ts b/packages/cli/src/utils/dockerRunArgs.ts index 832460e61..508d966b9 100644 --- a/packages/cli/src/utils/dockerRunArgs.ts +++ b/packages/cli/src/utils/dockerRunArgs.ts @@ -50,6 +50,7 @@ export interface DockerRenderOptions { videoBitrate?: string; videoFrameFormat?: "auto" | "jpg" | "png"; quiet: boolean; + debug?: boolean; variables?: Record; 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/` 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"] : []), diff --git a/packages/producer/src/services/renderOrchestrator.ts b/packages/producer/src/services/renderOrchestrator.ts index 9a3634ec7..6bc15b7a8 100644 --- a/packages/producer/src/services/renderOrchestrator.ts +++ b/packages/producer/src/services/renderOrchestrator.ts @@ -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)) {