From 5a9a437c5793f9ef643c4668d03b6833a03ccb9a Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Thu, 20 Nov 2025 15:56:02 +0900 Subject: [PATCH 01/21] initial commit --- .../cloudflare/src/api/cloudflare-context.ts | 3 + .../src/cli/build/open-next/compile-images.ts | 24 + .../cloudflare/src/cli/templates/images.ts | 573 ++++++++++++++---- .../cloudflare/src/cli/templates/worker.ts | 6 +- 4 files changed, 475 insertions(+), 131 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index fbdc111fe..cde8b7bd1 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -13,6 +13,9 @@ declare global { // Asset binding ASSETS?: Fetcher; + // Images binding + IMAGES?: ImagesBinding; + // Environment to use when loading Next `.env` files // Default to "production" NEXTJS_ENV?: string; diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index 6d4991fbb..12ccb0307 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -19,7 +19,17 @@ export async function compileImages(options: BuildOptions) { : {}; const __IMAGES_REMOTE_PATTERNS__ = JSON.stringify(imagesManifest?.images?.remotePatterns ?? []); + const __IMAGES_LOCAL_PATTERNS_DEFINED__ = JSON.stringify( + Array.isArray(imagesManifest?.images?.localPatterns) + ); const __IMAGES_LOCAL_PATTERNS__ = JSON.stringify(imagesManifest?.images?.localPatterns ?? []); + const __IMAGES_DEVICE_SIZES__ = JSON.stringify(imagesManifest?.images?.deviceSizes ?? defaultDeviceSizes); + const __IMAGES_IMAGE_SIZES__ = JSON.stringify(imagesManifest?.images?.imageSizes ?? defaultImageSizes); + const __IMAGES_QUALITIES__ = JSON.stringify(imagesManifest?.images?.qualities ?? defaultQualities); + const __IMAGES_FORMATS__ = JSON.stringify(imagesManifest?.images?.formats ?? defaultFormats); + const __IMAGES_MINIMUM_CACHE_TTL__ = JSON.stringify( + imagesManifest?.images?.minimumCacheTTL ?? defaultMinimumCacheTTL + ); const __IMAGES_ALLOW_SVG__ = JSON.stringify(Boolean(imagesManifest?.images?.dangerouslyAllowSVG)); const __IMAGES_CONTENT_SECURITY_POLICY__ = JSON.stringify( imagesManifest?.images?.contentSecurityPolicy ?? "script-src 'none'; frame-src 'none'; sandbox;" @@ -27,6 +37,7 @@ export async function compileImages(options: BuildOptions) { const __IMAGES_CONTENT_DISPOSITION__ = JSON.stringify( imagesManifest?.images?.contentDispositionType ?? "attachment" ); + const __IMAGES_MAX_REDIRECTS__ = JSON.stringify(imagesManifest?.images?.maximumRedirects ?? 3); await build({ entryPoints: [imagesPath], @@ -38,10 +49,23 @@ export async function compileImages(options: BuildOptions) { platform: "node", define: { __IMAGES_REMOTE_PATTERNS__, + __IMAGES_LOCAL_PATTERNS_DEFINED__, __IMAGES_LOCAL_PATTERNS__, + __IMAGES_DEVICE_SIZES__, + __IMAGES_IMAGE_SIZES__, + __IMAGES_QUALITIES__, + __IMAGES_FORMATS__, + __IMAGES_MINIMUM_CACHE_TTL__, __IMAGES_ALLOW_SVG__, __IMAGES_CONTENT_SECURITY_POLICY__, __IMAGES_CONTENT_DISPOSITION__, + __IMAGES_MAX_REDIRECTS__, }, }); } + +const defaultDeviceSizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]; +const defaultImageSizes = [32, 48, 64, 96, 128, 256, 384]; +const defaultQualities = [75]; +const defaultFormats = ["image/webp"]; +const defaultMinimumCacheTTL = 14400; diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 3fa47f312..bb12dc66a 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -13,120 +13,463 @@ export type LocalPattern = { search?: string; }; -let NEXT_IMAGE_REGEXP: RegExp; +export async function handleImageRequest( + requestURL: URL, + requestHeaders: Headers, + env: CloudflareEnv +): Promise { + const parseResult = parseImageRequest(requestURL, requestHeaders); + if (!parseResult.ok) { + return new Response(parseResult.message, { + status: 400, + }); + } -/** - * Fetches an images. - * - * Local images (starting with a '/' as fetched using the passed fetcher). - * Remote images should match the configured remote patterns or a 404 response is returned. - */ -export async function fetchImage(fetcher: Fetcher | undefined, imageUrl: string, ctx: ExecutionContext) { - // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L208 - if (!imageUrl || imageUrl.length > 3072 || imageUrl.startsWith("//")) { - return getUrlErrorResponse(); + let imageResponse: Response; + if (parseResult.url.startsWith("/")) { + if (env.ASSETS === undefined) { + console.error("env.ASSETS binding is not defined."); + return new Response('"url" parameter is valid but upstream response is invalid', { + status: 404, + }); + } + const absoluteURL = new URL(parseResult.url, requestURL); + imageResponse = await env.ASSETS.fetch(absoluteURL); + } else { + const fetchImageResult = await fetchImage(parseResult.url, __IMAGES_MAX_REDIRECTS__); + if (!fetchImageResult.ok) { + if (fetchImageResult.error === "timed_out") { + return new Response('"url" parameter is valid but upstream response timed out', { + status: 504, + }); + } + if (fetchImageResult.error === "too_many_redirects") { + return new Response('"url" parameter is valid but upstream response is invalid', { + status: 508, + }); + } + throw new Error("Failed to fetch image"); + } + imageResponse = fetchImageResult.response; } - // Local - if (imageUrl.startsWith("/")) { - // @ts-expect-error TS2339 Missing types for URL.parse - const url = URL.parse(imageUrl, "http://n"); + if (imageResponse.status !== 200 || imageResponse.body === null) { + return new Response('"url" parameter is valid but upstream response is invalid', { + status: imageResponse.status, + }); + } - if (url == null) { - return getUrlErrorResponse(); + let immutable = false; + if (parseResult.static) { + immutable = true; + } else { + const cacheControlHeader = imageResponse.headers.get("Cache-Control"); + if (cacheControlHeader !== null) { + immutable = cacheControlHeader.includes("immutable"); } + } - // This method will never throw because URL parser will handle invalid input. - const pathname = decodeURIComponent(url.pathname); + const [contentTypeImageStream, imageStream] = imageResponse.body.tee(); + const imageHeaderBytes = new Uint8Array(32); + const contentTypeImageReader = contentTypeImageStream.getReader({ + mode: "byob", + }); + const readImageHeaderBytesResult = await contentTypeImageReader.readAtLeast(32, imageHeaderBytes); + if (readImageHeaderBytesResult.value === undefined) { + await imageResponse.body.cancel(); - NEXT_IMAGE_REGEXP ??= /\/_next\/image($|\/)/; - if (NEXT_IMAGE_REGEXP.test(pathname)) { - return getUrlErrorResponse(); + return new Response('"url" parameter is valid but upstream response is invalid', { + status: imageResponse.status, + }); + } + const contentType = detectImageContentType(readImageHeaderBytesResult.value); + if (contentType === null) { + return new Response('"url" parameter is valid but image type is not allowed', { + status: 400, + }); + } + if (contentType === SVG) { + if (!__IMAGES_ALLOW_SVG__) { + return new Response('"url" parameter is valid but image type is not allowed', { + status: 400, + }); } + const response = createImageResponse(imageStream, contentType, { + immutable, + }); + return response; + } - // If localPatterns are not defined all local images are allowed. - if ( - __IMAGES_LOCAL_PATTERNS__.length > 0 && - !__IMAGES_LOCAL_PATTERNS__.some((p: LocalPattern) => matchLocalPattern(p, url)) - ) { - return getUrlErrorResponse(); + if (contentType === GIF) { + if (env.IMAGES === undefined) { + console.warn("env.IMAGES binding is not defined."); + const response = createImageResponse(imageStream, contentType, { + immutable, + }); + return response; } - return fetcher?.fetch(`http://assets.local${imageUrl}`); + const imageSource = env.IMAGES.input(imageStream); + const imageTransformationResult = await imageSource + .transform({ + width: parseResult.width, + fit: "scale-down", + }) + .output({ + quality: parseResult.quality, + format: GIF, + }); + const outputImageStream = imageTransformationResult.image(); + const response = createImageResponse(outputImageStream, GIF, { + immutable, + }); + return response; } - // Remote - let url: URL; - try { - url = new URL(imageUrl); - } catch { - return getUrlErrorResponse(); - } + if (contentType === AVIF || contentType === WEBP || contentType === JPEG || contentType === PNG) { + if (env.IMAGES === undefined) { + console.warn("env.IMAGES binding is not defined."); + const response = createImageResponse(imageStream, contentType, { + immutable, + }); + return response; + } - if (url.protocol !== "http:" && url.protocol !== "https:") { - return getUrlErrorResponse(); + const outputFormat = parseResult.format ?? contentType; + const imageSource = env.IMAGES.input(imageStream); + const imageTransformationResult = await imageSource + .transform({ + width: parseResult.width, + fit: "scale-down", + }) + .output({ + quality: parseResult.quality, + format: outputFormat, + }); + const outputImageStream = imageTransformationResult.image(); + const response = createImageResponse(outputImageStream, outputFormat, { + immutable, + }); + return response; } - // The remotePatterns is used to allow images from specific remote external paths and block all others. - if (!__IMAGES_REMOTE_PATTERNS__.some((p: RemotePattern) => matchRemotePattern(p, url))) { - return getUrlErrorResponse(); + await imageResponse.body.cancel(); + + return new Response('"url" parameter is valid but image type is not allowed', { + status: 400, + }); +} + +async function fetchImage(url: string, count: number): Promise { + let response: Response; + try { + response = await fetch(url, { + signal: AbortSignal.timeout(7_000), + redirect: "manual", + }); + } catch (e) { + if (e instanceof Error && e.name === "TimeoutError") { + const result: FetchImageErrorResult = { + ok: false, + error: "timed_out", + }; + return result; + } + throw e; } + if (redirectResponseStatuses.includes(response.status)) { + const locationHeader = response.headers.get("Location"); + if (locationHeader !== null) { + if (count < 1) { + const result: FetchImageErrorResult = { + ok: false, + error: "too_many_redirects", + }; + return result; + } + let redirectTarget: string; + if (locationHeader.startsWith("/")) { + redirectTarget = new URL(locationHeader, url).href; + } else { + redirectTarget = locationHeader; + } + const result = await fetchImage(redirectTarget, count - 1); + return result; + } + } + const result: FetchImageSuccessResult = { + ok: true, + response: response, + }; + return result; +} + +type FetchImageResult = FetchImageSuccessResult | FetchImageErrorResult; - const imgResponse = await fetch(imageUrl, { cf: { cacheEverything: true } }); +type FetchImageSuccessResult = { + ok: true; + response: Response; +}; + +type FetchImageErrorResult = { + ok: false; + error: FetchImageError; +}; - if (!imgResponse.body) { - return imgResponse; +type FetchImageError = "timed_out" | "too_many_redirects"; + +const redirectResponseStatuses = [301, 302, 303, 307, 308]; + +function createImageResponse( + image: ReadableStream, + contentType: string, + imageResponseFlags: ImageResponseFlags +): Response { + const response = new Response(image); + response.headers.set("Vary", "Accept"); + response.headers.set("Content-Type", contentType); + response.headers.set("Content-Disposition", __IMAGES_CONTENT_DISPOSITION__); + response.headers.set("Content-Security-Policy", __IMAGES_CONTENT_SECURITY_POLICY__); + if (imageResponseFlags.immutable) { + response.headers.set("Cache-Control", "public, max-age=315360000, immutable"); } + return response; +} - const buffer = new ArrayBuffer(32); +type ImageResponseFlags = { + immutable: boolean; +}; - try { - let contentType: string | undefined; - // respBody is eventually used for the response - // contentBody is used to detect the content type - const [respBody, contentBody] = imgResponse.body.tee(); - const reader = contentBody.getReader({ mode: "byob" }); - const { value } = await reader.read(new Uint8Array(buffer)); - // Release resources by calling `reader.cancel()` - // `ctx.waitUntil` keeps the runtime running until the promise settles without having to wait here. - ctx.waitUntil(reader.cancel()); - - if (value) { - contentType = detectContentType(value); - } +function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImageRequestURLResult { + const deviceSizes: number[] = __IMAGES_DEVICE_SIZES__; + const imageSizes: number[] = __IMAGES_IMAGE_SIZES__; + // const minimumCacheTTLSeconds = __IMAGES_MINIMUM_CACHE_TTL__; + const formats = __IMAGES_FORMATS__; + const remotePatterns = __IMAGES_REMOTE_PATTERNS__; + const localPatterns = __IMAGES_LOCAL_PATTERNS__; + const qualities: number[] = __IMAGES_QUALITIES__; + + const urlQueryValues = requestURL.searchParams.getAll("url"); + if (urlQueryValues.length < 1) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is required', + }; + return result; + } + if (urlQueryValues.length > 1) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter cannot be an array', + }; + return result; + } + const urlQueryValue = urlQueryValues[0]!; + if (urlQueryValue.length > 3072) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is too long', + }; + return result; + } + if (urlQueryValue.startsWith("//")) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter cannot be a protocol-relative URL (//)', + }; + return result; + } - if (!contentType) { - // Fallback to upstream header when the type can not be detected - // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/server/image-optimizer.ts#L748 - contentType = imgResponse.headers.get("content-type") ?? ""; + let url: string; + if (urlQueryValue.startsWith("/")) { + url = urlQueryValue; + + const pathname = getPathnameFromRelativeURL(url); + if (/\/_next\/image($|\/)/.test(decodeURIComponent(pathname))) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter cannot be recursive', + }; + return result; + } + if (__IMAGES_LOCAL_PATTERNS_DEFINED__) { + if (!hasLocalMatch(localPatterns, url)) { + const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; + return result; + } + } + } else { + let parsedURL: URL; + try { + parsedURL = new URL(urlQueryValue); + } catch { + const result: ErrorResult = { ok: false, message: '"url" parameter is invalid' }; + return result; } - // Sanitize the content type: - // - Accept images only - // - Reject multiple content types - if (!contentType.startsWith("image/") || contentType.includes(",")) { - contentType = undefined; + const validProtocols = ["http:", "https:"]; + if (!validProtocols.includes(parsedURL.protocol)) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is invalid', + }; + return result; + } + if (!hasRemoteMatch(remotePatterns, parsedURL)) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is not allowed', + }; + return result; } - if (contentType && !(contentType === SVG && !__IMAGES_ALLOW_SVG__)) { - const headers = new Headers(imgResponse.headers); - headers.set("content-type", contentType); - headers.set("content-disposition", __IMAGES_CONTENT_DISPOSITION__); - headers.set("content-security-policy", __IMAGES_CONTENT_SECURITY_POLICY__); - return new Response(respBody, { ...imgResponse, headers }); + url = parsedURL.href; + } + const staticAsset = url.startsWith(`${__NEXT_BASE_PATH__ || ""}/_next/static/media`); + + const widthQueryValues = requestURL.searchParams.getAll("w"); + if (widthQueryValues.length < 1) { + const result: ErrorResult = { + ok: false, + message: '"w" parameter (width) is required', + }; + return result; + } + if (widthQueryValues.length > 1) { + const result: ErrorResult = { + ok: false, + message: '"w" parameter (width) cannot be an array', + }; + return result; + } + const widthQueryValue = widthQueryValues[0]!; + if (!/^[0-9]+$/.test(widthQueryValue)) { + const result: ErrorResult = { + ok: false, + message: '"w" parameter (width) must be an integer greater than 0', + }; + return result; + } + const width = parseInt(widthQueryValue, 10); + if (width <= 0 || isNaN(width)) { + const result: ErrorResult = { + ok: false, + message: '"w" parameter (width) must be an integer greater than 0', + }; + return result; + } + + const sizeValid = deviceSizes.includes(width) || imageSizes.includes(width); + if (!sizeValid) { + const result: ErrorResult = { + ok: false, + message: `"w" parameter (width) of ${width} is not allowed`, + }; + return result; + } + + const qualityQueryValues = requestURL.searchParams.getAll("q"); + if (qualityQueryValues.length < 1) { + const result: ErrorResult = { + ok: false, + message: '"q" parameter (quality) is required', + }; + return result; + } + if (qualityQueryValues.length > 1) { + const result: ErrorResult = { + ok: false, + message: '"q" parameter (quality) cannot be an array', + }; + return result; + } + const qualityQueryValue = qualityQueryValues[0]!; + if (!/^[0-9]+$/.test(qualityQueryValue)) { + const result: ErrorResult = { + ok: false, + message: '"q" parameter (quality) must be an integer between 1 and 100', + }; + return result; + } + const quality = parseInt(qualityQueryValue, 10); + if (isNaN(quality) || quality < 1 || quality > 100) { + const result: ErrorResult = { + ok: false, + message: '"q" parameter (quality) must be an integer between 1 and 100', + }; + return result; + } + if (!qualities.includes(quality)) { + const result: ErrorResult = { + ok: false, + message: `"w" parameter (width) of ${width} is not allowed`, + }; + return result; + } + + const acceptHeader = requestHeaders.get("Accept") ?? ""; + let format: OptimizedImageFormat | null = null; + // Find a more specific format that the client accepts. + for (const allowedFormat of formats) { + if (acceptHeader.includes(allowedFormat)) { + format = allowedFormat; + break; } + } - // Cancel the unused stream - ctx.waitUntil(respBody.cancel()); + const result: ParseImageRequestURLSuccessResult = { + ok: true, + url: url, + width: width, + quality: quality, + format: format, + static: staticAsset, + }; + return result; +} - return new Response('"url" parameter is valid but image type is not allowed', { - status: 400, - }); - } catch { - return new Response('"url" parameter is valid but upstream response is invalid', { - status: 400, - }); +type ParseImageRequestURLResult = ParseImageRequestURLSuccessResult | ErrorResult; + +type ParseImageRequestURLSuccessResult = { + ok: true; + /** Absolute or relative URL. */ + url: string; + width: number; + quality: number; + format: OptimizedImageFormat | null; + static: boolean; +}; + +export type OptimizedImageFormat = "image/avif" | "image/webp"; + +type ErrorResult = { + ok: false; + message: string; +}; + +function getPathnameFromRelativeURL(relativeURL: string): string { + return relativeURL.split("?")[0]!; +} + +function hasLocalMatch(localPatterns: LocalPattern[], relativeURL: string): boolean { + const pathname = getPathnameFromRelativeURL(relativeURL); + for (const localPattern of localPatterns) { + const patternRegExp = new RegExp(localPattern.pathname); + if (patternRegExp.test(pathname)) { + return true; + } + } + return false; +} + +function hasRemoteMatch(remotePatterns: RemotePattern[], url: URL): boolean { + for (const remotePattern of remotePatterns) { + const matched = matchRemotePattern(remotePattern, url); + if (matched) { + return true; + } } + return false; } export function matchRemotePattern(pattern: RemotePattern, url: URL): boolean { @@ -163,28 +506,22 @@ export function matchLocalPattern(pattern: LocalPattern, url: URL): boolean { return new RegExp(pattern.pathname).test(url.pathname); } -/** - * @returns same error as Next.js when the url query parameter is not accepted. - */ -function getUrlErrorResponse() { - return new Response(`"url" parameter is not allowed`, { status: 400 }); -} - const AVIF = "image/avif"; const WEBP = "image/webp"; const PNG = "image/png"; const JPEG = "image/jpeg"; -const JXL = "image/jxl"; -const JP2 = "image/jp2"; const HEIC = "image/heic"; const GIF = "image/gif"; const SVG = "image/svg+xml"; -const ICO = "image/x-icon"; -const ICNS = "image/x-icns"; -const TIFF = "image/tiff"; -const BMP = "image/bmp"; -// pdf will be rejected (not an `image/...` type) -const PDF = "application/pdf"; + +type ImageContentType = + | "image/avif" + | "image/webp" + | "image/png" + | "image/jpeg" + | "image/heic" + | "image/gif" + | "image/svg+xml"; /** * Detects the content type by looking at the first few bytes of a file @@ -194,7 +531,7 @@ const PDF = "application/pdf"; * @param buffer The image bytes * @returns a content type of undefined for unsupported content */ -export function detectContentType(buffer: Uint8Array) { +export function detectImageContentType(buffer: Uint8Array): ImageContentType | null { if ([0xff, 0xd8, 0xff].every((b, i) => buffer[i] === b)) { return JPEG; } @@ -216,43 +553,25 @@ export function detectContentType(buffer: Uint8Array) { if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every((b, i) => !b || buffer[i] === b)) { return AVIF; } - if ([0x00, 0x00, 0x01, 0x00].every((b, i) => buffer[i] === b)) { - return ICO; - } - if ([0x69, 0x63, 0x6e, 0x73].every((b, i) => buffer[i] === b)) { - return ICNS; - } - if ([0x49, 0x49, 0x2a, 0x00].every((b, i) => buffer[i] === b)) { - return TIFF; - } - if ([0x42, 0x4d].every((b, i) => buffer[i] === b)) { - return BMP; - } - if ([0xff, 0x0a].every((b, i) => buffer[i] === b)) { - return JXL; - } - if ( - [0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) - ) { - return JXL; - } if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => !b || buffer[i] === b)) { return HEIC; } - if ([0x25, 0x50, 0x44, 0x46, 0x2d].every((b, i) => buffer[i] === b)) { - return PDF; - } - if ( - [0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) - ) { - return JP2; - } + return null; } declare global { var __IMAGES_REMOTE_PATTERNS__: RemotePattern[]; + var __IMAGES_LOCAL_PATTERNS_DEFINED__: boolean; var __IMAGES_LOCAL_PATTERNS__: LocalPattern[]; + var __IMAGES_DEVICE_SIZES__: number[]; + var __IMAGES_IMAGE_SIZES__: number[]; + var __IMAGES_QUALITIES__: number[]; + var __IMAGES_FORMATS__: NextConfigImageFormat[]; + var __IMAGES_MINIMUM_CACHE_TTL__: number; var __IMAGES_ALLOW_SVG__: boolean; var __IMAGES_CONTENT_SECURITY_POLICY__: string; var __IMAGES_CONTENT_DISPOSITION__: string; + var __IMAGES_MAX_REDIRECTS__: number; + + type NextConfigImageFormat = "image/avif" | "image/webp"; } diff --git a/packages/cloudflare/src/cli/templates/worker.ts b/packages/cloudflare/src/cli/templates/worker.ts index 6d2c3fdf0..95dded9ca 100644 --- a/packages/cloudflare/src/cli/templates/worker.ts +++ b/packages/cloudflare/src/cli/templates/worker.ts @@ -1,12 +1,11 @@ //@ts-expect-error: Will be resolved by wrangler build -import { fetchImage } from "./cloudflare/images.js"; +import { handleImageRequest } from "./cloudflare/images.js"; //@ts-expect-error: Will be resolved by wrangler build import { runWithCloudflareRequestContext } from "./cloudflare/init.js"; //@ts-expect-error: Will be resolved by wrangler build import { maybeGetSkewProtectionResponse } from "./cloudflare/skew-protection.js"; // @ts-expect-error: Will be resolved by wrangler build import { handler as middlewareHandler } from "./middleware/handler.mjs"; - //@ts-expect-error: Will be resolved by wrangler build export { DOQueueHandler } from "./.build/durable-objects/queue.js"; //@ts-expect-error: Will be resolved by wrangler build @@ -43,8 +42,7 @@ export default { url.pathname === `${globalThis.__NEXT_BASE_PATH__}/_next/image${globalThis.__TRAILING_SLASH__ ? "/" : ""}` ) { - const imageUrl = url.searchParams.get("url") ?? ""; - return await fetchImage(env.ASSETS, imageUrl, ctx); + return await handleImageRequest(url, request.headers, env); } // - `Request`s are handled by the Next server From 58ab5a5480581928c5f9b329bcbc3cf94e769e64 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Thu, 20 Nov 2025 15:56:06 +0900 Subject: [PATCH 02/21] update examples --- examples/playground15/next.config.ts | 3 +++ examples/playground15/wrangler.jsonc | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/playground15/next.config.ts b/examples/playground15/next.config.ts index 5a9fdb889..fa7885f61 100644 --- a/examples/playground15/next.config.ts +++ b/examples/playground15/next.config.ts @@ -12,6 +12,9 @@ const nextConfig: NextConfig = { }, deploymentId: getDeploymentId(), trailingSlash: true, + images: { + formats: ["image/avif", "image/webp"], + }, }; export default nextConfig; diff --git a/examples/playground15/wrangler.jsonc b/examples/playground15/wrangler.jsonc index 56965f57b..1c6c3718e 100644 --- a/examples/playground15/wrangler.jsonc +++ b/examples/playground15/wrangler.jsonc @@ -44,5 +44,8 @@ "database_id": "db_id", "database_name": "db_name" } - ] + ], + "images": { + "binding": "IMAGES" + } } From 43dea04808b0469485f1e3c47b7839c14339a61d Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Tue, 25 Nov 2025 12:50:50 +0900 Subject: [PATCH 03/21] add jsdoc --- .../cloudflare/src/cli/templates/images.ts | 58 ++++++++++++++----- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index bb12dc66a..c2dbaaf8f 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -13,6 +13,16 @@ export type LocalPattern = { search?: string; }; +/** + * Handles requests to /_next/image(/), including image optimizations. + * Image optimization is disabled if env.IMAGES is undefined. + * + * Throws an exception on unexpected errors. + * @param requestURL + * @param requestHeaders + * @param env + * @returns A promise that resolves to the resolved request. + */ export async function handleImageRequest( requestURL: URL, requestHeaders: Headers, @@ -36,7 +46,12 @@ export async function handleImageRequest( const absoluteURL = new URL(parseResult.url, requestURL); imageResponse = await env.ASSETS.fetch(absoluteURL); } else { - const fetchImageResult = await fetchImage(parseResult.url, __IMAGES_MAX_REDIRECTS__); + let fetchImageResult: FetchWithRedirectsResult; + try { + fetchImageResult = await fetchWithRedirects(parseResult.url, 7_000, __IMAGES_MAX_REDIRECTS__); + } catch (e) { + throw new Error("Failed to fetch image", { cause: e }); + } if (!fetchImageResult.ok) { if (fetchImageResult.error === "timed_out") { return new Response('"url" parameter is valid but upstream response timed out', { @@ -160,16 +175,29 @@ export async function handleImageRequest( }); } -async function fetchImage(url: string, count: number): Promise { +/** + * Fetch call with max redirects and timeouts. + * + * Re-throws the exception thrown by a fetch call. + * @param url + * @param timeout Timeout for a single fetch call. + * @param maxRedirectCount + * @returns + */ +async function fetchWithRedirects( + url: string, + timeout: number, + maxRedirectCount: number +): Promise { let response: Response; try { response = await fetch(url, { - signal: AbortSignal.timeout(7_000), + signal: AbortSignal.timeout(timeout), redirect: "manual", }); } catch (e) { if (e instanceof Error && e.name === "TimeoutError") { - const result: FetchImageErrorResult = { + const result: FetchWithRedirectsErrorResult = { ok: false, error: "timed_out", }; @@ -180,8 +208,8 @@ async function fetchImage(url: string, count: number): Promise if (redirectResponseStatuses.includes(response.status)) { const locationHeader = response.headers.get("Location"); if (locationHeader !== null) { - if (count < 1) { - const result: FetchImageErrorResult = { + if (maxRedirectCount < 1) { + const result: FetchWithRedirectsErrorResult = { ok: false, error: "too_many_redirects", }; @@ -193,25 +221,25 @@ async function fetchImage(url: string, count: number): Promise } else { redirectTarget = locationHeader; } - const result = await fetchImage(redirectTarget, count - 1); + const result = await fetchWithRedirects(redirectTarget, timeout, maxRedirectCount - 1); return result; } } - const result: FetchImageSuccessResult = { + const result: FetchWithRedirectsSuccessResult = { ok: true, response: response, }; return result; } -type FetchImageResult = FetchImageSuccessResult | FetchImageErrorResult; +type FetchWithRedirectsResult = FetchWithRedirectsSuccessResult | FetchWithRedirectsErrorResult; -type FetchImageSuccessResult = { +type FetchWithRedirectsSuccessResult = { ok: true; response: Response; }; -type FetchImageErrorResult = { +type FetchWithRedirectsErrorResult = { ok: false; error: FetchImageError; }; @@ -419,10 +447,10 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage const result: ParseImageRequestURLSuccessResult = { ok: true, - url: url, - width: width, - quality: quality, - format: format, + url, + width, + quality, + format, static: staticAsset, }; return result; From faa5b0df2ff7353506d1343e5e706dfe6137a4c6 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Tue, 25 Nov 2025 16:38:04 +0900 Subject: [PATCH 04/21] use opennext logger --- .../cloudflare/src/cli/build/open-next/compile-images.ts | 2 +- packages/cloudflare/src/cli/templates/images.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index 12ccb0307..c6f0fe831 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -42,7 +42,7 @@ export async function compileImages(options: BuildOptions) { await build({ entryPoints: [imagesPath], outdir: path.join(options.outputDir, "cloudflare"), - bundle: false, + bundle: true, minify: false, format: "esm", target: "esnext", diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index c2dbaaf8f..13c5ef180 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -1,3 +1,5 @@ +import { error, warn } from "@opennextjs/aws/adapters/logger.js"; + export type RemotePattern = { protocol?: "http" | "https"; hostname: string; @@ -38,7 +40,7 @@ export async function handleImageRequest( let imageResponse: Response; if (parseResult.url.startsWith("/")) { if (env.ASSETS === undefined) { - console.error("env.ASSETS binding is not defined."); + error("env.ASSETS binding is not defined."); return new Response('"url" parameter is valid but upstream response is invalid', { status: 404, }); @@ -117,7 +119,7 @@ export async function handleImageRequest( if (contentType === GIF) { if (env.IMAGES === undefined) { - console.warn("env.IMAGES binding is not defined."); + warn("env.IMAGES binding is not defined."); const response = createImageResponse(imageStream, contentType, { immutable, }); @@ -143,7 +145,7 @@ export async function handleImageRequest( if (contentType === AVIF || contentType === WEBP || contentType === JPEG || contentType === PNG) { if (env.IMAGES === undefined) { - console.warn("env.IMAGES binding is not defined."); + warn("env.IMAGES binding is not defined."); const response = createImageResponse(imageStream, contentType, { immutable, }); From 0b1b6136e2a25f921b04a53a40fb058b859a9b3d Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Tue, 25 Nov 2025 17:43:46 +0900 Subject: [PATCH 05/21] update tests --- examples/playground14/e2e/cloudflare.spec.ts | 28 ++++++++++++++++---- examples/playground14/wrangler.jsonc | 3 +++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/examples/playground14/e2e/cloudflare.spec.ts b/examples/playground14/e2e/cloudflare.spec.ts index fd3615521..96cc4797a 100644 --- a/examples/playground14/e2e/cloudflare.spec.ts +++ b/examples/playground14/e2e/cloudflare.spec.ts @@ -21,20 +21,24 @@ test.describe("playground/cloudflare", () => { test.describe("remotePatterns", () => { test("fetch an image allowed by remotePatterns", async ({ page }) => { - const res = await page.request.get("/_next/image?url=https://avatars.githubusercontent.com/u/248818"); + const res = await page.request.get( + "/_next/image?url=https://avatars.githubusercontent.com/u/248818&w=256&q=75" + ); expect(res.status()).toBe(200); expect(res.headers()).toMatchObject({ "content-type": "image/jpeg" }); }); test("400 when fetching an image disallowed by remotePatterns", async ({ page }) => { - const res = await page.request.get("/_next/image?url=https://avatars.githubusercontent.com/u/248817"); + const res = await page.request.get( + "/_next/image?url=https://avatars.githubusercontent.com/u/248817&w=256&q=75" + ); expect(res.status()).toBe(400); }); }); test.describe("localPatterns", () => { test("fetch an image allowed by localPatterns", async ({ page }) => { - const res = await page.request.get("/_next/image?url=/snipp/snipp.webp?iscute=yes"); + const res = await page.request.get("/_next/image?url=/snipp/snipp.webp?iscute=yes&w=256&q=75"); expect(res.status()).toBe(200); expect(res.headers()).toMatchObject({ "content-type": "image/webp" }); }); @@ -42,14 +46,28 @@ test.describe("playground/cloudflare", () => { test("400 when fetching an image disallowed by localPatterns with wrong query parameter", async ({ page, }) => { - const res = await page.request.get("/_next/image?url=/snipp/snipp?iscute=no"); + const res = await page.request.get("/_next/image?url=/snipp/snipp?iscute=no&w=256&q=75"); expect(res.status()).toBe(400); }); test("400 when fetching an image disallowed by localPatterns without query parameter", async ({ page, }) => { - const res = await page.request.get("/_next/image?url=/snipp/snipp"); + const res = await page.request.get("/_next/image?url=/snipp/snipp&w=256&q=75"); + expect(res.status()).toBe(400); + }); + }); + + test.describe("imageSizes", () => { + test("400 when fetching an image with unsupported width value", async ({ page }) => { + const res = await page.request.get("/_next/image?url=/snipp/snipp.webp?iscute=yes&w=100&q=75"); + expect(res.status()).toBe(400); + }); + }); + + test.describe("qualities", () => { + test("400 when fetching an image with unsupported quality value", async ({ page }) => { + const res = await page.request.get("/_next/image?url=/snipp/snipp.webp?iscute=yes&w=256&q=100"); expect(res.status()).toBe(400); }); }); diff --git a/examples/playground14/wrangler.jsonc b/examples/playground14/wrangler.jsonc index f7c13c4e8..aa2a649f7 100644 --- a/examples/playground14/wrangler.jsonc +++ b/examples/playground14/wrangler.jsonc @@ -18,5 +18,8 @@ "hello": "Hello World from the cloudflare context!", "PROCESS_ENV_VAR": "process.env", "NEXT_INC_CACHE_KV_PREFIX": "custom_prefix" + }, + "images": { + "binding": "IMAGES" } } From 44c13c3bbb65c315165bcc044f8e4e9e5eabc539 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 10:44:32 +0900 Subject: [PATCH 06/21] fix local pattern matching --- .../cloudflare/src/cli/templates/images.ts | 50 ++++++++++++++----- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 13c5ef180..fdae36861 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -482,16 +482,51 @@ function getPathnameFromRelativeURL(relativeURL: string): string { } function hasLocalMatch(localPatterns: LocalPattern[], relativeURL: string): boolean { - const pathname = getPathnameFromRelativeURL(relativeURL); + const parseRelativeURLResult = parseRelativeURL(relativeURL); for (const localPattern of localPatterns) { - const patternRegExp = new RegExp(localPattern.pathname); - if (patternRegExp.test(pathname)) { + const matched = matchLocalPattern( + localPattern, + parseRelativeURLResult.pathname, + parseRelativeURLResult.search + ); + if (matched) { return true; } } return false; } +function parseRelativeURL(relativeURL: string): ParseRelativeURLResult { + if (!relativeURL.includes("?")) { + const result: ParseRelativeURLResult = { + pathname: relativeURL, + search: "", + }; + return result; + } + const parts = relativeURL.split("?"); + const pathname = parts[0]!; + const search = "?" + parts.slice(1).join("?"); + const result: ParseRelativeURLResult = { + pathname, + search, + }; + return result; +} + +type ParseRelativeURLResult = { + pathname: string; + search: string; +}; + +export function matchLocalPattern(pattern: LocalPattern, pathname: string, search: string): boolean { + if (pattern.search !== undefined && pattern.search !== search) { + return false; + } + + return new RegExp(pattern.pathname).test(pathname); +} + function hasRemoteMatch(remotePatterns: RemotePattern[], url: URL): boolean { for (const remotePattern of remotePatterns) { const matched = matchRemotePattern(remotePattern, url); @@ -527,15 +562,6 @@ export function matchRemotePattern(pattern: RemotePattern, url: URL): boolean { return new RegExp(pattern.pathname).test(url.pathname); } -export function matchLocalPattern(pattern: LocalPattern, url: URL): boolean { - // https://github.com/vercel/next.js/blob/d76f0b1/packages/next/src/shared/lib/match-local-pattern.ts - if (pattern.search !== undefined && pattern.search !== url.search) { - return false; - } - - return new RegExp(pattern.pathname).test(url.pathname); -} - const AVIF = "image/avif"; const WEBP = "image/webp"; const PNG = "image/png"; From 2435a019ccd743a0f774f55af121ebda09b7c0a6 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 10:46:00 +0900 Subject: [PATCH 07/21] remove comment --- packages/cloudflare/src/cli/templates/images.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index fdae36861..0262ad27c 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -273,7 +273,6 @@ type ImageResponseFlags = { function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImageRequestURLResult { const deviceSizes: number[] = __IMAGES_DEVICE_SIZES__; const imageSizes: number[] = __IMAGES_IMAGE_SIZES__; - // const minimumCacheTTLSeconds = __IMAGES_MINIMUM_CACHE_TTL__; const formats = __IMAGES_FORMATS__; const remotePatterns = __IMAGES_REMOTE_PATTERNS__; const localPatterns = __IMAGES_LOCAL_PATTERNS__; From 9a44189c0d80553bba53a7ffb4e0f1c372a63a3c Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 11:04:57 +0900 Subject: [PATCH 08/21] small fixes --- .../cloudflare/src/api/cloudflare-context.ts | 3 ++- .../cloudflare/src/cli/templates/images.ts | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/cloudflare/src/api/cloudflare-context.ts b/packages/cloudflare/src/api/cloudflare-context.ts index cde8b7bd1..cfde3167b 100644 --- a/packages/cloudflare/src/api/cloudflare-context.ts +++ b/packages/cloudflare/src/api/cloudflare-context.ts @@ -13,7 +13,8 @@ declare global { // Asset binding ASSETS?: Fetcher; - // Images binding + // Images binding for image optimization + // Optimization is disabled if undefined IMAGES?: ImagesBinding; // Environment to use when loading Next `.env` files diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 0262ad27c..269285fc8 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -17,7 +17,7 @@ export type LocalPattern = { /** * Handles requests to /_next/image(/), including image optimizations. - * Image optimization is disabled if env.IMAGES is undefined. + * Image optimization is disabled and the original image is returned if env.IMAGES is undefined. * * Throws an exception on unexpected errors. * @param requestURL @@ -70,7 +70,7 @@ export async function handleImageRequest( imageResponse = fetchImageResult.response; } - if (imageResponse.status !== 200 || imageResponse.body === null) { + if (!imageResponse.ok || imageResponse.body === null) { return new Response('"url" parameter is valid but upstream response is invalid', { status: imageResponse.status, }); @@ -96,11 +96,12 @@ export async function handleImageRequest( await imageResponse.body.cancel(); return new Response('"url" parameter is valid but upstream response is invalid', { - status: imageResponse.status, + status: 400, }); } const contentType = detectImageContentType(readImageHeaderBytesResult.value); if (contentType === null) { + warn(`Failed to detect content type of "${parseResult.url}"`); return new Response('"url" parameter is valid but image type is not allowed', { status: 400, }); @@ -170,6 +171,8 @@ export async function handleImageRequest( return response; } + warn(`Image content type ${contentType} not supported.`); + await imageResponse.body.cancel(); return new Response('"url" parameter is valid but image type is not allowed', { @@ -182,19 +185,19 @@ export async function handleImageRequest( * * Re-throws the exception thrown by a fetch call. * @param url - * @param timeout Timeout for a single fetch call. + * @param timeoutMS Timeout for a single fetch call. * @param maxRedirectCount * @returns */ async function fetchWithRedirects( url: string, - timeout: number, + timeoutMS: number, maxRedirectCount: number ): Promise { let response: Response; try { response = await fetch(url, { - signal: AbortSignal.timeout(timeout), + signal: AbortSignal.timeout(timeoutMS), redirect: "manual", }); } catch (e) { @@ -223,7 +226,7 @@ async function fetchWithRedirects( } else { redirectTarget = locationHeader; } - const result = await fetchWithRedirects(redirectTarget, timeout, maxRedirectCount - 1); + const result = await fetchWithRedirects(redirectTarget, timeoutMS, maxRedirectCount - 1); return result; } } @@ -255,7 +258,7 @@ function createImageResponse( contentType: string, imageResponseFlags: ImageResponseFlags ): Response { - const response = new Response(image); + const response = new Response(image, {}); response.headers.set("Vary", "Accept"); response.headers.set("Content-Type", contentType); response.headers.set("Content-Disposition", __IMAGES_CONTENT_DISPOSITION__); From 62331b660236d8e774decec52a8b398641db172a Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 11:10:49 +0900 Subject: [PATCH 09/21] removed . from logs --- packages/cloudflare/src/cli/templates/images.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 269285fc8..669b28d7b 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -40,7 +40,7 @@ export async function handleImageRequest( let imageResponse: Response; if (parseResult.url.startsWith("/")) { if (env.ASSETS === undefined) { - error("env.ASSETS binding is not defined."); + error("env.ASSETS binding is not defined"); return new Response('"url" parameter is valid but upstream response is invalid', { status: 404, }); @@ -120,7 +120,7 @@ export async function handleImageRequest( if (contentType === GIF) { if (env.IMAGES === undefined) { - warn("env.IMAGES binding is not defined."); + warn("env.IMAGES binding is not defined"); const response = createImageResponse(imageStream, contentType, { immutable, }); @@ -146,7 +146,7 @@ export async function handleImageRequest( if (contentType === AVIF || contentType === WEBP || contentType === JPEG || contentType === PNG) { if (env.IMAGES === undefined) { - warn("env.IMAGES binding is not defined."); + warn("env.IMAGES binding is not defined"); const response = createImageResponse(imageStream, contentType, { immutable, }); From 20b8d92b7108325aaf400d44eca2d21ae8319106 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 11:11:43 +0900 Subject: [PATCH 10/21] add todo --- packages/cloudflare/src/cli/templates/images.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 669b28d7b..6f0995b12 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -82,6 +82,7 @@ export async function handleImageRequest( } else { const cacheControlHeader = imageResponse.headers.get("Cache-Control"); if (cacheControlHeader !== null) { + // TODO: Properly parse header immutable = cacheControlHeader.includes("immutable"); } } From d732cc61d10cab24bc489b02e7247b6f471567b2 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 11:12:26 +0900 Subject: [PATCH 11/21] update log --- packages/cloudflare/src/cli/templates/images.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 6f0995b12..ea6576975 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -172,7 +172,7 @@ export async function handleImageRequest( return response; } - warn(`Image content type ${contentType} not supported.`); + warn(`Image content type ${contentType} not supported`); await imageResponse.body.cancel(); From 43583cf735b2dcbf680ce40b625ec04bce95312c Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 13:06:00 +0900 Subject: [PATCH 12/21] add width test --- examples/playground14/e2e/cloudflare.spec.ts | 11 + examples/playground14/package.json | 1 + pnpm-lock.yaml | 372 ++++++++++++++++--- 3 files changed, 335 insertions(+), 49 deletions(-) diff --git a/examples/playground14/e2e/cloudflare.spec.ts b/examples/playground14/e2e/cloudflare.spec.ts index 96cc4797a..7a9a8b9be 100644 --- a/examples/playground14/e2e/cloudflare.spec.ts +++ b/examples/playground14/e2e/cloudflare.spec.ts @@ -5,6 +5,7 @@ */ import { test, expect } from "@playwright/test"; +import sharp from "sharp"; test.describe("playground/cloudflare", () => { test("NextConfig", async ({ page }) => { @@ -71,4 +72,14 @@ test.describe("playground/cloudflare", () => { expect(res.status()).toBe(400); }); }); + + test.describe('"w" parameter', () => { + test("Image is shrunk to target width", async ({ page }) => { + const res = await page.request.get("/_next/image?url=/snipp/snipp.webp?iscute=yes&w=256&q=75"); + expect(res.status()).toBe(200); + const buffer = await res.body(); + const metadata = await sharp(buffer).metadata(); + expect(metadata.width).toBe(256); + }); + }); }); diff --git a/examples/playground14/package.json b/examples/playground14/package.json index 8caf82f0c..276eff66f 100644 --- a/examples/playground14/package.json +++ b/examples/playground14/package.json @@ -24,6 +24,7 @@ "@opennextjs/cloudflare": "workspace:*", "@playwright/test": "catalog:", "@types/node": "catalog:", + "sharp": "^0.34.5", "wrangler": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccf529033..3e169e055 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -911,6 +911,9 @@ importers: '@types/node': specifier: 'catalog:' version: 22.2.0 + sharp: + specifier: ^0.34.5 + version: 0.34.5 wrangler: specifier: 'catalog:' version: 4.49.1(@cloudflare/workers-types@4.20250924.0) @@ -1854,12 +1857,12 @@ packages: resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} engines: {node: '>=16'} - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} - '@emnapi/runtime@1.4.5': resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} deprecated: 'Merged into tsx: https://tsx.is' @@ -3119,6 +3122,10 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3131,6 +3138,12 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.33.5': resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3143,6 +3156,12 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.0.4': resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} cpu: [arm64] @@ -3153,6 +3172,11 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} cpu: [x64] @@ -3163,6 +3187,11 @@ packages: cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.4': resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] @@ -3173,6 +3202,11 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] @@ -3183,11 +3217,26 @@ packages: cpu: [arm] os: [linux] + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + '@img/sharp-libvips-linux-ppc64@1.2.0': resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} cpu: [ppc64] os: [linux] + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] @@ -3198,6 +3247,11 @@ packages: cpu: [s390x] os: [linux] + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] @@ -3208,6 +3262,11 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] @@ -3218,6 +3277,11 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] @@ -3228,6 +3292,11 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3240,6 +3309,12 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3252,12 +3327,30 @@ packages: cpu: [arm] os: [linux] + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + '@img/sharp-linux-ppc64@0.34.3': resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3270,6 +3363,12 @@ packages: cpu: [s390x] os: [linux] + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3282,6 +3381,12 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3294,6 +3399,12 @@ packages: cpu: [arm64] os: [linux] + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3306,6 +3417,12 @@ packages: cpu: [x64] os: [linux] + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3316,12 +3433,23 @@ packages: engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + '@img/sharp-win32-arm64@0.34.3': resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.33.5': resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3334,6 +3462,12 @@ packages: cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.33.5': resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3346,13 +3480,11 @@ packages: cpu: [x64] os: [win32] - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -5888,14 +6020,14 @@ packages: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} - detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} - detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -8935,6 +9067,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -8972,6 +9109,10 @@ packages: resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -11633,12 +11774,12 @@ snapshots: dependencies: '@edge-runtime/primitives': 4.1.0 - '@emnapi/runtime@1.4.3': + '@emnapi/runtime@1.4.5': dependencies: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.4.5': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true @@ -12655,6 +12796,8 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.0.0': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -12665,6 +12808,11 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.2.0 optional: true + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + '@img/sharp-darwin-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.0.4 @@ -12675,57 +12823,92 @@ snapshots: '@img/sharp-libvips-darwin-x64': 1.2.0 optional: true + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': optional: true '@img/sharp-libvips-darwin-arm64@1.2.0': optional: true + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': optional: true '@img/sharp-libvips-darwin-x64@1.2.0': optional: true + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': optional: true '@img/sharp-libvips-linux-arm64@1.2.0': optional: true + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + '@img/sharp-libvips-linux-arm@1.0.5': optional: true '@img/sharp-libvips-linux-arm@1.2.0': optional: true + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + '@img/sharp-libvips-linux-ppc64@1.2.0': optional: true + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': optional: true '@img/sharp-libvips-linux-s390x@1.2.0': optional: true + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + '@img/sharp-libvips-linux-x64@1.0.4': optional: true '@img/sharp-libvips-linux-x64@1.2.0': optional: true + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-arm64@1.2.0': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': optional: true '@img/sharp-libvips-linuxmusl-x64@1.2.0': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + '@img/sharp-linux-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.0.4 @@ -12736,6 +12919,11 @@ snapshots: '@img/sharp-libvips-linux-arm64': 1.2.0 optional: true + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + '@img/sharp-linux-arm@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.0.5 @@ -12746,11 +12934,26 @@ snapshots: '@img/sharp-libvips-linux-arm': 1.2.0 optional: true + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + '@img/sharp-linux-ppc64@0.34.3': optionalDependencies: '@img/sharp-libvips-linux-ppc64': 1.2.0 optional: true + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + '@img/sharp-linux-s390x@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.0.4 @@ -12761,6 +12964,11 @@ snapshots: '@img/sharp-libvips-linux-s390x': 1.2.0 optional: true + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + '@img/sharp-linux-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.0.4 @@ -12771,6 +12979,11 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.2.0 optional: true + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 @@ -12781,6 +12994,11 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 optional: true + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + '@img/sharp-linuxmusl-x64@0.33.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.0.4 @@ -12791,9 +13009,14 @@ snapshots: '@img/sharp-libvips-linuxmusl-x64': 1.2.0 optional: true + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + '@img/sharp-wasm32@0.33.5': dependencies: - '@emnapi/runtime': 1.4.3 + '@emnapi/runtime': 1.4.5 optional: true '@img/sharp-wasm32@0.34.3': @@ -12801,26 +13024,34 @@ snapshots: '@emnapi/runtime': 1.4.5 optional: true + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + '@img/sharp-win32-arm64@0.34.3': optional: true + '@img/sharp-win32-arm64@0.34.5': + optional: true + '@img/sharp-win32-ia32@0.33.5': optional: true '@img/sharp-win32-ia32@0.34.3': optional: true + '@img/sharp-win32-ia32@0.34.5': + optional: true + '@img/sharp-win32-x64@0.33.5': optional: true '@img/sharp-win32-x64@0.34.3': optional: true - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 + '@img/sharp-win32-x64@0.34.5': + optional: true '@isaacs/cliui@8.0.2': dependencies: @@ -12957,7 +13188,7 @@ snapshots: https-proxy-agent: 7.0.6 node-fetch: 2.7.0 nopt: 8.1.0 - semver: 7.7.1 + semver: 7.7.2 tar: 7.4.3 transitivePeerDependencies: - encoding @@ -14889,9 +15120,9 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.7.3) + typescript: 5.7.3 transitivePeerDependencies: - supports-color @@ -14903,7 +15134,7 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-api-utils: 1.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 @@ -15848,10 +16079,10 @@ snapshots: detect-libc@2.0.2: {} - detect-libc@2.0.3: {} - detect-libc@2.0.4: {} + detect-libc@2.1.2: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -16402,8 +16633,8 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -16462,7 +16693,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.19.0(jiti@1.21.6)) @@ -16482,13 +16713,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16539,13 +16770,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 9.19.0(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16558,6 +16789,17 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 @@ -16591,14 +16833,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color @@ -16634,14 +16876,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color @@ -16656,7 +16898,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16771,7 +17013,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -17967,7 +18209,7 @@ snapshots: is-bun-module@1.2.1: dependencies: - semver: 7.7.1 + semver: 7.7.2 is-callable@1.2.7: {} @@ -18210,7 +18452,7 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.1 + semver: 7.7.2 jsx-ast-utils@3.3.5: dependencies: @@ -19031,7 +19273,7 @@ snapshots: node-abi@3.73.0: dependencies: - semver: 7.7.1 + semver: 7.7.2 node-domexception@1.0.0: {} @@ -19519,7 +19761,7 @@ snapshots: prebuild-install@7.1.3: dependencies: - detect-libc: 2.0.3 + detect-libc: 2.0.4 expand-template: 2.0.3 github-from-package: 0.0.0 minimist: 1.2.8 @@ -19980,8 +20222,9 @@ snapshots: semver@7.7.1: {} - semver@7.7.2: - optional: true + semver@7.7.2: {} + + semver@7.7.3: {} send@1.2.0: dependencies: @@ -20040,7 +20283,7 @@ snapshots: dependencies: color: 4.2.3 detect-libc: 2.0.4 - semver: 7.7.1 + semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -20092,6 +20335,37 @@ snapshots: '@img/sharp-win32-x64': 0.34.3 optional: true + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 From 8f95b97c913c53cb979ca9997c901868fe895815 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 13:42:58 +0900 Subject: [PATCH 13/21] always allow static assets --- .../src/cli/build/open-next/compile-images.ts | 6 +++--- packages/cloudflare/src/cli/templates/images.ts | 16 ++++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index c6f0fe831..5197f6bf7 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -19,8 +19,8 @@ export async function compileImages(options: BuildOptions) { : {}; const __IMAGES_REMOTE_PATTERNS__ = JSON.stringify(imagesManifest?.images?.remotePatterns ?? []); - const __IMAGES_LOCAL_PATTERNS_DEFINED__ = JSON.stringify( - Array.isArray(imagesManifest?.images?.localPatterns) + const __IMAGES_ALLOW_ALL_LOCAL_PATHS__ = JSON.stringify( + imagesManifest?.images?.localPatterns === undefined ); const __IMAGES_LOCAL_PATTERNS__ = JSON.stringify(imagesManifest?.images?.localPatterns ?? []); const __IMAGES_DEVICE_SIZES__ = JSON.stringify(imagesManifest?.images?.deviceSizes ?? defaultDeviceSizes); @@ -49,7 +49,7 @@ export async function compileImages(options: BuildOptions) { platform: "node", define: { __IMAGES_REMOTE_PATTERNS__, - __IMAGES_LOCAL_PATTERNS_DEFINED__, + __IMAGES_ALLOW_ALL_LOCAL_PATHS__, __IMAGES_LOCAL_PATTERNS__, __IMAGES_DEVICE_SIZES__, __IMAGES_IMAGE_SIZES__, diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index ea6576975..73faef488 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -314,8 +314,10 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage } let url: string; + let staticAsset = false; if (urlQueryValue.startsWith("/")) { url = urlQueryValue; + staticAsset = url.startsWith(`${__NEXT_BASE_PATH__ || ""}/_next/static/media`); const pathname = getPathnameFromRelativeURL(url); if (/\/_next\/image($|\/)/.test(decodeURIComponent(pathname))) { @@ -325,10 +327,13 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage }; return result; } - if (__IMAGES_LOCAL_PATTERNS_DEFINED__) { - if (!hasLocalMatch(localPatterns, url)) { - const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; - return result; + + if (!staticAsset) { + if (!__IMAGES_ALLOW_ALL_LOCAL_PATHS__) { + if (!hasLocalMatch(localPatterns, url)) { + const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; + return result; + } } } } else { @@ -358,7 +363,6 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage url = parsedURL.href; } - const staticAsset = url.startsWith(`${__NEXT_BASE_PATH__ || ""}/_next/static/media`); const widthQueryValues = requestURL.searchParams.getAll("w"); if (widthQueryValues.length < 1) { @@ -620,7 +624,7 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n declare global { var __IMAGES_REMOTE_PATTERNS__: RemotePattern[]; - var __IMAGES_LOCAL_PATTERNS_DEFINED__: boolean; + var __IMAGES_ALLOW_ALL_LOCAL_PATHS__: boolean; var __IMAGES_LOCAL_PATTERNS__: LocalPattern[]; var __IMAGES_DEVICE_SIZES__: number[]; var __IMAGES_IMAGE_SIZES__: number[]; From f89fdf4216fe1b853f5a4300382fdcfb919107b2 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 23:14:45 +0900 Subject: [PATCH 14/21] add comments --- .../src/cli/build/open-next/compile-images.ts | 13 ++++++++++++- packages/cloudflare/src/cli/templates/images.ts | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index 5197f6bf7..dbad43590 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -37,7 +37,9 @@ export async function compileImages(options: BuildOptions) { const __IMAGES_CONTENT_DISPOSITION__ = JSON.stringify( imagesManifest?.images?.contentDispositionType ?? "attachment" ); - const __IMAGES_MAX_REDIRECTS__ = JSON.stringify(imagesManifest?.images?.maximumRedirects ?? 3); + const __IMAGES_MAX_REDIRECTS__ = JSON.stringify( + imagesManifest?.images?.maximumRedirects ?? defaultMaxRedirects + ); await build({ entryPoints: [imagesPath], @@ -65,7 +67,16 @@ export async function compileImages(options: BuildOptions) { } const defaultDeviceSizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]; + +// 16 was included in Next.js 15 const defaultImageSizes = [32, 48, 64, 96, 128, 256, 384]; + +// All values between 1-100 was allowed in Next.js 16 const defaultQualities = [75]; + +// Was unlimited in Next.js 15 +const defaultMaxRedirects = 3; + const defaultFormats = ["image/webp"]; + const defaultMinimumCacheTTL = 14400; diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 73faef488..5cae5d28b 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -195,6 +195,8 @@ async function fetchWithRedirects( timeoutMS: number, maxRedirectCount: number ): Promise { + // TODO: Add dangerouslyAllowLocalIP support + let response: Response; try { response = await fetch(url, { From 3df84c9aa433c44a5e192ab7d603684051a1b942 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 23:17:13 +0900 Subject: [PATCH 15/21] update response headers initialization --- packages/cloudflare/src/cli/templates/images.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 5cae5d28b..ca067930d 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -261,11 +261,14 @@ function createImageResponse( contentType: string, imageResponseFlags: ImageResponseFlags ): Response { - const response = new Response(image, {}); - response.headers.set("Vary", "Accept"); - response.headers.set("Content-Type", contentType); - response.headers.set("Content-Disposition", __IMAGES_CONTENT_DISPOSITION__); - response.headers.set("Content-Security-Policy", __IMAGES_CONTENT_SECURITY_POLICY__); + const response = new Response(image, { + headers: { + Vary: "Accept", + "Content-Type": contentType, + "Content-Disposition": __IMAGES_CONTENT_DISPOSITION__, + "Content-Security-Policy": __IMAGES_CONTENT_SECURITY_POLICY__, + }, + }); if (imageResponseFlags.immutable) { response.headers.set("Cache-Control", "public, max-age=315360000, immutable"); } From 48ce9107bcd4997455500623660cc4306950f1b6 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 23:25:48 +0900 Subject: [PATCH 16/21] allow images not supported by image optimization --- .../cloudflare/src/cli/templates/images.ts | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index ca067930d..61eb95d35 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -174,11 +174,11 @@ export async function handleImageRequest( warn(`Image content type ${contentType} not supported`); - await imageResponse.body.cancel(); - - return new Response('"url" parameter is valid but image type is not allowed', { - status: 400, + const response = createImageResponse(imageStream, contentType, { + immutable, }); + + return response; } /** @@ -578,18 +578,30 @@ const AVIF = "image/avif"; const WEBP = "image/webp"; const PNG = "image/png"; const JPEG = "image/jpeg"; +const JXL = "image/jxl"; +const JP2 = "image/jp2"; const HEIC = "image/heic"; const GIF = "image/gif"; const SVG = "image/svg+xml"; +const ICO = "image/x-icon"; +const ICNS = "image/x-icns"; +const TIFF = "image/tiff"; +const BMP = "image/bmp"; type ImageContentType = | "image/avif" | "image/webp" | "image/png" | "image/jpeg" + | "image/jxl" + | "image/jp2" | "image/heic" | "image/gif" - | "image/svg+xml"; + | "image/svg+xml" + | "image/x-icon" + | "image/x-icns" + | "image/tiff" + | "image/bmp"; /** * Detects the content type by looking at the first few bytes of a file @@ -609,7 +621,7 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n if ([0x47, 0x49, 0x46, 0x38].every((b, i) => buffer[i] === b)) { return GIF; } - if ([0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50].every((b, i) => !b || buffer[i] === b)) { + if ([0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50].every((b, i) => buffer[i] === b)) { return WEBP; } if ([0x3c, 0x3f, 0x78, 0x6d, 0x6c].every((b, i) => buffer[i] === b)) { @@ -618,12 +630,37 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n if ([0x3c, 0x73, 0x76, 0x67].every((b, i) => buffer[i] === b)) { return SVG; } - if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every((b, i) => !b || buffer[i] === b)) { + if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every((b, i) => buffer[i] === b)) { return AVIF; } - if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => !b || buffer[i] === b)) { + if ([0x00, 0x00, 0x01, 0x00].every((b, i) => buffer[i] === b)) { + return ICO; + } + if ([0x69, 0x63, 0x6e, 0x73].every((b, i) => buffer[i] === b)) { + return ICNS; + } + if ([0x49, 0x49, 0x2a, 0x00].every((b, i) => buffer[i] === b)) { + return TIFF; + } + if ([0x42, 0x4d].every((b, i) => buffer[i] === b)) { + return BMP; + } + if ([0xff, 0x0a].every((b, i) => buffer[i] === b)) { + return JXL; + } + if ( + [0x00, 0x00, 0x00, 0x0c, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) + ) { + return JXL; + } + if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => buffer[i] === b)) { return HEIC; } + if ( + [0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a].every((b, i) => buffer[i] === b) + ) { + return JP2; + } return null; } From 23284de95bb1792cddaa0b91914ac42a5659ec43 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 23:37:13 +0900 Subject: [PATCH 17/21] improve local patterns --- .../cloudflare/src/cli/build/open-next/compile-images.ts | 8 ++++---- packages/cloudflare/src/cli/templates/images.ts | 9 +++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index dbad43590..b9bd667e2 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -19,10 +19,9 @@ export async function compileImages(options: BuildOptions) { : {}; const __IMAGES_REMOTE_PATTERNS__ = JSON.stringify(imagesManifest?.images?.remotePatterns ?? []); - const __IMAGES_ALLOW_ALL_LOCAL_PATHS__ = JSON.stringify( - imagesManifest?.images?.localPatterns === undefined + const __IMAGES_LOCAL_PATTERNS__ = JSON.stringify( + imagesManifest?.images?.localPatterns ?? defaultLocalPatterns ); - const __IMAGES_LOCAL_PATTERNS__ = JSON.stringify(imagesManifest?.images?.localPatterns ?? []); const __IMAGES_DEVICE_SIZES__ = JSON.stringify(imagesManifest?.images?.deviceSizes ?? defaultDeviceSizes); const __IMAGES_IMAGE_SIZES__ = JSON.stringify(imagesManifest?.images?.imageSizes ?? defaultImageSizes); const __IMAGES_QUALITIES__ = JSON.stringify(imagesManifest?.images?.qualities ?? defaultQualities); @@ -51,7 +50,6 @@ export async function compileImages(options: BuildOptions) { platform: "node", define: { __IMAGES_REMOTE_PATTERNS__, - __IMAGES_ALLOW_ALL_LOCAL_PATHS__, __IMAGES_LOCAL_PATTERNS__, __IMAGES_DEVICE_SIZES__, __IMAGES_IMAGE_SIZES__, @@ -80,3 +78,5 @@ const defaultMaxRedirects = 3; const defaultFormats = ["image/webp"]; const defaultMinimumCacheTTL = 14400; + +const defaultLocalPatterns = { pathname: "/**" }; diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 61eb95d35..57a2d429f 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -334,11 +334,9 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage } if (!staticAsset) { - if (!__IMAGES_ALLOW_ALL_LOCAL_PATHS__) { - if (!hasLocalMatch(localPatterns, url)) { - const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; - return result; - } + if (!hasLocalMatch(localPatterns, url)) { + const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; + return result; } } } else { @@ -666,7 +664,6 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n declare global { var __IMAGES_REMOTE_PATTERNS__: RemotePattern[]; - var __IMAGES_ALLOW_ALL_LOCAL_PATHS__: boolean; var __IMAGES_LOCAL_PATTERNS__: LocalPattern[]; var __IMAGES_DEVICE_SIZES__: number[]; var __IMAGES_IMAGE_SIZES__: number[]; From a587d60648cb0ac37c151c95c34e463cdb1b63d7 Mon Sep 17 00:00:00 2001 From: Hinata Masaki Date: Wed, 26 Nov 2025 23:38:11 +0900 Subject: [PATCH 18/21] fix image content type detection --- packages/cloudflare/src/cli/templates/images.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 57a2d429f..7eef6a186 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -619,7 +619,7 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n if ([0x47, 0x49, 0x46, 0x38].every((b, i) => buffer[i] === b)) { return GIF; } - if ([0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50].every((b, i) => buffer[i] === b)) { + if ([0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50].every((b, i) => !b || buffer[i] === b)) { return WEBP; } if ([0x3c, 0x3f, 0x78, 0x6d, 0x6c].every((b, i) => buffer[i] === b)) { @@ -628,7 +628,7 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n if ([0x3c, 0x73, 0x76, 0x67].every((b, i) => buffer[i] === b)) { return SVG; } - if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every((b, i) => buffer[i] === b)) { + if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66].every((b, i) => !b || buffer[i] === b)) { return AVIF; } if ([0x00, 0x00, 0x01, 0x00].every((b, i) => buffer[i] === b)) { @@ -651,7 +651,7 @@ export function detectImageContentType(buffer: Uint8Array): ImageContentType | n ) { return JXL; } - if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => buffer[i] === b)) { + if ([0, 0, 0, 0, 0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63].every((b, i) => !b || buffer[i] === b)) { return HEIC; } if ( From 347f1f55b979519860aeebf9cbc2024d6f077ee0 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 27 Nov 2025 14:57:21 +0100 Subject: [PATCH 19/21] fixup! minor copy edits --- .../src/cli/build/open-next/compile-images.ts | 10 +- .../cloudflare/src/cli/templates/images.ts | 219 +++++++++++------- 2 files changed, 139 insertions(+), 90 deletions(-) diff --git a/packages/cloudflare/src/cli/build/open-next/compile-images.ts b/packages/cloudflare/src/cli/build/open-next/compile-images.ts index b9bd667e2..4fb1c296b 100644 --- a/packages/cloudflare/src/cli/build/open-next/compile-images.ts +++ b/packages/cloudflare/src/cli/build/open-next/compile-images.ts @@ -26,8 +26,8 @@ export async function compileImages(options: BuildOptions) { const __IMAGES_IMAGE_SIZES__ = JSON.stringify(imagesManifest?.images?.imageSizes ?? defaultImageSizes); const __IMAGES_QUALITIES__ = JSON.stringify(imagesManifest?.images?.qualities ?? defaultQualities); const __IMAGES_FORMATS__ = JSON.stringify(imagesManifest?.images?.formats ?? defaultFormats); - const __IMAGES_MINIMUM_CACHE_TTL__ = JSON.stringify( - imagesManifest?.images?.minimumCacheTTL ?? defaultMinimumCacheTTL + const __IMAGES_MINIMUM_CACHE_TTL_SEC__ = JSON.stringify( + imagesManifest?.images?.minimumCacheTTL ?? defaultMinimumCacheTTLSec ); const __IMAGES_ALLOW_SVG__ = JSON.stringify(Boolean(imagesManifest?.images?.dangerouslyAllowSVG)); const __IMAGES_CONTENT_SECURITY_POLICY__ = JSON.stringify( @@ -55,7 +55,7 @@ export async function compileImages(options: BuildOptions) { __IMAGES_IMAGE_SIZES__, __IMAGES_QUALITIES__, __IMAGES_FORMATS__, - __IMAGES_MINIMUM_CACHE_TTL__, + __IMAGES_MINIMUM_CACHE_TTL_SEC__, __IMAGES_ALLOW_SVG__, __IMAGES_CONTENT_SECURITY_POLICY__, __IMAGES_CONTENT_DISPOSITION__, @@ -69,7 +69,7 @@ const defaultDeviceSizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840]; // 16 was included in Next.js 15 const defaultImageSizes = [32, 48, 64, 96, 128, 256, 384]; -// All values between 1-100 was allowed in Next.js 16 +// All values between 1-100 were allowed in Next.js 15 const defaultQualities = [75]; // Was unlimited in Next.js 15 @@ -77,6 +77,6 @@ const defaultMaxRedirects = 3; const defaultFormats = ["image/webp"]; -const defaultMinimumCacheTTL = 14400; +const defaultMinimumCacheTTLSec = 14400; const defaultLocalPatterns = { pathname: "/**" }; diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index 7eef6a186..afe727742 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -17,9 +17,11 @@ export type LocalPattern = { /** * Handles requests to /_next/image(/), including image optimizations. - * Image optimization is disabled and the original image is returned if env.IMAGES is undefined. + * + * Image optimization is disabled and the original image is returned if `env.IMAGES` is undefined. * * Throws an exception on unexpected errors. + * * @param requestURL * @param requestHeaders * @param env @@ -279,38 +281,107 @@ type ImageResponseFlags = { immutable: boolean; }; -function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImageRequestURLResult { - const deviceSizes: number[] = __IMAGES_DEVICE_SIZES__; - const imageSizes: number[] = __IMAGES_IMAGE_SIZES__; +/** + * Parses the image request URL and headers. + * + * This function validates the parameters and returns either the parsed result or an error message. + * + * @param requestURL request URL + * @param requestHeaders request headers + * @returns an instance of `ParseImageRequestURLSuccessResult` when successful, or an instance of `ErrorResult` when failed. + */ +function parseImageRequest( + requestURL: URL, + requestHeaders: Headers +): ParseImageRequestURLSuccessResult | ErrorResult { const formats = __IMAGES_FORMATS__; - const remotePatterns = __IMAGES_REMOTE_PATTERNS__; - const localPatterns = __IMAGES_LOCAL_PATTERNS__; - const qualities: number[] = __IMAGES_QUALITIES__; - const urlQueryValues = requestURL.searchParams.getAll("url"); - if (urlQueryValues.length < 1) { + const parsedUrlOrError = validateUrlQueryParameter(requestURL); + if (!("url" in parsedUrlOrError)) { + return parsedUrlOrError; + } + + const widthOrError = validateWidthQueryParameter(requestURL); + if (typeof widthOrError !== "number") { + return widthOrError; + } + + const qualityOrError = validateQualityQueryParameter(requestURL); + if (typeof qualityOrError !== "number") { + return qualityOrError; + } + + const acceptHeader = requestHeaders.get("Accept") ?? ""; + let format: OptimizedImageFormat | null = null; + // Find a more specific format that the client accepts. + for (const allowedFormat of formats) { + if (acceptHeader.includes(allowedFormat)) { + format = allowedFormat; + break; + } + } + + const result: ParseImageRequestURLSuccessResult = { + ok: true, + url: parsedUrlOrError.url, + width: widthOrError, + quality: qualityOrError, + format, + static: parsedUrlOrError.static, + }; + return result; +} + +type ParseImageRequestURLSuccessResult = { + ok: true; + /** Absolute or relative URL. */ + url: string; + width: number; + quality: number; + format: OptimizedImageFormat | null; + static: boolean; +}; + +export type OptimizedImageFormat = "image/avif" | "image/webp"; + +type ErrorResult = { + ok: false; + message: string; +}; + +/** + * Validates that there is exactly one "url" query parameter. + * + * @returns the validated URL or an error result. + */ +function validateUrlQueryParameter(requestURL: URL): ErrorResult | { url: string; static: boolean } { + // There should be a single "url" parameter. + const urls = requestURL.searchParams.getAll("url"); + if (urls.length < 1) { const result: ErrorResult = { ok: false, message: '"url" parameter is required', }; return result; } - if (urlQueryValues.length > 1) { + if (urls.length > 1) { const result: ErrorResult = { ok: false, message: '"url" parameter cannot be an array', }; return result; } - const urlQueryValue = urlQueryValues[0]!; - if (urlQueryValue.length > 3072) { + + // The url parameter value should be a valid URL or a valid relative URL. + const url = urls[0]!; + if (url.length > 3072) { const result: ErrorResult = { ok: false, message: '"url" parameter is too long', }; return result; } - if (urlQueryValue.startsWith("//")) { + if (url.startsWith("//")) { const result: ErrorResult = { ok: false, message: '"url" parameter cannot be a protocol-relative URL (//)', @@ -318,11 +389,8 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage return result; } - let url: string; - let staticAsset = false; - if (urlQueryValue.startsWith("/")) { - url = urlQueryValue; - staticAsset = url.startsWith(`${__NEXT_BASE_PATH__ || ""}/_next/static/media`); + if (url.startsWith("/")) { + const staticAsset = url.startsWith(`${__NEXT_BASE_PATH__ || ""}/_next/static/media`); const pathname = getPathnameFromRelativeURL(url); if (/\/_next\/image($|\/)/.test(decodeURIComponent(pathname))) { @@ -334,39 +402,48 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage } if (!staticAsset) { - if (!hasLocalMatch(localPatterns, url)) { + if (!hasLocalMatch(__IMAGES_LOCAL_PATTERNS__, url)) { const result: ErrorResult = { ok: false, message: '"url" parameter is not allowed' }; return result; } } - } else { - let parsedURL: URL; - try { - parsedURL = new URL(urlQueryValue); - } catch { - const result: ErrorResult = { ok: false, message: '"url" parameter is invalid' }; - return result; - } - const validProtocols = ["http:", "https:"]; - if (!validProtocols.includes(parsedURL.protocol)) { - const result: ErrorResult = { - ok: false, - message: '"url" parameter is invalid', - }; - return result; - } - if (!hasRemoteMatch(remotePatterns, parsedURL)) { - const result: ErrorResult = { - ok: false, - message: '"url" parameter is not allowed', - }; - return result; - } + return { url, static: staticAsset }; + } - url = parsedURL.href; + let parsedURL: URL; + try { + parsedURL = new URL(url); + } catch { + const result: ErrorResult = { ok: false, message: '"url" parameter is invalid' }; + return result; } + const validProtocols = ["http:", "https:"]; + if (!validProtocols.includes(parsedURL.protocol)) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is invalid', + }; + return result; + } + if (!hasRemoteMatch(__IMAGES_REMOTE_PATTERNS__, parsedURL)) { + const result: ErrorResult = { + ok: false, + message: '"url" parameter is not allowed', + }; + return result; + } + + return { url: parsedURL.href, static: false }; +} + +/** + * Validates the "w" (width) query parameter. + * + * @returns the validated width number or an error result. + */ +function validateWidthQueryParameter(requestURL: URL): ErrorResult | number { const widthQueryValues = requestURL.searchParams.getAll("w"); if (widthQueryValues.length < 1) { const result: ErrorResult = { @@ -399,7 +476,7 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage return result; } - const sizeValid = deviceSizes.includes(width) || imageSizes.includes(width); + const sizeValid = __IMAGES_DEVICE_SIZES__.includes(width) || __IMAGES_IMAGE_SIZES__.includes(width); if (!sizeValid) { const result: ErrorResult = { ok: false, @@ -408,6 +485,15 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage return result; } + return width; +} + +/** + * Validates the "q" (quality) query parameter. + * + * @returns the validated quality number or an error result. + */ +function validateQualityQueryParameter(requestURL: URL): ErrorResult | number { const qualityQueryValues = requestURL.searchParams.getAll("q"); if (qualityQueryValues.length < 1) { const result: ErrorResult = { @@ -439,54 +525,17 @@ function parseImageRequest(requestURL: URL, requestHeaders: Headers): ParseImage }; return result; } - if (!qualities.includes(quality)) { + if (!__IMAGES_QUALITIES__.includes(quality)) { const result: ErrorResult = { ok: false, - message: `"w" parameter (width) of ${width} is not allowed`, + message: `"q" parameter (quality) of ${quality} is not allowed`, }; return result; } - const acceptHeader = requestHeaders.get("Accept") ?? ""; - let format: OptimizedImageFormat | null = null; - // Find a more specific format that the client accepts. - for (const allowedFormat of formats) { - if (acceptHeader.includes(allowedFormat)) { - format = allowedFormat; - break; - } - } - - const result: ParseImageRequestURLSuccessResult = { - ok: true, - url, - width, - quality, - format, - static: staticAsset, - }; - return result; + return quality; } -type ParseImageRequestURLResult = ParseImageRequestURLSuccessResult | ErrorResult; - -type ParseImageRequestURLSuccessResult = { - ok: true; - /** Absolute or relative URL. */ - url: string; - width: number; - quality: number; - format: OptimizedImageFormat | null; - static: boolean; -}; - -export type OptimizedImageFormat = "image/avif" | "image/webp"; - -type ErrorResult = { - ok: false; - message: string; -}; - function getPathnameFromRelativeURL(relativeURL: string): string { return relativeURL.split("?")[0]!; } @@ -669,7 +718,7 @@ declare global { var __IMAGES_IMAGE_SIZES__: number[]; var __IMAGES_QUALITIES__: number[]; var __IMAGES_FORMATS__: NextConfigImageFormat[]; - var __IMAGES_MINIMUM_CACHE_TTL__: number; + var __IMAGES_MINIMUM_CACHE_TTL_SEC__: number; var __IMAGES_ALLOW_SVG__: boolean; var __IMAGES_CONTENT_SECURITY_POLICY__: string; var __IMAGES_CONTENT_DISPOSITION__: string; From d4075eae2140fadb2630099c1bcfb148f7d6114d Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 27 Nov 2025 15:10:20 +0100 Subject: [PATCH 20/21] fixup! lock file --- pnpm-lock.yaml | 119 ++++++++++++++++++++++++++----------------------- 1 file changed, 62 insertions(+), 57 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e169e055..037e60bf2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3486,6 +3486,14 @@ packages: cpu: [x64] os: [win32] + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -6549,16 +6557,6 @@ packages: eslint-import-resolver-webpack: optional: true - eslint-plugin-import@2.30.0: - resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint-plugin-import@2.31.0: resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} engines: {node: '>=4'} @@ -13053,6 +13051,12 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -15121,8 +15125,8 @@ snapshots: is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.7.3) - typescript: 5.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -15149,7 +15153,7 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-api-utils: 1.4.3(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 @@ -16633,8 +16637,8 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -16653,7 +16657,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) @@ -16673,7 +16677,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.11.1(jiti@1.21.6)) @@ -16713,13 +16717,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16732,32 +16736,32 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0 enhanced-resolve: 5.17.1 eslint: 9.11.1(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16789,47 +16793,36 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color @@ -16854,25 +16847,36 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) + eslint: 8.57.1 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color @@ -16887,7 +16891,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3))(eslint@9.31.0(jiti@1.21.6)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -16896,9 +16900,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 9.31.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.31.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16907,15 +16911,16 @@ snapshots: object.groupby: 1.0.3 object.values: 1.2.1 semver: 6.3.1 + string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3))(eslint@9.31.0(jiti@1.21.6)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -16924,9 +16929,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.31.0(jiti@1.21.6) + eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.31.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16938,13 +16943,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.6))(typescript@5.9.3) + '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -16955,7 +16960,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -16967,7 +16972,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) + '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -16984,7 +16989,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From d2b12f7118a8bcb52b86298b6d3819b3c1119ca6 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 27 Nov 2025 15:22:09 +0100 Subject: [PATCH 21/21] fixup! tests --- packages/cloudflare/src/cli/templates/images.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/cloudflare/src/cli/templates/images.ts b/packages/cloudflare/src/cli/templates/images.ts index afe727742..dbe6ef445 100644 --- a/packages/cloudflare/src/cli/templates/images.ts +++ b/packages/cloudflare/src/cli/templates/images.ts @@ -543,11 +543,7 @@ function getPathnameFromRelativeURL(relativeURL: string): string { function hasLocalMatch(localPatterns: LocalPattern[], relativeURL: string): boolean { const parseRelativeURLResult = parseRelativeURL(relativeURL); for (const localPattern of localPatterns) { - const matched = matchLocalPattern( - localPattern, - parseRelativeURLResult.pathname, - parseRelativeURLResult.search - ); + const matched = matchLocalPattern(localPattern, parseRelativeURLResult); if (matched) { return true; } @@ -578,12 +574,12 @@ type ParseRelativeURLResult = { search: string; }; -export function matchLocalPattern(pattern: LocalPattern, pathname: string, search: string): boolean { - if (pattern.search !== undefined && pattern.search !== search) { +export function matchLocalPattern(pattern: LocalPattern, url: { pathname: string; search: string }): boolean { + if (pattern.search !== undefined && pattern.search !== url.search) { return false; } - return new RegExp(pattern.pathname).test(pathname); + return new RegExp(pattern.pathname).test(url.pathname); } function hasRemoteMatch(remotePatterns: RemotePattern[], url: URL): boolean {