payload 3.88.0 → 3.89.0-internal.95c9cba

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/collections/operations/create.d.ts.map +1 -1
  2. package/dist/collections/operations/create.js +10 -0
  3. package/dist/collections/operations/create.js.map +1 -1
  4. package/dist/collections/operations/update.d.ts.map +1 -1
  5. package/dist/collections/operations/update.js +10 -0
  6. package/dist/collections/operations/update.js.map +1 -1
  7. package/dist/collections/operations/updateByID.d.ts.map +1 -1
  8. package/dist/collections/operations/updateByID.js +10 -0
  9. package/dist/collections/operations/updateByID.js.map +1 -1
  10. package/dist/index.bundled.d.ts +10 -0
  11. package/dist/uploads/cropImage.d.ts.map +1 -1
  12. package/dist/uploads/cropImage.js +2 -5
  13. package/dist/uploads/cropImage.js.map +1 -1
  14. package/dist/uploads/generateFileData.d.ts +8 -0
  15. package/dist/uploads/generateFileData.d.ts.map +1 -1
  16. package/dist/uploads/generateFileData.js +55 -29
  17. package/dist/uploads/generateFileData.js.map +1 -1
  18. package/dist/uploads/generateFileData.spec.js +144 -0
  19. package/dist/uploads/generateFileData.spec.js.map +1 -0
  20. package/dist/uploads/generateFileData.tempFileBuffering.spec.js +81 -0
  21. package/dist/uploads/generateFileData.tempFileBuffering.spec.js.map +1 -0
  22. package/dist/uploads/getFileContentRequirement.d.ts +9 -0
  23. package/dist/uploads/getFileContentRequirement.d.ts.map +1 -0
  24. package/dist/uploads/getFileContentRequirement.js +24 -0
  25. package/dist/uploads/getFileContentRequirement.js.map +1 -0
  26. package/dist/uploads/getFileContentRequirement.spec.js +95 -0
  27. package/dist/uploads/getFileContentRequirement.spec.js.map +1 -0
  28. package/dist/uploads/getFileFromClientUpload.d.ts +20 -0
  29. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -0
  30. package/dist/uploads/getFileFromClientUpload.js +199 -0
  31. package/dist/uploads/getFileFromClientUpload.js.map +1 -0
  32. package/dist/uploads/getFileFromClientUpload.spec.js +528 -0
  33. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -0
  34. package/dist/uploads/image-resizing/createImageSizes.d.ts.map +1 -1
  35. package/dist/uploads/image-resizing/createImageSizes.js +2 -5
  36. package/dist/uploads/image-resizing/createImageSizes.js.map +1 -1
  37. package/dist/uploads/isAnimatedImage.d.ts +6 -0
  38. package/dist/uploads/isAnimatedImage.d.ts.map +1 -0
  39. package/dist/uploads/isAnimatedImage.js +19 -0
  40. package/dist/uploads/isAnimatedImage.js.map +1 -0
  41. package/dist/uploads/isAnimatedImage.spec.js +19 -0
  42. package/dist/uploads/isAnimatedImage.spec.js.map +1 -0
  43. package/dist/uploads/types.d.ts +10 -0
  44. package/dist/uploads/types.d.ts.map +1 -1
  45. package/dist/uploads/types.js.map +1 -1
  46. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  47. package/dist/uploads/unlinkTempFiles.js +12 -2
  48. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  49. package/dist/uploads/unlinkTempFiles.spec.js +157 -0
  50. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -0
  51. package/dist/uploads/uploadFiles.d.ts.map +1 -1
  52. package/dist/uploads/uploadFiles.js +7 -2
  53. package/dist/uploads/uploadFiles.js.map +1 -1
  54. package/dist/uploads/uploadFiles.spec.js +44 -0
  55. package/dist/uploads/uploadFiles.spec.js.map +1 -0
  56. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  57. package/dist/utilities/addDataAndFileToRequest.js +7 -46
  58. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  59. package/dist/utilities/addDataAndFileToRequest.spec.js +131 -1
  60. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  61. package/package.json +2 -2
@@ -11,6 +11,7 @@ import { getFileByPath } from './getFileByPath.js';
11
11
  import { getImageSize } from './getImageSize.js';
12
12
  import { getSafeFileName } from './getSafeFilename.js';
13
13
  import { createImageSizes } from './image-resizing/createImageSizes.js';
14
+ import { isAnimatedImage } from './isAnimatedImage.js';
14
15
  import { isImage } from './isImage.js';
15
16
  import { optionallyAppendMetadata } from './optionallyAppendMetadata.js';
16
17
  const shouldReupload = (uploadEdits, fileData)=>{
@@ -31,7 +32,14 @@ const shouldReupload = (uploadEdits, fileData)=>{
31
32
  }
32
33
  return false;
33
34
  };
