fix(render): serve media assets with registered content types (#3677)

Port the MIME mappings reported by fix2015 in #1836 to both render file servers.

Co-authored-by: vitalii.semianchuk <fix20152@gmail.com>
This commit is contained in:
James Russo
2026-09-04 19:54:29 -04:00
committed by GitHub
co-authored by vitalii.semianchuk
parent 7fcb5be6ea
commit 19dee4cede
4 changed files with 72 additions and 0 deletions
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createFileServer } from "./fileServer.js";
describe("file server media responses", () => {
it.each([
["avif", "image/avif"],
["flac", "audio/flac"],
["M4A", "audio/mp4"],
["mov", "video/quicktime"],
["ico", "image/vnd.microsoft.icon"],
])("serves .%s with its content type and unchanged bytes", async (ext, type) => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-engine-media-type-"));
const bytes = new Uint8Array([0, 255, 128, 64, 32]);
try {
writeFileSync(join(projectDir, `asset.${ext}`), bytes);
const server = await createFileServer({ projectDir });
try {
const response = await fetch(`${server.url}/asset.${ext}`);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(type);
expect(new Uint8Array(await response.arrayBuffer())).toEqual(bytes);
} finally {
server.close();
}
} finally {
rmSync(projectDir, { recursive: true, force: true });
}
});
});
@@ -24,12 +24,17 @@ const MIME_TYPES: Record<string, string> = {
".gif": "image/gif",
".svg": "image/svg+xml",
".webp": "image/webp",
".avif": "image/avif",
".ico": "image/vnd.microsoft.icon",
".mp4": "video/mp4",
".mov": "video/quicktime",
".webm": "video/webm",
".mp3": "audio/mpeg",
".wav": "audio/wav",
".ogg": "audio/ogg",
".aac": "audio/aac",
".flac": "audio/flac",
".m4a": "audio/mp4",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
@@ -316,6 +316,36 @@ describe("parseRangeHeader", () => {
});
describe("createFileServer", () => {
it.each([
["avif", "image/avif"],
["flac", "audio/flac"],
["M4A", "audio/mp4"],
["mov", "video/quicktime"],
["ico", "image/vnd.microsoft.icon"],
])("serves .%s media with its content type for full and range requests", async (ext, type) => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-media-content-type-"));
const bytes = new Uint8Array([0, 255, 128, 64, 32]);
try {
writeFileSync(join(projectDir, `asset.${ext}`), bytes);
await withFileServer(projectDir, async (server) => {
const response = await fetch(`${server.url}/asset.${ext}`);
expect(response.status).toBe(200);
expect(response.headers.get("content-type")).toBe(type);
expect(new Uint8Array(await response.arrayBuffer())).toEqual(bytes);
const partial = await fetch(`${server.url}/asset.${ext}`, {
headers: { Range: "bytes=1-3" },
});
expect(partial.status).toBe(206);
expect(partial.headers.get("content-type")).toBe(type);
expect(partial.headers.get("content-range")).toBe("bytes 1-3/5");
expect(new Uint8Array(await partial.arrayBuffer())).toEqual(bytes.slice(1, 4));
});
} finally {
rmSync(projectDir, { recursive: true, force: true });
}
});
it("marks producer pages as render capture before the runtime loads", async () => {
const projectDir = mkdtempSync(join(tmpdir(), "hf-file-server-render-mode-"));
try {
@@ -88,12 +88,17 @@ const MIME_TYPES: Record<string, string> = {
".gif": "image/gif",
".svg": "image/svg+xml",
".webp": "image/webp",
".avif": "image/avif",
".ico": "image/vnd.microsoft.icon",
".mp4": "video/mp4",
".mov": "video/quicktime",
".webm": "video/webm",
".mp3": "audio/mpeg",
".wav": "audio/wav",
".ogg": "audio/ogg",
".aac": "audio/aac",
".flac": "audio/flac",
".m4a": "audio/mp4",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",