From 906c8d04f85b909252753b24102cdbad1780378d Mon Sep 17 00:00:00 2001 From: Xuanru Li <157947275+xuanruli@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:20:18 -0700 Subject: [PATCH] fix(cli): localize remote assets before validate so it matches render (#2001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit validate served the composition over a loopback origin and let headless Chrome fetch remote /@font-face assets cross-origin, while the render pipeline downloads them to disk first. Buckets whose CORS allowlist omits the loopback origin then failed the CORS-mode request with a false net::ERR_FAILED that never occurs in the real render, pushing authors (and agent pipelines) to delete crossorigin — which disables WebGL color-grading/shaders for that asset. Reuse producer's localizeRemote{Media,Image,FontFace}Sources in validate, downloading into a temp dir served as an extra static-server asset root (project dir untouched, cleaned up after). validate now matches render. Co-authored-by: Claude Opus 4.8 --- packages/cli/src/commands/validate.test.ts | 19 ++++++++ packages/cli/src/commands/validate.ts | 47 ++++++++++++++++++- .../cli/src/utils/staticProjectServer.test.ts | 45 +++++++++++++++++- packages/cli/src/utils/staticProjectServer.ts | 23 +++++---- packages/producer/src/index.ts | 9 ++++ 5 files changed, 130 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/commands/validate.test.ts b/packages/cli/src/commands/validate.test.ts index 1cc7ce36d..7353806c4 100644 --- a/packages/cli/src/commands/validate.test.ts +++ b/packages/cli/src/commands/validate.test.ts @@ -9,6 +9,25 @@ import { } from "./validate.js"; import type { ProjectLintResult } from "../utils/lintProject.js"; +// validateInBrowser lazy-loads the producer localize helpers via loadProducer; +// mock it so these unit tests never resolve @hyperframes/producer's built dist. +vi.mock("../utils/producer.js", () => ({ + loadProducer: vi.fn(async () => ({ + localizeRemoteMediaSources: vi.fn(async (html: string) => ({ + html, + remoteMediaAssets: new Map(), + })), + localizeRemoteImageSources: vi.fn(async (html: string) => ({ + html, + remoteMediaAssets: new Map(), + })), + localizeRemoteFontFaces: vi.fn(async (html: string) => ({ + html, + remoteMediaAssets: new Map(), + })), + })), +})); + // Regression for the validate audio-duration-probe timeout: a slow-loading // media element's duration was snapshotted once, at a fixed point in time, // and any element still mid-load was permanently misreported as unreadable. diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index 9b20f9b97..07b5334ba 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -1,5 +1,6 @@ import { defineCommand } from "citty"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { resolveProject, type ProjectDir } from "../utils/project.js"; @@ -334,6 +335,37 @@ export function extractCompositionErrorsFromLint( .map((f) => ({ level: "error" as const, text: f.message })); } +// Match the render pipeline: localize remote /