34
- export const generateFileData = async ({ collection: { config: collectionConfig }, data, draft, isDuplicating, operation, originalDoc, overwriteExistingFiles, req, throwOnMissingFile })=>{
35
+ /**
36
+ * Builds the document's file metadata and the list of files to write to disk.
37
+ *
38
+ * A file uploaded with `useTempFiles` enabled arrives as a temp file path instead of an
39
+ * in-memory buffer, and that temp file can be far larger than server memory allows. To avoid
40
+ * loading it into memory unnecessarily, this skips reading the temp file entirely when local
41
+ * storage is disabled, and copies it straight to its destination when local storage is enabled.
42
+ */ export const generateFileData = async ({ collection: { config: collectionConfig }, data, draft, isDuplicating, operation, originalDoc, overwriteExistingFiles, req, throwOnMissingFile })=>{
35
43
  if (!collectionConfig.upload) {
36
44
  return {
37
45
  data,
@@ -104,11 +112,7 @@ export const generateFileData = async ({ collection: { config: collectionConfig
104
112
  let newData = incomingFileData;
105
113
  const filesToSave = [];
106
114
  const fileData = {};
107
- const fileIsAnimatedType = [
108
- 'image/avif',
109
- 'image/gif',
110
- 'image/webp'
111
- ].includes(file.mimetype);
115
+ const fileIsAnimatedType = isAnimatedImage(file.mimetype);
112
116
  const cropData = typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined;
113
117
  try {
114
118
  const fileSupportsResize = canResizeImage(file.mimetype);
@@ -118,7 +122,9 @@ export const generateFileData = async ({ collection: { config: collectionConfig
118
122
  let fileBuffer;
119
123
  let ext;
120
124
  let mime;
121
- const fileHasAdjustments = fileSupportsResize && Boolean(resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath);
125
+ // Depends only on configured resize/format/trim options, not on whether the bytes are on
126
+ // disk or in memory.
127
+ const fileHasAdjustments = fileSupportsResize && Boolean(resizeOptions || formatOptions || trimOptions || constructorOptions);
122
128
  const sharpOptions = {
123
129
  ...constructorOptions
124
130
  };
@@ -263,30 +269,50 @@ export const generateFileData = async ({ collection: { config: collectionConfig
263
269
  }
264
270
  } else {
265
271
  // For non-image files with useTempFiles, read the buffer from the temp file
266
- // since file.data is empty when using temp files
267
- let bufferToSave;
268
- if (fileBuffer?.data) {
269
- bufferToSave = fileBuffer.data;
270
- } else if (file.tempFilePath) {
271
- bufferToSave = await fs.readFile(file.tempFilePath);
272
+ // since file.data is empty when using temp files.
273
+ //
274
+ // When local storage is disabled, filesToSave is never written to disk (create/update
275
+ // skip uploadFiles for it), and an unmodified temp file's bytes on disk are already
276
+ // correct, so there's nothing to read into memory or write back out. Skipping this avoids
277
+ // buffering the entire file just to discard or rewrite it unchanged - a tempFilePath can
278
+ // point at a file far larger than server memory allows.
279
+ const skipTempFileBuffer = disableLocalStorage && Boolean(file.tempFilePath) && !fileBuffer?.data;
280
+ // When local storage is enabled and the temp file itself is unmodified, copy it straight
281
+ // to its destination instead of reading it into memory first - a tempFilePath can point at
282
+ // a file far larger than server memory allows.
283
+ const shouldCopyFromTempFile = !fileBuffer?.data && Boolean(file.tempFilePath) && !disableLocalStorage;
284
+ if (shouldCopyFromTempFile) {
285
+ filesToSave.push({
286
+ path: `${staticPath}/${fsSafeName}`,
287
+ sourcePath: file.tempFilePath
288
+ });
272
289
  } else {
273
- bufferToSave = file.data;
274
- }
275
- filesToSave.push({
276
- buffer: bufferToSave,
277
- path: `${staticPath}/${fsSafeName}`
278
- });
279
- // If using temp files and the image is being resized, write the file to the temp path
280
- if (fileBuffer?.data || bufferToSave.length > 0) {
281
- if (file.tempFilePath) {
282
- await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave); // write fileBuffer to the temp path
290
+ let bufferToSave;
291
+ if (fileBuffer?.data) {
292
+ bufferToSave = fileBuffer.data;
293
+ } else if (file.tempFilePath) {
294
+ bufferToSave = skipTempFileBuffer ? Buffer.alloc(0) : await fs.readFile(file.tempFilePath);
283
295
  } else {
284
- // Assign the _possibly modified_ file to the request object
285
- req.file = {
286
- ...file,
287
- data: fileBuffer?.data || bufferToSave,
288
- size: fileBuffer?.info.size
289
- };
296
+ bufferToSave = file.data;
297
+ }
298
+ if (!skipTempFileBuffer) {
299
+ filesToSave.push({
300
+ buffer: bufferToSave,
301
+ path: `${staticPath}/${fsSafeName}`
302
+ });
303
+ // If using temp files and the image is being resized, write the file to the temp path
304
+ if (fileBuffer?.data || bufferToSave.length > 0) {
305
+ if (file.tempFilePath) {
306
+ await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave); // write fileBuffer to the temp path
307
+ } else {
308
+ // Assign the _possibly modified_ file to the request object
309
+ req.file = {
310
+ ...file,
311
+ data: fileBuffer?.data || bufferToSave,
312
+ size: fileBuffer?.info.size
313
+ };
314
+ }
315
+ }
290
316
  }
291
317
  }
292
318
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/generateFileData.ts"],"sourcesContent":["import type { OutputInfo, Sharp, SharpOptions } from 'sharp'\n\nimport { fileTypeFromBuffer } from 'file-type'\nimport fs from 'fs/promises'\nimport sanitize from 'sanitize-filename'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { Document, PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'\n\nimport { FileRetrievalError, FileUploadError, Forbidden, MissingFile } from '../errors/index.js'\nimport { isNumber } from '../utilities/isNumber.js'\nimport { canResizeImage } from './canResizeImage.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\nimport { cropImage } from './cropImage.js'\nimport { getExternalFile } from './getExternalFile.js'\nimport { getFileByPath } from './getFileByPath.js'\nimport { getImageSize } from './getImageSize.js'\nimport { getSafeFileName } from './getSafeFilename.js'\nimport { createImageSizes } from './image-resizing/createImageSizes.js'\nimport { isImage } from './isImage.js'\nimport { optionallyAppendMetadata } from './optionallyAppendMetadata.js'\ntype Args<T> = {\n collection: Collection\n config: SanitizedConfig\n data: T\n draft?: boolean\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc?: T\n overwriteExistingFiles?: boolean\n req: PayloadRequest\n throwOnMissingFile?: boolean\n}\n\ntype Result<T> = Promise<{\n data: T\n files: FileToSave[]\n}>\n\nconst shouldReupload = (\n uploadEdits: UploadEdits,\n fileData: Record<string, unknown> | undefined,\n) => {\n if (!fileData) {\n return false\n }\n\n if (uploadEdits.crop || uploadEdits.heightInPixels || uploadEdits.widthInPixels) {\n return true\n }\n\n // Since uploadEdits always has focalPoint, compare to the value in the data if it was changed\n if (uploadEdits.focalPoint) {\n const incomingFocalX = uploadEdits.focalPoint.x\n const incomingFocalY = uploadEdits.focalPoint.y\n\n const currentFocalX = 'focalX' in fileData && fileData.focalX\n const currentFocalY = 'focalY' in fileData && fileData.focalY\n\n const isEqual = incomingFocalX === currentFocalX && incomingFocalY === currentFocalY\n return !isEqual\n }\n\n return false\n}\n\nexport const generateFileData = async <T>({\n collection: { config: collectionConfig },\n data,\n draft,\n isDuplicating,\n operation,\n originalDoc,\n overwriteExistingFiles,\n req,\n throwOnMissingFile,\n}: Args<T>): Result<T> => {\n if (!collectionConfig.upload) {\n return {\n data,\n files: [],\n }\n }\n\n const { serverURL, sharp } = req.payload.config\n\n let file = isDuplicating ? undefined : req.file\n\n const uploadEdits = parseUploadEditsFromReqOrIncomingData({\n data,\n isDuplicating,\n operation,\n originalDoc,\n req,\n })\n\n const {\n constructorOptions,\n disableLocalStorage,\n focalPoint: focalPointEnabled = true,\n formatOptions,\n imageSizes,\n resizeOptions,\n staticDir,\n trimOptions,\n withMetadata,\n } = collectionConfig.upload\n\n const staticPath = staticDir\n\n const incomingFileData: Document = isDuplicating ? originalDoc : data\n let isLocalFile = false\n\n if (\n !file &&\n (isDuplicating || shouldReupload(uploadEdits, incomingFileData as Record<string, unknown>))\n ) {\n const { filename, url } = incomingFileData as unknown as FileData\n if (filename && (filename.includes('../') || filename.includes('..\\\\'))) {\n throw new Forbidden(req.t)\n }\n\n if ((serverURL && url?.startsWith(serverURL)) || url?.startsWith('/')) {\n isLocalFile = true\n }\n\n try {\n if (!disableLocalStorage && isLocalFile) {\n // File is stored locally\n const filePath = `${staticPath}/${filename}`\n const response = await getFileByPath(filePath)\n file = response\n overwriteExistingFiles = true\n } else if (filename && url) {\n // File is remote\n file = await getExternalFile({\n data: incomingFileData as unknown as FileData,\n req,\n uploadConfig: collectionConfig.upload,\n })\n overwriteExistingFiles = true\n }\n } catch (err: unknown) {\n throw new FileRetrievalError(req.t, err instanceof Error ? err.message : undefined)\n }\n }\n\n if (isDuplicating) {\n overwriteExistingFiles = false\n }\n\n if (!file) {\n if (throwOnMissingFile) {\n throw new MissingFile(req.t)\n }\n\n return {\n data: incomingFileData!,\n files: [],\n }\n }\n\n await checkFileRestrictions({\n collection: collectionConfig,\n file,\n req,\n })\n\n if (!disableLocalStorage) {\n await fs.mkdir(staticPath!, { recursive: true })\n }\n\n let newData = incomingFileData as T\n const filesToSave: FileToSave[] = []\n const fileData: Partial<FileData> = {}\n const fileIsAnimatedType = ['image/avif', 'image/gif', 'image/webp'].includes(file.mimetype)\n const cropData =\n typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined\n\n try {\n const fileSupportsResize = canResizeImage(file.mimetype)\n let fsSafeName: string\n let sharpFile: Sharp | undefined\n let dimensions: ProbedImageSize | undefined\n let fileBuffer!: { data: Buffer; info: OutputInfo }\n let ext\n let mime: string\n const fileHasAdjustments =\n fileSupportsResize &&\n Boolean(\n resizeOptions || formatOptions || trimOptions || constructorOptions || file.tempFilePath,\n )\n\n const sharpOptions: SharpOptions = { ...constructorOptions }\n\n if (fileIsAnimatedType) {\n sharpOptions.animated = true\n }\n\n if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {\n if (file.tempFilePath) {\n sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n } else {\n sharpFile = sharp(file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n }\n\n if (fileHasAdjustments) {\n if (resizeOptions) {\n sharpFile = sharpFile.resize(resizeOptions)\n }\n if (formatOptions) {\n sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)\n }\n if (trimOptions) {\n sharpFile = sharpFile.trim(trimOptions)\n }\n }\n }\n\n if (fileSupportsResize || isImage(file.mimetype)) {\n dimensions = await getImageSize({ file, sharp })\n fileData.width = dimensions.width\n fileData.height = dimensions.height\n }\n\n if (sharpFile) {\n const metadata = await sharpFile.metadata()\n sharpFile = await optionallyAppendMetadata({\n req,\n sharpFile,\n withMetadata: withMetadata!,\n })\n fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })\n ;({ ext, mime } = (await fileTypeFromBuffer(fileBuffer.data))!) // This is getting an incorrect gif height back.\n fileData.width = fileBuffer.info.width\n fileData.height = fileBuffer.info.height\n fileData.filesize = fileBuffer.info.size\n\n // Animated GIFs + WebP aggregate the height from every frame, so we need to use divide by number of pages\n if (metadata.pages) {\n fileData.height = fileBuffer.info.height / metadata.pages\n fileData.filesize = fileBuffer.data.length\n }\n } else {\n mime = file.mimetype\n fileData.filesize = file.size\n\n if (file.name.includes('.')) {\n ext = file.name.split('.').pop()?.split('?')[0]\n } else {\n ext = ''\n }\n }\n\n // Adjust SVG mime type. fromBuffer modifies it.\n if (mime === 'application/xml' && ext === 'svg') {\n mime = 'image/svg+xml'\n }\n fileData.mimeType = mime\n\n const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name)\n fsSafeName = `${baseFilename}${ext ? `.${ext}` : ''}`\n\n if (!overwriteExistingFiles) {\n // Extract prefix if present (added by plugin-cloud-storage)\n const prefix = (data as Record<string, unknown>)?.prefix as string | undefined\n fsSafeName = await getSafeFileName({\n collectionSlug: collectionConfig.slug,\n desiredFilename: fsSafeName,\n prefix,\n req,\n staticPath: staticPath!,\n })\n }\n\n fileData.filename = fsSafeName\n\n let fileForResize = file\n\n if (cropData && sharp) {\n const { data: croppedImage, info } = await cropImage({\n cropData,\n dimensions: dimensions!,\n file,\n heightInPixels: uploadEdits.heightInPixels!,\n req,\n sharp,\n widthInPixels: uploadEdits.widthInPixels!,\n withMetadata,\n })\n\n // Apply resize after cropping to ensure it conforms to resizeOptions\n if (resizeOptions && !resizeOptions.withoutEnlargement) {\n const resizedAfterCrop = await sharp(croppedImage)\n .resize({\n fit: resizeOptions?.fit || 'cover',\n height: resizeOptions?.height,\n position: resizeOptions?.position || 'center',\n width: resizeOptions?.width,\n })\n .toBuffer({ resolveWithObject: true })\n\n filesToSave.push({\n buffer: resizedAfterCrop.data,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...fileForResize,\n data: resizedAfterCrop.data,\n size: resizedAfterCrop.info.size,\n }\n\n fileData.width = resizedAfterCrop.info.width\n fileData.height = resizedAfterCrop.info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages\n ? resizedAfterCrop.info.height / metadata.pages\n : resizedAfterCrop.info.height\n }\n fileData.filesize = resizedAfterCrop.info.size\n } else {\n // If resizeOptions is not present, just save the cropped image\n filesToSave.push({\n buffer: croppedImage,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...file,\n data: croppedImage,\n size: info.size,\n }\n\n fileData.width = info.width\n fileData.height = info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages ? info.height / metadata.pages : info.height\n }\n fileData.filesize = info.size\n }\n\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path\n } else {\n req.file = fileForResize\n }\n } else {\n // For non-image files with useTempFiles, read the buffer from the temp file\n // since file.data is empty when using temp files\n let bufferToSave: Buffer\n if (fileBuffer?.data) {\n bufferToSave = fileBuffer.data\n } else if (file.tempFilePath) {\n bufferToSave = await fs.readFile(file.tempFilePath)\n } else {\n bufferToSave = file.data\n }\n\n filesToSave.push({\n buffer: bufferToSave,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n // If using temp files and the image is being resized, write the file to the temp path\n if (fileBuffer?.data || bufferToSave.length > 0) {\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave) // write fileBuffer to the temp path\n } else {\n // Assign the _possibly modified_ file to the request object\n req.file = {\n ...file,\n data: fileBuffer?.data || bufferToSave,\n size: fileBuffer?.info.size,\n }\n }\n }\n }\n\n if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {\n req.payloadUploadSizes = {}\n // Focal point adjustments\n const focalPoint =\n focalPointEnabled && uploadEdits?.focalPoint\n ? {\n x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,\n y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,\n }\n : undefined\n\n const { sizeData, sizesToSave } = await createImageSizes({\n config: collectionConfig,\n dimensions: !cropData\n ? dimensions!\n : {\n ...dimensions,\n height: fileData.height!,\n width: fileData.width!,\n },\n file: fileForResize,\n focalPoint,\n mimeType: fileData.mimeType,\n req,\n savedFilename: fsSafeName || file.name,\n sharp,\n staticPath: staticPath!,\n withMetadata,\n })\n\n fileData.sizes = sizeData\n fileData.focalX = focalPoint?.x\n fileData.focalY = focalPoint?.y\n filesToSave.push(...sizesToSave)\n }\n } catch (err) {\n req.payload.logger.error(err)\n throw new FileUploadError(req.t)\n }\n\n newData = {\n ...newData,\n ...fileData,\n ...(draft ? { _status: 'draft' } : {}),\n }\n\n return {\n data: newData,\n files: filesToSave,\n }\n}\n\n/**\n * Parse upload edits from req or incoming data\n */\nfunction parseUploadEditsFromReqOrIncomingData(args: {\n data: unknown\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc: unknown\n req: PayloadRequest\n}): UploadEdits {\n const { data, isDuplicating, operation, originalDoc, req } = args\n\n // Get intended focal point change from query string or incoming data\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : {}\n\n if (uploadEdits.focalPoint) {\n return uploadEdits\n }\n\n const incomingData = data as FileData\n const origDoc = originalDoc as FileData\n\n if (origDoc && 'focalX' in origDoc && 'focalY' in origDoc) {\n // If no change in focal point, return undefined.\n // This prevents a refocal operation triggered from admin, because it always sends the focal point.\n if (incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {\n return undefined!\n }\n\n if (isDuplicating) {\n uploadEdits.focalPoint = {\n x: incomingData?.focalX || origDoc.focalX!,\n y: incomingData?.focalY || origDoc.focalY!,\n }\n return uploadEdits\n }\n }\n\n if (incomingData?.focalX && incomingData?.focalY) {\n uploadEdits.focalPoint = {\n x: incomingData.focalX,\n y: incomingData.focalY,\n }\n return uploadEdits\n }\n\n // If no focal point is set, default to center\n if (operation === 'create') {\n uploadEdits.focalPoint = {\n x: 50,\n y: 50,\n }\n }\n\n return uploadEdits\n}\n"],"names":["fileTypeFromBuffer","fs","sanitize","FileRetrievalError","FileUploadError","Forbidden","MissingFile","isNumber","canResizeImage","checkFileRestrictions","cropImage","getExternalFile","getFileByPath","getImageSize","getSafeFileName","createImageSizes","isImage","optionallyAppendMetadata","shouldReupload","uploadEdits","fileData","crop","heightInPixels","widthInPixels","focalPoint","incomingFocalX","x","incomingFocalY","y","currentFocalX","focalX","currentFocalY","focalY","isEqual","generateFileData","collection","config","collectionConfig","data","draft","isDuplicating","operation","originalDoc","overwriteExistingFiles","req","throwOnMissingFile","upload","files","serverURL","sharp","payload","file","undefined","parseUploadEditsFromReqOrIncomingData","constructorOptions","disableLocalStorage","focalPointEnabled","formatOptions","imageSizes","resizeOptions","staticDir","trimOptions","withMetadata","staticPath","incomingFileData","isLocalFile","filename","url","includes","t","startsWith","filePath","response","uploadConfig","err","Error","message","mkdir","recursive","newData","filesToSave","fileIsAnimatedType","mimetype","cropData","fileSupportsResize","fsSafeName","sharpFile","dimensions","fileBuffer","ext","mime","fileHasAdjustments","Boolean","tempFilePath","sharpOptions","animated","rotate","resize","toFormat","format","options","trim","width","height","metadata","toBuffer","resolveWithObject","info","filesize","size","pages","length","name","split","pop","mimeType","baseFilename","substring","lastIndexOf","prefix","collectionSlug","slug","desiredFilename","fileForResize","croppedImage","withoutEnlargement","resizedAfterCrop","fit","position","push","buffer","path","writeFile","bufferToSave","readFile","Array","isArray","payloadUploadSizes","Math","round","sizeData","sizesToSave","savedFilename","sizes","logger","error","_status","args","query","incomingData","origDoc"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/generateFileData.ts"],"sourcesContent":["import type { OutputInfo, Sharp, SharpOptions } from 'sharp'\n\nimport { fileTypeFromBuffer } from 'file-type'\nimport fs from 'fs/promises'\nimport sanitize from 'sanitize-filename'\n\nimport type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { Document, PayloadRequest } from '../types/index.js'\nimport type { FileData, FileToSave, ProbedImageSize, UploadEdits } from './types.js'\n\nimport { FileRetrievalError, FileUploadError, Forbidden, MissingFile } from '../errors/index.js'\nimport { isNumber } from '../utilities/isNumber.js'\nimport { canResizeImage } from './canResizeImage.js'\nimport { checkFileRestrictions } from './checkFileRestrictions.js'\nimport { cropImage } from './cropImage.js'\nimport { getExternalFile } from './getExternalFile.js'\nimport { getFileByPath } from './getFileByPath.js'\nimport { getImageSize } from './getImageSize.js'\nimport { getSafeFileName } from './getSafeFilename.js'\nimport { createImageSizes } from './image-resizing/createImageSizes.js'\nimport { isAnimatedImage } from './isAnimatedImage.js'\nimport { isImage } from './isImage.js'\nimport { optionallyAppendMetadata } from './optionallyAppendMetadata.js'\ntype Args<T> = {\n collection: Collection\n config: SanitizedConfig\n data: T\n draft?: boolean\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc?: T\n overwriteExistingFiles?: boolean\n req: PayloadRequest\n throwOnMissingFile?: boolean\n}\n\ntype Result<T> = Promise<{\n data: T\n files: FileToSave[]\n}>\n\nconst shouldReupload = (\n uploadEdits: UploadEdits,\n fileData: Record<string, unknown> | undefined,\n) => {\n if (!fileData) {\n return false\n }\n\n if (uploadEdits.crop || uploadEdits.heightInPixels || uploadEdits.widthInPixels) {\n return true\n }\n\n // Since uploadEdits always has focalPoint, compare to the value in the data if it was changed\n if (uploadEdits.focalPoint) {\n const incomingFocalX = uploadEdits.focalPoint.x\n const incomingFocalY = uploadEdits.focalPoint.y\n\n const currentFocalX = 'focalX' in fileData && fileData.focalX\n const currentFocalY = 'focalY' in fileData && fileData.focalY\n\n const isEqual = incomingFocalX === currentFocalX && incomingFocalY === currentFocalY\n return !isEqual\n }\n\n return false\n}\n\n/**\n * Builds the document's file metadata and the list of files to write to disk.\n *\n * A file uploaded with `useTempFiles` enabled arrives as a temp file path instead of an\n * in-memory buffer, and that temp file can be far larger than server memory allows. To avoid\n * loading it into memory unnecessarily, this skips reading the temp file entirely when local\n * storage is disabled, and copies it straight to its destination when local storage is enabled.\n */\nexport const generateFileData = async <T>({\n collection: { config: collectionConfig },\n data,\n draft,\n isDuplicating,\n operation,\n originalDoc,\n overwriteExistingFiles,\n req,\n throwOnMissingFile,\n}: Args<T>): Result<T> => {\n if (!collectionConfig.upload) {\n return {\n data,\n files: [],\n }\n }\n\n const { serverURL, sharp } = req.payload.config\n\n let file = isDuplicating ? undefined : req.file\n\n const uploadEdits = parseUploadEditsFromReqOrIncomingData({\n data,\n isDuplicating,\n operation,\n originalDoc,\n req,\n })\n\n const {\n constructorOptions,\n disableLocalStorage,\n focalPoint: focalPointEnabled = true,\n formatOptions,\n imageSizes,\n resizeOptions,\n staticDir,\n trimOptions,\n withMetadata,\n } = collectionConfig.upload\n\n const staticPath = staticDir\n\n const incomingFileData: Document = isDuplicating ? originalDoc : data\n let isLocalFile = false\n\n if (\n !file &&\n (isDuplicating || shouldReupload(uploadEdits, incomingFileData as Record<string, unknown>))\n ) {\n const { filename, url } = incomingFileData as unknown as FileData\n if (filename && (filename.includes('../') || filename.includes('..\\\\'))) {\n throw new Forbidden(req.t)\n }\n\n if ((serverURL && url?.startsWith(serverURL)) || url?.startsWith('/')) {\n isLocalFile = true\n }\n\n try {\n if (!disableLocalStorage && isLocalFile) {\n // File is stored locally\n const filePath = `${staticPath}/${filename}`\n const response = await getFileByPath(filePath)\n file = response\n overwriteExistingFiles = true\n } else if (filename && url) {\n // File is remote\n file = await getExternalFile({\n data: incomingFileData as unknown as FileData,\n req,\n uploadConfig: collectionConfig.upload,\n })\n overwriteExistingFiles = true\n }\n } catch (err: unknown) {\n throw new FileRetrievalError(req.t, err instanceof Error ? err.message : undefined)\n }\n }\n\n if (isDuplicating) {\n overwriteExistingFiles = false\n }\n\n if (!file) {\n if (throwOnMissingFile) {\n throw new MissingFile(req.t)\n }\n\n return {\n data: incomingFileData!,\n files: [],\n }\n }\n\n await checkFileRestrictions({\n collection: collectionConfig,\n file,\n req,\n })\n\n if (!disableLocalStorage) {\n await fs.mkdir(staticPath!, { recursive: true })\n }\n\n let newData = incomingFileData as T\n const filesToSave: FileToSave[] = []\n const fileData: Partial<FileData> = {}\n const fileIsAnimatedType = isAnimatedImage(file.mimetype)\n const cropData =\n typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined\n\n try {\n const fileSupportsResize = canResizeImage(file.mimetype)\n let fsSafeName: string\n let sharpFile: Sharp | undefined\n let dimensions: ProbedImageSize | undefined\n let fileBuffer!: { data: Buffer; info: OutputInfo }\n let ext\n let mime: string\n // Depends only on configured resize/format/trim options, not on whether the bytes are on\n // disk or in memory.\n const fileHasAdjustments =\n fileSupportsResize &&\n Boolean(resizeOptions || formatOptions || trimOptions || constructorOptions)\n\n const sharpOptions: SharpOptions = { ...constructorOptions }\n\n if (fileIsAnimatedType) {\n sharpOptions.animated = true\n }\n\n if (sharp && (fileIsAnimatedType || fileHasAdjustments)) {\n if (file.tempFilePath) {\n sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n } else {\n sharpFile = sharp(file.data, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n }\n\n if (fileHasAdjustments) {\n if (resizeOptions) {\n sharpFile = sharpFile.resize(resizeOptions)\n }\n if (formatOptions) {\n sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options)\n }\n if (trimOptions) {\n sharpFile = sharpFile.trim(trimOptions)\n }\n }\n }\n\n if (fileSupportsResize || isImage(file.mimetype)) {\n dimensions = await getImageSize({ file, sharp })\n fileData.width = dimensions.width\n fileData.height = dimensions.height\n }\n\n if (sharpFile) {\n const metadata = await sharpFile.metadata()\n sharpFile = await optionallyAppendMetadata({\n req,\n sharpFile,\n withMetadata: withMetadata!,\n })\n fileBuffer = await sharpFile.toBuffer({ resolveWithObject: true })\n ;({ ext, mime } = (await fileTypeFromBuffer(fileBuffer.data))!) // This is getting an incorrect gif height back.\n fileData.width = fileBuffer.info.width\n fileData.height = fileBuffer.info.height\n fileData.filesize = fileBuffer.info.size\n\n // Animated GIFs + WebP aggregate the height from every frame, so we need to use divide by number of pages\n if (metadata.pages) {\n fileData.height = fileBuffer.info.height / metadata.pages\n fileData.filesize = fileBuffer.data.length\n }\n } else {\n mime = file.mimetype\n fileData.filesize = file.size\n\n if (file.name.includes('.')) {\n ext = file.name.split('.').pop()?.split('?')[0]\n } else {\n ext = ''\n }\n }\n\n // Adjust SVG mime type. fromBuffer modifies it.\n if (mime === 'application/xml' && ext === 'svg') {\n mime = 'image/svg+xml'\n }\n fileData.mimeType = mime\n\n const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name)\n fsSafeName = `${baseFilename}${ext ? `.${ext}` : ''}`\n\n if (!overwriteExistingFiles) {\n // Extract prefix if present (added by plugin-cloud-storage)\n const prefix = (data as Record<string, unknown>)?.prefix as string | undefined\n fsSafeName = await getSafeFileName({\n collectionSlug: collectionConfig.slug,\n desiredFilename: fsSafeName,\n prefix,\n req,\n staticPath: staticPath!,\n })\n }\n\n fileData.filename = fsSafeName\n\n let fileForResize = file\n\n if (cropData && sharp) {\n const { data: croppedImage, info } = await cropImage({\n cropData,\n dimensions: dimensions!,\n file,\n heightInPixels: uploadEdits.heightInPixels!,\n req,\n sharp,\n widthInPixels: uploadEdits.widthInPixels!,\n withMetadata,\n })\n\n // Apply resize after cropping to ensure it conforms to resizeOptions\n if (resizeOptions && !resizeOptions.withoutEnlargement) {\n const resizedAfterCrop = await sharp(croppedImage)\n .resize({\n fit: resizeOptions?.fit || 'cover',\n height: resizeOptions?.height,\n position: resizeOptions?.position || 'center',\n width: resizeOptions?.width,\n })\n .toBuffer({ resolveWithObject: true })\n\n filesToSave.push({\n buffer: resizedAfterCrop.data,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...fileForResize,\n data: resizedAfterCrop.data,\n size: resizedAfterCrop.info.size,\n }\n\n fileData.width = resizedAfterCrop.info.width\n fileData.height = resizedAfterCrop.info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages\n ? resizedAfterCrop.info.height / metadata.pages\n : resizedAfterCrop.info.height\n }\n fileData.filesize = resizedAfterCrop.info.size\n } else {\n // If resizeOptions is not present, just save the cropped image\n filesToSave.push({\n buffer: croppedImage,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n fileForResize = {\n ...file,\n data: croppedImage,\n size: info.size,\n }\n\n fileData.width = info.width\n fileData.height = info.height\n if (fileIsAnimatedType) {\n const metadata = await sharpFile!.metadata()\n fileData.height = metadata.pages ? info.height / metadata.pages : info.height\n }\n fileData.filesize = info.size\n }\n\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, croppedImage) // write fileBuffer to the temp path\n } else {\n req.file = fileForResize\n }\n } else {\n // For non-image files with useTempFiles, read the buffer from the temp file\n // since file.data is empty when using temp files.\n //\n // When local storage is disabled, filesToSave is never written to disk (create/update\n // skip uploadFiles for it), and an unmodified temp file's bytes on disk are already\n // correct, so there's nothing to read into memory or write back out. Skipping this avoids\n // buffering the entire file just to discard or rewrite it unchanged - a tempFilePath can\n // point at a file far larger than server memory allows.\n const skipTempFileBuffer =\n disableLocalStorage && Boolean(file.tempFilePath) && !fileBuffer?.data\n\n // When local storage is enabled and the temp file itself is unmodified, copy it straight\n // to its destination instead of reading it into memory first - a tempFilePath can point at\n // a file far larger than server memory allows.\n const shouldCopyFromTempFile =\n !fileBuffer?.data && Boolean(file.tempFilePath) && !disableLocalStorage\n\n if (shouldCopyFromTempFile) {\n filesToSave.push({\n path: `${staticPath}/${fsSafeName}`,\n sourcePath: file.tempFilePath!,\n })\n } else {\n let bufferToSave: Buffer\n if (fileBuffer?.data) {\n bufferToSave = fileBuffer.data\n } else if (file.tempFilePath) {\n bufferToSave = skipTempFileBuffer ? Buffer.alloc(0) : await fs.readFile(file.tempFilePath)\n } else {\n bufferToSave = file.data\n }\n\n if (!skipTempFileBuffer) {\n filesToSave.push({\n buffer: bufferToSave,\n path: `${staticPath}/${fsSafeName}`,\n })\n\n // If using temp files and the image is being resized, write the file to the temp path\n if (fileBuffer?.data || bufferToSave.length > 0) {\n if (file.tempFilePath) {\n await fs.writeFile(file.tempFilePath, fileBuffer?.data || bufferToSave) // write fileBuffer to the temp path\n } else {\n // Assign the _possibly modified_ file to the request object\n req.file = {\n ...file,\n data: fileBuffer?.data || bufferToSave,\n size: fileBuffer?.info.size,\n }\n }\n }\n }\n }\n }\n\n if (fileSupportsResize && (Array.isArray(imageSizes) || focalPointEnabled !== false)) {\n req.payloadUploadSizes = {}\n // Focal point adjustments\n const focalPoint =\n focalPointEnabled && uploadEdits?.focalPoint\n ? {\n x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,\n y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,\n }\n : undefined\n\n const { sizeData, sizesToSave } = await createImageSizes({\n config: collectionConfig,\n dimensions: !cropData\n ? dimensions!\n : {\n ...dimensions,\n height: fileData.height!,\n width: fileData.width!,\n },\n file: fileForResize,\n focalPoint,\n mimeType: fileData.mimeType,\n req,\n savedFilename: fsSafeName || file.name,\n sharp,\n staticPath: staticPath!,\n withMetadata,\n })\n\n fileData.sizes = sizeData\n fileData.focalX = focalPoint?.x\n fileData.focalY = focalPoint?.y\n filesToSave.push(...sizesToSave)\n }\n } catch (err) {\n req.payload.logger.error(err)\n throw new FileUploadError(req.t)\n }\n\n newData = {\n ...newData,\n ...fileData,\n ...(draft ? { _status: 'draft' } : {}),\n }\n\n return {\n data: newData,\n files: filesToSave,\n }\n}\n\n/**\n * Parse upload edits from req or incoming data\n */\nfunction parseUploadEditsFromReqOrIncomingData(args: {\n data: unknown\n isDuplicating?: boolean\n operation: 'create' | 'update'\n originalDoc: unknown\n req: PayloadRequest\n}): UploadEdits {\n const { data, isDuplicating, operation, originalDoc, req } = args\n\n // Get intended focal point change from query string or incoming data\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : {}\n\n if (uploadEdits.focalPoint) {\n return uploadEdits\n }\n\n const incomingData = data as FileData\n const origDoc = originalDoc as FileData\n\n if (origDoc && 'focalX' in origDoc && 'focalY' in origDoc) {\n // If no change in focal point, return undefined.\n // This prevents a refocal operation triggered from admin, because it always sends the focal point.\n if (incomingData.focalX === origDoc.focalX && incomingData.focalY === origDoc.focalY) {\n return undefined!\n }\n\n if (isDuplicating) {\n uploadEdits.focalPoint = {\n x: incomingData?.focalX || origDoc.focalX!,\n y: incomingData?.focalY || origDoc.focalY!,\n }\n return uploadEdits\n }\n }\n\n if (incomingData?.focalX && incomingData?.focalY) {\n uploadEdits.focalPoint = {\n x: incomingData.focalX,\n y: incomingData.focalY,\n }\n return uploadEdits\n }\n\n // If no focal point is set, default to center\n if (operation === 'create') {\n uploadEdits.focalPoint = {\n x: 50,\n y: 50,\n }\n }\n\n return uploadEdits\n}\n"],"names":["fileTypeFromBuffer","fs","sanitize","FileRetrievalError","FileUploadError","Forbidden","MissingFile","isNumber","canResizeImage","checkFileRestrictions","cropImage","getExternalFile","getFileByPath","getImageSize","getSafeFileName","createImageSizes","isAnimatedImage","isImage","optionallyAppendMetadata","shouldReupload","uploadEdits","fileData","crop","heightInPixels","widthInPixels","focalPoint","incomingFocalX","x","incomingFocalY","y","currentFocalX","focalX","currentFocalY","focalY","isEqual","generateFileData","collection","config","collectionConfig","data","draft","isDuplicating","operation","originalDoc","overwriteExistingFiles","req","throwOnMissingFile","upload","files","serverURL","sharp","payload","file","undefined","parseUploadEditsFromReqOrIncomingData","constructorOptions","disableLocalStorage","focalPointEnabled","formatOptions","imageSizes","resizeOptions","staticDir","trimOptions","withMetadata","staticPath","incomingFileData","isLocalFile","filename","url","includes","t","startsWith","filePath","response","uploadConfig","err","Error","message","mkdir","recursive","newData","filesToSave","fileIsAnimatedType","mimetype","cropData","fileSupportsResize","fsSafeName","sharpFile","dimensions","fileBuffer","ext","mime","fileHasAdjustments","Boolean","sharpOptions","animated","tempFilePath","rotate","resize","toFormat","format","options","trim","width","height","metadata","toBuffer","resolveWithObject","info","filesize","size","pages","length","name","split","pop","mimeType","baseFilename","substring","lastIndexOf","prefix","collectionSlug","slug","desiredFilename","fileForResize","croppedImage","withoutEnlargement","resizedAfterCrop","fit","position","push","buffer","path","writeFile","skipTempFileBuffer","shouldCopyFromTempFile","sourcePath","bufferToSave","Buffer","alloc","readFile","Array","isArray","payloadUploadSizes","Math","round","sizeData","sizesToSave","savedFilename","sizes","logger","error","_status","args","query","incomingData","origDoc"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { generateFileData } from './generateFileData.js';
7
+ // A minimal valid 1x1 transparent PNG, so `file-type` can detect `image/png` from it.
8
+ const PNG_SIGNATURE = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==', 'base64');
9
+ const createSharpMock = ()=>{
10
+ const toBufferMock = vi.fn().mockResolvedValue({
11
+ data: PNG_SIGNATURE,
12
+ info: {
13
+ height: 1,
14
+ width: 1,
15
+ size: PNG_SIGNATURE.length
16
+ }
17
+ });
18
+ const metadataMock = vi.fn().mockResolvedValue({
19
+ height: 1,
20
+ width: 1
21
+ });
22
+ const chain = {
23
+ metadata: metadataMock,
24
+ resize: vi.fn(()=>chain),
25
+ rotate: vi.fn(()=>chain),
26
+ toBuffer: toBufferMock,
27
+ toFormat: vi.fn(()=>chain),
28
+ withMetadata: vi.fn(()=>chain)
29
+ };
30
+ chain.trim = vi.fn(()=>chain);
31
+ const sharp = vi.fn(()=>chain);
32
+ return {
33
+ sharp,
34
+ toBufferMock
35
+ };
36
+ };
37
+ const createCollection = (uploadOverrides = {})=>({
38
+ config: {
39
+ slug: 'media',
40
+ upload: {
41
+ focalPoint: false,
42
+ staticDir: os.tmpdir(),
43
+ ...uploadOverrides
44
+ }
45
+ }
46
+ });
47
+ describe('generateFileData', ()=>{
48
+ let tempFilePath;
49
+ beforeEach(async ()=>{
50
+ tempFilePath = path.join(os.tmpdir(), `generate-file-data-test-${randomUUID()}`);
51
+ await fs.writeFile(tempFilePath, PNG_SIGNATURE);
52
+ });
53
+ afterEach(async ()=>{
54
+ await fs.rm(tempFilePath, {
55
+ force: true
56
+ });
57
+ });
58
+ const createReq = (sharp)=>({
59
+ file: {
60
+ data: Buffer.alloc(0),
61
+ mimetype: 'image/png',
62
+ name: 'photo.png',
63
+ size: PNG_SIGNATURE.length,
64
+ tempFilePath
65
+ },
66
+ payload: {
67
+ config: {
68
+ sharp
69
+ },
70
+ logger: {
71
+ error: vi.fn()
72
+ }
73
+ }
74
+ });
75
+ describe('when local storage is disabled', ()=>{
76
+ it('does not run full sharp processing on an image with no configured adjustments, even when it arrives via tempFilePath', async ()=>{
77
+ const { sharp, toBufferMock } = createSharpMock();
78
+ await generateFileData({
79
+ collection: createCollection({
80
+ disableLocalStorage: true
81
+ }),
82
+ config: {},
83
+ data: {},
84
+ operation: 'create',
85
+ overwriteExistingFiles: true,
86
+ req: createReq(sharp)
87
+ });
88
+ expect(toBufferMock).not.toHaveBeenCalled();
89
+ });
90
+ it('does not save anything when no processing is needed', async ()=>{
91
+ const { sharp } = createSharpMock();
92
+ const { files } = await generateFileData({
93
+ collection: createCollection({
94
+ disableLocalStorage: true
95
+ }),
96
+ config: {},
97
+ data: {},
98
+ operation: 'create',
99
+ overwriteExistingFiles: true,
100
+ req: createReq(sharp)
101
+ });
102
+ expect(files).toEqual([]);
103
+ });
104
+ it('still runs sharp processing when resize options are configured', async ()=>{
105
+ const { sharp, toBufferMock } = createSharpMock();
106
+ await generateFileData({
107
+ collection: createCollection({
108
+ disableLocalStorage: true,
109
+ resizeOptions: {
110
+ width: 100
111
+ }
112
+ }),
113
+ config: {},
114
+ data: {},
115
+ operation: 'create',
116
+ overwriteExistingFiles: true,
117
+ req: createReq(sharp)
118
+ });
119
+ expect(toBufferMock).toHaveBeenCalledTimes(1);
120
+ });
121
+ });
122
+ describe('when local storage is enabled (default)', ()=>{
123
+ it('copies straight from the temp file instead of running sharp processing, when no adjustments are configured', async ()=>{
124
+ const { sharp, toBufferMock } = createSharpMock();
125
+ const { files } = await generateFileData({
126
+ collection: createCollection(),
127
+ config: {},
128
+ data: {},
129
+ operation: 'create',
130
+ overwriteExistingFiles: true,
131
+ req: createReq(sharp)
132
+ });
133
+ expect(toBufferMock).not.toHaveBeenCalled();
134
+ expect(files).toEqual([
135
+ {
136
+ path: `${os.tmpdir()}/photo.png`,
137
+ sourcePath: tempFilePath
138
+ }
139
+ ]);
140
+ });
141
+ });
142
+ });
143
+
144
+ //# sourceMappingURL=generateFileData.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/generateFileData.spec.ts"],"sourcesContent":["import type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { randomUUID } from 'node:crypto'\nimport fs from 'node:fs/promises'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\n\nimport { generateFileData } from './generateFileData.js'\n\n// A minimal valid 1x1 transparent PNG, so `file-type` can detect `image/png` from it.\nconst PNG_SIGNATURE = Buffer.from(\n 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==',\n 'base64',\n)\n\nconst createSharpMock = () => {\n const toBufferMock = vi.fn().mockResolvedValue({\n data: PNG_SIGNATURE,\n info: { height: 1, width: 1, size: PNG_SIGNATURE.length },\n })\n const metadataMock = vi.fn().mockResolvedValue({ height: 1, width: 1 })\n\n const chain: any = {\n metadata: metadataMock,\n resize: vi.fn(() => chain),\n rotate: vi.fn(() => chain),\n toBuffer: toBufferMock,\n toFormat: vi.fn(() => chain),\n withMetadata: vi.fn(() => chain),\n }\n chain.trim = vi.fn(() => chain)\n\n const sharp = vi.fn(() => chain)\n\n return { sharp, toBufferMock }\n}\n\nconst createCollection = (uploadOverrides: Record<string, unknown> = {}): Collection =>\n ({\n config: {\n slug: 'media',\n upload: {\n focalPoint: false,\n staticDir: os.tmpdir(),\n ...uploadOverrides,\n },\n },\n }) as unknown as Collection\n\ndescribe('generateFileData', () => {\n let tempFilePath: string\n\n beforeEach(async () => {\n tempFilePath = path.join(os.tmpdir(), `generate-file-data-test-${randomUUID()}`)\n await fs.writeFile(tempFilePath, PNG_SIGNATURE)\n })\n\n afterEach(async () => {\n await fs.rm(tempFilePath, { force: true })\n })\n\n const createReq = (sharp: unknown): PayloadRequest =>\n ({\n file: {\n data: Buffer.alloc(0),\n mimetype: 'image/png',\n name: 'photo.png',\n size: PNG_SIGNATURE.length,\n tempFilePath,\n },\n payload: {\n config: { sharp },\n logger: { error: vi.fn() },\n },\n }) as unknown as PayloadRequest\n\n describe('when local storage is disabled', () => {\n it('does not run full sharp processing on an image with no configured adjustments, even when it arrives via tempFilePath', async () => {\n const { sharp, toBufferMock } = createSharpMock()\n\n await generateFileData({\n collection: createCollection({ disableLocalStorage: true }),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req: createReq(sharp),\n })\n\n expect(toBufferMock).not.toHaveBeenCalled()\n })\n\n it('does not save anything when no processing is needed', async () => {\n const { sharp } = createSharpMock()\n\n const { files } = await generateFileData({\n collection: createCollection({ disableLocalStorage: true }),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req: createReq(sharp),\n })\n\n expect(files).toEqual([])\n })\n\n it('still runs sharp processing when resize options are configured', async () => {\n const { sharp, toBufferMock } = createSharpMock()\n\n await generateFileData({\n collection: createCollection({ disableLocalStorage: true, resizeOptions: { width: 100 } }),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req: createReq(sharp),\n })\n\n expect(toBufferMock).toHaveBeenCalledTimes(1)\n })\n })\n\n describe('when local storage is enabled (default)', () => {\n it('copies straight from the temp file instead of running sharp processing, when no adjustments are configured', async () => {\n const { sharp, toBufferMock } = createSharpMock()\n\n const { files } = await generateFileData({\n collection: createCollection(),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req: createReq(sharp),\n })\n\n expect(toBufferMock).not.toHaveBeenCalled()\n expect(files).toEqual([{ path: `${os.tmpdir()}/photo.png`, sourcePath: tempFilePath }])\n })\n })\n})\n"],"names":["randomUUID","fs","os","path","afterEach","beforeEach","describe","expect","it","vi","generateFileData","PNG_SIGNATURE","Buffer","from","createSharpMock","toBufferMock","fn","mockResolvedValue","data","info","height","width","size","length","metadataMock","chain","metadata","resize","rotate","toBuffer","toFormat","withMetadata","trim","sharp","createCollection","uploadOverrides","config","slug","upload","focalPoint","staticDir","tmpdir","tempFilePath","join","writeFile","rm","force","createReq","file","alloc","mimetype","name","payload","logger","error","collection","disableLocalStorage","operation","overwriteExistingFiles","req","not","toHaveBeenCalled","files","toEqual","resizeOptions","toHaveBeenCalledTimes","sourcePath"],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const mkdirMock = vi.fn().mockResolvedValue(undefined);
3
+ const readFileMock = vi.fn().mockResolvedValue(Buffer.from('unused'));
4
+ const writeFileMock = vi.fn().mockResolvedValue(undefined);
5
+ vi.mock('fs/promises', ()=>({
6
+ default: {
7
+ mkdir: mkdirMock,
8
+ readFile: readFileMock,
9
+ writeFile: writeFileMock
10
+ }
11
+ }));
12
+ const { generateFileData } = await import('./generateFileData.js');
13
+ const createCollection = (disableLocalStorage)=>({
14
+ config: {
15
+ slug: 'media',
16
+ upload: {
17
+ disableLocalStorage,
18
+ staticDir: '/tmp/media'
19
+ }
20
+ }
21
+ });
22
+ const createReq = (tempFilePath, size)=>({
23
+ file: {
24
+ data: Buffer.alloc(0),
25
+ mimetype: 'video/mp4',
26
+ name: 'big-video.mp4',
27
+ size,
28
+ tempFilePath
29
+ },
30
+ payload: {
31
+ config: {},
32
+ logger: {
33
+ error: vi.fn()
34
+ }
35
+ }
36
+ });
37
+ describe('generateFileData - non-image temp file buffering', ()=>{
38
+ beforeEach(()=>{
39
+ vi.clearAllMocks();
40
+ readFileMock.mockResolvedValue(Buffer.from('unused'));
41
+ });
42
+ it('does not read or rewrite a temp file into memory when local storage is disabled', async ()=>{
43
+ const req = createReq('/tmp/payload-upload-abc', 5_000_000_000);
44
+ const result = await generateFileData({
45
+ collection: createCollection(true),
46
+ config: {},
47
+ data: {},
48
+ operation: 'create',
49
+ overwriteExistingFiles: true,
50
+ req
51
+ });
52
+ expect(readFileMock).not.toHaveBeenCalled();
53
+ expect(writeFileMock).not.toHaveBeenCalled();
54
+ expect(result.files).toEqual([]);
55
+ expect(result.data).toMatchObject({
56
+ filesize: 5_000_000_000,
57
+ mimeType: 'video/mp4'
58
+ });
59
+ });
60
+ it('copies the temp file directly, without reading it into memory, when local storage is enabled', async ()=>{
61
+ const req = createReq('/tmp/payload-upload-def', 5_000_000_000);
62
+ const result = await generateFileData({
63
+ collection: createCollection(false),
64
+ config: {},
65
+ data: {},
66
+ operation: 'create',
67
+ overwriteExistingFiles: true,
68
+ req
69
+ });
70
+ expect(readFileMock).not.toHaveBeenCalled();
71
+ expect(writeFileMock).not.toHaveBeenCalled();
72
+ expect(result.files).toEqual([
73
+ {
74
+ path: '/tmp/media/big-video.mp4',
75
+ sourcePath: '/tmp/payload-upload-def'
76
+ }
77
+ ]);
78
+ });
79
+ });
80
+
81
+ //# sourceMappingURL=generateFileData.tempFileBuffering.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/generateFileData.tempFileBuffering.spec.ts"],"sourcesContent":["import type { Collection } from '../collections/config/types.js'\nimport type { SanitizedConfig } from '../config/types.js'\nimport type { PayloadRequest } from '../types/index.js'\n\nimport { beforeEach, describe, expect, it, vi } from 'vitest'\n\nconst mkdirMock = vi.fn().mockResolvedValue(undefined)\nconst readFileMock = vi.fn().mockResolvedValue(Buffer.from('unused'))\nconst writeFileMock = vi.fn().mockResolvedValue(undefined)\n\nvi.mock('fs/promises', () => ({\n default: {\n mkdir: mkdirMock,\n readFile: readFileMock,\n writeFile: writeFileMock,\n },\n}))\n\nconst { generateFileData } = await import('./generateFileData.js')\n\nconst createCollection = (disableLocalStorage: boolean): Collection =>\n ({\n config: {\n slug: 'media',\n upload: {\n disableLocalStorage,\n staticDir: '/tmp/media',\n },\n },\n }) as unknown as Collection\n\nconst createReq = (tempFilePath: string, size: number): PayloadRequest =>\n ({\n file: {\n data: Buffer.alloc(0),\n mimetype: 'video/mp4',\n name: 'big-video.mp4',\n size,\n tempFilePath,\n },\n payload: {\n config: {},\n logger: { error: vi.fn() },\n },\n }) as unknown as PayloadRequest\n\ndescribe('generateFileData - non-image temp file buffering', () => {\n beforeEach(() => {\n vi.clearAllMocks()\n readFileMock.mockResolvedValue(Buffer.from('unused'))\n })\n\n it('does not read or rewrite a temp file into memory when local storage is disabled', async () => {\n const req = createReq('/tmp/payload-upload-abc', 5_000_000_000)\n\n const result = await generateFileData({\n collection: createCollection(true),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req,\n })\n\n expect(readFileMock).not.toHaveBeenCalled()\n expect(writeFileMock).not.toHaveBeenCalled()\n expect(result.files).toEqual([])\n expect(result.data).toMatchObject({ filesize: 5_000_000_000, mimeType: 'video/mp4' })\n })\n\n it('copies the temp file directly, without reading it into memory, when local storage is enabled', async () => {\n const req = createReq('/tmp/payload-upload-def', 5_000_000_000)\n\n const result = await generateFileData({\n collection: createCollection(false),\n config: {} as SanitizedConfig,\n data: {},\n operation: 'create',\n overwriteExistingFiles: true,\n req,\n })\n\n expect(readFileMock).not.toHaveBeenCalled()\n expect(writeFileMock).not.toHaveBeenCalled()\n expect(result.files).toEqual([\n { path: '/tmp/media/big-video.mp4', sourcePath: '/tmp/payload-upload-def' },\n ])\n })\n})\n"],"names":["beforeEach","describe","expect","it","vi","mkdirMock","fn","mockResolvedValue","undefined","readFileMock","Buffer","from","writeFileMock","mock","default","mkdir","readFile","writeFile","generateFileData","createCollection","disableLocalStorage","config","slug","upload","staticDir","createReq","tempFilePath","size","file","data","alloc","mimetype","name","payload","logger","error","clearAllMocks","req","result","collection","operation","overwriteExistingFiles","not","toHaveBeenCalled","files","toEqual","toMatchObject","filesize","mimeType","path","sourcePath"],"mappings":"AAIA,SAASA,UAAU,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAE7D,MAAMC,YAAYD,GAAGE,EAAE,GAAGC,iBAAiB,CAACC;AAC5C,MAAMC,eAAeL,GAAGE,EAAE,GAAGC,iBAAiB,CAACG,OAAOC,IAAI,CAAC;AAC3D,MAAMC,gBAAgBR,GAAGE,EAAE,GAAGC,iBAAiB,CAACC;AAEhDJ,GAAGS,IAAI,CAAC,eAAe,IAAO,CAAA;QAC5BC,SAAS;YACPC,OAAOV;YACPW,UAAUP;YACVQ,WAAWL;QACb;IACF,CAAA;AAEA,MAAM,EAAEM,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC;AAE1C,MAAMC,mBAAmB,CAACC,sBACvB,CAAA;QACCC,QAAQ;YACNC,MAAM;YACNC,QAAQ;gBACNH;gBACAI,WAAW;YACb;QACF;IACF,CAAA;AAEF,MAAMC,YAAY,CAACC,cAAsBC,OACtC,CAAA;QACCC,MAAM;YACJC,MAAMnB,OAAOoB,KAAK,CAAC;YACnBC,UAAU;YACVC,MAAM;YACNL;YACAD;QACF;QACAO,SAAS;YACPZ,QAAQ,CAAC;YACTa,QAAQ;gBAAEC,OAAO/B,GAAGE,EAAE;YAAG;QAC3B;IACF,CAAA;AAEFL,SAAS,oDAAoD;IAC3DD,WAAW;QACTI,GAAGgC,aAAa;QAChB3B,aAAaF,iBAAiB,CAACG,OAAOC,IAAI,CAAC;IAC7C;IAEAR,GAAG,mFAAmF;QACpF,MAAMkC,MAAMZ,UAAU,2BAA2B;QAEjD,MAAMa,SAAS,MAAMpB,iBAAiB;YACpCqB,YAAYpB,iBAAiB;YAC7BE,QAAQ,CAAC;YACTQ,MAAM,CAAC;YACPW,WAAW;YACXC,wBAAwB;YACxBJ;QACF;QAEAnC,OAAOO,cAAciC,GAAG,CAACC,gBAAgB;QACzCzC,OAAOU,eAAe8B,GAAG,CAACC,gBAAgB;QAC1CzC,OAAOoC,OAAOM,KAAK,EAAEC,OAAO,CAAC,EAAE;QAC/B3C,OAAOoC,OAAOT,IAAI,EAAEiB,aAAa,CAAC;YAAEC,UAAU;YAAeC,UAAU;QAAY;IACrF;IAEA7C,GAAG,gGAAgG;QACjG,MAAMkC,MAAMZ,UAAU,2BAA2B;QAEjD,MAAMa,SAAS,MAAMpB,iBAAiB;YACpCqB,YAAYpB,iBAAiB;YAC7BE,QAAQ,CAAC;YACTQ,MAAM,CAAC;YACPW,WAAW;YACXC,wBAAwB;YACxBJ;QACF;QAEAnC,OAAOO,cAAciC,GAAG,CAACC,gBAAgB;QACzCzC,OAAOU,eAAe8B,GAAG,CAACC,gBAAgB;QAC1CzC,OAAOoC,OAAOM,KAAK,EAAEC,OAAO,CAAC;YAC3B;gBAAEI,MAAM;gBAA4BC,YAAY;YAA0B;SAC3E;IACH;AACF"}
@@ -0,0 +1,9 @@
1
+ import type { SanitizedUploadConfig } from './types.js';
2
+ export declare const HEADER_PROBE_BYTE_LENGTH: number;
3
+ export type FileContentRequirement = 'full' | 'header' | 'none';
4
+ export declare function getFileContentRequirement({ hasSizeEdits, mimeType, uploadConfig, }: {
5
+ hasSizeEdits?: boolean;
6
+ mimeType: string;
7
+ uploadConfig: SanitizedUploadConfig;
8
+ }): FileContentRequirement;
9
+ //# sourceMappingURL=getFileContentRequirement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileContentRequirement.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileContentRequirement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAMvD,eAAO,MAAM,wBAAwB,QAAc,CAAA;AAEnD,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE/D,wBAAgB,yBAAyB,CAAC,EACxC,YAAY,EACZ,QAAQ,EACR,YAAY,GACb,EAAE;IACD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,qBAAqB,CAAA;CACpC,GAAG,sBAAsB,CAgCzB"}
@@ -0,0 +1,24 @@
1
+ import { canResizeImage } from './canResizeImage.js';
2
+ import { isAnimatedImage } from './isAnimatedImage.js';
3
+ import { isImage } from './isImage.js';
4
+ export const HEADER_PROBE_BYTE_LENGTH = 1024 * 1024;
5
+ export function getFileContentRequirement({ hasSizeEdits, mimeType, uploadConfig }) {
6
+ if (!uploadConfig.disableLocalStorage) {
7
+ return 'full';
8
+ }
9
+ const hasMimeTypeAllowList = !uploadConfig.allowRestrictedFileTypes && Array.isArray(uploadConfig.mimeTypes) && uploadConfig.mimeTypes.length > 0;
10
+ if (hasMimeTypeAllowList) {
11
+ return 'full';
12
+ }
13
+ const isResizableImage = canResizeImage(mimeType);
14
+ const hasConfiguredAdjustments = Boolean(uploadConfig.resizeOptions || uploadConfig.formatOptions || uploadConfig.trimOptions || uploadConfig.constructorOptions || Array.isArray(uploadConfig.imageSizes) && uploadConfig.imageSizes.length > 0);
15
+ if (hasSizeEdits || isResizableImage && hasConfiguredAdjustments || isAnimatedImage(mimeType)) {
16
+ return 'full';
17
+ }
18
+ if (isResizableImage || isImage(mimeType)) {
19
+ return 'header';
20
+ }
21
+ return 'none';
22
+ }
23
+
24
+ //# sourceMappingURL=getFileContentRequirement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/getFileContentRequirement.ts"],"sourcesContent":["import type { SanitizedUploadConfig } from './types.js'\n\nimport { canResizeImage } from './canResizeImage.js'\nimport { isAnimatedImage } from './isAnimatedImage.js'\nimport { isImage } from './isImage.js'\n\nexport const HEADER_PROBE_BYTE_LENGTH = 1024 * 1024\n\nexport type FileContentRequirement = 'full' | 'header' | 'none'\n\nexport function getFileContentRequirement({\n hasSizeEdits,\n mimeType,\n uploadConfig,\n}: {\n hasSizeEdits?: boolean\n mimeType: string\n uploadConfig: SanitizedUploadConfig\n}): FileContentRequirement {\n if (!uploadConfig.disableLocalStorage) {\n return 'full'\n }\n\n const hasMimeTypeAllowList =\n !uploadConfig.allowRestrictedFileTypes &&\n Array.isArray(uploadConfig.mimeTypes) &&\n uploadConfig.mimeTypes.length > 0\n\n if (hasMimeTypeAllowList) {\n return 'full'\n }\n\n const isResizableImage = canResizeImage(mimeType)\n const hasConfiguredAdjustments = Boolean(\n uploadConfig.resizeOptions ||\n uploadConfig.formatOptions ||\n uploadConfig.trimOptions ||\n uploadConfig.constructorOptions ||\n (Array.isArray(uploadConfig.imageSizes) && uploadConfig.imageSizes.length > 0),\n )\n\n if (hasSizeEdits || (isResizableImage && hasConfiguredAdjustments) || isAnimatedImage(mimeType)) {\n return 'full'\n }\n\n if (isResizableImage || isImage(mimeType)) {\n return 'header'\n }\n\n return 'none'\n}\n"],"names":["canResizeImage","isAnimatedImage","isImage","HEADER_PROBE_BYTE_LENGTH","getFileContentRequirement","hasSizeEdits","mimeType","uploadConfig","disableLocalStorage","hasMimeTypeAllowList","allowRestrictedFileTypes","Array","isArray","mimeTypes","length","isResizableImage","hasConfiguredAdjustments","Boolean","resizeOptions","formatOptions","trimOptions","constructorOptions","imageSizes"],"mappings":"AAEA,SAASA,cAAc,QAAQ,sBAAqB;AACpD,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,OAAO,QAAQ,eAAc;AAEtC,OAAO,MAAMC,2BAA2B,OAAO,KAAI;AAInD,OAAO,SAASC,0BAA0B,EACxCC,YAAY,EACZC,QAAQ,EACRC,YAAY,EAKb;IACC,IAAI,CAACA,aAAaC,mBAAmB,EAAE;QACrC,OAAO;IACT;IAEA,MAAMC,uBACJ,CAACF,aAAaG,wBAAwB,IACtCC,MAAMC,OAAO,CAACL,aAAaM,SAAS,KACpCN,aAAaM,SAAS,CAACC,MAAM,GAAG;IAElC,IAAIL,sBAAsB;QACxB,OAAO;IACT;IAEA,MAAMM,mBAAmBf,eAAeM;IACxC,MAAMU,2BAA2BC,QAC/BV,aAAaW,aAAa,IACxBX,aAAaY,aAAa,IAC1BZ,aAAaa,WAAW,IACxBb,aAAac,kBAAkB,IAC9BV,MAAMC,OAAO,CAACL,aAAae,UAAU,KAAKf,aAAae,UAAU,CAACR,MAAM,GAAG;IAGhF,IAAIT,gBAAiBU,oBAAoBC,4BAA6Bf,gBAAgBK,WAAW;QAC/F,OAAO;IACT;IAEA,IAAIS,oBAAoBb,QAAQI,WAAW;QACzC,OAAO;IACT;IAEA,OAAO;AACT"}