From b9f4969f7f30006379fcb90f13c2269e42503bc8 Mon Sep 17 00:00:00 2001 From: "Abdullah M. Nazif" Date: Fri, 9 Oct 2026 06:44:52 +0800 Subject: [PATCH 1/3] fix(cli): include captured raster formats in contact sheets --- packages/cli/src/capture/contactSheet.test.ts | 44 +++++++++++++++++++ packages/cli/src/capture/contactSheet.ts | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/capture/contactSheet.test.ts b/packages/cli/src/capture/contactSheet.test.ts index 9833edf2e87..11550c029ec 100644 --- a/packages/cli/src/capture/contactSheet.test.ts +++ b/packages/cli/src/capture/contactSheet.test.ts @@ -12,6 +12,7 @@ import { join } from "node:path"; import sharp from "sharp"; import { describe, expect, it } from "vitest"; import { + createAssetContactSheet, createContactSheet, createScrollContactSheet, createSvgContactSheet, @@ -105,6 +106,49 @@ describe("createContactSheet", () => { ); }); +describe("createAssetContactSheet", () => { + it.each(["png", "avif", "gif", "tiff"])( + "includes captured %s images without changing their original bytes", + async (format) => { + const dir = tempDir(); + try { + const original = join(dir, `feature.${format}`); + const red = sharp({ + create: { width: 8, height: 8, channels: 3, background: "red" }, + }); + let bytes: Buffer; + if (format === "avif") bytes = await red.avif().toBuffer(); + else if (format === "tiff") bytes = await red.tiff().toBuffer(); + else if (format === "gif") { + const frames = Buffer.alloc(8 * 16 * 3); + for (let i = 0; i < 128; i++) frames[i * 3 + (i < 64 ? 0 : 1)] = 255; + bytes = await sharp(frames, { + raw: { width: 8, height: 16, channels: 3, pageHeight: 8 }, + }) + .gif({ delay: [100, 100], loop: 0 }) + .toBuffer(); + } else bytes = await red.png().toBuffer(); + writeFileSync(original, bytes); + + const out = join(dir, "contact-sheet.jpg"); + expect(await createAssetContactSheet(dir, out)).toEqual([out]); + const pixel = await sharp(out) + .extract({ left: 244, top: 270, width: 1, height: 1 }) + .removeAlpha() + .raw() + .toBuffer(); + expect(pixel[0]).toBeGreaterThan(220); + expect(pixel[1]).toBeLessThan(25); + expect(pixel[2]).toBeLessThan(25); + expect(readFileSync(original)).toEqual(bytes); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }, + 60_000, + ); +}); + describe("contact-sheet capture budget", () => { it("does not start another Sharp page after the budget is exhausted", async () => { const dir = tempDir(); diff --git a/packages/cli/src/capture/contactSheet.ts b/packages/cli/src/capture/contactSheet.ts index 69e2180bd86..12e387961e8 100644 --- a/packages/cli/src/capture/contactSheet.ts +++ b/packages/cli/src/capture/contactSheet.ts @@ -251,7 +251,7 @@ export async function createAssetContactSheet( ): Promise { if (!existsSync(assetsDir)) return []; - const imageExts = new Set([".png", ".jpg", ".jpeg", ".webp"]); + const imageExts = new Set([".png", ".jpg", ".jpeg", ".webp", ".avif", ".gif", ".heic", ".tiff"]); const assetFiles = readdirSync(assetsDir) .filter((f) => imageExts.has(extname(f).toLowerCase()) && !f.includes("contact-sheet")) .sort(); From 85f67e2b150b0a2bd0e4d441573ee28ce71e2be7 Mon Sep 17 00:00:00 2001 From: "Abdullah M. Nazif" Date: Fri, 9 Oct 2026 07:01:46 +0800 Subject: [PATCH 2/3] test(cli): release cached GIF handles before fixture cleanup --- packages/cli/src/capture/contactSheet.test.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/capture/contactSheet.test.ts b/packages/cli/src/capture/contactSheet.test.ts index 11550c029ec..68cfb4c7be5 100644 --- a/packages/cli/src/capture/contactSheet.test.ts +++ b/packages/cli/src/capture/contactSheet.test.ts @@ -142,7 +142,14 @@ describe("createAssetContactSheet", () => { expect(pixel[2]).toBeLessThan(25); expect(readFileSync(original)).toEqual(bytes); } finally { - rmSync(dir, { recursive: true, force: true }); + // libvips caches GIF file handles, which prevent Windows fixture cleanup. + const cache = sharp.cache(); + sharp.cache(false); + try { + rmSync(dir, { recursive: true, force: true }); + } finally { + sharp.cache({ memory: cache.memory.max, files: cache.files.max, items: cache.items.max }); + } } }, 60_000, From cbbe9bb213c6f33f34b68acdc1c5729190e2a242 Mon Sep 17 00:00:00 2001 From: "Abdullah M. Nazif" Date: Sun, 25 Oct 2026 02:14:54 +0800 Subject: [PATCH 3/3] fix(cli): reconcile contact sheet tests with main --- packages/cli/src/capture/contactSheet.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/capture/contactSheet.test.ts b/packages/cli/src/capture/contactSheet.test.ts index 589993e1602..2a1db0cb6d6 100644 --- a/packages/cli/src/capture/contactSheet.test.ts +++ b/packages/cli/src/capture/contactSheet.test.ts @@ -12,7 +12,6 @@ import { join } from "node:path"; import sharp from "sharp"; import { describe, expect, it } from "vitest"; import { - createAssetContactSheet, createContactSheet, createAssetContactSheet, createScrollContactSheet,