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.
- package/dist/collections/operations/create.d.ts.map +1 -1
- package/dist/collections/operations/create.js +10 -0
- package/dist/collections/operations/create.js.map +1 -1
- package/dist/collections/operations/update.d.ts.map +1 -1
- package/dist/collections/operations/update.js +10 -0
- package/dist/collections/operations/update.js.map +1 -1
- package/dist/collections/operations/updateByID.d.ts.map +1 -1
- package/dist/collections/operations/updateByID.js +10 -0
- package/dist/collections/operations/updateByID.js.map +1 -1
- package/dist/index.bundled.d.ts +10 -0
- package/dist/uploads/cropImage.d.ts.map +1 -1
- package/dist/uploads/cropImage.js +2 -5
- package/dist/uploads/cropImage.js.map +1 -1
- package/dist/uploads/generateFileData.d.ts +8 -0
- package/dist/uploads/generateFileData.d.ts.map +1 -1
- package/dist/uploads/generateFileData.js +55 -29
- package/dist/uploads/generateFileData.js.map +1 -1
- package/dist/uploads/generateFileData.spec.js +144 -0
- package/dist/uploads/generateFileData.spec.js.map +1 -0
- package/dist/uploads/generateFileData.tempFileBuffering.spec.js +81 -0
- package/dist/uploads/generateFileData.tempFileBuffering.spec.js.map +1 -0
- package/dist/uploads/getFileContentRequirement.d.ts +9 -0
- package/dist/uploads/getFileContentRequirement.d.ts.map +1 -0
- package/dist/uploads/getFileContentRequirement.js +24 -0
- package/dist/uploads/getFileContentRequirement.js.map +1 -0
- package/dist/uploads/getFileContentRequirement.spec.js +95 -0
- package/dist/uploads/getFileContentRequirement.spec.js.map +1 -0
- package/dist/uploads/getFileFromClientUpload.d.ts +20 -0
- package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -0
- package/dist/uploads/getFileFromClientUpload.js +199 -0
- package/dist/uploads/getFileFromClientUpload.js.map +1 -0
- package/dist/uploads/getFileFromClientUpload.spec.js +528 -0
- package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -0
- package/dist/uploads/image-resizing/createImageSizes.d.ts.map +1 -1
- package/dist/uploads/image-resizing/createImageSizes.js +2 -5
- package/dist/uploads/image-resizing/createImageSizes.js.map +1 -1
- package/dist/uploads/isAnimatedImage.d.ts +6 -0
- package/dist/uploads/isAnimatedImage.d.ts.map +1 -0
- package/dist/uploads/isAnimatedImage.js +19 -0
- package/dist/uploads/isAnimatedImage.js.map +1 -0
- package/dist/uploads/isAnimatedImage.spec.js +19 -0
- package/dist/uploads/isAnimatedImage.spec.js.map +1 -0
- package/dist/uploads/types.d.ts +10 -0
- package/dist/uploads/types.d.ts.map +1 -1
- package/dist/uploads/types.js.map +1 -1
- package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
- package/dist/uploads/unlinkTempFiles.js +12 -2
- package/dist/uploads/unlinkTempFiles.js.map +1 -1
- package/dist/uploads/unlinkTempFiles.spec.js +157 -0
- package/dist/uploads/unlinkTempFiles.spec.js.map +1 -0
- package/dist/uploads/uploadFiles.d.ts.map +1 -1
- package/dist/uploads/uploadFiles.js +7 -2
- package/dist/uploads/uploadFiles.js.map +1 -1
- package/dist/uploads/uploadFiles.spec.js +44 -0
- package/dist/uploads/uploadFiles.spec.js.map +1 -0
- package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.js +7 -46
- package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
- package/dist/utilities/addDataAndFileToRequest.spec.js +131 -1
- package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
- 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;
|
|
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;
|