diff --git a/package.json b/package.json index 18d72371930..243cda1ee0c 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "build:storage-vercel-blob": "turbo build --filter \"@payloadcms/storage-vercel-blob\"", "build:tests": "pnpm --filter payload-test-suite run typecheck", "build:tools": "turbo build --filter=./tools/*", + "build:transformer-sharp": "turbo build --filter \"@payloadcms/transformer-sharp\"", "build:translations": "turbo build --filter \"@payloadcms/translations\"", "build:ui": "turbo build --filter \"@payloadcms/ui\"", "clean": "turbo clean", @@ -206,7 +207,7 @@ "react": "catalog:", "react-dom": "catalog:", "rimraf": "6.0.1", - "sharp": "0.32.6", + "sharp": "0.35.4", "shelljs": "0.8.5", "slash": "3.0.0", "sort-package-json": "^2.10.0", diff --git a/packages/payload/package.json b/packages/payload/package.json index 2e83147090c..70b96bd018b 100644 --- a/packages/payload/package.json +++ b/packages/payload/package.json @@ -169,8 +169,7 @@ "react-datepicker": "7.6.0", "rimraf": "6.0.1", "rollup": "4.59.0", - "rollup-plugin-dts": "6.2.3", - "sharp": "0.32.6" + "rollup-plugin-dts": "6.2.3" }, "peerDependencies": { "graphql": "^16.8.1" diff --git a/packages/transformer-sharp/.swcrc b/packages/transformer-sharp/.swcrc new file mode 100644 index 00000000000..14463f4b08b --- /dev/null +++ b/packages/transformer-sharp/.swcrc @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "sourceMaps": true, + "jsc": { + "target": "esnext", + "parser": { + "syntax": "typescript", + "tsx": true, + "dts": true + } + }, + "module": { + "type": "es6" + } +} diff --git a/packages/transformer-sharp/README.md b/packages/transformer-sharp/README.md new file mode 100644 index 00000000000..ea4caa434df --- /dev/null +++ b/packages/transformer-sharp/README.md @@ -0,0 +1,105 @@ +# Sharp Transformer for Payload + +Payload's official [file transformer](https://payloadcms.com/docs/upload/transformers) for Sharp-based image processing: upload-time resizing/format conversion (the same behavior Payload has always provided) plus opt-in request-time dynamic resizing. + +## Installation + +```sh +pnpm add @payloadcms/transformer-sharp +``` + +## Usage + +Register `sharpTransformer()` under `upload.transformers` in your Payload Config: + +```ts +import { buildConfig } from 'payload' +import { sharpTransformer } from '@payloadcms/transformer-sharp' + +export default buildConfig({ + upload: { + transformers: [sharpTransformer()], + }, +}) +``` + +### Upload-time image processing + +Configure per-collection Sharp settings through `sharpTransformer({ collections })`: + +```ts +sharpTransformer({ + collections: { + media: { + variants: [ + { name: 'thumbnail', width: 400, height: 300 }, + { name: 'card', width: 768, height: 1024 }, + ], + resizeOptions: { width: 2048, height: 2048, withoutEnlargement: true }, + formatOptions: { format: 'webp' }, + trimOptions: undefined, + constructorOptions: undefined, + withMetadata: false, + crop: true, + focalPoint: true, + }, + }, +}) +``` + +`variants`, `crop`, and `focalPoint` are also written back onto the collection's own sanitized `upload` config at startup (name/`admin`/`generateImageName` only, for `variants`), so the Admin Panel, generated types, and the `sizes` shape on your documents behave exactly as before. + +### Dynamic (request-time) resizing + +Dynamic resizing is **disabled by default** — resize query parameters are ignored and the original file is served. Enable it for every upload collection, or only the ones that need it: + +```ts +sharpTransformer({ dynamic: true }) +sharpTransformer({ dynamic: { collections: ['media'] } }) +``` + +Once enabled, requesting a stored image with recognized query parameters resizes it on the fly, without storing the result: + +``` +GET /api/media/file/photo.png?width=400 +GET /api/media/file/photo.png?width=400&height=300 +GET /api/media/file/photo.png?width=400&withoutEnlargement=true +``` + +Configure limits and defaults for these requests: + +```ts +sharpTransformer({ + dynamic: { + collections: ['media'], // omit to allow every upload collection + fit: 'cover', // default + position: 'center', // default + maxWidth: 4096, // default + maxHeight: 4096, // default + maxPixels: 16_777_216, // default + withoutEnlargement: false, // default + }, +}) +``` + +Resized output is not cached, and anyone who can read a file can request it at any size within these limits. Before enabling dynamic resizing on a public collection, see [Securing dynamic resizing](https://payloadcms.com/docs/upload/transformers#securing-dynamic-resizing) for how to restrict it with access control, tighten the limits, and cache the output. + +### Injecting a custom Sharp build + +```ts +import customSharp from 'sharp' + +sharpTransformer({ sharp: customSharp }) +``` + +See the [File Transformers](https://payloadcms.com/docs/upload/transformers) docs for the full transformer contract, access-control lifecycle, and non-goals (no caching, no CDN integration, no jobs queue). + +## Migrating from Payload 3.x + +If you previously configured `sharp`/`resizeOptions`/`imageSizes`/etc. directly on your Payload Config or collections, run: + +```sh +npx @payloadcms/codemod --transform migrate-sharp-to-transformer +``` + +See the [v4 migration guide](https://payloadcms.com/docs/migration-guide/v4#sharp-moved-out-of-core-into-payloadcmstransformer-sharp) for details. diff --git a/packages/transformer-sharp/package.json b/packages/transformer-sharp/package.json new file mode 100644 index 00000000000..d74fe2ce4f3 --- /dev/null +++ b/packages/transformer-sharp/package.json @@ -0,0 +1,67 @@ +{ + "name": "@payloadcms/transformer-sharp", + "version": "4.0.0-canary.37", + "description": "Official Sharp-based file transformer for Payload: request-time image resizing and upload-time image processing", + "homepage": "https://payloadcms.com", + "repository": { + "type": "git", + "url": "https://github.com/payloadcms/payload.git", + "directory": "packages/transformer-sharp" + }, + "license": "MIT", + "author": "Payload (https://payloadcms.com)", + "maintainers": [ + { + "name": "Payload", + "email": "info@payloadcms.com", + "url": "https://payloadcms.com" + } + ], + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "import": "./src/index.ts", + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "main": "./src/index.ts", + "types": "./src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "pnpm build:types && pnpm build:swc", + "build:clean": "find . \\( -type d \\( -name build -o -name dist -o -name .cache \\) -o -type f -name tsconfig.tsbuildinfo \\) -exec rm -rf {} + && pnpm build", + "build:debug": "pnpm build", + "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths", + "build:types": "tsc --emitDeclarationOnly --outDir dist", + "clean": "rimraf -g {dist,*.tsbuildinfo}", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "dependencies": { + "sharp": "0.35.4" + }, + "devDependencies": { + "payload": "workspace:*" + }, + "peerDependencies": { + "payload": "workspace:*" + }, + "engines": { + "node": ">=24.15.0" + }, + "publishConfig": { + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts" + } +} diff --git a/packages/transformer-sharp/src/canResizeImage.ts b/packages/transformer-sharp/src/canResizeImage.ts new file mode 100644 index 00000000000..b3df32d1b3f --- /dev/null +++ b/packages/transformer-sharp/src/canResizeImage.ts @@ -0,0 +1,7 @@ +export function canResizeImage(mimeType: string): boolean { + return ( + ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/tiff', 'image/avif'].indexOf( + mimeType, + ) > -1 + ) +} diff --git a/packages/transformer-sharp/src/getImageResizeAction.ts b/packages/transformer-sharp/src/getImageResizeAction.ts new file mode 100644 index 00000000000..15f1bc2290b --- /dev/null +++ b/packages/transformer-sharp/src/getImageResizeAction.ts @@ -0,0 +1,76 @@ +import type { ImageSize, ProbedImageSize } from 'payload' + +import { isNumber } from 'payload/shared' + +/** + * Determine whether or not to resize the image. + * - resize using image config + * - resize using image config with focal adjustments + * - do not resize at all + * + * `imageResizeConfig.withoutEnlargement`: + * - undefined [default]: uploading images with smaller width AND height than the image size will return null + * - false: always enlarge images to the image size + * - true: if the image is smaller than the image size, return the original image + * + * `imageResizeConfig.withoutReduction`: + * - false [default]: always enlarge images to the image size + * - true: if the image is smaller than the image size, return the original image + * + * @return 'omit' | 'resize' | 'resizeWithFocalPoint' + */ +export const getImageResizeAction = ({ + dimensions: originalImage, + hasFocalPoint, + imageResizeConfig, +}: { + dimensions: ProbedImageSize + hasFocalPoint?: boolean + imageResizeConfig: ImageSize +}): 'omit' | 'resize' | 'resizeWithFocalPoint' => { + const { fit, withoutEnlargement, withoutReduction } = imageResizeConfig + const targetWidth = imageResizeConfig.width! + const targetHeight = imageResizeConfig.height! + + // prevent upscaling by default when x and y are both smaller than target image size + if (targetHeight && targetWidth) { + const originalImageIsSmallerXAndY = + originalImage.width < targetWidth && originalImage.height < targetHeight + if (withoutEnlargement === undefined && originalImageIsSmallerXAndY) { + return 'omit' // prevent image size from being enlarged + } + } + + if (withoutEnlargement === undefined && (!targetWidth || !targetHeight)) { + if ( + (targetWidth && originalImage.width < targetWidth) || + (targetHeight && originalImage.height < targetHeight) + ) { + return 'omit' + } + } + + const originalImageIsSmallerXOrY = + originalImage.width < targetWidth || originalImage.height < targetHeight + if (fit === 'contain' || fit === 'inside') { + return 'resize' + } + if (!isNumber(targetHeight) && !isNumber(targetWidth)) { + return 'resize' + } + + const targetAspectRatio = targetWidth / targetHeight + const originalAspectRatio = originalImage.width / originalImage.height + if (originalAspectRatio === targetAspectRatio) { + return 'resize' + } + + if (withoutEnlargement && originalImageIsSmallerXOrY) { + return 'resize' + } + if (withoutReduction && !originalImageIsSmallerXOrY) { + return 'resize' + } + + return hasFocalPoint ? 'resizeWithFocalPoint' : 'resize' +} diff --git a/packages/transformer-sharp/src/handleRequest.spec.ts b/packages/transformer-sharp/src/handleRequest.spec.ts new file mode 100644 index 00000000000..6ad20e212ff --- /dev/null +++ b/packages/transformer-sharp/src/handleRequest.spec.ts @@ -0,0 +1,179 @@ +import type { PayloadRequest } from 'payload' + +import { readFileSync } from 'fs' +import path from 'path' +import sharp from 'sharp' +import { fileURLToPath } from 'url' +import { describe, expect, it, vi } from 'vitest' + +import { createHandleRequest } from './handleRequest.js' +import { resolveSharpDynamicDefaults } from './sharpTransformer.js' + +const dirname = path.dirname(fileURLToPath(import.meta.url)) +// 200x200, 44 frames. +const animatedWebp = readFileSync(path.resolve(dirname, '../../../test/uploads/animated.webp')) + +const makeReq = ({ query = '' }: { query?: string } = {}): PayloadRequest => + ({ + headers: new Headers(), + method: 'GET', + payload: { logger: { error: vi.fn() } }, + searchParams: new URLSearchParams(query), + }) as unknown as PayloadRequest + +const makeSourceImage = async ({ + background = { b: 0, g: 128, r: 255 }, + format = 'png' as Parameters['toFormat']>[0], + height = 200, + width = 400, +} = {}): Promise => + sharp({ create: { background, channels: 3, height, width } }) + .toFormat(format) + .toBuffer() + +const resizeReal = async ({ + dynamicDefaults = resolveSharpDynamicDefaults(), + mimeType = 'image/png', + query, + sourceBuffer, +}: { + dynamicDefaults?: ReturnType + mimeType?: string + query: string + sourceBuffer: Buffer +}) => { + const handleRequest = createHandleRequest({ dynamicDefaults, sharpDependency: sharp }) + const getSourceFile = vi.fn().mockResolvedValue(new Response(sourceBuffer)) + + const result = await handleRequest({ + collectionSlug: 'media', + documentID: '1', + filename: 'logo.png', + getSourceFile, + mimeType, + req: makeReq({ query }), + }) + + return result +} + +const getOutputMetadata = async (result: Awaited>) => + sharp(Buffer.from(await result.response!.arrayBuffer())).metadata() + +describe('createHandleRequest', () => { + it.each([ + // 10x100 source. + { orientation: undefined, sourceHeight: 100, sourceWidth: 10 }, + // Stored 100x10 but tagged orientation 6, so it displays as 10x100. + { orientation: 6, sourceHeight: 10, sourceWidth: 100 }, + ])( + 'should reject a width-only request whose displayed aspect ratio exceeds maxPixels (orientation $orientation)', + async ({ orientation, sourceHeight, sourceWidth }) => { + const source = sharp( + await makeSourceImage({ format: 'jpeg', height: sourceHeight, width: sourceWidth }), + ) + const sourceBuffer = await ( + orientation ? source.withMetadata({ orientation }) : source + ).toBuffer() + + // Displayed 10x100 at width=100 renders 100x1000 = 100,000 pixels, 10x the limit. + const result = await resizeReal({ + dynamicDefaults: resolveSharpDynamicDefaults({ maxPixels: 10_000 }), + mimeType: 'image/jpeg', + query: 'width=100', + sourceBuffer, + }) + + expect(result.response?.status).toBe(400) + }, + ) + + it.each(['width=1000&height=1000', 'width=1000'])( + 'should count every frame of an animated source against maxPixels (%s)', + async (query) => { + // 1000x1000 per frame is within the 16,777,216 default, but across 44 frames + // Sharp would render 44,000,000 pixels. + const result = await resizeReal({ + mimeType: 'image/webp', + query, + sourceBuffer: animatedWebp, + }) + + expect(result.response?.status).toBe(400) + }, + ) + + it('should resize every frame of an animated source within maxPixels', async () => { + const metadata = await sharp( + Buffer.from( + await ( + await resizeReal({ + mimeType: 'image/webp', + query: 'width=100', + sourceBuffer: animatedWebp, + }) + ).response!.arrayBuffer(), + ), + { animated: true }, + ).metadata() + + expect(metadata.width).toBe(100) + expect(metadata.pages).toBe(44) + }) + + it('should apply the EXIF orientation of the source before resizing', async () => { + // Stored 400x200 but tagged orientation 6, so it displays as 200x400. + const sourceBuffer = await sharp(await makeSourceImage({ format: 'jpeg' })) + .withMetadata({ orientation: 6 }) + .toBuffer() + + const metadata = await getOutputMetadata( + await resizeReal({ mimeType: 'image/jpeg', query: 'width=100', sourceBuffer }), + ) + + expect(metadata.width).toBe(100) + expect(metadata.height).toBe(200) + }) + + it.each([ + { expectedWidth: 100, query: 'width=300' }, + { expectedWidth: 300, query: 'width=300&withoutEnlargement=false' }, + ])( + 'should apply a configured withoutEnlargement=true default unless the request overrides it ($query)', + async ({ expectedWidth, query }) => { + const sourceBuffer = await makeSourceImage({ height: 100, width: 100 }) + + const metadata = await getOutputMetadata( + await resizeReal({ + dynamicDefaults: resolveSharpDynamicDefaults({ withoutEnlargement: true }), + query, + sourceBuffer, + }), + ) + + expect(metadata.width).toBe(expectedWidth) + }, + ) + + it.each([ + ['jpeg', 'jpeg'], + ['png', 'png'], + ['webp', 'webp'], + ['tiff', 'tiff'], + ['gif', 'gif'], + // AVIF is stored in a HEIF container — Sharp reports its metadata format as + // "heif", not "avif", even though the MIME type and file extension are AVIF. + ['avif', 'heif'], + ] as const)( + 'should retain the source format (%s) in the resized output', + async (format, expectedMetadataFormat) => { + const sourceBuffer = await makeSourceImage({ format }) + + const metadata = await getOutputMetadata( + await resizeReal({ mimeType: `image/${format}`, query: 'width=100', sourceBuffer }), + ) + + expect(metadata.format).toBe(expectedMetadataFormat) + }, + ) +}) diff --git a/packages/transformer-sharp/src/handleRequest.ts b/packages/transformer-sharp/src/handleRequest.ts new file mode 100644 index 00000000000..3743d6b10d7 --- /dev/null +++ b/packages/transformer-sharp/src/handleRequest.ts @@ -0,0 +1,175 @@ +import type { HandleTransformRequestArgs, HandleTransformRequestResult } from 'payload' +import type { SharpOptions } from 'sharp' + +import type { SharpDependency, SharpDynamicDefaults } from './types.js' + +import { parseDynamicResize } from './parseDynamicResize.js' + +// Must match generateFileData.ts's allow-list — the only MIME types Sharp auto-detects multi-frame animation for. +const ANIMATED_MIME_TYPES = ['image/avif', 'image/gif', 'image/webp'] + +/** + * Builds the request-time `handleRequest` stage: parses and validates the v1 + * dynamic parameters (`width`, `height`, `withoutEnlargement`), retrieves the + * source exactly once — only after validation passes — and resizes it while + * preserving the source format. Unexpected Sharp errors are left to propagate + * uncaught; Payload's orchestrator logs and aborts the pipeline. + */ +export function createHandleRequest({ + dynamicDefaults, + sharpDependency, +}: { + dynamicDefaults: Required + sharpDependency: SharpDependency +}): (args: HandleTransformRequestArgs) => Promise { + return async ({ getSourceFile, mimeType, req }) => { + const parseResult = parseDynamicResize({ + limits: dynamicDefaults, + searchParams: req.searchParams ?? new URLSearchParams(), + }) + + if (!parseResult.isRouted) { + return { status: 'continue' } + } + + if (!parseResult.valid) { + return { + response: Response.json({ errors: [{ message: parseResult.error }] }, { status: 400 }), + status: 'complete', + } + } + + if (req.headers?.get('range')) { + return { response: new Response(null, { status: 416 }), status: 'complete' } + } + + const source = await getSourceFile() + + if (!source.ok) { + return { response: source, status: 'complete' } + } + + const sourceBuffer = Buffer.from(await source.arrayBuffer()) + + const isAnimated = ANIMATED_MIME_TYPES.includes(mimeType) + const sharpOptions: SharpOptions = isAnimated ? { animated: true } : {} + + const withoutEnlargement = parseResult.withoutEnlargement ?? dynamicDefaults.withoutEnlargement + + // The output size is only known once the source is probed: with a single dimension + // Sharp derives the other from the aspect ratio, `fit: 'outside'` can overflow the + // requested box along one axis, and an animated source is resized frame by frame, + // so its real cost is the per-frame output times the frame count. + if ( + isAnimated || + parseResult.width === undefined || + parseResult.height === undefined || + dynamicDefaults.fit === 'outside' + ) { + const metadata = await sharpDependency(sourceBuffer, sharpOptions).metadata() + const frameCount = isAnimated ? (metadata.pages ?? 1) : 1 + const frameHeight = metadata.pageHeight ?? metadata.height + // EXIF orientations 5-8 rotate the image by 90°, so `.rotate()` below swaps its axes. + const isRotatedQuarterTurn = [5, 6, 7, 8].includes(metadata.orientation!) + const output = getOutputDimensions({ + fit: dynamicDefaults.fit, + height: parseResult.height, + sourceHeight: isRotatedQuarterTurn ? metadata.width : frameHeight, + sourceWidth: isRotatedQuarterTurn ? frameHeight : metadata.width, + width: parseResult.width, + withoutEnlargement, + }) + + if ( + output && + (output.width > dynamicDefaults.maxWidth || + output.height > dynamicDefaults.maxHeight || + output.width * output.height * frameCount > dynamicDefaults.maxPixels) + ) { + const frameDescription = frameCount > 1 ? ` across ${frameCount} frames` : '' + + return { + response: Response.json( + { + errors: [ + { + message: `Requested dimensions (${output.width}x${output.height}${frameDescription}) exceed the configured maximum.`, + }, + ], + }, + { status: 400 }, + ), + status: 'complete', + } + } + } + + // Sharp drops the EXIF orientation tag on output, so apply it to the pixels first. + const resizedBuffer = await sharpDependency(sourceBuffer, sharpOptions) + .rotate() + .resize({ + fit: dynamicDefaults.fit, + height: parseResult.height, + position: dynamicDefaults.position, + width: parseResult.width, + withoutEnlargement, + }) + .toBuffer() + + const headers = new Headers() + headers.set('Content-Type', mimeType) + headers.set('Content-Length', String(resizedBuffer.length)) + + return { + response: new Response(req.method === 'HEAD' ? null : resizedBuffer, { + headers, + status: 200, + }), + status: 'continue', + } + } +} + +/** + * The per-frame output size. When both dimensions are requested with any `fit` other + * than `'outside'` this is the requested box, an upper bound (`fit: 'contain'`/`'inside'` + * or `withoutEnlargement` can render smaller), which is what a resource budget needs. + * `'outside'` scales to cover the box, so one axis can exceed it. + */ +function getOutputDimensions({ + fit, + height, + sourceHeight, + sourceWidth, + width, + withoutEnlargement, +}: { + fit: SharpDynamicDefaults['fit'] + height: number | undefined + sourceHeight: number | undefined + sourceWidth: number | undefined + width: number | undefined + withoutEnlargement: boolean +}): { height: number; width: number } | undefined { + const hasBothDimensions = width !== undefined && height !== undefined + + if (hasBothDimensions && fit !== 'outside') { + return { height, width } + } + + if (!sourceWidth || !sourceHeight) { + return undefined + } + + const scale = hasBothDimensions + ? Math.max(width / sourceWidth, height / sourceHeight) + : width !== undefined + ? width / sourceWidth + : height! / sourceHeight + const effectiveScale = withoutEnlargement ? Math.min(scale, 1) : scale + + return { + height: Math.round(sourceHeight * effectiveScale), + width: Math.round(sourceWidth * effectiveScale), + } +} diff --git a/packages/transformer-sharp/src/index.ts b/packages/transformer-sharp/src/index.ts new file mode 100644 index 00000000000..40e8d6ff7f9 --- /dev/null +++ b/packages/transformer-sharp/src/index.ts @@ -0,0 +1,9 @@ +export { sharpTransformer } from './sharpTransformer.js' +export type { + DynamicResizeParseResult, + SharpCollectionConfig, + SharpDependency, + SharpDynamicDefaults, + SharpDynamicOptions, + SharpTransformerOptions, +} from './types.js' diff --git a/packages/transformer-sharp/src/initSharpCollections.spec.ts b/packages/transformer-sharp/src/initSharpCollections.spec.ts new file mode 100644 index 00000000000..f5d8c9524e3 --- /dev/null +++ b/packages/transformer-sharp/src/initSharpCollections.spec.ts @@ -0,0 +1,110 @@ +import type { Config } from 'payload' + +import { describe, expect, it } from 'vitest' + +import type { SharpCollectionConfig } from './types.js' + +import { initSharpCollections } from './initSharpCollections.js' + +const makeConfig = (collections: Config['collections']): Config => + ({ + collections, + }) as unknown as Config + +const uploadCollection = ({ + slug, + upload = {}, +}: { + slug: string + upload?: boolean | Record +}) => ({ slug, upload }) as unknown as NonNullable[number] + +describe('initSharpCollections', () => { + it('should not throw when the config has no collections', () => { + const config = makeConfig(undefined) + + expect(() => initSharpCollections({ collections: {}, config })).not.toThrow() + }) + + it.each([ + { expected: { crop: false, focalPoint: false }, sharpConfig: {} }, + { expected: { crop: true, focalPoint: true }, sharpConfig: { crop: true, focalPoint: true } }, + ])( + "should let Sharp's crop and focalPoint override the collection's only when set (%#)", + ({ expected, sharpConfig }) => { + const config = makeConfig([ + uploadCollection({ slug: 'media', upload: { crop: false, focalPoint: false } }), + ]) + + const result = initSharpCollections({ collections: { media: sharpConfig }, config }) + + expect(result.collections?.[0]?.upload).toMatchObject(expected) + }, + ) + + it('should write variants onto a copy, leaving the caller config untouched', () => { + const authoredCollection = uploadCollection({ slug: 'media', upload: { staticDir: 'media' } }) + const authoredCollections = [authoredCollection] + const config = makeConfig(authoredCollections) + + const result = initSharpCollections({ + collections: { media: { variants: [{ name: 'thumbnail', width: 100 }] } }, + config, + }) + + expect(result.collections?.[0]?.upload).toMatchObject({ + variants: [{ name: 'thumbnail' }], + staticDir: 'media', + }) + expect(result).not.toBe(config) + expect(result.collections?.[0]).not.toBe(authoredCollection) + expect(config.collections).toBe(authoredCollections) + expect(authoredCollection.upload).toEqual({ staticDir: 'media' }) + }) + + it.each([ + { collection: uploadCollection({ slug: 'media' }), slug: 'missing' }, + { collection: uploadCollection({ slug: 'posts', upload: false }), slug: 'posts' }, + ])( + 'should throw when the configured collection "$slug" is missing or not upload-enabled', + ({ collection, slug }) => { + const config = makeConfig([collection]) + + expect(() => initSharpCollections({ collections: { [slug]: {} }, config })).toThrow( + new RegExp(`"${slug}"`), + ) + }, + ) + + it.each([ + { + error: /duplicate/i, + variants: [ + { name: 'square', width: 100 }, + { name: 'square', width: 200 }, + ], + }, + { error: /reserved/i, variants: [{ name: 'filename', width: 100 }] }, + { error: /name/i, variants: [{ width: 100 }] }, + ])('should reject invalid variants ($error)', ({ error, variants }) => { + const config = makeConfig([uploadCollection({ slug: 'media' })]) + + expect(() => + initSharpCollections({ + collections: { media: { variants: variants as SharpCollectionConfig['variants'] } }, + config, + }), + ).toThrow(error) + }) + + it('should allow a variants entry with neither width nor height (format-only/pass-through size)', () => { + const config = makeConfig([uploadCollection({ slug: 'media' })]) + + expect(() => + initSharpCollections({ + collections: { media: { variants: [{ name: 'noDimensions' }] } }, + config, + }), + ).not.toThrow() + }) +}) diff --git a/packages/transformer-sharp/src/initSharpCollections.ts b/packages/transformer-sharp/src/initSharpCollections.ts new file mode 100644 index 00000000000..88a9fe3a97e --- /dev/null +++ b/packages/transformer-sharp/src/initSharpCollections.ts @@ -0,0 +1,121 @@ +import type { Config, SanitizedUploadConfig } from 'payload' + +import type { SharpCollectionConfig } from './types.js' + +const RESERVED_IMAGE_SIZE_NAMES = [ + 'filename', + 'mimeType', + 'filesize', + 'width', + 'height', + 'url', + 'thumbnailURL', + 'sizes', + 'focalX', + 'focalY', +] + +/** + * Validates `sharpTransformer({ collections })` against the config's real + * collections, then writes a narrowed, Sharp-agnostic projection of + * `variants`/`crop`/`focalPoint`/`hasImageAdjustments` back onto each + * targeted collection's sanitized `upload` config, so core's own field + * generation and Admin UI keep working without knowing about Sharp. + */ +export function initSharpCollections({ + collections, + config, +}: { + collections: Partial> + config: Config +}): Config { + const errors: string[] = [] + + for (const [slug, sharpConfig] of Object.entries(collections)) { + if (!sharpConfig) { + continue + } + + const collection = config.collections?.find((candidate) => candidate.slug === slug) + + if (!collection) { + errors.push( + `sharpTransformer collections."${slug}" does not match any configured collection slug.`, + ) + continue + } + + if (!collection.upload) { + errors.push( + `sharpTransformer collections."${slug}" targets a collection whose \`upload\` option is not set. Enable uploads on "${slug}" first.`, + ) + continue + } + + const seenSizeNames = new Set() + + for (const size of sharpConfig.variants ?? []) { + if (typeof size.name !== 'string' || size.name.trim().length === 0) { + errors.push( + `sharpTransformer collections."${slug}".variants has an entry missing a valid \`name\`.`, + ) + continue + } + + if (seenSizeNames.has(size.name)) { + errors.push( + `sharpTransformer collections."${slug}".variants has a duplicate size name: "${size.name}".`, + ) + } + seenSizeNames.add(size.name) + + if (RESERVED_IMAGE_SIZE_NAMES.includes(size.name)) { + errors.push( + `sharpTransformer collections."${slug}".variants uses reserved name "${size.name}", which collides with a built-in upload field. Choose a different name.`, + ) + } + } + } + + if (errors.length > 0) { + throw new Error( + `Invalid \`sharpTransformer({ collections })\` configuration:\n${errors.join('\n')}`, + ) + } + + // Write onto copies, never the caller's config or collection objects: a rebuilt config (e.g. on + // a dev reload) would otherwise see `upload.variants` on the collection and reject it as authored. + const sanitizedCollections = config.collections?.map((collection) => { + const sharpConfig = collections[collection.slug] + + if (!sharpConfig || !collection.upload) { + return collection + } + + const authoredUpload = typeof collection.upload === 'object' ? collection.upload : {} + + const upload: Partial = { + ...authoredUpload, + // Sharp's setting wins when given; otherwise keep the collection's own, so an + // unset Sharp option can't silently re-enable a collection's `crop: false`. + crop: sharpConfig.crop ?? authoredUpload.crop, + focalPoint: sharpConfig.focalPoint ?? authoredUpload.focalPoint, + hasImageAdjustments: Boolean( + sharpConfig.resizeOptions || + sharpConfig.formatOptions || + sharpConfig.trimOptions || + sharpConfig.constructorOptions || + sharpConfig.withMetadata, + ), + variants: sharpConfig.variants?.map(({ name, admin, generateImageName }) => ({ + name, + admin, + generateImageName, + })), + } + + return { ...collection, upload } + }) + + return { ...config, collections: sanitizedCollections } +} diff --git a/packages/transformer-sharp/src/mapWithBoundedConcurrency.spec.ts b/packages/transformer-sharp/src/mapWithBoundedConcurrency.spec.ts new file mode 100644 index 00000000000..c7c3ef9fbe0 --- /dev/null +++ b/packages/transformer-sharp/src/mapWithBoundedConcurrency.spec.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' + +import { mapWithBoundedConcurrency } from './mapWithBoundedConcurrency.js' + +describe('mapWithBoundedConcurrency', () => { + it('should preserve result order regardless of completion order', async () => { + const items = [30, 10, 20] + + const results = await mapWithBoundedConcurrency(items, async (delay) => { + await new Promise((resolve) => setTimeout(resolve, delay)) + return delay + }) + + expect(results).toEqual([30, 10, 20]) + }) + + it('should never run more than the configured concurrency at once', async () => { + let inFlight = 0 + let maxInFlight = 0 + + await mapWithBoundedConcurrency( + Array.from({ length: 10 }, (_, i) => i), + async () => { + inFlight += 1 + maxInFlight = Math.max(maxInFlight, inFlight) + await new Promise((resolve) => setTimeout(resolve, 5)) + inFlight -= 1 + }, + 2, + ) + + expect(maxInFlight).toBeLessThanOrEqual(2) + }) +}) diff --git a/packages/transformer-sharp/src/mapWithBoundedConcurrency.ts b/packages/transformer-sharp/src/mapWithBoundedConcurrency.ts new file mode 100644 index 00000000000..73be00d2a12 --- /dev/null +++ b/packages/transformer-sharp/src/mapWithBoundedConcurrency.ts @@ -0,0 +1,33 @@ +const DEFAULT_CONCURRENCY = 4 + +/** + * Runs `mapper` over every item, at most `concurrency` items in flight at + * once. Results preserve `items`' order regardless of completion order — + * used so configured image sizes keep their declared metadata order. + */ +export async function mapWithBoundedConcurrency( + items: TItem[], + mapper: (item: TItem, index: number) => Promise, + concurrency: number = DEFAULT_CONCURRENCY, +): Promise { + const results: TResult[] = new Array(items.length) + let nextIndex = 0 + + async function runWorker(): Promise { + while (true) { + const currentIndex = nextIndex + nextIndex += 1 + + if (currentIndex >= items.length) { + return + } + + results[currentIndex] = await mapper(items[currentIndex]!, currentIndex) + } + } + + const workerCount = Math.min(concurrency, items.length) + await Promise.all(Array.from({ length: workerCount }, runWorker)) + + return results +} diff --git a/packages/transformer-sharp/src/optionallyAppendMetadata.ts b/packages/transformer-sharp/src/optionallyAppendMetadata.ts new file mode 100644 index 00000000000..b4b04e5fb97 --- /dev/null +++ b/packages/transformer-sharp/src/optionallyAppendMetadata.ts @@ -0,0 +1,28 @@ +import type { PayloadRequest } from 'payload' +import type { Sharp } from 'sharp' + +import type { WithMetadata } from './types.js' + +export async function optionallyAppendMetadata({ + req, + sharpFile, + withMetadata, +}: { + req: PayloadRequest + sharpFile: Sharp + withMetadata: undefined | WithMetadata +}): Promise { + const metadata = await sharpFile.metadata() + + if (withMetadata === true) { + return sharpFile.withMetadata() + } else if (typeof withMetadata === 'function') { + const useMetadata = await withMetadata({ metadata, req }) + + if (useMetadata) { + return sharpFile.withMetadata() + } + } + + return sharpFile +} diff --git a/packages/transformer-sharp/src/parseDynamicResize.spec.ts b/packages/transformer-sharp/src/parseDynamicResize.spec.ts new file mode 100644 index 00000000000..0ebc217a5e8 --- /dev/null +++ b/packages/transformer-sharp/src/parseDynamicResize.spec.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest' + +import { parseDynamicResize } from './parseDynamicResize.js' + +const DEFAULT_LIMITS = { maxHeight: 4096, maxPixels: 16_777_216, maxWidth: 4096 } + +const parse = (query: string) => + parseDynamicResize({ limits: DEFAULT_LIMITS, searchParams: new URLSearchParams(query) }) + +describe('parseDynamicResize', () => { + it('should route a request containing only withoutEnlargement, but reject it as invalid', () => { + const result = parse('withoutEnlargement=true') + expect(result.isRouted).toBe(true) + expect(result).toMatchObject({ valid: false }) + }) + + it.each([true, false])( + 'should parse a valid withoutEnlargement=%s alongside width', + (withoutEnlargement) => { + expect(parse(`width=500&withoutEnlargement=${withoutEnlargement}`)).toEqual({ + height: undefined, + isRouted: true, + valid: true, + width: 500, + withoutEnlargement, + }) + }, + ) + + it('should reject an invalid withoutEnlargement value', () => { + const result = parse('width=500&withoutEnlargement=yes') + expect(result).toMatchObject({ isRouted: true, valid: false }) + }) + + it.each([ + 'width=500&width=600', + 'height=500&height=600', + 'width=500&withoutEnlargement=true&withoutEnlargement=false', + ])('should reject a repeated parameter: %s', (query) => { + expect(parse(query)).toMatchObject({ isRouted: true, valid: false }) + }) + + it.each(['abc', '12.5', '-5', '0', '', '5px', ' 5'])( + 'should reject a non-positive-integer width: %s', + (value) => { + const result = parse(`width=${encodeURIComponent(value)}`) + expect(result).toMatchObject({ isRouted: true, valid: false }) + }, + ) + + it('should reject an unsafe-integer width without throwing', () => { + const result = parse('width=99999999999999999999999999999999') + expect(result).toMatchObject({ isRouted: true, valid: false }) + }) + + it('should reject a width exceeding the configured maxWidth', () => { + const result = parse('width=5000') + expect(result).toMatchObject({ isRouted: true, valid: false }) + }) + + it('should reject a height exceeding the configured maxHeight', () => { + const result = parse('height=5000') + expect(result).toMatchObject({ isRouted: true, valid: false }) + }) + + it('should accept a width exactly at the configured maxWidth', () => { + expect(parse('width=4096')).toMatchObject({ isRouted: true, valid: true }) + }) + + it('should reject a width/height combination whose product exceeds maxPixels even when each dimension is individually within its own limit', () => { + const result = parseDynamicResize({ + limits: { maxHeight: 4096, maxPixels: 1000, maxWidth: 4096 }, + searchParams: new URLSearchParams('width=100&height=100'), + }) + expect(result).toMatchObject({ isRouted: true, valid: false }) + }) + + it('should accept a width/height combination within the configured maxPixels', () => { + const result = parseDynamicResize({ + limits: { maxHeight: 4096, maxPixels: 1000, maxWidth: 4096 }, + searchParams: new URLSearchParams('width=10&height=10'), + }) + expect(result).toMatchObject({ height: 10, isRouted: true, valid: true, width: 10 }) + }) +}) diff --git a/packages/transformer-sharp/src/parseDynamicResize.ts b/packages/transformer-sharp/src/parseDynamicResize.ts new file mode 100644 index 00000000000..f8e4013a99f --- /dev/null +++ b/packages/transformer-sharp/src/parseDynamicResize.ts @@ -0,0 +1,125 @@ +import type { DynamicResizeParseResult } from './types.js' + +const POSITIVE_INTEGER_PATTERN = /^[1-9]\d*$/ + +function parsePositiveInteger(value: string): number | undefined { + if (!POSITIVE_INTEGER_PATTERN.test(value)) { + return undefined + } + + const parsed = Number(value) + + return Number.isSafeInteger(parsed) ? parsed : undefined +} + +/** + * Strictly parses the v1 dynamic resize query parameters (`width`, `height`, + * `withoutEnlargement`). The presence of any of them routes the request — + * a malformed combination is `valid: false` (mapped to `400`), not treated as + * an ordinary read. Every other query key (`draft`, `depth`, `where`, `prefix`, + * …) is ignored. + */ +export function parseDynamicResize({ + limits, + searchParams, +}: { + limits: { maxHeight: number; maxPixels: number; maxWidth: number } + searchParams: URLSearchParams +}): DynamicResizeParseResult { + const widthValues = searchParams.getAll('width') + const heightValues = searchParams.getAll('height') + const withoutEnlargementValues = searchParams.getAll('withoutEnlargement') + + const isRouted = + widthValues.length > 0 || heightValues.length > 0 || withoutEnlargementValues.length > 0 + + if (!isRouted) { + return { isRouted: false } + } + + if (widthValues.length > 1) { + return { error: '`width` may only be specified once.', isRouted: true, valid: false } + } + + if (heightValues.length > 1) { + return { error: '`height` may only be specified once.', isRouted: true, valid: false } + } + + if (withoutEnlargementValues.length > 1) { + return { + error: '`withoutEnlargement` may only be specified once.', + isRouted: true, + valid: false, + } + } + + if (widthValues.length === 0 && heightValues.length === 0) { + return { + error: 'At least one of `width` or `height` is required to resize.', + isRouted: true, + valid: false, + } + } + + let width: number | undefined + + if (widthValues.length === 1) { + width = parsePositiveInteger(widthValues[0]!) + + if (width === undefined) { + return { error: '`width` must be a positive integer.', isRouted: true, valid: false } + } + + if (width > limits.maxWidth) { + return { + error: `\`width\` exceeds the configured maximum of ${limits.maxWidth}.`, + isRouted: true, + valid: false, + } + } + } + + let height: number | undefined + + if (heightValues.length === 1) { + height = parsePositiveInteger(heightValues[0]!) + + if (height === undefined) { + return { error: '`height` must be a positive integer.', isRouted: true, valid: false } + } + + if (height > limits.maxHeight) { + return { + error: `\`height\` exceeds the configured maximum of ${limits.maxHeight}.`, + isRouted: true, + valid: false, + } + } + } + + if (width !== undefined && height !== undefined && width * height > limits.maxPixels) { + return { + error: `Requested dimensions exceed the configured maximum of ${limits.maxPixels} pixels.`, + isRouted: true, + valid: false, + } + } + + let withoutEnlargement: boolean | undefined + + if (withoutEnlargementValues.length === 1) { + const raw = withoutEnlargementValues[0] + + if (raw !== 'true' && raw !== 'false') { + return { + error: '`withoutEnlargement` must be "true" or "false".', + isRouted: true, + valid: false, + } + } + + withoutEnlargement = raw === 'true' + } + + return { height, isRouted: true, valid: true, width, withoutEnlargement } +} diff --git a/packages/transformer-sharp/src/prepareLegacyUpload.spec.ts b/packages/transformer-sharp/src/prepareLegacyUpload.spec.ts new file mode 100644 index 00000000000..409a37968f7 --- /dev/null +++ b/packages/transformer-sharp/src/prepareLegacyUpload.spec.ts @@ -0,0 +1,63 @@ +import type { PayloadRequest, UploadEdits } from 'payload' +import type { UploadTransformTask } from 'payload/internal' + +import { describe, expect, it, vi } from 'vitest' + +import type { SharpCollectionConfig, SharpDependency, SharpUploadTaskOptions } from './types.js' + +import { createPrepareLegacyUpload } from './prepareLegacyUpload.js' + +const sharpDependency = (() => ({ + metadata: async () => ({ height: 1000, width: 1000 }), +})) as unknown as SharpDependency + +const makeReq = ({ focalPoint }: { focalPoint?: boolean }): PayloadRequest => + ({ + payload: { collections: { media: { config: { upload: { focalPoint } } } } }, + }) as unknown as PayloadRequest + +const runPrepareUpload = async ({ + collectionFocalPoint, + sharpConfig, +}: { + collectionFocalPoint?: boolean + sharpConfig: SharpCollectionConfig +}) => { + const transform = vi.fn( + async (_task: UploadTransformTask) => new File(['x'], 'image.png', { type: 'image/png' }), + ) + + await createPrepareLegacyUpload({ collections: { media: sharpConfig }, sharpDependency })({ + collectionSlug: 'media', + file: new File(['x'], 'image.png', { type: 'image/png' }), + req: makeReq({ focalPoint: collectionFocalPoint }), + transform, + uploadEdits: { focalPoint: { x: 10, y: 90 } } as UploadEdits, + }) + + const sizeTask = transform.mock.calls + .map(([task]) => task.options as SharpUploadTaskOptions) + .find((options) => options.kind === 'size') + + return sizeTask?.kind === 'size' ? sizeTask.focalPoint : undefined +} + +const variants = [{ name: 'card', height: 200, width: 400 }] + +describe('createPrepareLegacyUpload', () => { + it.each([ + { collectionFocalPoint: false, expected: undefined, sharpFocalPoint: undefined }, + { collectionFocalPoint: false, expected: { x: 10, y: 90 }, sharpFocalPoint: true }, + { collectionFocalPoint: undefined, expected: { x: 10, y: 90 }, sharpFocalPoint: undefined }, + ])( + 'should resolve the size focal point from Sharp ($sharpFocalPoint) over the collection ($collectionFocalPoint)', + async ({ collectionFocalPoint, expected, sharpFocalPoint }) => { + expect( + await runPrepareUpload({ + collectionFocalPoint, + sharpConfig: { focalPoint: sharpFocalPoint, variants }, + }), + ).toEqual(expected) + }, + ) +}) diff --git a/packages/transformer-sharp/src/prepareLegacyUpload.ts b/packages/transformer-sharp/src/prepareLegacyUpload.ts new file mode 100644 index 00000000000..daaca3eb47c --- /dev/null +++ b/packages/transformer-sharp/src/prepareLegacyUpload.ts @@ -0,0 +1,181 @@ +import type { FocalPoint, ProbedImageSize } from 'payload' +import type { PreparedUploadTransformation, UploadTransformerInternal } from 'payload/internal' + +import { isNumber } from 'payload/shared' + +import type { SharpCollectionConfig, SharpDependency, SharpUploadTaskOptions } from './types.js' + +import { canResizeImage } from './canResizeImage.js' +import { getImageResizeAction } from './getImageResizeAction.js' +import { mapWithBoundedConcurrency } from './mapWithBoundedConcurrency.js' +import { sanitizeResizeConfig } from './sanitizeResizeConfig.js' + +/** + * Sharp throws on truncated/header-only files. Returns `undefined` instead — + * matching core's dependency-free probe — since unreadable dimensions shouldn't fail the upload. + */ +async function tryProbe(file: File, sharpDependency: SharpDependency) { + try { + const buffer = Buffer.from(await file.arrayBuffer()) + const metadata = await sharpDependency(buffer).metadata() + return metadata.width && metadata.height ? metadata : undefined + } catch { + return undefined + } +} + +/** + * Computes the legacy upload tasks (the main file, optionally cropped, and every + * configured legacy image size) and hands each to the injected `transform` callback + * exactly once. Reads the Sharp-owned config from `sharpTransformer({ collections })` + * at factory-construction time — not from the sanitized collection, which only gets + * the narrowed projection `init()` writes back. + */ +export function createPrepareLegacyUpload({ + collections, + sharpDependency, +}: { + collections: Partial> + sharpDependency: SharpDependency +}): NonNullable { + return async ({ collectionSlug, file, req, transform, uploadEdits }) => { + const collectionUpload = collections[collectionSlug] ?? {} + + const fileSupportsResize = canResizeImage(file.type) + + const cropData = + uploadEdits && typeof uploadEdits === 'object' && 'crop' in uploadEdits + ? uploadEdits.crop + : undefined + + let originalDimensions: ProbedImageSize | undefined + + if (fileSupportsResize) { + const originalMeta = await tryProbe(file, sharpDependency) + if (originalMeta) { + originalDimensions = { height: originalMeta.height, width: originalMeta.width } + } + } + + // Unreadable dimensions can't be cropped/resized — treat as non-resizable rather than failing the upload. + const canProcessAsImage = fileSupportsResize && originalDimensions !== undefined + + const crop = + cropData && originalDimensions + ? { + cropData, + heightInPixels: uploadEdits.heightInPixels!, + originalDimensions, + widthInPixels: uploadEdits.widthInPixels!, + } + : undefined + + const mainResultFile = await transform({ + fieldPath: 'filename', + options: { collectionUpload, crop, kind: 'main' } satisfies SharpUploadTaskOptions, + }) + + const mainResult = await describeResult({ + fieldPath: 'filename', + fileSupportsResize: canProcessAsImage, + resultFile: mainResultFile, + sharpDependency, + }) + + const results: PreparedUploadTransformation[] = [mainResult] + + // A cropped upload derives its sizes from the crop output, not the original. + const isCropped = Boolean(crop && mainResult.width && mainResult.height) + const sizeSourceFile = isCropped ? mainResultFile : undefined + const sizeSourceDimensions: ProbedImageSize | undefined = isCropped + ? { height: mainResult.height!, width: mainResult.width! } + : originalDimensions + + // Same precedence `init()` applies: Sharp's setting, else the collection's own `focalPoint`. + const effectiveFocalPoint = + collectionUpload.focalPoint ?? + req.payload.collections[collectionSlug]?.config.upload?.focalPoint + const focalPointEnabled = effectiveFocalPoint !== false + const variants = collectionUpload.variants + + if (canProcessAsImage && Array.isArray(variants) && sizeSourceDimensions) { + const focalPoint: FocalPoint | undefined = + focalPointEnabled && uploadEdits?.focalPoint + ? { + x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50, + y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50, + } + : undefined + + const sizeResults = await mapWithBoundedConcurrency(variants, async (rawConfig) => { + const imageResizeConfig = sanitizeResizeConfig(rawConfig) + const fieldPath = `sizes.${imageResizeConfig.name}` as const + + const resizeAction = getImageResizeAction({ + dimensions: sizeSourceDimensions, + hasFocalPoint: Boolean(focalPoint), + imageResizeConfig, + }) + + if (resizeAction === 'omit') { + return { fieldPath } satisfies PreparedUploadTransformation + } + + const sizeResultFile = await transform({ + fieldPath, + file: sizeSourceFile, + options: { + collectionUpload, + focalPoint: resizeAction === 'resizeWithFocalPoint' ? focalPoint : undefined, + imageResizeConfig, + kind: 'size', + originalDimensions: sizeSourceDimensions, + } satisfies SharpUploadTaskOptions, + }) + + return describeResult({ + fieldPath, + fileSupportsResize: true, + resultFile: sizeResultFile, + sharpDependency, + }) + }) + + results.push(...sizeResults) + } + + return results + } +} + +async function describeResult({ + fieldPath, + fileSupportsResize, + resultFile, + sharpDependency, +}: { + fieldPath: PreparedUploadTransformation['fieldPath'] + fileSupportsResize: boolean + resultFile: File + sharpDependency: SharpDependency +}): Promise { + if (!fileSupportsResize) { + return { fieldPath, file: resultFile, mimeType: resultFile.type } + } + + const meta = await tryProbe(resultFile, sharpDependency) + + if (!meta) { + return { fieldPath, file: resultFile, mimeType: resultFile.type } + } + + // `tryProbe` never sets `animated: true`, so `meta.height` is already the + // single-frame height — do not divide by page count here. + return { + fieldPath, + file: resultFile, + height: meta.height, + mimeType: resultFile.type, + width: meta.width, + } +} diff --git a/packages/transformer-sharp/src/sanitizeResizeConfig.ts b/packages/transformer-sharp/src/sanitizeResizeConfig.ts new file mode 100644 index 00000000000..5ae20a55f88 --- /dev/null +++ b/packages/transformer-sharp/src/sanitizeResizeConfig.ts @@ -0,0 +1,21 @@ +import type { ImageSize } from 'payload' + +/** + * Sanitize the resize config. If the resize config has the `withoutReduction` + * property set to true, the `fit` and `position` properties will be set to `contain` + * and `top left` respectively. + * + * @param resizeConfig - the resize config + * @returns a sanitized resize config + */ +export const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => { + if (resizeConfig.withoutReduction) { + return { + ...resizeConfig, + // Why fit `contain` should also be set to https://github.com/lovell/sharp/issues/3595 + fit: resizeConfig?.fit || 'contain', + position: resizeConfig?.position || 'left top', + } + } + return resizeConfig +} diff --git a/packages/transformer-sharp/src/sharpTransformer.spec.ts b/packages/transformer-sharp/src/sharpTransformer.spec.ts new file mode 100644 index 00000000000..ba415d6ad41 --- /dev/null +++ b/packages/transformer-sharp/src/sharpTransformer.spec.ts @@ -0,0 +1,47 @@ +import type { Config, UploadCollectionSlug } from 'payload' + +import { describe, expect, it } from 'vitest' + +import { resolveSharpDynamicDefaults, sharpTransformer } from './sharpTransformer.js' + +describe('sharpTransformer', () => { + it("should default mimeTypes to canResizeImage's allow-list exactly, excluding jxl", () => { + expect(sharpTransformer().mimeTypes).toEqual([ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/tiff', + 'image/avif', + ]) + }) + + it('should reject dynamic.collections that are unknown or not upload-enabled on init', () => { + const config = { + collections: [ + { slug: 'media', fields: [], upload: true }, + { slug: 'posts', fields: [] }, + ], + } as unknown as Config + const transformer = sharpTransformer({ + dynamic: { collections: ['posts', 'missing'] as unknown as UploadCollectionSlug[] }, + }) + + expect(() => transformer.init!(config)).toThrow( + /not an upload-enabled collection: "posts", "missing"/, + ) + }) +}) + +describe('resolveSharpDynamicDefaults', () => { + it('should default to fit=cover, position=center, maxWidth=4096, maxHeight=4096, maxPixels=16_777_216, withoutEnlargement=false', () => { + expect(resolveSharpDynamicDefaults()).toEqual({ + fit: 'cover', + maxHeight: 4096, + maxPixels: 16_777_216, + maxWidth: 4096, + position: 'center', + withoutEnlargement: false, + }) + }) +}) diff --git a/packages/transformer-sharp/src/sharpTransformer.ts b/packages/transformer-sharp/src/sharpTransformer.ts new file mode 100644 index 00000000000..1574037767d --- /dev/null +++ b/packages/transformer-sharp/src/sharpTransformer.ts @@ -0,0 +1,125 @@ +import type { Config, TransformFileArgs, TransformFileResult, UploadTransformer } from 'payload' +import type { TransformerWithInternalBridge } from 'payload/internal' + +import { uploadTransformerInternal } from 'payload/internal' +import bundledSharp from 'sharp' + +import type { SharpDynamicDefaults, SharpDynamicOptions, SharpTransformerOptions } from './types.js' + +import { createHandleRequest } from './handleRequest.js' +import { initSharpCollections } from './initSharpCollections.js' +import { parseDynamicResize } from './parseDynamicResize.js' +import { createPrepareLegacyUpload } from './prepareLegacyUpload.js' +import { createTransformFile } from './transformFile.js' + +const DEFAULT_MIME_TYPES = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/tiff', + 'image/avif', +] + +export function resolveSharpDynamicDefaults( + overrides?: SharpDynamicDefaults, +): Required { + return { + fit: overrides?.fit ?? 'cover', + maxHeight: overrides?.maxHeight ?? 4096, + maxPixels: overrides?.maxPixels ?? 16_777_216, + maxWidth: overrides?.maxWidth ?? 4096, + position: overrides?.position ?? 'center', + withoutEnlargement: overrides?.withoutEnlargement ?? false, + } +} + +/** + * Normalizes the `dynamic` option: `false`/omitted disables request-time + * resizing, `true` enables it with defaults for every upload collection. + */ +function resolveSharpDynamicOptions( + dynamic: SharpTransformerOptions['dynamic'], +): false | SharpDynamicOptions { + if (!dynamic) { + return false + } + + return dynamic === true ? {} : dynamic +} + +/** + * Payload's official Sharp-based file transformer: upload-time image processing, + * plus opt-in (`dynamic`) request-time width/height/`withoutEnlargement` resizing. + */ +export function sharpTransformer( + options: SharpTransformerOptions = {}, +): TransformerWithInternalBridge & UploadTransformer { + const dynamicOptions = resolveSharpDynamicOptions(options.dynamic) + const dynamicDefaults = resolveSharpDynamicDefaults(dynamicOptions || undefined) + const sharpDependency = options.sharp ?? bundledSharp + const collections = options.collections ?? {} + + return { + slug: options.slug ?? 'sharp', + canTransform: (args) => { + // Upload-time eligibility is already decided by the MIME match that ran + // before `canTransform`; only dynamic request routing needs the query. + if (args.operation === 'upload') { + return true + } + + if ( + !dynamicOptions || + (dynamicOptions.collections && !dynamicOptions.collections.includes(args.collectionSlug)) + ) { + return false + } + + const result = parseDynamicResize({ + limits: dynamicDefaults, + searchParams: args.req.searchParams ?? new URLSearchParams(), + }) + + return result.isRouted + }, + handleRequest: createHandleRequest({ dynamicDefaults, sharpDependency }), + init: (config) => { + assertDynamicCollectionsExist({ config, dynamicOptions }) + + return initSharpCollections({ collections, config }) + }, + mimeTypes: DEFAULT_MIME_TYPES, + [uploadTransformerInternal]: { + prepareUpload: createPrepareLegacyUpload({ collections, sharpDependency }), + }, + // `options` here is always what this transformer computed via `prepareUpload`'s + // `transform` callback; the public contract's `unknown` just reflects that core never inspects it. + transformFile: createTransformFile({ sharpDependency }) as ( + args: TransformFileArgs, + ) => Promise, + } +} + +function assertDynamicCollectionsExist({ + config, + dynamicOptions, +}: { + config: Config + dynamicOptions: false | SharpDynamicOptions +}): void { + if (!dynamicOptions || !dynamicOptions.collections) { + return + } + + const invalidSlugs = dynamicOptions.collections.filter( + (slug) => + !config.collections?.some((collection) => collection.slug === slug && collection.upload), + ) + + if (invalidSlugs.length > 0) { + throw new Error( + `Invalid \`sharpTransformer({ dynamic: { collections } })\` configuration: not an upload-enabled collection: ${invalidSlugs.map((slug) => `"${slug}"`).join(', ')}.`, + ) + } +} diff --git a/packages/transformer-sharp/src/transformFile.spec.ts b/packages/transformer-sharp/src/transformFile.spec.ts new file mode 100644 index 00000000000..87332c98d1b --- /dev/null +++ b/packages/transformer-sharp/src/transformFile.spec.ts @@ -0,0 +1,190 @@ +import type { PayloadRequest } from 'payload' + +import sharp from 'sharp' +import { describe, expect, it } from 'vitest' + +import type { SharpUploadTaskOptions } from './types.js' + +import { createTransformFile } from './transformFile.js' + +const makeReq = (): PayloadRequest => ({}) as PayloadRequest + +const toBuffer = async (file: File): Promise => Buffer.from(await file.arrayBuffer()) + +const makeImageBuffer = async ({ + background = { b: 0, g: 128, r: 255 }, + height, + width, +}: { + background?: { b: number; g: number; r: number } + height: number + width: number +}): Promise => + sharp({ create: { background, channels: 3, height, width } }) + .png() + .toBuffer() + +/** + * A two-colour image split along `splitAxis` (red/blue halves) — proves the + * focal-point extract region in `transformSize` lands where the math says, + * not just that the output dimensions are right. + */ +const makeTwoColorImage = async ({ + height, + splitAxis, + width, +}: { + height: number + splitAxis: 'x' | 'y' + width: number +}): Promise => { + const raw = Buffer.alloc(width * height * 3) + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (y * width + x) * 3 + const isFirstHalf = splitAxis === 'x' ? x < width / 2 : y < height / 2 + + if (isFirstHalf) { + raw[idx] = 255 // red + } else { + raw[idx + 2] = 255 // blue + } + } + } + + return sharp(raw, { raw: { channels: 3, height, width } }) + .png() + .toBuffer() +} + +const sampleRawPixel = ({ + data, + info, + x, + y, +}: { + data: Buffer + info: { channels: number; width: number } + x: number + y: number +}): { b: number; g: number; r: number } => { + const idx = (y * info.width + x) * info.channels + return { b: data[idx + 2]!, g: data[idx + 1]!, r: data[idx]! } +} + +describe('createTransformFile', () => { + describe('main (transformMain)', () => { + it('should trim uniform-colour padding from the main file', async () => { + const square = await makeImageBuffer({ + background: { b: 0, g: 0, r: 0 }, + height: 20, + width: 20, + }) + const framed = await sharp({ + create: { background: { b: 255, g: 255, r: 255 }, channels: 3, height: 60, width: 60 }, + }) + .composite([{ input: square, left: 20, top: 20 }]) + .png() + .toBuffer() + const file = new File([framed], 'photo.png', { type: 'image/png' }) + const transformFile = createTransformFile({ sharpDependency: sharp }) + + const result = await transformFile({ + file, + options: { + collectionUpload: { trimOptions: {} }, + kind: 'main', + } satisfies SharpUploadTaskOptions, + req: makeReq(), + }) + + const metadata = await sharp(await toBuffer(result.file!)).metadata() + expect(metadata.width).toBe(20) + expect(metadata.height).toBe(20) + }) + + it.each([ + // Re-applied by default, so the 40x40 crop is resized down to 20x20. + { expectedSize: 20, resizeOptions: { fit: 'cover' as const, height: 20, width: 20 } }, + { expectedSize: 40, resizeOptions: { height: 20, width: 20, withoutEnlargement: true } }, + ])( + 'should re-apply resizeOptions to the crop output unless withoutEnlargement is set (%#)', + async ({ expectedSize, resizeOptions }) => { + const buffer = await makeImageBuffer({ height: 100, width: 100 }) + const file = new File([buffer], 'photo.png', { type: 'image/png' }) + const transformFile = createTransformFile({ sharpDependency: sharp }) + + const result = await transformFile({ + file, + options: { + collectionUpload: { resizeOptions }, + crop: { + cropData: { height: 40, unit: '%', width: 40, x: 25, y: 25 }, + heightInPixels: 40, + originalDimensions: { height: 100, width: 100 }, + widthInPixels: 40, + }, + kind: 'main', + } satisfies SharpUploadTaskOptions, + req: makeReq(), + }) + + const metadata = await sharp(await toBuffer(result.file!)).metadata() + expect(metadata.width).toBe(expectedSize) + expect(metadata.height).toBe(expectedSize) + }, + ) + }) + + describe('size (transformSize)', () => { + // Each original forces one resize-priority branch, and a focal point at 80% pushes the + // 50px extract window against the clamped far edge, so a correct crop is entirely blue. + it.each([ + { + focalPoint: { x: 80, y: 50 }, + original: { height: 100, splitAxis: 'x' as const, width: 200 }, + samples: [ + { x: 10, y: 25 }, + { x: 40, y: 25 }, + ], + }, + { + focalPoint: { x: 50, y: 80 }, + original: { height: 200, splitAxis: 'y' as const, width: 100 }, + samples: [ + { x: 25, y: 10 }, + { x: 25, y: 40 }, + ], + }, + ])( + 'should extract the focal-point region along the $original.splitAxis axis', + async ({ focalPoint, original, samples }) => { + const buffer = await makeTwoColorImage(original) + const file = new File([buffer], 'photo.png', { type: 'image/png' }) + const transformFile = createTransformFile({ sharpDependency: sharp }) + + const result = await transformFile({ + file, + options: { + collectionUpload: {}, + focalPoint, + imageResizeConfig: { name: 'thumb', height: 50, width: 50 }, + kind: 'size', + originalDimensions: { height: original.height, width: original.width }, + } satisfies SharpUploadTaskOptions, + req: makeReq(), + }) + + const { data, info } = await sharp(await toBuffer(result.file!)) + .raw() + .toBuffer({ resolveWithObject: true }) + expect(info.width).toBe(50) + expect(info.height).toBe(50) + for (const { x, y } of samples) { + expect(sampleRawPixel({ data, info, x, y })).toEqual({ b: 255, g: 0, r: 0 }) + } + }, + ) + }) +}) diff --git a/packages/transformer-sharp/src/transformFile.ts b/packages/transformer-sharp/src/transformFile.ts new file mode 100644 index 00000000000..eaa7e222f0a --- /dev/null +++ b/packages/transformer-sharp/src/transformFile.ts @@ -0,0 +1,286 @@ +import type { PayloadRequest, TransformFileArgs, TransformFileResult } from 'payload' +import type { ResizeOptions, SharpOptions } from 'sharp' + +import type { SharpDependency, SharpUploadTaskOptions } from './types.js' + +import { optionallyAppendMetadata } from './optionallyAppendMetadata.js' + +const ANIMATED_MIME_TYPES = ['image/avif', 'image/gif', 'image/webp'] + +const percentToPixel = (value: number, dimension: number) => Math.floor((value / 100) * dimension) + +async function toBuffer(file: File): Promise { + return Buffer.from(await file.arrayBuffer()) +} + +/** + * The Sharp package's public one-file-in/one-file-out upload primitive. + * `options.kind` selects which legacy task to run: the main file (optionally + * cropped, with `resizeOptions` re-applied to the crop output), or one named + * legacy image size. Never writes to storage. + */ +export function createTransformFile({ sharpDependency }: { sharpDependency: SharpDependency }) { + return async function transformFile({ + file, + options, + req, + }: TransformFileArgs): Promise { + if (options.kind === 'main') { + return transformMain({ file, options, req, sharpDependency }) + } + + return transformSize({ file, options, req, sharpDependency }) + } +} + +async function transformMain({ + file, + options, + req, + sharpDependency, +}: { + file: File + options: Extract + req: PayloadRequest + sharpDependency: SharpDependency +}): Promise { + const { collectionUpload, crop } = options + const { constructorOptions, formatOptions, resizeOptions, trimOptions, withMetadata } = + collectionUpload + + const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type) + const fileHasAdjustments = Boolean( + resizeOptions || formatOptions || trimOptions || constructorOptions, + ) + + if (crop) { + return transformCrop({ + crop, + file, + fileIsAnimatedType, + req, + resizeOptions, + sharpDependency, + withMetadata, + }) + } + + if (!fileIsAnimatedType && !fileHasAdjustments) { + return { status: 'continue' } + } + + const buffer = await toBuffer(file) + const sharpOptions: SharpOptions = { ...constructorOptions } + + if (fileIsAnimatedType) { + sharpOptions.animated = true + } + + // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081 + let sharpFile = sharpDependency(buffer, sharpOptions).rotate() + + if (resizeOptions) { + sharpFile = sharpFile.resize(resizeOptions) + } + if (formatOptions) { + sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options) + } + if (trimOptions) { + sharpFile = sharpFile.trim(trimOptions) + } + + sharpFile = await optionallyAppendMetadata({ req, sharpFile, withMetadata }) + const { data: outputData } = await sharpFile.toBuffer({ resolveWithObject: true }) + + return { + file: new File([outputData], file.name, { type: file.type }), + status: 'continue', + } +} + +async function transformCrop({ + crop, + file, + fileIsAnimatedType, + req, + resizeOptions, + sharpDependency, + withMetadata, +}: { + crop: NonNullable['crop']> + file: File + fileIsAnimatedType: boolean + req: PayloadRequest + resizeOptions: ResizeOptions | undefined + sharpDependency: SharpDependency + withMetadata: SharpUploadTaskOptions['collectionUpload']['withMetadata'] +}): Promise { + const { cropData, heightInPixels, originalDimensions, widthInPixels } = crop + const buffer = await toBuffer(file) + + const sharpOptions: SharpOptions = fileIsAnimatedType ? { animated: true } : {} + + const newWidth = Number(widthInPixels) + const newHeight = Number(heightInPixels) + const dimensionsChanged = + originalDimensions.width !== newWidth || originalDimensions.height !== newHeight + + let croppedBuffer: Buffer + + if (!dimensionsChanged) { + croppedBuffer = buffer + } else { + const formattedCropData = { + height: newHeight, + left: percentToPixel(cropData.x, originalDimensions.width), + top: percentToPixel(cropData.y, originalDimensions.height), + width: newWidth, + } + + let cropped = sharpDependency(buffer, sharpOptions).extract(formattedCropData) + cropped = await optionallyAppendMetadata({ req, sharpFile: cropped, withMetadata }) + const { data } = await cropped.toBuffer({ resolveWithObject: true }) + croppedBuffer = data + } + + if (resizeOptions && !resizeOptions.withoutEnlargement) { + const { data: resizedBuffer } = await sharpDependency(croppedBuffer) + .resize({ + fit: resizeOptions.fit || 'cover', + height: resizeOptions.height, + position: resizeOptions.position || 'center', + width: resizeOptions.width, + }) + .toBuffer({ resolveWithObject: true }) + + return { + file: new File([resizedBuffer], file.name, { type: file.type }), + status: 'continue', + } + } + + return { + file: new File([croppedBuffer], file.name, { type: file.type }), + status: 'continue', + } +} + +async function transformSize({ + file, + options, + req, + sharpDependency, +}: { + file: File + options: Extract + req: PayloadRequest + sharpDependency: SharpDependency +}): Promise { + const { collectionUpload, focalPoint, imageResizeConfig, originalDimensions } = options + const { withMetadata } = collectionUpload + + const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type) + const buffer = await toBuffer(file) + const sharpOptions: SharpOptions = fileIsAnimatedType ? { animated: true } : {} + + const sharpBase = sharpDependency(buffer, sharpOptions).rotate() + const originalImageMeta = await sharpBase.metadata() + + let adjustedDimensions = { ...originalDimensions } + + if ([5, 6, 7, 8].includes(originalImageMeta.orientation!)) { + adjustedDimensions = { + height: originalDimensions.width, + width: originalDimensions.height, + } + } + + let resized = sharpBase.clone() + + if (focalPoint) { + let { height: resizeHeight, width: resizeWidth } = imageResizeConfig + + const originalAspectRatio = adjustedDimensions.width / adjustedDimensions.height + + if (resizeHeight && !resizeWidth) { + resizeWidth = Math.round(resizeHeight * originalAspectRatio) + } + if (resizeWidth && !resizeHeight) { + resizeHeight = Math.round(resizeWidth / originalAspectRatio) + } + if (!resizeHeight) { + resizeHeight = originalImageMeta.height + } + if (!resizeWidth) { + resizeWidth = originalImageMeta.width + } + + const resizeAspectRatio = resizeWidth / resizeHeight + const prioritizeHeight = resizeAspectRatio < originalAspectRatio + + resized = resized.resize({ + fastShrinkOnLoad: false, + height: prioritizeHeight ? resizeHeight : undefined, + width: prioritizeHeight ? undefined : resizeWidth, + }) + + const metadataAppended = await optionallyAppendMetadata({ + req, + sharpFile: resized, + withMetadata, + }) + const { info } = await metadataAppended.toBuffer({ resolveWithObject: true }) + + const resizedHeight = originalImageMeta.pages + ? info.height / originalImageMeta.pages + : info.height + const resizedWidth = info.width + + const halfResizeX = resizeWidth / 2 + const xFocalCenter = resizedWidth * (focalPoint.x / 100) + let leftBound = xFocalCenter - halfResizeX + if (xFocalCenter + halfResizeX > resizedWidth) { + leftBound = resizedWidth - resizeWidth + } + if (leftBound < 0) { + leftBound = 0 + } + + const halfResizeY = resizeHeight / 2 + const yFocalCenter = resizedHeight * (focalPoint.y / 100) + let topBound = yFocalCenter - halfResizeY + if (yFocalCenter + halfResizeY > resizedHeight) { + topBound = resizedHeight - resizeHeight + } + if (topBound < 0) { + topBound = 0 + } + + resized = resized.extract({ + height: resizeHeight, + left: Math.floor(leftBound), + top: Math.floor(topBound), + width: resizeWidth, + }) + } else { + resized = resized.resize(imageResizeConfig) + } + + if (imageResizeConfig.formatOptions) { + resized = resized.toFormat( + imageResizeConfig.formatOptions.format, + imageResizeConfig.formatOptions.options, + ) + } + if (imageResizeConfig.trimOptions) { + resized = resized.trim(imageResizeConfig.trimOptions) + } + + resized = await optionallyAppendMetadata({ req, sharpFile: resized, withMetadata }) + const { data: outputData } = await resized.toBuffer({ resolveWithObject: true }) + + return { + file: new File([outputData], file.name, { type: file.type }), + status: 'continue', + } +} diff --git a/packages/transformer-sharp/src/types.ts b/packages/transformer-sharp/src/types.ts new file mode 100644 index 00000000000..144d2351b76 --- /dev/null +++ b/packages/transformer-sharp/src/types.ts @@ -0,0 +1,187 @@ +import type { + FocalPoint, + ImageSize, + PayloadRequest, + ProbedImageSize, + UploadCollectionSlug, + UploadEdits, +} from 'payload' +import type { FitEnum, ResizeOptions, Sharp, Metadata as SharpMetadata, SharpOptions } from 'sharp' + +/** + * Params sent to the sharp `toFormat()` function + * @link https://sharp.pixelplumbing.com/api-output#toformat + */ +export type ImageUploadFormatOptions = { + format: Parameters[0] + options?: Parameters[1] +} + +/** + * Params sent to the sharp trim() function + * @link https://sharp.pixelplumbing.com/api-resize#trim + */ +export type ImageUploadTrimOptions = Parameters[0] + +export type WithMetadata = + | ((options: { metadata: SharpMetadata; req: PayloadRequest }) => Promise) + | boolean + +/** + * Image size options implemented by Payload's default Sharp image processor. + */ +export type SharpImageSizeOptions = { + /** + * @deprecated prefer position + */ + crop?: string // comes from sharp package + formatOptions?: ImageUploadFormatOptions + trimOptions?: ImageUploadTrimOptions + /** Image sizes generated for each upload, stored under the document's `sizes`. */ + variants?: ImageSize[] + /** + * When an uploaded image is smaller than the defined image size, we have 3 options: + * + * `undefined | false | true` + * + * 1. `undefined` [default]: uploading images with smaller width AND height than the image size will return null + * 2. `false`: always enlarge images to the image size + * 3. `true`: if the image is smaller than the image size, return the original image + */ + withoutEnlargement?: ResizeOptions['withoutEnlargement'] +} & Omit + +declare module 'payload' { + interface RegisteredImageSizeOptions { + sharp: SharpImageSizeOptions + } +} + +/** + * A collection's Sharp-owned upload-time settings, authored via + * `sharpTransformer({ collections: { : {...} } })`. `init()` writes a + * narrowed, Sharp-agnostic projection of `variants`/`crop`/`focalPoint` + * back onto the sanitized collection's `upload` config for core's own use (Admin UI, + * field generation); this richer shape is what the transformer itself reads. + */ +export type SharpCollectionConfig = { + constructorOptions?: SharpOptions + crop?: boolean + focalPoint?: boolean + formatOptions?: ImageUploadFormatOptions + resizeOptions?: ResizeOptions + trimOptions?: ImageUploadTrimOptions + /** Image sizes generated for each upload, stored under the document's `sizes`. */ + variants?: ImageSize[] + withMetadata?: WithMetadata +} + +/** + * Matches core's (soon-removed) `SharpDependency` shape, for advanced injection + * of a compatible custom Sharp build or version via `sharpTransformer({ sharp })`. + */ +export type SharpDependency = ( + input?: + | ArrayBuffer + | Buffer + | Float32Array + | Float64Array + | Int8Array + | Int16Array + | Int32Array + | string + | Uint8Array + | Uint8ClampedArray + | Uint16Array + | Uint32Array, + options?: SharpOptions, +) => Sharp + +export type SharpDynamicDefaults = { + /** @default 'cover' */ + fit?: keyof FitEnum + /** @default 4096 */ + maxHeight?: number + /** + * Total output pixels, counted across every frame of an animated image. + * @default 16_777_216 + */ + maxPixels?: number + /** @default 4096 */ + maxWidth?: number + /** @default 'center' */ + position?: ResizeOptions['position'] + /** @default false */ + withoutEnlargement?: boolean +} + +/** + * Enables request-time resizing and tunes its limits. `collections` restricts it + * to the listed upload collections; omit it to allow every upload collection. + */ +export type SharpDynamicOptions = { + collections?: UploadCollectionSlug[] +} & SharpDynamicDefaults + +/** + * The result of parsing a request's dynamic resize query parameters. + * `isRouted: false` means none of the recognized parameters were present at + * all — an ordinary file read, not a dynamic transformation attempt. + */ +export type DynamicResizeParseResult = + | { + error: string + isRouted: true + valid: false + } + | { + height?: number + isRouted: true + valid: true + width?: number + withoutEnlargement?: boolean + } + | { + isRouted: false + } + +export type SharpTransformerOptions = { + /** Per-collection upload-time image processing settings. */ + collections?: Partial> + /** + * Request-time resizing via `?width=`/`?height=` on the file endpoint. `true` + * enables it with default limits for every upload collection; an object enables + * it with custom limits, optionally restricted to specific collections. + * + * @default false + */ + dynamic?: boolean | SharpDynamicOptions + /** Inject a compatible custom Sharp build or version. Defaults to the bundled dependency. */ + sharp?: SharpDependency + /** @default 'sharp' */ + slug?: string +} + +/** + * `options` for the one-file-in/one-file-out `transformFile` primitive, computed + * by `prepareLegacyUpload` for each task it hands to the injected `transform` + * callback: the main file (optionally a crop), or one named legacy image size. + */ +export type SharpUploadTaskOptions = + | { + collectionUpload: SharpCollectionConfig + crop?: { + cropData: NonNullable + heightInPixels: number + originalDimensions: ProbedImageSize + widthInPixels: number + } + kind: 'main' + } + | { + collectionUpload: SharpCollectionConfig + focalPoint?: FocalPoint + imageResizeConfig: ImageSize + kind: 'size' + originalDimensions: ProbedImageSize + } diff --git a/packages/transformer-sharp/tsconfig.json b/packages/transformer-sharp/tsconfig.json new file mode 100644 index 00000000000..a387f7959d5 --- /dev/null +++ b/packages/transformer-sharp/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "references": [{ "path": "../payload" }], + "compilerOptions": { + // Do not include DOM and DOM.Iterable as this is a server-only package. + "lib": ["ES2022"], + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bacc4bda194..2ebd60c2ec6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -72,7 +72,7 @@ importers: version: 1.63.0 '@sentry/nextjs': specifier: ^8.33.1 - version: 8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)) + version: 8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) '@sentry/node': specifier: ^8.33.1 version: 8.55.2 @@ -203,8 +203,8 @@ importers: specifier: 6.0.1 version: 6.0.1 sharp: - specifier: 0.32.6 - version: 0.32.6 + specifier: 0.35.4 + version: 0.35.4(@types/node@24.12.3) shelljs: specifier: 0.8.5 version: 0.8.5 @@ -860,7 +860,7 @@ importers: version: 2.1.0 next: specifier: '>=16.2.6 <17.0.0' - version: 16.3.8(@babel/core@7.27.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) + version: 16.3.6(@babel/core@7.27.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) path-to-regexp: specifier: 6.3.0 version: 6.3.0 @@ -1082,24 +1082,21 @@ importers: rollup-plugin-dts: specifier: 6.2.3 version: 6.2.3(@typescript/typescript6@6.0.2)(rollup@4.59.0) - sharp: - specifier: 0.32.6 - version: 0.32.6 packages/payload-cloud: dependencies: '@aws-sdk/client-cognito-identity': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@aws-sdk/client-s3': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@aws-sdk/credential-providers': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@aws-sdk/lib-storage': specifier: ^3.614.0 - version: 3.1144.0(@aws-sdk/client-s3@3.1144.0) + version: 3.1143.0(@aws-sdk/client-s3@3.1143.0) '@payloadcms/email-nodemailer': specifier: workspace:* version: link:../email-nodemailer @@ -1341,7 +1338,7 @@ importers: dependencies: '@sentry/nextjs': specifier: ^9.5.0 - version: 9.47.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)) + version: 9.47.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) '@sentry/types': specifier: ^9.5.0 version: 9.47.2 @@ -1649,13 +1646,13 @@ importers: dependencies: '@aws-sdk/client-s3': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@aws-sdk/lib-storage': specifier: ^3.614.0 - version: 3.1144.0(@aws-sdk/client-s3@3.1144.0) + version: 3.1143.0(@aws-sdk/client-s3@3.1143.0) '@aws-sdk/s3-request-presigner': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@payloadcms/plugin-cloud-storage': specifier: workspace:* version: link:../plugin-cloud-storage @@ -1694,10 +1691,10 @@ importers: devDependencies: '@tanstack/react-router': specifier: ^1.120.0 - version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': specifier: ^1.168.26 - version: 1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + version: 1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) '@types/react': specifier: ^19.0.0 version: 19.3.0 @@ -1723,6 +1720,16 @@ importers: specifier: '>=8.0.0' version: 8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) + packages/transformer-sharp: + dependencies: + sharp: + specifier: 0.35.4 + version: 0.35.4(@types/node@24.12.3) + devDependencies: + payload: + specifier: workspace:* + version: link:../payload + packages/translations: dependencies: date-fns: @@ -1898,6 +1905,9 @@ importers: '@payloadcms/richtext-lexical': specifier: workspace:* version: link:../../packages/richtext-lexical + '@payloadcms/transformer-sharp': + specifier: workspace:* + version: link:../../packages/transformer-sharp '@payloadcms/ui': specifier: workspace:* version: link:../../packages/ui @@ -1922,9 +1932,6 @@ importers: react-dom: specifier: 19.3.0 version: 19.3.0(react@19.3.0) - sharp: - specifier: 0.35.4 - version: 0.35.4(@types/node@24.12.3) devDependencies: '@playwright/test': specifier: 1.63.0 @@ -1991,10 +1998,10 @@ importers: version: link:../../packages/ui '@tanstack/react-router': specifier: ^1.120.0 - version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': specifier: ^1.168.26 - version: 1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + version: 1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) cross-env: specifier: 10.1.0 version: 10.1.0 @@ -2013,9 +2020,6 @@ importers: react-dom: specifier: 19.3.0 version: 19.3.0(react@19.3.0) - sharp: - specifier: 0.35.4 - version: 0.35.4(@types/node@24.12.3) srvx: specifier: 0.11.21 version: 0.11.21 @@ -2206,7 +2210,7 @@ importers: version: 3.3.7 '@next/eslint-plugin-next': specifier: ^15.5.4 - version: 15.5.27 + version: 15.5.26 '@playwright/test': specifier: 1.63.0 version: 1.63.0 @@ -2318,6 +2322,9 @@ importers: '@payloadcms/richtext-lexical': specifier: workspace:* version: link:../../packages/richtext-lexical + '@payloadcms/transformer-sharp': + specifier: workspace:* + version: link:../../packages/transformer-sharp '@payloadcms/ui': specifier: workspace:* version: link:../../packages/ui @@ -2375,9 +2382,6 @@ importers: react-hook-form: specifier: 7.71.1 version: 7.71.1(react@19.3.0) - sharp: - specifier: 0.35.4 - version: 0.35.4(@types/node@24.12.3) tailwind-merge: specifier: ^3.4.0 version: 3.7.0 @@ -2457,7 +2461,7 @@ importers: devDependencies: '@aws-sdk/client-s3': specifier: ^3.614.0 - version: 3.1144.0 + version: 3.1143.0 '@azure/storage-blob': specifier: ^12.11.0 version: 12.34.0 @@ -2523,7 +2527,7 @@ importers: version: link:../packages/eslint-plugin '@payloadcms/figma': specifier: latest - version: 0.1.0-alpha.10(@payloadcms/plugin-cloud-storage@packages+plugin-cloud-storage)(@payloadcms/richtext-lexical@packages+richtext-lexical)(@payloadcms/translations@packages+translations)(@payloadcms/ui@packages+ui)(payload@packages+payload)(react@19.3.0) + version: 0.1.0-alpha.8(@payloadcms/plugin-cloud-storage@packages+plugin-cloud-storage)(@payloadcms/richtext-lexical@packages+richtext-lexical)(@payloadcms/translations@packages+translations)(@payloadcms/ui@packages+ui)(payload@packages+payload)(react@19.3.0) '@payloadcms/graphql': specifier: workspace:* version: link:../packages/graphql @@ -2602,6 +2606,9 @@ importers: '@payloadcms/tanstack-start': specifier: workspace:* version: link:../packages/tanstack-start + '@payloadcms/transformer-sharp': + specifier: workspace:* + version: link:../packages/transformer-sharp '@payloadcms/translations': specifier: workspace:* version: link:../packages/translations @@ -2610,7 +2617,7 @@ importers: version: link:../packages/ui '@sentry/nextjs': specifier: ^8.33.1 - version: 8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.8)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + version: 8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.8)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) '@sentry/react': specifier: ^7.77.0 version: 7.120.4(react@19.3.0) @@ -2622,10 +2629,10 @@ importers: version: 7.3.1 '@tanstack/react-router': specifier: ^1.120.0 - version: 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/react-start': specifier: ^1.168.26 - version: 1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + version: 1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) '@types/diff': specifier: ^8.0.0 version: 8.0.0 @@ -2726,8 +2733,8 @@ importers: specifier: ^0.0.1 version: 0.0.1 sharp: - specifier: 0.32.6 - version: 0.32.6 + specifier: 0.35.4 + version: 0.35.4(@types/node@25.9.8) shiki: specifier: ^4.0.2 version: 4.4.3 @@ -3001,8 +3008,8 @@ packages: resolution: {integrity: sha512-kISKhqN1k48TaMPbLgq9jj7mO2jvbJdhirvfu4JW3jhFhENnkY0oCwTPvR4Q6Ne2as6GFAMo2XZDZq4rxC7YDw==} engines: {node: '>=14.0.0'} - '@aws-sdk/client-cognito-identity@3.1144.0': - resolution: {integrity: sha512-U0K8iB56okRFj9+b7VJ4roIRkOYV4//tCB/cYOVRi5J6f7fxyZz2AbEvYKp2/s6F1dXeBFG3UtzebHEVxLQtrA==} + '@aws-sdk/client-cognito-identity@3.1143.0': + resolution: {integrity: sha512-RBGhBzvLk3l8QE2dJGMrpDoj8e6yaiEI3RDXqkrkaVHT0FodVt2XOHmZal55cZesHNPJ4reZBi3gs0sUnl07Aw==} engines: {node: '>=20.0.0'} '@aws-sdk/client-dynamodb@3.1144.0': @@ -3013,6 +3020,10 @@ packages: resolution: {integrity: sha512-EgkYatgp/AoQN1OiTzezN+eH5D2X5jrzlhPdOUcAPFj85RIMx9NJkZINKsQjL4lUyq0hkGu0NRt3UlwkoLA2Dg==} engines: {node: '>=20.0.0'} + '@aws-sdk/client-s3@3.1143.0': + resolution: {integrity: sha512-YVcwL17cUDru7OJFMoGB6aJCg7TmMiuAUjKfswtSm+t8NhO5BLLFTUhjnGWrhdw7ZgYyU2Y8u19t8a7yu0EhEg==} + engines: {node: '>=20.0.0'} + '@aws-sdk/client-s3@3.1144.0': resolution: {integrity: sha512-CAicFlKAuCAUEU1tIptCTh6Eikr4rig9CWYwofZdEuJm9i24XNclgCspV4DKXlOY2nK2AMGHRt88oxmwDdNW7w==} engines: {node: '>=20.0.0'} @@ -3093,6 +3104,10 @@ packages: resolution: {integrity: sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==} engines: {node: '>=20.0.0'} + '@aws-sdk/credential-providers@3.1143.0': + resolution: {integrity: sha512-1Tifq2jFMJy4JN6buq7fY8qlN8+3yZvD7aG9ZWRneRlwjvjurS56aftzMXTmBWSOa2eJHlLx4eNuFu6pLc6K5g==} + engines: {node: '>=20.0.0'} + '@aws-sdk/credential-providers@3.1144.0': resolution: {integrity: sha512-c8j7f4aYj2ALSOPU3Gdrw7ZZfkbRmTgXiEYdMYMwH1/r90APNzQ6KjfnMvJrzh1r3/j/hQmzpbexZ4u7SDfPBA==} engines: {node: '>=20.0.0'} @@ -3105,11 +3120,11 @@ packages: resolution: {integrity: sha512-8q1ICxcDjHId3bBryuu/j+1L9y5/3uQnwzLDt5j2ElcjZSoWmFtymdJy7OjLrluSMe0Z4mq5bcH4fxBXvlEHfw==} engines: {node: '>=20.0.0'} - '@aws-sdk/lib-storage@3.1144.0': - resolution: {integrity: sha512-arRkmqrn2xvqyJEMfKsb/wOKJ8U04l733CNtQdxWpe6S95zrg5z7faV7O2naacevWvDzYXND2p7rH2Cv7hYm6w==} + '@aws-sdk/lib-storage@3.1143.0': + resolution: {integrity: sha512-omEWFYDyzYClz63lxXc6kl2+RFY31URm0i9AgoEhVg/94zWjpT8yQyddIurycVkdS5hh0JpYKmgdr20JrXSIMw==} engines: {node: '>=20.0.0'} peerDependencies: - '@aws-sdk/client-s3': ^3.1144.0 + '@aws-sdk/client-s3': ^3.1143.0 '@aws-sdk/middleware-endpoint-discovery@3.972.31': resolution: {integrity: sha512-JqOIXupzP6cpAmCCmuEaacbXo/bVCcg1NkglCJC6q4wDAvJO09qIs7POf50JNzmhOhWPtfXvzgWxb5nMQl8QiA==} @@ -3151,8 +3166,8 @@ packages: resolution: {integrity: sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/s3-request-presigner@3.1144.0': - resolution: {integrity: sha512-2dTx+VJx0bpCsDIhbLRI0D4DY0NvuTBpqTf/CKcwF3hXxaN4NcLC/IKasAdwvgckgZf0cjLiBc3VfgQ6vzFU/A==} + '@aws-sdk/s3-request-presigner@3.1143.0': + resolution: {integrity: sha512-E+NP73AhU0NXXNAYv6JTdN9Jdm9yUS0WheSIh/QOhpM1Dd4JeTsamxbiLYvboyhdnPQAkqFYITHImaS1Bhqafg==} engines: {node: '>=20.0.0'} '@aws-sdk/signature-v4-multi-region@3.996.47': @@ -6057,27 +6072,49 @@ packages: '@next/env@13.5.11': resolution: {integrity: sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==} + '@next/env@16.3.6': + resolution: {integrity: sha512-x9Vblze1EbtltQYnNH38xCPWU3TVfBd1eXqA3+w9+BTpedkkdNpAaltXlGQ/nsc1+E0mVTNrtcbX3GoO09zeLQ==} + '@next/env@16.3.8': resolution: {integrity: sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==} - '@next/eslint-plugin-next@15.5.27': - resolution: {integrity: sha512-a94xlzvOLaULh4Fzcvj9B2hwIOtPHJW75gghkXiJ8s1Jk0eo00UknrDVhx04Y+8ARu9ds13vy/jHg5u/PdVGqw==} + '@next/eslint-plugin-next@15.5.26': + resolution: {integrity: sha512-xeVpG8CbnTf9IelaFbZQ7TN4BXHV2x+ejnXXbY7Z4kD+sgJ8j97psraV+4/fGjLCjNQuEofUHMeIeieDMyKO/w==} '@next/eslint-plugin-next@16.3.8': resolution: {integrity: sha512-eCR9RcLTZrVS+K3+VOhi0xMdUUGNuriCHHhU1ZoABC/QQ/Gp0OWbLKQxV2/9DkmihYdYRcQWFmZXW8M1AMUXYw==} + '@next/swc-darwin-arm64@16.3.6': + resolution: {integrity: sha512-E/7GEqaUkt8mk/T8v9lAnrhzR06kdq1ZBkC12F8tAMkdIadwNp3H1KqHynDHrpcTlGCUdq/qu6vUL2aYVyYBdw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-arm64@16.3.8': resolution: {integrity: sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@next/swc-darwin-x64@16.3.6': + resolution: {integrity: sha512-yBE893/nDWTlaiBD1p+qgt7NUen4U5R6FXyH0s67Npq1S3E0cVSef1WIXC2xBRgQvwAvJq6DnS6Y6PrY0cy4Ew==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-darwin-x64@16.3.8': resolution: {integrity: sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@next/swc-linux-arm64-gnu@16.3.6': + resolution: {integrity: sha512-KJDpjBqBPYlvkivmyrp+Qys6k/7ksbqGQvRVc6ZEGfR+cjQxx+nUkJaWmNZJsmoOrqYNbaXByF8wa0lBwDhB3Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@next/swc-linux-arm64-gnu@16.3.8': resolution: {integrity: sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==} engines: {node: '>= 10'} @@ -6085,6 +6122,13 @@ packages: os: [linux] libc: [glibc] + '@next/swc-linux-arm64-musl@16.3.6': + resolution: {integrity: sha512-mqNg2K+hvWskSRb/QM+Ix412DvBsuSF0XV+frTSw5vmoucNnIlynFwKYew8D01bfATErMOM7Bujrf0BA5DRKFA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + '@next/swc-linux-arm64-musl@16.3.8': resolution: {integrity: sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==} engines: {node: '>= 10'} @@ -6092,6 +6136,13 @@ packages: os: [linux] libc: [musl] + '@next/swc-linux-x64-gnu@16.3.6': + resolution: {integrity: sha512-nFncBNGAYouRHjRVaITs9beZRfhX4ssVwpnvPIAbkZVH6LtGoAVlH4bJ8Cnf9SOo9bsXgPFer/GdHtEE3JNOkw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + '@next/swc-linux-x64-gnu@16.3.8': resolution: {integrity: sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==} engines: {node: '>= 10'} @@ -6099,6 +6150,13 @@ packages: os: [linux] libc: [glibc] + '@next/swc-linux-x64-musl@16.3.6': + resolution: {integrity: sha512-5Mf3cHDGR/Iz0ng2Bj3zUR3p5QS9YK3Hn2QiAfavFmyF48zwThAjpFoiTKNIcOHLYS4zEk+gzyJ/9deQ2ZB8yQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + '@next/swc-linux-x64-musl@16.3.8': resolution: {integrity: sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==} engines: {node: '>= 10'} @@ -6106,12 +6164,24 @@ packages: os: [linux] libc: [musl] + '@next/swc-win32-arm64-msvc@16.3.6': + resolution: {integrity: sha512-0jkJy0C2kbrJWTk4YLa3xk80pVBpx8FCHJym7CnUfDAXe/FWv5qT7SQJbR0KuemyxaEDlEx5WT4VQJoTW+/9Qw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-arm64-msvc@16.3.8': resolution: {integrity: sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@next/swc-win32-x64-msvc@16.3.6': + resolution: {integrity: sha512-/YXjI1e5OXcZ7YpxRwgP/1jAV/SBKTzeVKqN2mk7mLpcICsyn3Gl5+dIfDTJp70M0ccMhyMMRso4v6mPDCGepg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@next/swc-win32-x64-msvc@16.3.8': resolution: {integrity: sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==} engines: {node: '>= 10'} @@ -6731,8 +6801,8 @@ packages: resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} engines: {node: '>= 10.0.0'} - '@payloadcms/figma@0.1.0-alpha.10': - resolution: {integrity: sha512-hEww919pkLs3EOTjqbYSpDcd0U1f1vwGdOX//hi7uel0jgqkFSo0mlJdWQGY8xGnas9hZRv/EYyhr215GQg3BQ==} + '@payloadcms/figma@0.1.0-alpha.8': + resolution: {integrity: sha512-2drOK1HNoJDAKdLPYULYOh77/5kUS0Uzysj1FhuHkmiVHS8Us5l/HqbMt7ked+YhBtVvaHIrnrZgfBVRdeYtpA==} hasBin: true peerDependencies: '@payloadcms/plugin-cloud-storage': '>=4.0.0-canary.28 <4.0.0-internal' @@ -8267,22 +8337,22 @@ packages: resolution: {integrity: sha512-utTS5L2OkeYUzXGohL1Z8sefu1GLNOJcxe8Hd6iIdc/Xo1K1nDB2JEp4iSFhvYh33xKC9V91TxrS8qfrpoKobQ==} engines: {node: '>=20.19'} - '@tanstack/react-router@1.170.41': - resolution: {integrity: sha512-Mpvw8Wm5MGDbTqI8Yc9flLKxbOHwoWocv0H2aGKcgw2a017oK5nZWqa954P+DrUx/8fI98YNywnHP5XcD3tQ9w==} + '@tanstack/react-router@1.170.40': + resolution: {integrity: sha512-vVr3va9Mcq05rZRrDZPGoptAClRMKHvekLbpg2GcfXvAAtOVhW6Q93tOz4dvS8j03vTVrU25VM6weP1SIHGEew==} engines: {node: '>=20.19'} peerDependencies: react: 19.3.0 react-dom: 19.3.0 - '@tanstack/react-start-client@1.168.39': - resolution: {integrity: sha512-Uby0uo37JlQgUKHU0snDwH9nYiEeAjm9+7pUOFgycU/UszEILC2SO2DSCoNnCMuHKj9z+4URKzBYIP2exT9+wg==} + '@tanstack/react-start-client@1.168.38': + resolution: {integrity: sha512-O7eUDb3MndvogAqpZaZA1v9WAeqfDPOG/S7lG2RpvjfcsrJzaL3+Hkf3RFybvyaLUKLKzlxt9D5mVw81a7LW8w==} engines: {node: '>=22.12.0'} peerDependencies: react: 19.3.0 react-dom: 19.3.0 - '@tanstack/react-start-rsc@0.1.59': - resolution: {integrity: sha512-M2Lqxkk5C1zQncQs5paqX+/OLdOht51pg6D84YfLMrNsjhU/nzb6wd92MIja0mi5i23+L+fxLLjQYR2DObjBlw==} + '@tanstack/react-start-rsc@0.1.58': + resolution: {integrity: sha512-yZCNQAw72MjYkGx5A634OJyL3A2M3q8dCt1xlAbNaxxJoVIdc24pAAAOmm2r7Zmbpi8HEViNsdN3YR3Qc1hKzw==} engines: {node: '>=22.12.0'} peerDependencies: '@rspack/core': '>=2.0.0-0' @@ -8298,15 +8368,15 @@ packages: react-server-dom-rspack: optional: true - '@tanstack/react-start-server@1.167.46': - resolution: {integrity: sha512-K7z8xBOEdp8u5CZCxWrbS0tFtk5XZGMdhssLeZlRZKlLDbX7BKLdMtJ7th5sR6QVt9MTUAg3LcoqvJMPSFF+lg==} + '@tanstack/react-start-server@1.167.45': + resolution: {integrity: sha512-fPoDwcQrxw4EDlcrAimN5gQ7PmF2Vh0QsNoJB39GiXw0JUVH+MLxOIxCiYi9f6tBp71aGZzTYMQUbGagbzT68g==} engines: {node: '>=22.12.0'} peerDependencies: react: 19.3.0 react-dom: 19.3.0 - '@tanstack/react-start@1.168.60': - resolution: {integrity: sha512-WMOM0Xlh2lPbVlbjYM6uOxOpiaWRPvODm/dwkLsGKq5tjyHOHZmKckJgwlrZl7OQUqFlOZHYj8bKUTgoeg8cUQ==} + '@tanstack/react-start@1.168.59': + resolution: {integrity: sha512-PbV0B5SB+K8xnfmpXRfcTOT+LWCx4vnUQTLI/2Ucmkb95VhJfcmD4+xwcbfy1nyqKMlyC/Zcd2mMOzPGhwL2Rw==} engines: {node: '>=22.12.0'} peerDependencies: '@rsbuild/core': ^2.0.0 @@ -8328,20 +8398,20 @@ packages: react: 19.3.0 react-dom: 19.3.0 - '@tanstack/router-core@1.171.34': - resolution: {integrity: sha512-X+19pGuuLVPvvAvPYw9v/+ktRk5cCll8AtfpBpOhDRmVgUMrfqze0467xtq8YRVVs/mmo5rVtWn/7Tp32RDBKw==} + '@tanstack/router-core@1.171.33': + resolution: {integrity: sha512-TK31LyRZuVwwmWrv3oSCBN7OuM4R1qQZePRTiexFyqQUuCo3ERHZgzArosynwZKtYlEf4qbFCvbW7UUfvGiiZw==} engines: {node: '>=20.19'} - '@tanstack/router-generator@1.167.40': - resolution: {integrity: sha512-WkEueqEvsBBudzO0fx1nWfOmRNW8OAvBoVABTGls9bAWzjdC+KDGHvjCNrvn88ofs1F6gqL8aWnzKCbxW3dCyQ==} + '@tanstack/router-generator@1.167.39': + resolution: {integrity: sha512-9W4ErxITHoJH+1eySMm6RmTXzH/7drSUcVwynkco01A6Dg5gsnQsfxyojvLASLp5QxQe4gyubfBVupjBIPdPeg==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.168.42': - resolution: {integrity: sha512-vAR5WXJh/ErseEWQftDhsJcr2c++ucRQ6FbqN3ygMO44+ZxxRfW3sIpZmddHROVSmBk8jc6MrwjTPNjVhd62Vw==} + '@tanstack/router-plugin@1.168.41': + resolution: {integrity: sha512-D6t6kkivRb0PzNgtYqnGBar9gOXEN8YrU2A3D5gnWMHv33DDshmuhrhRMk/u9cvumoeH88P1R1eWbQ3ZegTgcA==} engines: {node: '>=20.19'} peerDependencies: '@rsbuild/core': '>=1.0.2 || ^2.0.0' - '@tanstack/react-router': ^1.170.41 + '@tanstack/react-router': ^1.170.40 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' vite-plugin-solid: ^2.11.10 || ^3.0.0-0 webpack: '>=5.92.0' @@ -8361,16 +8431,16 @@ packages: resolution: {integrity: sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==} engines: {node: '>=20.19'} - '@tanstack/start-client-core@1.170.34': - resolution: {integrity: sha512-LfwZSbau2MLLZ/uPAoO+NC9fK3J66c0wLhnNQqIa+Goqw7bXPjkHwlsjY7evlUz1AnyhhQ1Qab95v+VmP0TZ9A==} + '@tanstack/start-client-core@1.170.33': + resolution: {integrity: sha512-ZaIC7zjcf6dAMk2UtcqPkaUyY6vVdXIfEAPCerVaKE742Doo1u2r/eOtRkxzsmz36n7+pCFC+XJLeUngcRJrEA==} engines: {node: '>=22.12.0'} '@tanstack/start-fn-stubs@1.162.0': resolution: {integrity: sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.171.49': - resolution: {integrity: sha512-HPOb7LnMc71k38cd5uvMybPPUU/Q/56KvSqQzSWz7cps1ZDFXaxUM6EFaFHLCdgTWqGexNGDdRE+sUVPVBM4Xg==} + '@tanstack/start-plugin-core@1.171.48': + resolution: {integrity: sha512-t8vCkl669yLhX40PKxriB9TBqvHdIKjxjNjC2MaZg8WPWynVbYzC9nfjTIgI3BnfSUXMjd7qv36YDtHN9JD9Sg==} engines: {node: '>=22.12.0'} peerDependencies: '@rsbuild/core': ^2.0.0 @@ -8381,12 +8451,12 @@ packages: vite: optional: true - '@tanstack/start-server-core@1.169.39': - resolution: {integrity: sha512-pqqkxhXY46nuFSYt6TsMGyABiplUzHFHndKAlRRGC0fWDrJCH1koWxVMoxySa51ZETV79f/z73b5UZ1jfIkXwQ==} + '@tanstack/start-server-core@1.169.38': + resolution: {integrity: sha512-jQG/tcxi676ox2PgqM1wHeXMs6WGKeJ0AyE05s1FF4OMS8p6DGAH0HEoT5WzFxUCnbZ5HaIbtbWKrQkkLDjP8w==} engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.167.36': - resolution: {integrity: sha512-x+xCXK2GtFzY6NbXqAg8YpU6cwAjDfMeYSNzxfGzntpjbwBRHHrXeC/kYUnBBQCV7wK4kJ8vBBQGP0OyYTasUg==} + '@tanstack/start-storage-context@1.167.35': + resolution: {integrity: sha512-yY0lF1hHt0yWJHziYH71htEhmRlh467E7LKc8qJMgd/Rh1d4mjgKAEd9n39TyCGs9xFVNxIO0WrIvkSqfJOPYQ==} engines: {node: '>=22.12.0'} '@tanstack/store@0.11.2': @@ -9942,13 +10012,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - colord@2.10.0: resolution: {integrity: sha512-AidJptpBJmjTclAp9BkLwJi0T93fo5epJnbaZslpg6QVzpHjAiveF55mE9AcUJiGMqRHgMDY8soMsQtuNYMHfw==} @@ -11903,9 +11966,6 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -13060,6 +13120,27 @@ packages: react: 19.3.0 react-dom: 19.3.0 + next@16.3.6: + resolution: {integrity: sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: 19.3.0 + react-dom: 19.3.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + next@16.3.8: resolution: {integrity: sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==} engines: {node: '>=20.9.0'} @@ -13085,9 +13166,6 @@ packages: resolution: {integrity: sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==} engines: {node: '>=10'} - node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} - node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -14359,10 +14437,6 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sharp@0.32.6: - resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} - engines: {node: '>=14.15.0'} - sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -14431,9 +14505,6 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - simple-wcswidth@1.1.2: resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==} @@ -14844,9 +14915,6 @@ packages: tar-fs@2.1.5: resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} - tar-fs@3.1.3: - resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} - tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -15994,7 +16062,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-cognito-identity@3.1144.0': + '@aws-sdk/client-cognito-identity@3.1143.0': dependencies: '@aws-sdk/core': 3.978.1 '@aws-sdk/credential-provider-node': 3.972.84 @@ -16029,6 +16097,20 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@aws-sdk/client-s3@3.1143.0': + dependencies: + '@aws-sdk/checksums': 3.1001.1 + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/middleware-sdk-s3': 3.972.77 + '@aws-sdk/signature-v4-multi-region': 3.996.47 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/fetch-http-handler': 5.8.0 + '@smithy/node-http-handler': 4.12.1 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/client-s3@3.1144.0': dependencies: '@aws-sdk/checksums': 3.1001.1 @@ -16303,6 +16385,25 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 + '@aws-sdk/credential-providers@3.1143.0': + dependencies: + '@aws-sdk/core': 3.978.1 + '@aws-sdk/credential-provider-cognito-identity': 3.972.71 + '@aws-sdk/credential-provider-env': 3.972.72 + '@aws-sdk/credential-provider-http': 3.972.74 + '@aws-sdk/credential-provider-ini': 3.973.17 + '@aws-sdk/credential-provider-login': 3.972.79 + '@aws-sdk/credential-provider-node': 3.972.84 + '@aws-sdk/credential-provider-process': 3.972.72 + '@aws-sdk/credential-provider-sso': 3.973.16 + '@aws-sdk/credential-provider-web-identity': 3.972.78 + '@aws-sdk/nested-clients': 3.997.46 + '@aws-sdk/types': 3.974.6 + '@smithy/core': 3.35.1 + '@smithy/credential-provider-imds': 4.5.2 + '@smithy/types': 4.19.0 + tslib: 2.8.1 + '@aws-sdk/credential-providers@3.1144.0': dependencies: '@aws-sdk/core': 3.978.1 @@ -16321,6 +16422,7 @@ snapshots: '@smithy/credential-provider-imds': 4.5.2 '@smithy/types': 4.19.0 tslib: 2.8.1 + optional: true '@aws-sdk/dynamodb-codec@3.973.46': dependencies: @@ -16334,9 +16436,9 @@ snapshots: mnemonist: 0.38.3 tslib: 2.8.1 - '@aws-sdk/lib-storage@3.1144.0(@aws-sdk/client-s3@3.1144.0)': + '@aws-sdk/lib-storage@3.1143.0(@aws-sdk/client-s3@3.1143.0)': dependencies: - '@aws-sdk/client-s3': 3.1144.0 + '@aws-sdk/client-s3': 3.1143.0 '@smithy/core': 3.35.1 '@smithy/types': 4.19.0 buffer: 5.6.0 @@ -16424,7 +16526,7 @@ snapshots: '@smithy/types': 4.19.0 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.1144.0': + '@aws-sdk/s3-request-presigner@3.1143.0': dependencies: '@aws-sdk/core': 3.978.1 '@aws-sdk/signature-v4-multi-region': 3.996.47 @@ -19272,9 +19374,11 @@ snapshots: '@next/env@13.5.11': {} + '@next/env@16.3.6': {} + '@next/env@16.3.8': {} - '@next/eslint-plugin-next@15.5.27': + '@next/eslint-plugin-next@15.5.26': dependencies: fast-glob: 3.3.1 @@ -19285,27 +19389,51 @@ snapshots: transitivePeerDependencies: - eslint + '@next/swc-darwin-arm64@16.3.6': + optional: true + '@next/swc-darwin-arm64@16.3.8': optional: true + '@next/swc-darwin-x64@16.3.6': + optional: true + '@next/swc-darwin-x64@16.3.8': optional: true + '@next/swc-linux-arm64-gnu@16.3.6': + optional: true + '@next/swc-linux-arm64-gnu@16.3.8': optional: true + '@next/swc-linux-arm64-musl@16.3.6': + optional: true + '@next/swc-linux-arm64-musl@16.3.8': optional: true + '@next/swc-linux-x64-gnu@16.3.6': + optional: true + '@next/swc-linux-x64-gnu@16.3.8': optional: true + '@next/swc-linux-x64-musl@16.3.6': + optional: true + '@next/swc-linux-x64-musl@16.3.8': optional: true + '@next/swc-win32-arm64-msvc@16.3.6': + optional: true + '@next/swc-win32-arm64-msvc@16.3.8': optional: true + '@next/swc-win32-x64-msvc@16.3.6': + optional: true + '@next/swc-win32-x64-msvc@16.3.8': optional: true @@ -20004,7 +20132,7 @@ snapshots: '@parcel/watcher-win32-x64': 2.6.0 optional: true - '@payloadcms/figma@0.1.0-alpha.10(@payloadcms/plugin-cloud-storage@packages+plugin-cloud-storage)(@payloadcms/richtext-lexical@packages+richtext-lexical)(@payloadcms/translations@packages+translations)(@payloadcms/ui@packages+ui)(payload@packages+payload)(react@19.3.0)': + '@payloadcms/figma@0.1.0-alpha.8(@payloadcms/plugin-cloud-storage@packages+plugin-cloud-storage)(@payloadcms/richtext-lexical@packages+richtext-lexical)(@payloadcms/translations@packages+translations)(@payloadcms/ui@packages+ui)(payload@packages+payload)(react@19.3.0)': dependencies: '@clack/prompts': 0.11.0 '@cloudflare/workers-types': 5.20260716.1 @@ -20860,7 +20988,7 @@ snapshots: '@sentry/utils': 7.120.4 localforage: 1.10.0 - '@sentry/nextjs@8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/nextjs@8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.43.0 @@ -20871,7 +20999,7 @@ snapshots: '@sentry/opentelemetry': 8.55.2(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) '@sentry/react': 8.55.2(react@19.3.0) '@sentry/vercel-edge': 8.55.2 - '@sentry/webpack-plugin': 2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)) + '@sentry/webpack-plugin': 2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) chalk: 3.0.0 next: 16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1) resolve: 1.22.8 @@ -20887,7 +21015,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.8)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/nextjs@8.55.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.8)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.43.0 @@ -20898,7 +21026,7 @@ snapshots: '@sentry/opentelemetry': 8.55.2(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) '@sentry/react': 8.55.2(react@19.3.0) '@sentry/vercel-edge': 8.55.2 - '@sentry/webpack-plugin': 2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + '@sentry/webpack-plugin': 2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) chalk: 3.0.0 next: 16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@25.9.8)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4) resolve: 1.22.8 @@ -20914,7 +21042,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@9.47.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/nextjs@9.47.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(next@16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1))(react@19.3.0)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.43.0 @@ -20925,7 +21053,7 @@ snapshots: '@sentry/opentelemetry': 9.47.2(@opentelemetry/api@1.9.1)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.43.0) '@sentry/react': 9.47.2(react@19.3.0) '@sentry/vercel-edge': 9.47.2(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.1)) - '@sentry/webpack-plugin': 3.6.1(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)) + '@sentry/webpack-plugin': 3.6.1(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) chalk: 3.0.0 next: 16.3.8(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.105.1) resolve: 1.22.8 @@ -21109,32 +21237,32 @@ snapshots: - '@opentelemetry/core' - '@opentelemetry/sdk-trace-base' - '@sentry/webpack-plugin@2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/webpack-plugin@2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@sentry/bundler-plugin-core': 2.22.7 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) transitivePeerDependencies: - encoding - supports-color - '@sentry/webpack-plugin@2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/webpack-plugin@2.22.7(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: '@sentry/bundler-plugin-core': 2.22.7 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)) transitivePeerDependencies: - encoding - supports-color - '@sentry/webpack-plugin@3.6.1(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6))': + '@sentry/webpack-plugin@3.6.1(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@sentry/bundler-plugin-core': 3.6.1 unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) transitivePeerDependencies: - encoding - supports-color @@ -21704,32 +21832,32 @@ snapshots: '@tanstack/history@1.162.4': {} - '@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/history': 1.162.4 '@tanstack/react-store': 0.11.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 + '@tanstack/router-core': 1.171.33 isbot: 5.2.2 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-client@1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-start-client@1.168.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 - '@tanstack/start-client-core': 1.170.34 + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.33 + '@tanstack/start-client-core': 1.170.33 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - '@tanstack/react-start-rsc@0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': + '@tanstack/react-start-rsc@0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.33 '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 + '@tanstack/start-client-core': 1.170.33 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) - '@tanstack/start-storage-context': 1.167.36 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/start-storage-context': 1.167.35 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21749,15 +21877,15 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start-rsc@0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/react-start-rsc@0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.33 '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 + '@tanstack/start-client-core': 1.170.33 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/start-storage-context': 1.167.36 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/start-storage-context': 1.167.35 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21777,15 +21905,15 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start-rsc@0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/react-start-rsc@0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.33 '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 + '@tanstack/start-client-core': 1.170.33 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/start-storage-context': 1.167.36 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) + '@tanstack/start-storage-context': 1.167.35 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21805,26 +21933,26 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start-server@1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@tanstack/react-start-server@1.167.45(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/router-core': 1.171.34 - '@tanstack/start-server-core': 1.169.39 + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-core': 1.171.33 + '@tanstack/start-server-core': 1.169.38 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': + '@tanstack/react-start@1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-client': 1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) - '@tanstack/react-start-server': 1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/react-start-server': 1.167.45(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-client-core': 1.170.33 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/start-server-core': 1.169.38 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21845,16 +21973,16 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start@1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/react-start@1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-client': 1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/react-start-server': 1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/react-start-server': 1.167.45(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-client-core': 1.170.33 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/start-server-core': 1.169.38 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21875,16 +22003,16 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/react-start@1.168.60(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/react-start@1.168.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-client': 1.168.39(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@tanstack/react-start-rsc': 0.1.59(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/react-start-server': 1.167.46(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.168.38(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-rsc': 0.1.58(@vitejs/plugin-rsc@0.5.35(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1)))(esbuild@0.28.2)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) + '@tanstack/react-start-server': 1.167.45(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-client-core': 1.170.34 - '@tanstack/start-plugin-core': 1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-client-core': 1.170.33 + '@tanstack/start-plugin-core': 1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) + '@tanstack/start-server-core': 1.169.38 pathe: 2.0.3 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) @@ -21912,17 +22040,17 @@ snapshots: react-dom: 19.3.0(react@19.3.0) use-sync-external-store: 1.7.0(react@19.3.0) - '@tanstack/router-core@1.171.34': + '@tanstack/router-core@1.171.33': dependencies: '@tanstack/history': 1.162.4 cookie-es: 3.1.1 seroval: 1.6.8 seroval-plugins: 1.6.8(seroval@1.6.8) - '@tanstack/router-generator@1.167.40': + '@tanstack/router-generator@1.167.39': dependencies: '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.34 + '@tanstack/router-core': 1.171.33 '@tanstack/router-utils': 1.162.3 '@tanstack/virtual-file-routes': 1.162.0 jiti: 2.7.0 @@ -21932,19 +22060,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': + '@tanstack/router-plugin@1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 '@tanstack/router-utils': 1.162.3 chokidar: 5.0.0 unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) zod: 4.6.5 optionalDependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) vite: 8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) transitivePeerDependencies: @@ -21957,21 +22085,21 @@ snapshots: - supports-color - unloader - '@tanstack/router-plugin@1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/router-plugin@1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 '@tanstack/router-utils': 1.162.3 chokidar: 5.0.0 - unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) zod: 4.6.5 optionalDependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) vite: 8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -21982,21 +22110,21 @@ snapshots: - supports-color - unloader - '@tanstack/router-plugin@1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/router-plugin@1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: '@babel/core': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 '@tanstack/router-utils': 1.162.3 chokidar: 5.0.0 - unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) zod: 4.6.5 optionalDependencies: - '@tanstack/react-router': 1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-router': 1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0) vite: 8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -22020,26 +22148,26 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.170.34': + '@tanstack/start-client-core@1.170.33': dependencies: - '@tanstack/router-core': 1.171.34 + '@tanstack/router-core': 1.171.33 '@tanstack/start-fn-stubs': 1.162.0 - '@tanstack/start-storage-context': 1.167.36 + '@tanstack/start-storage-context': 1.167.35 seroval: 1.6.8 '@tanstack/start-fn-stubs@1.162.0': {} - '@tanstack/start-plugin-core@1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': + '@tanstack/start-plugin-core@1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 - '@tanstack/router-plugin': 1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 + '@tanstack/router-plugin': 1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-server-core': 1.169.38 exsolve: 1.1.1 lightningcss: 1.33.0 pathe: 2.0.3 @@ -22068,17 +22196,17 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-plugin-core@1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/start-plugin-core@1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)))': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 - '@tanstack/router-plugin': 1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 + '@tanstack/router-plugin': 1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-server-core': 1.169.38 exsolve: 1.1.1 lightningcss: 1.33.0 pathe: 2.0.3 @@ -22107,17 +22235,17 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-plugin-core@1.171.49(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6))': + '@tanstack/start-plugin-core@1.171.48(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)))': dependencies: '@babel/code-frame': 7.29.7 '@babel/core': 7.29.7 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 - '@tanstack/router-core': 1.171.34 - '@tanstack/router-generator': 1.167.40 - '@tanstack/router-plugin': 1.168.42(@tanstack/react-router@1.170.41(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + '@tanstack/router-core': 1.171.33 + '@tanstack/router-generator': 1.167.39 + '@tanstack/router-plugin': 1.168.41(@tanstack/react-router@1.170.40(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) '@tanstack/router-utils': 1.162.3 - '@tanstack/start-server-core': 1.169.39 + '@tanstack/start-server-core': 1.169.38 exsolve: 1.1.1 lightningcss: 1.33.0 pathe: 2.0.3 @@ -22146,21 +22274,21 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.169.39': + '@tanstack/start-server-core@1.169.38': dependencies: '@tanstack/history': 1.162.4 - '@tanstack/router-core': 1.171.34 - '@tanstack/start-client-core': 1.170.34 - '@tanstack/start-storage-context': 1.167.36 + '@tanstack/router-core': 1.171.33 + '@tanstack/start-client-core': 1.170.33 + '@tanstack/start-storage-context': 1.167.35 fetchdts: 0.1.7 h3-v2: h3@2.0.1-rc.20 seroval: 1.6.8 transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.167.36': + '@tanstack/start-storage-context@1.167.35': dependencies: - '@tanstack/router-core': 1.171.34 + '@tanstack/router-core': 1.171.33 '@tanstack/store@0.11.2': {} @@ -24101,16 +24229,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colord@2.10.0: {} colorette@2.0.20: {} @@ -26424,8 +26542,6 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.4: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -27427,45 +27543,45 @@ snapshots: minimist@1.2.8: {} - minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)): + minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.5.0 terser: 5.51.2 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) optionalDependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.23) esbuild: 0.25.12 postcss: 8.5.28 - sharp: 0.32.6 + sharp: 0.35.4(@types/node@24.12.3) - minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)): + minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.5.0 terser: 5.51.2 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) optionalDependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.23) esbuild: 0.28.2 postcss: 8.5.28 - sharp: 0.32.6 + sharp: 0.35.4(@types/node@24.12.3) + optional: true - minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))): + minimizer-webpack-plugin@5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.5.0 terser: 5.51.2 - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)) optionalDependencies: '@swc/core': 1.15.3(@swc/helpers@0.5.23) esbuild: 0.28.2 postcss: 8.5.28 - sharp: 0.35.4(@types/node@24.12.3) - optional: true + sharp: 0.35.4(@types/node@25.9.8) minipass@4.2.8: {} @@ -27640,9 +27756,9 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - next@16.3.8(@babel/core@7.27.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4): + next@16.3.6(@babel/core@7.27.3)(@opentelemetry/api@1.9.1)(@playwright/test@1.63.0)(@types/node@24.12.3)(babel-plugin-react-compiler@19.1.0-rc.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(sass@1.77.4): dependencies: - '@next/env': 16.3.8 + '@next/env': 16.3.6 '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.11.26 caniuse-lite: 1.0.30001814 @@ -27651,14 +27767,14 @@ snapshots: react-dom: 19.3.0(react@19.3.0) styled-jsx: 5.1.6(@babel/core@7.27.3)(react@19.3.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.3.8 - '@next/swc-darwin-x64': 16.3.8 - '@next/swc-linux-arm64-gnu': 16.3.8 - '@next/swc-linux-arm64-musl': 16.3.8 - '@next/swc-linux-x64-gnu': 16.3.8 - '@next/swc-linux-x64-musl': 16.3.8 - '@next/swc-win32-arm64-msvc': 16.3.8 - '@next/swc-win32-x64-msvc': 16.3.8 + '@next/swc-darwin-arm64': 16.3.6 + '@next/swc-darwin-x64': 16.3.6 + '@next/swc-linux-arm64-gnu': 16.3.6 + '@next/swc-linux-arm64-musl': 16.3.6 + '@next/swc-linux-x64-gnu': 16.3.6 + '@next/swc-linux-x64-musl': 16.3.6 + '@next/swc-win32-arm64-msvc': 16.3.6 + '@next/swc-win32-x64-msvc': 16.3.6 '@opentelemetry/api': 1.9.1 '@playwright/test': 1.63.0 babel-plugin-react-compiler: 19.1.0-rc.3 @@ -27758,8 +27874,6 @@ snapshots: dependencies: semver: 7.8.5 - node-addon-api@6.1.0: {} - node-addon-api@7.1.1: optional: true @@ -29074,21 +29188,6 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.32.6: - dependencies: - color: 4.2.3 - detect-libc: 2.1.2 - node-addon-api: 6.1.0 - prebuild-install: 7.1.3 - semver: 7.8.5 - simple-get: 4.0.1 - tar-fs: 3.1.3 - tunnel-agent: 0.6.0 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - sharp@0.34.5: dependencies: '@img/colour': 1.1.0 @@ -29185,7 +29284,6 @@ snapshots: '@img/sharp-win32-ia32': 0.35.4 '@img/sharp-win32-x64': 0.35.4 '@types/node': 25.9.8 - optional: true shebang-command@2.0.0: dependencies: @@ -29256,10 +29354,6 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - simple-wcswidth@1.1.2: {} sirv@2.0.4: @@ -29723,18 +29817,6 @@ snapshots: pump: 3.0.4 tar-stream: 2.2.0 - tar-fs@3.1.3: - dependencies: - pump: 3.0.4 - tar-stream: 3.2.1 - optionalDependencies: - bare-fs: 4.8.2 - bare-path: 3.1.2 - transitivePeerDependencies: - - bare-abort-controller - - bare-buffer - - react-native-b4a - tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -30165,7 +30247,7 @@ snapshots: vite: 8.3.0(@types/node@24.12.3)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) - unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)): + unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -30175,9 +30257,9 @@ snapshots: rolldown: 1.2.12 rollup: 4.59.0 vite: 8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.105.1)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)) - unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)): + unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.12)(rollup@4.59.0)(vite@8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -30187,7 +30269,7 @@ snapshots: rolldown: 1.2.12 rollup: 4.59.0 vite: 8.3.0(@types/node@25.9.8)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.1)(sass@1.77.4)(terser@5.51.2)(tsx@4.23.15)(yaml@2.9.1) - webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6) + webpack: 5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)) unrs-resolver@1.12.2: dependencies: @@ -30577,7 +30659,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6): + webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -30591,7 +30673,7 @@ snapshots: events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.32.6)) + minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.25.12)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) schema-utils: 4.5.0 tapable: 2.3.3 watchpack: 2.5.2 @@ -30614,7 +30696,7 @@ snapshots: - svgo - uglify-js - webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6): + webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -30628,7 +30710,7 @@ snapshots: events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.32.6)) + minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) schema-utils: 4.5.0 tapable: 2.3.3 watchpack: 2.5.2 @@ -30650,8 +30732,9 @@ snapshots: - sharp - svgo - uglify-js + optional: true - webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3)): + webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8)): dependencies: '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 @@ -30665,7 +30748,7 @@ snapshots: events: 3.3.0 graceful-fs: 4.2.11 mime-db: 1.54.0 - minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@24.12.3))) + minimizer-webpack-plugin: 5.12.0(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))(webpack@5.111.1(@swc/core@1.15.3(@swc/helpers@0.5.23))(esbuild@0.28.2)(postcss@8.5.28)(sharp@0.35.4(@types/node@25.9.8))) schema-utils: 4.5.0 tapable: 2.3.3 watchpack: 2.5.2 @@ -30687,7 +30770,6 @@ snapshots: - sharp - svgo - uglify-js - optional: true whatwg-mimetype@3.0.0: {} diff --git a/tools/releaser/src/lib/publishList.spec.ts b/tools/releaser/src/lib/publishList.spec.ts index 94ff103f247..df40e96cced 100644 --- a/tools/releaser/src/lib/publishList.spec.ts +++ b/tools/releaser/src/lib/publishList.spec.ts @@ -1,5 +1,6 @@ -import { PROJECT_ROOT } from '@tools/constants' +import { PACKAGES_DIR, PROJECT_ROOT } from '@tools/constants' import { execSync } from 'child_process' +import fs from 'fs' import path from 'path' import { describe, expect, it } from 'vitest' @@ -15,6 +16,34 @@ type TurboTask = { const shortNameFromTaskId = (taskId: string, byPackage: Map): string | undefined => byPackage.get(taskId.replace(/#.*$/, '')) +/** Public packages released on their own version line, outside the monorepo release. */ +const independentlyReleasedPackages = new Set(['eslint-config', 'eslint-plugin']) + +describe('packagePublishList completeness', () => { + it('should list every public package in packages/', () => { + const publicPackages = fs + .readdirSync(PACKAGES_DIR) + .filter((directory) => { + const packageJsonPath = path.join(PACKAGES_DIR, directory, 'package.json') + + if (!fs.existsSync(packageJsonPath)) { + return false + } + + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { + private?: boolean + } + + return !packageJson.private + }) + .filter((directory) => !independentlyReleasedPackages.has(directory)) + + const publishSet = new Set(packagePublishList) + + expect(publicPackages.filter((directory) => !publishSet.has(directory))).toEqual([]) + }) +}) + describe('packagePublishList topological order (turbo-derived)', () => { it('should list every build dependency before its dependents', async () => { const raw = execSync('pnpm turbo run build --dry-run=json', { diff --git a/tools/releaser/src/lib/publishList.ts b/tools/releaser/src/lib/publishList.ts index fe34a819d34..cf68f43efd7 100644 --- a/tools/releaser/src/lib/publishList.ts +++ b/tools/releaser/src/lib/publishList.ts @@ -9,6 +9,7 @@ export const packagePublishList = [ // Foundation 'translations', 'payload', + 'transformer-sharp', // Core UI/API 'ui', diff --git a/tsconfig.json b/tsconfig.json index a1c0508b142..9c70448ae6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -77,6 +77,9 @@ { "path": "./packages/richtext-lexical" }, + { + "path": "./packages/transformer-sharp" + }, { "path": "./packages/translations" },