From f419a64461b9a9977afa77d1844f9b4c351cd4bb Mon Sep 17 00:00:00 2001 From: Zachary Lowden Date: Wed, 19 Aug 2026 12:20:51 -0500 Subject: [PATCH] chore(deps): delete the inert next@16.3.1 patch and correct the comments about it (#4086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next 16.3.1 is the first release containing upstream's SVG-loader fix (vercel/next.js#96681) — it ships 'VipsForeignLoadSvg' in the sharp.unblock list itself, in both the CJS and ESM image-optimizer copies. Our local patch was therefore inert: the installed files are byte-identical (sha256) to the published tarball, and the patch does not even apply (its hunk context wants Png immediately followed by Tiff; 16.3.1 has Svg between them). It looked healthy because pnpm silently no-ops an already-applied patch — install exits 0, prints no warning, and still creates a patch_hash= virtual-store directory. Deletes the patch and its patchedDependencies entry (@mantine/hooks untouched), regenerates the lockfile, and corrects three comments that asserted upstream still lacked the entry. The CI guard stays: it asserts the outcome (the installed Next unblocks the SVG loader), so it keeps protecting against a future Next regressing this the way 16.3.0 did. Verified post-removal: guard green, still goes red when the loader entry is stripped from both installed copies, and /api/og renders PNGs on both paths on the preview build. Co-Authored-By: Claude Opus 5 (1M context) --- package.json | 3 +- patches/next@16.3.1.patch | 24 --------- pnpm-lock.yaml | 31 +++++------ scripts/ci/assert-next-svg-patch-applied.mjs | 51 ++++++++++++------- .../api/og.image-optimizer-sharp.test.ts | 30 +++++++++-- 5 files changed, 75 insertions(+), 64 deletions(-) delete mode 100644 patches/next@16.3.1.patch diff --git a/package.json b/package.json index 439cb66002..1cc741a8ea 100644 --- a/package.json +++ b/package.json @@ -434,8 +434,7 @@ "utf-8-validate" ], "patchedDependencies": { - "@mantine/hooks": "patches/@mantine__hooks.patch", - "next@16.3.1": "patches/next@16.3.1.patch" + "@mantine/hooks": "patches/@mantine__hooks.patch" } } } diff --git a/patches/next@16.3.1.patch b/patches/next@16.3.1.patch deleted file mode 100644 index b34896b683..0000000000 --- a/patches/next@16.3.1.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/dist/esm/server/image-optimizer.js b/dist/esm/server/image-optimizer.js -index 09277f1df5ed73d006e1769b405f148cab406dff..50135855db356fa22ab03b050ce24a4182c60a8d 100644 ---- a/dist/esm/server/image-optimizer.js -+++ b/dist/esm/server/image-optimizer.js -@@ -90,6 +90,7 @@ export function getSharp(concurrency, operationCache) { - 'VipsForeignLoadJpeg', - 'VipsForeignLoadNsgif', - 'VipsForeignLoadPng', -+ 'VipsForeignLoadSvg', - 'VipsForeignLoadTiff', - 'VipsForeignLoadWebp' - ] -diff --git a/dist/server/image-optimizer.js b/dist/server/image-optimizer.js -index 9be0c9ae912f3824e9253641eee3273f17080a69..31b5c6e3e46f40a994f62c4d43df6a8bbf971869 100644 ---- a/dist/server/image-optimizer.js -+++ b/dist/server/image-optimizer.js -@@ -210,6 +210,7 @@ function getSharp(concurrency, operationCache) { - 'VipsForeignLoadJpeg', - 'VipsForeignLoadNsgif', - 'VipsForeignLoadPng', -+ 'VipsForeignLoadSvg', - 'VipsForeignLoadTiff', - 'VipsForeignLoadWebp' - ] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f26df6ea4..97bc968538 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,6 @@ patchedDependencies: '@mantine/hooks': hash: b45a85bd4d007c796b93eb90a75d3234b81cce13964921b25721734c7ed79f64 path: patches/@mantine__hooks.patch - next@16.3.1: - hash: d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6 - path: patches/next@16.3.1.patch importers: @@ -66,7 +63,7 @@ importers: version: link:packages/civitai-flipt '@civitai/next-axiom': specifier: ^0.17.0 - version: 0.17.0(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) + version: 0.17.0(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0)) '@civitai/shared': specifier: workspace:* version: link:packages/civitai-shared @@ -156,7 +153,7 @@ importers: version: 16.3.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@next/third-parties': specifier: ^15.0.3 - version: 15.4.6(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) + version: 15.4.6(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) '@node-oauth/oauth2-server': specifier: ^5.3.0 version: 5.3.0 @@ -312,7 +309,7 @@ importers: version: 11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2) '@trpc/next': specifier: ^11.17.0 - version: 11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(react@18.3.1)(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) + version: 11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(react@18.3.1)(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2) '@trpc/react-query': specifier: ^11.17.0 version: 11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(react@18.3.1)(typescript@5.9.2) @@ -483,7 +480,7 @@ importers: version: 1.11.5 next: specifier: ^16.3.1 - version: 16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + version: 16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) nodemailer: specifier: ^6.8.0 version: 6.10.1 @@ -543,7 +540,7 @@ importers: version: 7.12.0(algoliasearch@4.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-instantsearch-router-nextjs: specifier: 7.12.0 - version: 7.12.0(algoliasearch@4.25.2)(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) + version: 7.12.0(algoliasearch@4.25.2)(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1) react-intersection-observer: specifier: ^9.4.0 version: 9.16.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13199,9 +13196,9 @@ snapshots: dependencies: fast-xml-parser: 5.10.1 - '@civitai/next-axiom@0.17.0(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))': + '@civitai/next-axiom@0.17.0(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))': dependencies: - next: 16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) whatwg-fetch: 3.6.20 '@clavata/sdk@0.2.3': @@ -14423,9 +14420,9 @@ snapshots: '@next/swc-win32-x64-msvc@16.3.1': optional: true - '@next/third-parties@15.4.6(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)': + '@next/third-parties@15.4.6(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1)': dependencies: - next: 16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: 18.3.1 third-party-capital: 1.0.20 @@ -16623,11 +16620,11 @@ snapshots: '@trpc/server': 11.17.0(typescript@5.9.2) typescript: 5.9.2 - '@trpc/next@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(react@18.3.1)(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': + '@trpc/next@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/react-query@11.17.0(@tanstack/react-query@5.101.0(react@18.3.1))(@trpc/client@11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(react@18.3.1)(typescript@5.9.2))(@trpc/server@11.17.0(typescript@5.9.2))(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.2)': dependencies: '@trpc/client': 11.17.0(@trpc/server@11.17.0(typescript@5.9.2))(typescript@5.9.2) '@trpc/server': 11.17.0(typescript@5.9.2) - next: 16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) typescript: 5.9.2 @@ -21926,7 +21923,7 @@ snapshots: neo-async@2.6.2: {} - next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): + next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0): dependencies: '@next/env': 16.3.1 '@swc/helpers': 0.5.23 @@ -23104,10 +23101,10 @@ snapshots: react: 18.3.1 use-sync-external-store: 1.5.0(react@18.3.1) - react-instantsearch-router-nextjs@7.12.0(algoliasearch@4.25.2)(next@16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1): + react-instantsearch-router-nextjs@7.12.0(algoliasearch@4.25.2)(next@16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0))(react@18.3.1): dependencies: instantsearch.js: 4.73.0(algoliasearch@4.25.2) - next: 16.3.1(patch_hash=d4cabfb715b050babab8b116679e9bd883334feb7bdf383883679c3cf22f94c6)(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) + next: 16.3.1(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(@types/node@24.13.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.90.0) react-instantsearch-core: 7.12.0(algoliasearch@4.25.2)(react@18.3.1) transitivePeerDependencies: - algoliasearch diff --git a/scripts/ci/assert-next-svg-patch-applied.mjs b/scripts/ci/assert-next-svg-patch-applied.mjs index ed5ec30f63..33455797d8 100644 --- a/scripts/ci/assert-next-svg-patch-applied.mjs +++ b/scripts/ci/assert-next-svg-patch-applied.mjs @@ -12,22 +12,32 @@ * WHAT IT ASSERTS, AND WHY THAT IS THE RIGHT INVARIANT. Next 16.3.0 applies a process-global * libvips block on first image-optimizer use and re-enables only six RASTER loaders; SVG is * not among them, so every `ImageResponse` in the process throws - * `Input buffer contains unsupported image format` from then on. We carry upstream - * vercel/next.js#96681 as `patches/next@16.3.1.patch` until we are on a Next that contains it. + * `Input buffer contains unsupported image format` from then on. Upstream fixed that in + * vercel/next.js#96681, and 16.3.1 is the first release that CONTAINS the fix: it ships + * `'VipsForeignLoadSvg'` in the unblock list itself. We carried the one-line fix as a pnpm + * patch only while we were on 16.3.0; there is no Next patch now, and this guard needs none. * - * The assertion is deliberately "the installed Next unblocks SVG", NOT "our patch file is on - * disk" and NOT "we are on any particular version": + * So what this protects against today is a FUTURE Next regressing the loader entry again — + * which is exactly why the assertion is deliberately "the installed Next unblocks SVG", NOT + * "our patch file is on disk" and NOT "we are on any particular version": * - * - reading `node_modules` rather than `patches/` is the whole point — a patch that exists - * but did not APPLY (unregistered key, failed hunk, a stale lockfile hash) is exactly the - * silent failure this catches, and a patch-file grep would sail through it; - * - staying version-agnostic means the guard does not have to be retired in the same commit - * that bumps Next. If a future Next ships the loader entry itself, this still passes with - * the patch retired; the patch is separately version-pinned through - * `pnpm.patchedDependencies`, so a bump fails the install rather than silently carrying a - * stale patch. (Measured on the 16.3.0 -> 16.3.1 bump: the patch still applies, and the - * installed 16.3.1 carries exactly ONE occurrence of the loader entry per copy — i.e. the - * patch is what puts it there, upstream still does not.) + * - reading `node_modules` rather than `patches/` is the whole point. It is the only form + * that is correct both now (upstream supplies the entry, no patch exists) and in the + * patched past (a patch that exists but did not APPLY — unregistered key, failed hunk, a + * stale lockfile hash — is exactly the silent failure this catches, and a patch-file grep + * would sail through it); + * - staying version-agnostic means the guard does not have to be retired, re-pinned or + * re-dated in the same commit that bumps Next. It keeps passing on any Next that unblocks + * the loader, and goes red the moment one does not — whoever hits that decides between + * re-introducing a patch and pinning back. + * + * 🔴 One occurrence per copy is NOT evidence about WHO supplied it. The count is the same + * whether upstream ships the entry or a patch inserted it, so it cannot distinguish the two — + * reading it as proof of a working patch is how the now-deleted 16.3.1 patch was carried for + * a while after it had gone inert (pnpm silently no-ops an already-applied patch: rc=0, no + * warning, and it still creates a `patch_hash=…` virtual-store dir, so it LOOKS applied). + * To attribute the entry, compare the installed file against the pristine published tarball — + * on 16.3.1 they are byte-identical, so upstream is the source. * * BOTH BUILDS, SKIPPING WHAT IS ABSENT. Next ships this module twice — `dist/server/` (CJS) * and `dist/esm/server/`. A full `pnpm install` has both; the production standalone image has @@ -115,10 +125,15 @@ function formatReport(report) { const FAILURE_EXPLANATION = `FAIL: the installed Next does not unblock the libvips SVG loader (${REQUIRED_LOADER}).\n` + 'Every `next/og` ImageResponse — i.e. all of /api/og — will return 500 as soon as anything\n' + - 'touches the image optimizer in that process. Check that `patches/next@.patch` is\n' + - 'still registered under `pnpm.patchedDependencies` in package.json and that\n' + - '`pnpm install` applied it; this reads node_modules, so a patch file that exists but did\n' + - 'not apply fails here. See src/tests/api/og.image-optimizer-sharp.test.ts for the outage.'; + 'touches the image optimizer in that process.\n' + + 'Next >= 16.3.1 ships this loader entry itself (vercel/next.js#96681), and we carry no Next\n' + + 'patch, so this failing means the Next you installed has REGRESSED it — or dependencies are\n' + + 'stale. Re-run `pnpm install` and re-check; if the installed Next genuinely lacks the entry,\n' + + 'pin Next back to a release that unblocks the loader, or re-introduce a pnpm patch adding\n' + + `'${REQUIRED_LOADER}' to the sharp.unblock({ operation: [...] }) list in BOTH\n` + + 'dist/server/image-optimizer.js and dist/esm/server/image-optimizer.js. This reads\n' + + 'node_modules, not patches/, so it judges the artifact you actually installed however the\n' + + 'entry got there. See src/tests/api/og.image-optimizer-sharp.test.ts for the outage.'; const NO_COPIES_EXPLANATION = 'FAIL: found no copy of next/dist/**/server/image-optimizer.js to read, so this check ' + diff --git a/src/tests/api/og.image-optimizer-sharp.test.ts b/src/tests/api/og.image-optimizer-sharp.test.ts index f661c765aa..cd53b76a86 100644 --- a/src/tests/api/og.image-optimizer-sharp.test.ts +++ b/src/tests/api/og.image-optimizer-sharp.test.ts @@ -22,9 +22,17 @@ import type { NextApiRequest, NextApiResponse } from 'next'; * `Input buffer contains unsupported image format` — and `/api/og` returns 500 * for the rest of that pod's life. Upstream: vercel/next.js#96301 introduced it, * vercel/next.js#96681 fixes it by adding `'VipsForeignLoadSvg'` to the unblock - * list. We carry that one-line fix as `patches/next@16.3.1.patch` until we are on - * a Next release that contains it — 16.3.1 still does not, so the bump to it - * renamed the patch and kept it. + * list. We carried that one-line fix as a pnpm patch while we were on 16.3.0. + * 16.3.1 is the first release that CONTAINS the fix — it ships the loader entry + * upstream — so the patch became inert on that bump and has been deleted. What + * these cases guard now is a FUTURE Next regressing the entry again. + * + * 🔴 Note for whoever reads this next: "the installed file has one occurrence of + * `'VipsForeignLoadSvg'`" does NOT tell you whether upstream or a patch put it + * there — the count is identical either way. That ambiguity is how the inert + * patch survived a bump (pnpm no-ops an already-applied patch with rc=0 and no + * warning). Attribute it by diffing the installed file against the published + * tarball; on 16.3.1 they are byte-identical. * * WHY THIS TEST IS SHAPED THE WAY IT IS. 🔴 The ordering is the whole test. * Rendering an `ImageResponse` in a fresh process SUCCEEDS even on a broken @@ -66,6 +74,22 @@ import type { NextApiRequest, NextApiResponse } from 'next'; * reports 1 copy read and the ESM copy as skipped. * - both copies absent: exits 1 rather than passing vacuously on zero reads. * + * Re-watched 2026-08-18 on stock 16.3.1 with the patch DELETED — the mutations + * above that reinstall or delete a patch no longer reproduce, since there is no + * Next patch to remove, so the guard was driven against node_modules directly: + * - installed copies byte-identical (sha256) to the published next-16.3.1 + * tarball, and the guard exits 0 reporting `copies read: 2`, both ok. The + * hash equality is the part that attributes the loader entry to upstream; + * the guard's own green cannot, since it counts rather than attributes. + * - `'VipsForeignLoadSvg',` stripped from BOTH installed copies, leaving the + * `unblock({ operation: [...] })` call structurally intact so the SVG branch + * is reached rather than the no-unblock-call fallback: exits 1 reporting + * both copies "does NOT unblock VipsForeignLoadSvg", `copies read: 2`. + * Restored from a `cp -a` backup (never `git checkout` — node_modules is + * untracked) and re-confirmed green. + * - both copies moved aside: exits 1 with "this check proved nothing", + * `copies read: 0` — still no vacuous pass. + * * SCOPE / WHAT THIS DOES NOT COVER. This runs in-process, so it does not cover * packaging failures under `output: 'standalone'` (the OTHER way `/api/og` has * broken — see the `outputFileTracingIncludes['/api/og']` entry in