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
@@ -0,0 +1,528 @@
1
+ import fs from 'fs/promises';
2
+ import os from 'os';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js';
5
+ import { getFileFromClientUpload } from './getFileFromClientUpload.js';
6
+ import { getImageSize } from './getImageSize.js';
7
+ const MINIMAL_PNG = (()=>{
8
+ const signature = Buffer.from([
9
+ 0x89,
10
+ 0x50,
11
+ 0x4e,
12
+ 0x47,
13
+ 0x0d,
14
+ 0x0a,
15
+ 0x1a,
16
+ 0x0a
17
+ ]);
18
+ const ihdrData = Buffer.alloc(13);
19
+ ihdrData.writeUInt32BE(400, 0); // width
20
+ ihdrData.writeUInt32BE(300, 4); // height
21
+ ihdrData[8] = 8; // bit depth
22
+ ihdrData[9] = 2; // color type: RGB
23
+ const ihdrChunk = Buffer.concat([
24
+ Buffer.from([
25
+ 0,
26
+ 0,
27
+ 0,
28
+ 13
29
+ ]),
30
+ Buffer.from('IHDR'),
31
+ ihdrData,
32
+ Buffer.alloc(4)
33
+ ]);
34
+ return Buffer.concat([
35
+ signature,
36
+ ihdrChunk
37
+ ]);
38
+ })();
39
+ const tempFilesToRemove = [];
40
+ afterEach(async ()=>{
41
+ vi.unstubAllGlobals();
42
+ for (const tempFilePath of tempFilesToRemove){
43
+ await fs.rm(tempFilePath, {
44
+ force: true
45
+ });
46
+ }
47
+ tempFilesToRemove.length = 0;
48
+ });
49
+ const createReq = ({ handlers, upload })=>{
50
+ const request = new Request('http://localhost/api/media');
51
+ const req = request;
52
+ req.query = {};
53
+ req.payload = {
54
+ collections: {
55
+ media: {
56
+ config: {
57
+ upload: {
58
+ disableLocalStorage: true,
59
+ handlers,
60
+ ...upload
61
+ }
62
+ }
63
+ }
64
+ },
65
+ config: {
66
+ upload: {}
67
+ },
68
+ logger: {
69
+ error: vi.fn()
70
+ }
71
+ };
72
+ return req;
73
+ };
74
+ const videoFile = (overrides = {})=>({
75
+ clientUploadContext: {
76
+ prefix: 'abc'
77
+ },
78
+ collectionSlug: 'media',
79
+ filename: 'clip.mp4',
80
+ mimeType: 'video/mp4',
81
+ size: 10,
82
+ ...overrides
83
+ });
84
+ const imageFile = (overrides = {})=>({
85
+ collectionSlug: 'media',
86
+ filename: 'photo.png',
87
+ mimeType: 'image/png',
88
+ size: MINIMAL_PNG.length,
89
+ ...overrides
90
+ });
91
+ describe('getFileFromClientUpload', ()=>{
92
+ it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async ()=>{
93
+ const handler = vi.fn();
94
+ const req = createReq({
95
+ handlers: [
96
+ handler
97
+ ],
98
+ upload: {
99
+ disableLocalStorage: true
100
+ }
101
+ });
102
+ const file = videoFile();
103
+ const result = await getFileFromClientUpload({
104
+ file,
105
+ req
106
+ });
107
+ expect(handler).not.toHaveBeenCalled();
108
+ expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true);
109
+ expect(result.clientUploadContext).toEqual({
110
+ prefix: 'abc'
111
+ });
112
+ expect(result.data.length).toBe(0);
113
+ expect(result.tempFilePath).toBeUndefined();
114
+ expect(result.name).toBe('clip.mp4');
115
+ expect(result.size).toBe(10);
116
+ });
117
+ it('requests a bounded byte range and returns dimensions for a header-only image', async ()=>{
118
+ const handler = vi.fn(async (handlerReq)=>{
119
+ expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`);
120
+ return new Response(MINIMAL_PNG, {
121
+ headers: {
122
+ 'Content-Type': 'image/png'
123
+ },
124
+ status: 206
125
+ });
126
+ });
127
+ const req = createReq({
128
+ handlers: [
129
+ handler
130
+ ],
131
+ upload: {
132
+ disableLocalStorage: true
133
+ }
134
+ });
135
+ const file = imageFile();
136
+ const result = await getFileFromClientUpload({
137
+ file,
138
+ req
139
+ });
140
+ expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH);
141
+ expect(result.tempFilePath).toBeUndefined();
142
+ await expect(getImageSize({
143
+ file: result
144
+ })).resolves.toEqual({
145
+ height: 300,
146
+ width: 400
147
+ });
148
+ });
149
+ it('caps the header read at the boundary and cancels the stream without pulling past it', async ()=>{
150
+ let hasCancelled = false;
151
+ let hasReadPastBoundary = false;
152
+ const firstChunk = Buffer.concat([
153
+ MINIMAL_PNG,
154
+ Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length)
155
+ ]);
156
+ const stream = new ReadableStream({
157
+ cancel () {
158
+ hasCancelled = true;
159
+ },
160
+ pull (controller) {
161
+ hasReadPastBoundary = true;
162
+ controller.error(new Error('Read past header boundary'));
163
+ },
164
+ start (controller) {
165
+ controller.enqueue(firstChunk);
166
+ }
167
+ }, {
168
+ highWaterMark: 0
169
+ });
170
+ const handler = vi.fn(async ()=>new Response(stream, {
171
+ headers: {
172
+ 'Content-Type': 'image/png'
173
+ },
174
+ status: 206
175
+ }));
176
+ const req = createReq({
177
+ handlers: [
178
+ handler
179
+ ],
180
+ upload: {
181
+ disableLocalStorage: true
182
+ }
183
+ });
184
+ const file = imageFile({
185
+ size: HEADER_PROBE_BYTE_LENGTH
186
+ });
187
+ const result = await getFileFromClientUpload({
188
+ file,
189
+ req
190
+ });
191
+ expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH);
192
+ expect(hasCancelled).toBe(true);
193
+ expect(hasReadPastBoundary).toBe(false);
194
+ });
195
+ it('lets a handler read native Request properties like signal through the Range-scoped proxy', async ()=>{
196
+ const handler = vi.fn(async (handlerReq)=>{
197
+ expect(()=>handlerReq.signal).not.toThrow();
198
+ expect(handlerReq.signal).toBeInstanceOf(AbortSignal);
199
+ return new Response(MINIMAL_PNG, {
200
+ headers: {
201
+ 'Content-Type': 'image/png'
202
+ },
203
+ status: 206
204
+ });
205
+ });
206
+ const req = createReq({
207
+ handlers: [
208
+ handler
209
+ ],
210
+ upload: {
211
+ disableLocalStorage: true
212
+ }
213
+ });
214
+ const file = imageFile();
215
+ await getFileFromClientUpload({
216
+ file,
217
+ req
218
+ });
219
+ expect(handler).toHaveBeenCalledTimes(1);
220
+ });
221
+ it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async ()=>{
222
+ const handler = vi.fn().mockResolvedValueOnce(new Response(Buffer.from('not an image'), {
223
+ headers: {
224
+ 'Content-Type': 'image/png'
225
+ },
226
+ status: 206
227
+ })).mockResolvedValueOnce(new Response(MINIMAL_PNG, {
228
+ headers: {
229
+ 'Content-Type': 'image/png'
230
+ },
231
+ status: 200
232
+ }));
233
+ const req = createReq({
234
+ handlers: [
235
+ handler
236
+ ],
237
+ upload: {
238
+ disableLocalStorage: true
239
+ }
240
+ });
241
+ const file = imageFile();
242
+ const result = await getFileFromClientUpload({
243
+ file,
244
+ req
245
+ });
246
+ tempFilesToRemove.push(result.tempFilePath);
247
+ expect(handler).toHaveBeenCalledTimes(2);
248
+ expect(result.tempFilePath).toBeDefined();
249
+ expect(result.data.length).toBe(0);
250
+ });
251
+ it('streams a full-content response to a temp file without buffering the whole body', async ()=>{
252
+ const chunks = [
253
+ Buffer.from('chunk-one-'),
254
+ Buffer.from('chunk-two-'),
255
+ Buffer.from('chunk-three')
256
+ ];
257
+ const stream = new ReadableStream({
258
+ start (controller) {
259
+ for (const chunk of chunks){
260
+ controller.enqueue(chunk);
261
+ }
262
+ controller.close();
263
+ }
264
+ });
265
+ const response = new Response(stream, {
266
+ headers: {
267
+ 'Content-Type': 'video/mp4'
268
+ },
269
+ status: 200
270
+ });
271
+ const arrayBufferTripwire = vi.fn(async ()=>{
272
+ throw new Error('Unexpected whole-body buffering');
273
+ });
274
+ Object.defineProperty(response, 'arrayBuffer', {
275
+ value: arrayBufferTripwire
276
+ });
277
+ const handler = vi.fn(async ()=>response);
278
+ const req = createReq({
279
+ handlers: [
280
+ handler
281
+ ],
282
+ upload: {
283
+ disableLocalStorage: true,
284
+ mimeTypes: [
285
+ 'video/*'
286
+ ]
287
+ }
288
+ });
289
+ const file = videoFile({
290
+ size: 30
291
+ });
292
+ const result = await getFileFromClientUpload({
293
+ file,
294
+ req
295
+ });
296
+ tempFilesToRemove.push(result.tempFilePath);
297
+ expect(result.data).toEqual(Buffer.alloc(0));
298
+ expect(arrayBufferTripwire).not.toHaveBeenCalled();
299
+ const written = await fs.readFile(result.tempFilePath);
300
+ expect(written.toString()).toBe(chunks.map((chunk)=>chunk.toString()).join(''));
301
+ });
302
+ it('runs every handler and uses the last one that returns a response, matching v3', async ()=>{
303
+ const firstHandler = vi.fn(async ()=>new Response(Buffer.from('first-response')));
304
+ const secondHandler = vi.fn(async ()=>new Response(Buffer.from('second-response')));
305
+ const req = createReq({
306
+ handlers: [
307
+ firstHandler,
308
+ secondHandler
309
+ ],
310
+ upload: {
311
+ disableLocalStorage: true,
312
+ mimeTypes: [
313
+ 'video/*'
314
+ ]
315
+ }
316
+ });
317
+ const file = videoFile();
318
+ const result = await getFileFromClientUpload({
319
+ file,
320
+ req
321
+ });
322
+ tempFilesToRemove.push(result.tempFilePath);
323
+ expect(firstHandler).toHaveBeenCalledTimes(1);
324
+ expect(secondHandler).toHaveBeenCalledTimes(1);
325
+ const written = await fs.readFile(result.tempFilePath);
326
+ expect(written.toString()).toBe('second-response');
327
+ });
328
+ it('falls through to the next handler when the first returns nothing', async ()=>{
329
+ const firstHandler = vi.fn(async ()=>undefined);
330
+ const secondHandler = vi.fn(async ()=>new Response(Buffer.alloc(10), {
331
+ status: 200
332
+ }));
333
+ const req = createReq({
334
+ handlers: [
335
+ firstHandler,
336
+ secondHandler
337
+ ],
338
+ upload: {
339
+ disableLocalStorage: true,
340
+ mimeTypes: [
341
+ 'video/*'
342
+ ]
343
+ }
344
+ });
345
+ const file = videoFile();
346
+ const result = await getFileFromClientUpload({
347
+ file,
348
+ req
349
+ });
350
+ tempFilesToRemove.push(result.tempFilePath);
351
+ expect(firstHandler).toHaveBeenCalledTimes(1);
352
+ expect(secondHandler).toHaveBeenCalledTimes(1);
353
+ });
354
+ it('follows exactly one redirect from a handler response', async ()=>{
355
+ const redirectTarget = 'http://storage.example.com/file.mp4';
356
+ const handler = vi.fn(async ()=>new Response(null, {
357
+ headers: {
358
+ Location: redirectTarget
359
+ },
360
+ status: 302
361
+ }));
362
+ const fetchMock = vi.fn(async ()=>new Response(Buffer.alloc(10), {
363
+ headers: {
364
+ 'Content-Type': 'video/mp4'
365
+ },
366
+ status: 200
367
+ }));
368
+ vi.stubGlobal('fetch', fetchMock);
369
+ const req = createReq({
370
+ handlers: [
371
+ handler
372
+ ],
373
+ upload: {
374
+ disableLocalStorage: true,
375
+ mimeTypes: [
376
+ 'video/*'
377
+ ]
378
+ }
379
+ });
380
+ const file = videoFile();
381
+ const result = await getFileFromClientUpload({
382
+ file,
383
+ req
384
+ });
385
+ tempFilesToRemove.push(result.tempFilePath);
386
+ expect(fetchMock).toHaveBeenCalledTimes(1);
387
+ expect(fetchMock).toHaveBeenCalledWith(redirectTarget);
388
+ });
389
+ it('forwards the Range header through a redirect during the bounded header probe', async ()=>{
390
+ const redirectTarget = 'http://storage.example.com/photo.png';
391
+ const handler = vi.fn(async ()=>new Response(null, {
392
+ headers: {
393
+ Location: redirectTarget
394
+ },
395
+ status: 302
396
+ }));
397
+ const fetchMock = vi.fn(async ()=>new Response(MINIMAL_PNG, {
398
+ headers: {
399
+ 'Content-Type': 'image/png'
400
+ },
401
+ status: 206
402
+ }));
403
+ vi.stubGlobal('fetch', fetchMock);
404
+ const req = createReq({
405
+ handlers: [
406
+ handler
407
+ ],
408
+ upload: {
409
+ disableLocalStorage: true
410
+ }
411
+ });
412
+ const file = imageFile();
413
+ const result = await getFileFromClientUpload({
414
+ file,
415
+ req
416
+ });
417
+ expect(result.tempFilePath).toBeUndefined();
418
+ expect(fetchMock).toHaveBeenCalledTimes(1);
419
+ expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {
420
+ headers: {
421
+ Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`
422
+ }
423
+ });
424
+ });
425
+ it('removes the partial temp file when the stream fails before completion', async ()=>{
426
+ const stream = new ReadableStream({
427
+ pull (controller) {
428
+ controller.error(new Error('stream broke'));
429
+ },
430
+ start (controller) {
431
+ controller.enqueue(Buffer.from('partial-chunk'));
432
+ }
433
+ });
434
+ const handler = vi.fn(async ()=>new Response(stream, {
435
+ headers: {
436
+ 'Content-Type': 'video/mp4'
437
+ },
438
+ status: 200
439
+ }));
440
+ const req = createReq({
441
+ handlers: [
442
+ handler
443
+ ],
444
+ upload: {
445
+ disableLocalStorage: true,
446
+ mimeTypes: [
447
+ 'video/*'
448
+ ]
449
+ }
450
+ });
451
+ const file = videoFile();
452
+ const tempFileDir = os.tmpdir();
453
+ const filesBefore = new Set(await fs.readdir(tempFileDir));
454
+ await expect(getFileFromClientUpload({
455
+ file,
456
+ req
457
+ })).rejects.toThrow('stream broke');
458
+ const filesAfter = await fs.readdir(tempFileDir);
459
+ const leftoverTempFiles = filesAfter.filter((entry)=>!filesBefore.has(entry) && entry.startsWith('payload-client-upload-'));
460
+ expect(leftoverTempFiles).toEqual([]);
461
+ });
462
+ it('requires full content and skips the Range header when the request carries crop or size edits', async ()=>{
463
+ const handler = vi.fn(async (handlerReq)=>{
464
+ expect(handlerReq.headers.get('Range')).toBeNull();
465
+ return new Response(MINIMAL_PNG, {
466
+ headers: {
467
+ 'Content-Type': 'image/png'
468
+ },
469
+ status: 200
470
+ });
471
+ });
472
+ const req = createReq({
473
+ handlers: [
474
+ handler
475
+ ],
476
+ upload: {
477
+ disableLocalStorage: true
478
+ }
479
+ });
480
+ req.query = {
481
+ uploadEdits: {
482
+ heightInPixels: 100,
483
+ widthInPixels: 100
484
+ }
485
+ };
486
+ const file = imageFile();
487
+ const result = await getFileFromClientUpload({
488
+ file,
489
+ req
490
+ });
491
+ tempFilesToRemove.push(result.tempFilePath);
492
+ expect(handler).toHaveBeenCalledTimes(1);
493
+ expect(result.tempFilePath).toBeDefined();
494
+ expect(result.data.length).toBe(0);
495
+ });
496
+ it('treats a null response body as a legitimate zero-byte file', async ()=>{
497
+ const handler = vi.fn(async ()=>new Response(null, {
498
+ headers: {
499
+ 'Content-Type': 'video/mp4'
500
+ },
501
+ status: 200
502
+ }));
503
+ const req = createReq({
504
+ handlers: [
505
+ handler
506
+ ],
507
+ upload: {
508
+ disableLocalStorage: true,
509
+ mimeTypes: [
510
+ 'video/*'
511
+ ]
512
+ }
513
+ });
514
+ const file = videoFile({
515
+ size: 0
516
+ });
517
+ const result = await getFileFromClientUpload({
518
+ file,
519
+ req
520
+ });
521
+ tempFilesToRemove.push(result.tempFilePath);
522
+ expect(result.tempFilePath).toBeDefined();
523
+ const written = await fs.readFile(result.tempFilePath);
524
+ expect(written.length).toBe(0);
525
+ });
526
+ });
527
+
528
+ //# sourceMappingURL=getFileFromClientUpload.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig } from './types.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { type ClientUploadData, getFileFromClientUpload } from './getFileFromClientUpload.js'\nimport { getImageSize } from './getImageSize.js'\n\nconst MINIMAL_PNG = (() => {\n const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const ihdrData = Buffer.alloc(13)\n ihdrData.writeUInt32BE(400, 0) // width\n ihdrData.writeUInt32BE(300, 4) // height\n ihdrData[8] = 8 // bit depth\n ihdrData[9] = 2 // color type: RGB\n const ihdrChunk = Buffer.concat([\n Buffer.from([0, 0, 0, 13]),\n Buffer.from('IHDR'),\n ihdrData,\n Buffer.alloc(4), // crc (unchecked by dimension probing)\n ])\n return Buffer.concat([signature, ihdrChunk])\n})()\n\nconst tempFilesToRemove: string[] = []\n\nafterEach(async () => {\n vi.unstubAllGlobals()\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n})\n\nconst createReq = ({\n handlers,\n upload,\n}: {\n handlers: NonNullable<SanitizedUploadConfig['handlers']>\n upload?: Partial<SanitizedUploadConfig>\n}): PayloadRequest => {\n const request = new Request('http://localhost/api/media')\n const req = request as unknown as PayloadRequest\n\n req.query = {}\n req.payload = {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers,\n ...upload,\n },\n },\n },\n },\n config: {\n upload: {},\n },\n logger: { error: vi.fn() },\n } as unknown as PayloadRequest['payload']\n\n return req\n}\n\nconst videoFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 10,\n ...overrides,\n})\n\nconst imageFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n collectionSlug: 'media',\n filename: 'photo.png',\n mimeType: 'image/png',\n size: MINIMAL_PNG.length,\n ...overrides,\n})\n\ndescribe('getFileFromClientUpload', () => {\n it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async () => {\n const handler = vi.fn()\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).not.toHaveBeenCalled()\n expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true)\n expect(result.clientUploadContext).toEqual({ prefix: 'abc' })\n expect(result.data.length).toBe(0)\n expect(result.tempFilePath).toBeUndefined()\n expect(result.name).toBe('clip.mp4')\n expect(result.size).toBe(10)\n })\n\n it('requests a bounded byte range and returns dimensions for a header-only image', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH)\n expect(result.tempFilePath).toBeUndefined()\n await expect(getImageSize({ file: result })).resolves.toEqual({ height: 300, width: 400 })\n })\n\n it('caps the header read at the boundary and cancels the stream without pulling past it', async () => {\n let hasCancelled = false\n let hasReadPastBoundary = false\n const firstChunk = Buffer.concat([\n MINIMAL_PNG,\n Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length),\n ])\n const stream = new ReadableStream(\n {\n cancel() {\n hasCancelled = true\n },\n pull(controller) {\n hasReadPastBoundary = true\n controller.error(new Error('Read past header boundary'))\n },\n start(controller) {\n controller.enqueue(firstChunk)\n },\n },\n { highWaterMark: 0 },\n )\n\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ size: HEADER_PROBE_BYTE_LENGTH })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH)\n expect(hasCancelled).toBe(true)\n expect(hasReadPastBoundary).toBe(false)\n })\n\n it('lets a handler read native Request properties like signal through the Range-scoped proxy', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(() => handlerReq.signal).not.toThrow()\n expect(handlerReq.signal).toBeInstanceOf(AbortSignal)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n })\n\n it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async () => {\n const handler = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(Buffer.from('not an image'), {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n }),\n )\n .mockResolvedValueOnce(\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(2)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('streams a full-content response to a temp file without buffering the whole body', async () => {\n const chunks = [\n Buffer.from('chunk-one-'),\n Buffer.from('chunk-two-'),\n Buffer.from('chunk-three'),\n ]\n const stream = new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk)\n }\n controller.close()\n },\n })\n\n const response = new Response(stream, {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n })\n const arrayBufferTripwire = vi.fn(async () => {\n throw new Error('Unexpected whole-body buffering')\n })\n Object.defineProperty(response, 'arrayBuffer', { value: arrayBufferTripwire })\n\n const handler = vi.fn(async () => response)\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 30 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.data).toEqual(Buffer.alloc(0))\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n\n it('runs every handler and uses the last one that returns a response, matching v3', async () => {\n const firstHandler = vi.fn(async () => new Response(Buffer.from('first-response')))\n const secondHandler = vi.fn(async () => new Response(Buffer.from('second-response')))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('second-response')\n })\n\n it('falls through to the next handler when the first returns nothing', async () => {\n const firstHandler = vi.fn(async () => undefined)\n const secondHandler = vi.fn(async () => new Response(Buffer.alloc(10), { status: 200 }))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n })\n\n it('follows exactly one redirect from a handler response', async () => {\n const redirectTarget = 'http://storage.example.com/file.mp4'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(Buffer.alloc(10), { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget)\n })\n\n it('forwards the Range header through a redirect during the bounded header probe', async () => {\n const redirectTarget = 'http://storage.example.com/photo.png'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.tempFilePath).toBeUndefined()\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {\n headers: { Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}` },\n })\n })\n\n it('removes the partial temp file when the stream fails before completion', async () => {\n const stream = new ReadableStream({\n pull(controller) {\n controller.error(new Error('stream broke'))\n },\n start(controller) {\n controller.enqueue(Buffer.from('partial-chunk'))\n },\n })\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const tempFileDir = os.tmpdir()\n const filesBefore = new Set(await fs.readdir(tempFileDir))\n\n await expect(getFileFromClientUpload({ file, req })).rejects.toThrow('stream broke')\n\n const filesAfter = await fs.readdir(tempFileDir)\n const leftoverTempFiles = filesAfter.filter(\n (entry) => !filesBefore.has(entry) && entry.startsWith('payload-client-upload-'),\n )\n expect(leftoverTempFiles).toEqual([])\n })\n\n it('requires full content and skips the Range header when the request carries crop or size edits', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n req.query = { uploadEdits: { heightInPixels: 100, widthInPixels: 100 } }\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('treats a null response body as a legitimate zero-byte file', async () => {\n const handler = vi.fn(\n async () => new Response(null, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 0 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.tempFilePath).toBeDefined()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.length).toBe(0)\n })\n})\n"],"names":["fs","os","afterEach","describe","expect","it","vi","HEADER_PROBE_BYTE_LENGTH","getFileFromClientUpload","getImageSize","MINIMAL_PNG","signature","Buffer","from","ihdrData","alloc","writeUInt32BE","ihdrChunk","concat","tempFilesToRemove","unstubAllGlobals","tempFilePath","rm","force","length","createReq","handlers","upload","request","Request","req","query","payload","collections","media","config","disableLocalStorage","logger","error","fn","videoFile","overrides","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","imageFile","handler","file","result","not","toHaveBeenCalled","Object","prototype","hasOwnProperty","call","toBe","toEqual","data","toBeUndefined","name","handlerReq","headers","get","Response","status","toBeLessThanOrEqual","resolves","height","width","hasCancelled","hasReadPastBoundary","firstChunk","stream","ReadableStream","cancel","pull","controller","Error","start","enqueue","highWaterMark","signal","toThrow","toBeInstanceOf","AbortSignal","toHaveBeenCalledTimes","mockResolvedValueOnce","push","toBeDefined","chunks","chunk","close","response","arrayBufferTripwire","defineProperty","value","mimeTypes","written","readFile","toString","map","join","firstHandler","secondHandler","undefined","redirectTarget","Location","fetchMock","stubGlobal","toHaveBeenCalledWith","Range","tempFileDir","tmpdir","filesBefore","Set","readdir","rejects","filesAfter","leftoverTempFiles","filter","entry","has","startsWith","toBeNull","uploadEdits","heightInPixels","widthInPixels"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createImageSizes.d.ts","sourceRoot":"","sources":["../../../src/uploads/image-resizing/createImageSizes.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,KAAK,EAAY,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAW/F;;;;;;;;;;;;;GAaG;AAEH,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,yBAAyB,CAAA;IACjC,UAAU,EAAE,eAAe,CAAA;IAC3B,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED,sEAAsE;AACtE,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,SAAS,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B,CAAA;AAED,wBAAsB,gBAAgB,CAAC,EACrC,MAAM,EACN,UAAU,EACV,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,GAAG,EACH,aAAa,EACb,KAAK,EACL,UAAU,EACV,YAAY,GACb,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyOxC"}
1
+ {"version":3,"file":"createImageSizes.d.ts","sourceRoot":"","sources":["../../../src/uploads/image-resizing/createImageSizes.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAA;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,KAAK,EAAY,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAY/F;;;;;;;;;;;;;GAaG;AAEH,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,yBAAyB,CAAA;IACjC,UAAU,EAAE,eAAe,CAAA;IAC3B,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,CAAA;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,cAAc,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B,CAAA;AAED,sEAAsE;AACtE,KAAK,gBAAgB,GAAG;IACtB,QAAQ,EAAE,SAAS,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;CAC1B,CAAA;AAED,wBAAsB,gBAAgB,CAAC,EACrC,MAAM,EACN,UAAU,EACV,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,GAAG,EACH,aAAa,EACb,KAAK,EACL,UAAU,EACV,YAAY,GACb,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyOxC"}
@@ -1,6 +1,7 @@
1
1
  import { fileTypeFromBuffer } from 'file-type';
2
2
  import fs from 'fs/promises';
3
3
  import { fileExists } from '../fileExists.js';
4
+ import { isAnimatedImage } from '../isAnimatedImage.js';
4
5
  import { optionallyAppendMetadata } from '../optionallyAppendMetadata.js';
5
6
  import { createImageSize } from './createImageSize.js';
6
7
  import { extractHeightFromImage } from './extractHeightFromImage.js';
@@ -17,11 +18,7 @@ export async function createImageSizes({ config, dimensions, file, focalPoint, m
17
18
  };
18
19
  }
19
20
  // Determine if the file is animated
20
- const fileIsAnimatedType = [
21
- 'image/avif',
22
- 'image/gif',
23
- 'image/webp'
24
- ].includes(file.mimetype);
21
+ const fileIsAnimatedType = isAnimatedImage(file.mimetype);
25
22
  const sharpOptions = {};
26
23
  if (fileIsAnimatedType) {
27
24
  sharpOptions.animated = true;