@payloadcms/transformer-sharp 4.0.0-internal.58473b9
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/LICENSE.md +22 -0
- package/README.md +105 -0
- package/dist/canResizeImage.d.ts +2 -0
- package/dist/canResizeImage.d.ts.map +1 -0
- package/dist/canResizeImage.js +12 -0
- package/dist/canResizeImage.js.map +1 -0
- package/dist/createSharpFromFile.d.ts +8 -0
- package/dist/createSharpFromFile.d.ts.map +1 -0
- package/dist/createSharpFromFile.js +31 -0
- package/dist/createSharpFromFile.js.map +1 -0
- package/dist/createSharpFromFile.spec.js +120 -0
- package/dist/createSharpFromFile.spec.js.map +1 -0
- package/dist/getImageResizeAction.d.ts +24 -0
- package/dist/getImageResizeAction.d.ts.map +1 -0
- package/dist/getImageResizeAction.js +56 -0
- package/dist/getImageResizeAction.js.map +1 -0
- package/dist/handleRequest.d.ts +14 -0
- package/dist/handleRequest.d.ts.map +1 -0
- package/dist/handleRequest.js +143 -0
- package/dist/handleRequest.js.map +1 -0
- package/dist/handleRequest.spec.js +188 -0
- package/dist/handleRequest.spec.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/initSharpCollections.d.ts +14 -0
- package/dist/initSharpCollections.d.ts.map +1 -0
- package/dist/initSharpCollections.js +84 -0
- package/dist/initSharpCollections.js.map +1 -0
- package/dist/initSharpCollections.spec.js +185 -0
- package/dist/initSharpCollections.spec.js.map +1 -0
- package/dist/mapWithBoundedConcurrency.d.ts +7 -0
- package/dist/mapWithBoundedConcurrency.d.ts.map +1 -0
- package/dist/mapWithBoundedConcurrency.js +26 -0
- package/dist/mapWithBoundedConcurrency.js.map +1 -0
- package/dist/mapWithBoundedConcurrency.spec.js +35 -0
- package/dist/mapWithBoundedConcurrency.spec.js.map +1 -0
- package/dist/optionallyAppendMetadata.d.ts +9 -0
- package/dist/optionallyAppendMetadata.d.ts.map +1 -0
- package/dist/optionallyAppendMetadata.js +17 -0
- package/dist/optionallyAppendMetadata.js.map +1 -0
- package/dist/parseDynamicResize.d.ts +17 -0
- package/dist/parseDynamicResize.d.ts.map +1 -0
- package/dist/parseDynamicResize.js +117 -0
- package/dist/parseDynamicResize.js.map +1 -0
- package/dist/parseDynamicResize.spec.js +123 -0
- package/dist/parseDynamicResize.spec.js.map +1 -0
- package/dist/prepareLegacyUpload.d.ts +14 -0
- package/dist/prepareLegacyUpload.d.ts.map +1 -0
- package/dist/prepareLegacyUpload.js +147 -0
- package/dist/prepareLegacyUpload.js.map +1 -0
- package/dist/prepareLegacyUpload.spec.js +95 -0
- package/dist/prepareLegacyUpload.spec.js.map +1 -0
- package/dist/sanitizeResizeConfig.d.ts +11 -0
- package/dist/sanitizeResizeConfig.d.ts.map +1 -0
- package/dist/sanitizeResizeConfig.js +20 -0
- package/dist/sanitizeResizeConfig.js.map +1 -0
- package/dist/sharpTransformer.d.ts +10 -0
- package/dist/sharpTransformer.d.ts.map +1 -0
- package/dist/sharpTransformer.js +122 -0
- package/dist/sharpTransformer.js.map +1 -0
- package/dist/sharpTransformer.spec.js +117 -0
- package/dist/sharpTransformer.spec.js.map +1 -0
- package/dist/transformFile.d.ts +12 -0
- package/dist/transformFile.d.ts.map +1 -0
- package/dist/transformFile.js +264 -0
- package/dist/transformFile.js.map +1 -0
- package/dist/transformFile.spec.js +259 -0
- package/dist/transformFile.spec.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { UploadTransformer } from 'payload';
|
|
2
|
+
import type { TransformerWithInternalBridge } from 'payload/internal';
|
|
3
|
+
import type { SharpDynamicDefaults, SharpTransformerOptions } from './types.js';
|
|
4
|
+
export declare function resolveSharpDynamicDefaults(overrides?: SharpDynamicDefaults): Required<SharpDynamicDefaults>;
|
|
5
|
+
/**
|
|
6
|
+
* Payload's official Sharp-based file transformer: upload-time image processing,
|
|
7
|
+
* plus opt-in (`dynamic`) request-time width/height/`withoutEnlargement` resizing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sharpTransformer(options?: SharpTransformerOptions): TransformerWithInternalBridge & UploadTransformer;
|
|
10
|
+
//# sourceMappingURL=sharpTransformer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharpTransformer.d.ts","sourceRoot":"","sources":["../src/sharpTransformer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkD,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAChG,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAA;AAKrE,OAAO,KAAK,EAAE,oBAAoB,EAAuB,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAiBpG,wBAAgB,2BAA2B,CACzC,SAAS,CAAC,EAAE,oBAAoB,GAC/B,QAAQ,CAAC,oBAAoB,CAAC,CAShC;AAgBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,6BAA6B,GAAG,iBAAiB,CAmDnD"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { getUploadTransformerInternal, uploadTransformerInternal } from 'payload/internal';
|
|
2
|
+
import bundledSharp from 'sharp';
|
|
3
|
+
import { createHandleRequest } from './handleRequest.js';
|
|
4
|
+
import { initSharpCollections } from './initSharpCollections.js';
|
|
5
|
+
import { parseDynamicResize } from './parseDynamicResize.js';
|
|
6
|
+
import { createPrepareLegacyUpload } from './prepareLegacyUpload.js';
|
|
7
|
+
import { createTransformFile } from './transformFile.js';
|
|
8
|
+
const DEFAULT_MIME_TYPES = [
|
|
9
|
+
'image/jpeg',
|
|
10
|
+
'image/png',
|
|
11
|
+
'image/gif',
|
|
12
|
+
'image/webp',
|
|
13
|
+
'image/tiff',
|
|
14
|
+
'image/avif'
|
|
15
|
+
];
|
|
16
|
+
export function resolveSharpDynamicDefaults(overrides) {
|
|
17
|
+
return {
|
|
18
|
+
fit: overrides?.fit ?? 'cover',
|
|
19
|
+
maxHeight: overrides?.maxHeight ?? 4096,
|
|
20
|
+
maxPixels: overrides?.maxPixels ?? 16_777_216,
|
|
21
|
+
maxWidth: overrides?.maxWidth ?? 4096,
|
|
22
|
+
position: overrides?.position ?? 'center',
|
|
23
|
+
withoutEnlargement: overrides?.withoutEnlargement ?? false
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Normalizes the `dynamic` option: `false`/omitted disables request-time
|
|
28
|
+
* resizing, `true` enables it with defaults for every upload collection.
|
|
29
|
+
*/ function resolveSharpDynamicOptions(dynamic) {
|
|
30
|
+
if (!dynamic) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return dynamic === true ? {} : dynamic;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Payload's official Sharp-based file transformer: upload-time image processing,
|
|
37
|
+
* plus opt-in (`dynamic`) request-time width/height/`withoutEnlargement` resizing.
|
|
38
|
+
*/ export function sharpTransformer(options = {}) {
|
|
39
|
+
const dynamicOptions = resolveSharpDynamicOptions(options.dynamic);
|
|
40
|
+
const dynamicDefaults = resolveSharpDynamicDefaults(dynamicOptions || undefined);
|
|
41
|
+
const sharpDependency = options.sharp ?? bundledSharp;
|
|
42
|
+
const collections = options.collections ?? {};
|
|
43
|
+
const handlesCollection = ({ collectionSlug })=>Boolean(collections[collectionSlug]);
|
|
44
|
+
const transformer = {
|
|
45
|
+
slug: options.slug ?? 'sharp',
|
|
46
|
+
canTransform: (args)=>{
|
|
47
|
+
// Upload-time eligibility is already decided by the MIME match that ran
|
|
48
|
+
// before `canTransform`; only dynamic request routing needs the query.
|
|
49
|
+
if (args.operation === 'upload') {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (!dynamicOptions || dynamicOptions.collections && !dynamicOptions.collections.includes(args.collectionSlug)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const result = parseDynamicResize({
|
|
56
|
+
limits: dynamicDefaults,
|
|
57
|
+
searchParams: args.req.searchParams ?? new URLSearchParams()
|
|
58
|
+
});
|
|
59
|
+
return result.isRouted;
|
|
60
|
+
},
|
|
61
|
+
handleRequest: createHandleRequest({
|
|
62
|
+
dynamicDefaults,
|
|
63
|
+
sharpDependency
|
|
64
|
+
}),
|
|
65
|
+
init: (config)=>{
|
|
66
|
+
assertDynamicCollectionsExist({
|
|
67
|
+
config,
|
|
68
|
+
dynamicOptions
|
|
69
|
+
});
|
|
70
|
+
assertCollectionsOwnedOnce({
|
|
71
|
+
collections,
|
|
72
|
+
config,
|
|
73
|
+
transformer
|
|
74
|
+
});
|
|
75
|
+
return initSharpCollections({
|
|
76
|
+
collections,
|
|
77
|
+
config
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
mimeTypes: DEFAULT_MIME_TYPES,
|
|
81
|
+
[uploadTransformerInternal]: {
|
|
82
|
+
handlesCollection,
|
|
83
|
+
prepareUpload: createPrepareLegacyUpload({
|
|
84
|
+
collections,
|
|
85
|
+
sharpDependency
|
|
86
|
+
})
|
|
87
|
+
},
|
|
88
|
+
// `options` here is always what this transformer computed via `prepareUpload`'s
|
|
89
|
+
// `transform` callback; the public contract's `unknown` just reflects that core never inspects it.
|
|
90
|
+
transformFile: createTransformFile({
|
|
91
|
+
sharpDependency
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
return transformer;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Core drives each upload through a single Sharp instance, so a collection's upload settings
|
|
98
|
+
* split across instances would silently lose all but one of them.
|
|
99
|
+
*/ function assertCollectionsOwnedOnce({ collections, config, transformer }) {
|
|
100
|
+
for (const [collectionSlug, collectionConfig] of Object.entries(collections)){
|
|
101
|
+
if (!collectionConfig) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const owners = (config.upload?.transformers ?? []).filter((candidate)=>candidate === transformer || getUploadTransformerInternal(candidate)?.handlesCollection?.({
|
|
105
|
+
collectionSlug
|
|
106
|
+
}));
|
|
107
|
+
if (owners.length > 1) {
|
|
108
|
+
throw new Error(`Invalid \`sharpTransformer({ collections })\` configuration: collection "${collectionSlug}" has upload settings on more than one Sharp transformer: ${owners.map((owner)=>`"${owner.slug}"`).join(', ')}. Configure each collection on exactly one instance.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function assertDynamicCollectionsExist({ config, dynamicOptions }) {
|
|
113
|
+
if (!dynamicOptions || !dynamicOptions.collections) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const invalidSlugs = dynamicOptions.collections.filter((slug)=>!config.collections?.some((collection)=>collection.slug === slug && collection.upload));
|
|
117
|
+
if (invalidSlugs.length > 0) {
|
|
118
|
+
throw new Error(`Invalid \`sharpTransformer({ dynamic: { collections } })\` configuration: not an upload-enabled collection: ${invalidSlugs.map((slug)=>`"${slug}"`).join(', ')}.`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=sharpTransformer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sharpTransformer.ts"],"sourcesContent":["import type { Config, TransformFileArgs, TransformFileResult, UploadTransformer } from 'payload'\nimport type { TransformerWithInternalBridge } from 'payload/internal'\n\nimport { getUploadTransformerInternal, uploadTransformerInternal } from 'payload/internal'\nimport bundledSharp from 'sharp'\n\nimport type { SharpDynamicDefaults, SharpDynamicOptions, SharpTransformerOptions } from './types.js'\n\nimport { createHandleRequest } from './handleRequest.js'\nimport { initSharpCollections } from './initSharpCollections.js'\nimport { parseDynamicResize } from './parseDynamicResize.js'\nimport { createPrepareLegacyUpload } from './prepareLegacyUpload.js'\nimport { createTransformFile } from './transformFile.js'\n\nconst DEFAULT_MIME_TYPES = [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/tiff',\n 'image/avif',\n]\n\nexport function resolveSharpDynamicDefaults(\n overrides?: SharpDynamicDefaults,\n): Required<SharpDynamicDefaults> {\n return {\n fit: overrides?.fit ?? 'cover',\n maxHeight: overrides?.maxHeight ?? 4096,\n maxPixels: overrides?.maxPixels ?? 16_777_216,\n maxWidth: overrides?.maxWidth ?? 4096,\n position: overrides?.position ?? 'center',\n withoutEnlargement: overrides?.withoutEnlargement ?? false,\n }\n}\n\n/**\n * Normalizes the `dynamic` option: `false`/omitted disables request-time\n * resizing, `true` enables it with defaults for every upload collection.\n */\nfunction resolveSharpDynamicOptions(\n dynamic: SharpTransformerOptions['dynamic'],\n): false | SharpDynamicOptions {\n if (!dynamic) {\n return false\n }\n\n return dynamic === true ? {} : dynamic\n}\n\n/**\n * Payload's official Sharp-based file transformer: upload-time image processing,\n * plus opt-in (`dynamic`) request-time width/height/`withoutEnlargement` resizing.\n */\nexport function sharpTransformer(\n options: SharpTransformerOptions = {},\n): TransformerWithInternalBridge & UploadTransformer {\n const dynamicOptions = resolveSharpDynamicOptions(options.dynamic)\n const dynamicDefaults = resolveSharpDynamicDefaults(dynamicOptions || undefined)\n const sharpDependency = options.sharp ?? bundledSharp\n const collections = options.collections ?? {}\n const handlesCollection = ({ collectionSlug }: { collectionSlug: string }) =>\n Boolean(collections[collectionSlug])\n\n const transformer: TransformerWithInternalBridge & UploadTransformer = {\n slug: options.slug ?? 'sharp',\n canTransform: (args) => {\n // Upload-time eligibility is already decided by the MIME match that ran\n // before `canTransform`; only dynamic request routing needs the query.\n if (args.operation === 'upload') {\n return true\n }\n\n if (\n !dynamicOptions ||\n (dynamicOptions.collections && !dynamicOptions.collections.includes(args.collectionSlug))\n ) {\n return false\n }\n\n const result = parseDynamicResize({\n limits: dynamicDefaults,\n searchParams: args.req.searchParams ?? new URLSearchParams(),\n })\n\n return result.isRouted\n },\n handleRequest: createHandleRequest({ dynamicDefaults, sharpDependency }),\n init: (config) => {\n assertDynamicCollectionsExist({ config, dynamicOptions })\n assertCollectionsOwnedOnce({ collections, config, transformer })\n\n return initSharpCollections({ collections, config })\n },\n mimeTypes: DEFAULT_MIME_TYPES,\n [uploadTransformerInternal]: {\n handlesCollection,\n prepareUpload: createPrepareLegacyUpload({ collections, sharpDependency }),\n },\n // `options` here is always what this transformer computed via `prepareUpload`'s\n // `transform` callback; the public contract's `unknown` just reflects that core never inspects it.\n transformFile: createTransformFile({ sharpDependency }) as (\n args: TransformFileArgs,\n ) => Promise<TransformFileResult>,\n }\n\n return transformer\n}\n\n/**\n * Core drives each upload through a single Sharp instance, so a collection's upload settings\n * split across instances would silently lose all but one of them.\n */\nfunction assertCollectionsOwnedOnce({\n collections,\n config,\n transformer,\n}: {\n collections: NonNullable<SharpTransformerOptions['collections']>\n config: Config\n transformer: UploadTransformer\n}): void {\n for (const [collectionSlug, collectionConfig] of Object.entries(collections)) {\n if (!collectionConfig) {\n continue\n }\n\n const owners = (config.upload?.transformers ?? []).filter(\n (candidate) =>\n candidate === transformer ||\n getUploadTransformerInternal(candidate)?.handlesCollection?.({ collectionSlug }),\n )\n\n if (owners.length > 1) {\n throw new Error(\n `Invalid \\`sharpTransformer({ collections })\\` configuration: collection \"${collectionSlug}\" has upload settings on more than one Sharp transformer: ${owners.map((owner) => `\"${owner.slug}\"`).join(', ')}. Configure each collection on exactly one instance.`,\n )\n }\n }\n}\n\nfunction assertDynamicCollectionsExist({\n config,\n dynamicOptions,\n}: {\n config: Config\n dynamicOptions: false | SharpDynamicOptions\n}): void {\n if (!dynamicOptions || !dynamicOptions.collections) {\n return\n }\n\n const invalidSlugs = dynamicOptions.collections.filter(\n (slug) =>\n !config.collections?.some((collection) => collection.slug === slug && collection.upload),\n )\n\n if (invalidSlugs.length > 0) {\n throw new Error(\n `Invalid \\`sharpTransformer({ dynamic: { collections } })\\` configuration: not an upload-enabled collection: ${invalidSlugs.map((slug) => `\"${slug}\"`).join(', ')}.`,\n )\n }\n}\n"],"names":["getUploadTransformerInternal","uploadTransformerInternal","bundledSharp","createHandleRequest","initSharpCollections","parseDynamicResize","createPrepareLegacyUpload","createTransformFile","DEFAULT_MIME_TYPES","resolveSharpDynamicDefaults","overrides","fit","maxHeight","maxPixels","maxWidth","position","withoutEnlargement","resolveSharpDynamicOptions","dynamic","sharpTransformer","options","dynamicOptions","dynamicDefaults","undefined","sharpDependency","sharp","collections","handlesCollection","collectionSlug","Boolean","transformer","slug","canTransform","args","operation","includes","result","limits","searchParams","req","URLSearchParams","isRouted","handleRequest","init","config","assertDynamicCollectionsExist","assertCollectionsOwnedOnce","mimeTypes","prepareUpload","transformFile","collectionConfig","Object","entries","owners","upload","transformers","filter","candidate","length","Error","map","owner","join","invalidSlugs","some","collection"],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { resolveSharpDynamicDefaults, sharpTransformer } from './sharpTransformer.js';
|
|
3
|
+
describe('sharpTransformer', ()=>{
|
|
4
|
+
it("should default mimeTypes to canResizeImage's allow-list exactly, excluding jxl", ()=>{
|
|
5
|
+
expect(sharpTransformer().mimeTypes).toEqual([
|
|
6
|
+
'image/jpeg',
|
|
7
|
+
'image/png',
|
|
8
|
+
'image/gif',
|
|
9
|
+
'image/webp',
|
|
10
|
+
'image/tiff',
|
|
11
|
+
'image/avif'
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
it('should reject dynamic.collections that are unknown or not upload-enabled on init', ()=>{
|
|
15
|
+
const config = {
|
|
16
|
+
collections: [
|
|
17
|
+
{
|
|
18
|
+
slug: 'media',
|
|
19
|
+
fields: [],
|
|
20
|
+
upload: true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
slug: 'posts',
|
|
24
|
+
fields: []
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
const transformer = sharpTransformer({
|
|
29
|
+
dynamic: {
|
|
30
|
+
collections: [
|
|
31
|
+
'posts',
|
|
32
|
+
'missing'
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
expect(()=>transformer.init(config)).toThrow(/not an upload-enabled collection: "posts", "missing"/);
|
|
37
|
+
});
|
|
38
|
+
it('should reject a collection whose upload settings are configured on more than one instance on init', ()=>{
|
|
39
|
+
const first = sharpTransformer({
|
|
40
|
+
collections: {
|
|
41
|
+
media: {
|
|
42
|
+
variants: []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const second = sharpTransformer({
|
|
47
|
+
slug: 'sharp-second',
|
|
48
|
+
collections: {
|
|
49
|
+
media: {}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const config = {
|
|
53
|
+
collections: [
|
|
54
|
+
{
|
|
55
|
+
slug: 'media',
|
|
56
|
+
fields: [],
|
|
57
|
+
upload: true
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
upload: {
|
|
61
|
+
transformers: [
|
|
62
|
+
first,
|
|
63
|
+
second
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
expect(()=>second.init(config)).toThrow(/"media" has upload settings on more than one Sharp transformer: "sharp", "sharp-second"/);
|
|
68
|
+
});
|
|
69
|
+
it('should allow different collections configured on different instances', ()=>{
|
|
70
|
+
const first = sharpTransformer({
|
|
71
|
+
collections: {
|
|
72
|
+
media: {}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const second = sharpTransformer({
|
|
76
|
+
slug: 'sharp-second',
|
|
77
|
+
collections: {
|
|
78
|
+
photos: {}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
const config = {
|
|
82
|
+
collections: [
|
|
83
|
+
{
|
|
84
|
+
slug: 'media',
|
|
85
|
+
fields: [],
|
|
86
|
+
upload: true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
slug: 'photos',
|
|
90
|
+
fields: [],
|
|
91
|
+
upload: true
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
upload: {
|
|
95
|
+
transformers: [
|
|
96
|
+
first,
|
|
97
|
+
second
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
expect(()=>second.init(config)).not.toThrow();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('resolveSharpDynamicDefaults', ()=>{
|
|
105
|
+
it('should default to fit=cover, position=center, maxWidth=4096, maxHeight=4096, maxPixels=16_777_216, withoutEnlargement=false', ()=>{
|
|
106
|
+
expect(resolveSharpDynamicDefaults()).toEqual({
|
|
107
|
+
fit: 'cover',
|
|
108
|
+
maxHeight: 4096,
|
|
109
|
+
maxPixels: 16_777_216,
|
|
110
|
+
maxWidth: 4096,
|
|
111
|
+
position: 'center',
|
|
112
|
+
withoutEnlargement: false
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=sharpTransformer.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sharpTransformer.spec.ts"],"sourcesContent":["import type { Config, UploadCollectionSlug } from 'payload'\n\nimport { describe, expect, it } from 'vitest'\n\nimport { resolveSharpDynamicDefaults, sharpTransformer } from './sharpTransformer.js'\n\ndescribe('sharpTransformer', () => {\n it(\"should default mimeTypes to canResizeImage's allow-list exactly, excluding jxl\", () => {\n expect(sharpTransformer().mimeTypes).toEqual([\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/tiff',\n 'image/avif',\n ])\n })\n\n it('should reject dynamic.collections that are unknown or not upload-enabled on init', () => {\n const config = {\n collections: [\n { slug: 'media', fields: [], upload: true },\n { slug: 'posts', fields: [] },\n ],\n } as unknown as Config\n const transformer = sharpTransformer({\n dynamic: { collections: ['posts', 'missing'] as unknown as UploadCollectionSlug[] },\n })\n\n expect(() => transformer.init!(config)).toThrow(\n /not an upload-enabled collection: \"posts\", \"missing\"/,\n )\n })\n\n it('should reject a collection whose upload settings are configured on more than one instance on init', () => {\n const first = sharpTransformer({ collections: { media: { variants: [] } } })\n const second = sharpTransformer({ slug: 'sharp-second', collections: { media: {} } })\n const config = {\n collections: [{ slug: 'media', fields: [], upload: true }],\n upload: { transformers: [first, second] },\n } as unknown as Config\n\n expect(() => second.init!(config)).toThrow(\n /\"media\" has upload settings on more than one Sharp transformer: \"sharp\", \"sharp-second\"/,\n )\n })\n\n it('should allow different collections configured on different instances', () => {\n const first = sharpTransformer({ collections: { media: {} } })\n const second = sharpTransformer({ slug: 'sharp-second', collections: { photos: {} } })\n const config = {\n collections: [\n { slug: 'media', fields: [], upload: true },\n { slug: 'photos', fields: [], upload: true },\n ],\n upload: { transformers: [first, second] },\n } as unknown as Config\n\n expect(() => second.init!(config)).not.toThrow()\n })\n})\n\ndescribe('resolveSharpDynamicDefaults', () => {\n it('should default to fit=cover, position=center, maxWidth=4096, maxHeight=4096, maxPixels=16_777_216, withoutEnlargement=false', () => {\n expect(resolveSharpDynamicDefaults()).toEqual({\n fit: 'cover',\n maxHeight: 4096,\n maxPixels: 16_777_216,\n maxWidth: 4096,\n position: 'center',\n withoutEnlargement: false,\n })\n })\n})\n"],"names":["describe","expect","it","resolveSharpDynamicDefaults","sharpTransformer","mimeTypes","toEqual","config","collections","slug","fields","upload","transformer","dynamic","init","toThrow","first","media","variants","second","transformers","photos","not","fit","maxHeight","maxPixels","maxWidth","position","withoutEnlargement"],"mappings":"AAEA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,2BAA2B,EAAEC,gBAAgB,QAAQ,wBAAuB;AAErFJ,SAAS,oBAAoB;IAC3BE,GAAG,kFAAkF;QACnFD,OAAOG,mBAAmBC,SAAS,EAAEC,OAAO,CAAC;YAC3C;YACA;YACA;YACA;YACA;YACA;SACD;IACH;IAEAJ,GAAG,oFAAoF;QACrF,MAAMK,SAAS;YACbC,aAAa;gBACX;oBAAEC,MAAM;oBAASC,QAAQ,EAAE;oBAAEC,QAAQ;gBAAK;gBAC1C;oBAAEF,MAAM;oBAASC,QAAQ,EAAE;gBAAC;aAC7B;QACH;QACA,MAAME,cAAcR,iBAAiB;YACnCS,SAAS;gBAAEL,aAAa;oBAAC;oBAAS;iBAAU;YAAsC;QACpF;QAEAP,OAAO,IAAMW,YAAYE,IAAI,CAAEP,SAASQ,OAAO,CAC7C;IAEJ;IAEAb,GAAG,qGAAqG;QACtG,MAAMc,QAAQZ,iBAAiB;YAAEI,aAAa;gBAAES,OAAO;oBAAEC,UAAU,EAAE;gBAAC;YAAE;QAAE;QAC1E,MAAMC,SAASf,iBAAiB;YAAEK,MAAM;YAAgBD,aAAa;gBAAES,OAAO,CAAC;YAAE;QAAE;QACnF,MAAMV,SAAS;YACbC,aAAa;gBAAC;oBAAEC,MAAM;oBAASC,QAAQ,EAAE;oBAAEC,QAAQ;gBAAK;aAAE;YAC1DA,QAAQ;gBAAES,cAAc;oBAACJ;oBAAOG;iBAAO;YAAC;QAC1C;QAEAlB,OAAO,IAAMkB,OAAOL,IAAI,CAAEP,SAASQ,OAAO,CACxC;IAEJ;IAEAb,GAAG,wEAAwE;QACzE,MAAMc,QAAQZ,iBAAiB;YAAEI,aAAa;gBAAES,OAAO,CAAC;YAAE;QAAE;QAC5D,MAAME,SAASf,iBAAiB;YAAEK,MAAM;YAAgBD,aAAa;gBAAEa,QAAQ,CAAC;YAAE;QAAE;QACpF,MAAMd,SAAS;YACbC,aAAa;gBACX;oBAAEC,MAAM;oBAASC,QAAQ,EAAE;oBAAEC,QAAQ;gBAAK;gBAC1C;oBAAEF,MAAM;oBAAUC,QAAQ,EAAE;oBAAEC,QAAQ;gBAAK;aAC5C;YACDA,QAAQ;gBAAES,cAAc;oBAACJ;oBAAOG;iBAAO;YAAC;QAC1C;QAEAlB,OAAO,IAAMkB,OAAOL,IAAI,CAAEP,SAASe,GAAG,CAACP,OAAO;IAChD;AACF;AAEAf,SAAS,+BAA+B;IACtCE,GAAG,+HAA+H;QAChID,OAAOE,+BAA+BG,OAAO,CAAC;YAC5CiB,KAAK;YACLC,WAAW;YACXC,WAAW;YACXC,UAAU;YACVC,UAAU;YACVC,oBAAoB;QACtB;IACF;AACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TransformFileArgs, TransformFileResult } from 'payload';
|
|
2
|
+
import type { SharpDependency, SharpUploadTaskOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The Sharp package's public one-file-in/one-file-out upload primitive.
|
|
5
|
+
* `options.kind` selects which legacy task to run: the main file (optionally
|
|
6
|
+
* cropped, with `resizeOptions` re-applied to the crop output), or one named
|
|
7
|
+
* legacy image size. Never writes to storage.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createTransformFile({ sharpDependency }: {
|
|
10
|
+
sharpDependency: SharpDependency;
|
|
11
|
+
}): ({ file, options, req, }: TransformFileArgs<SharpUploadTaskOptions>) => Promise<TransformFileResult>;
|
|
12
|
+
//# sourceMappingURL=transformFile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transformFile.d.ts","sourceRoot":"","sources":["../src/transformFile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGrF,OAAO,KAAK,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AASzE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,eAAe,EAAE,EAAE;IAAE,eAAe,EAAE,eAAe,CAAA;CAAE,6BAKxF,iBAAiB,CAAC,sBAAsB,CAAC,KAAG,OAAO,CAAC,mBAAmB,CAAC,CAO5E"}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { createSharpFromFile } from './createSharpFromFile.js';
|
|
2
|
+
import { optionallyAppendMetadata } from './optionallyAppendMetadata.js';
|
|
3
|
+
const ANIMATED_MIME_TYPES = [
|
|
4
|
+
'image/avif',
|
|
5
|
+
'image/gif',
|
|
6
|
+
'image/webp'
|
|
7
|
+
];
|
|
8
|
+
const percentToPixel = (value, dimension)=>Math.floor(value / 100 * dimension);
|
|
9
|
+
/**
|
|
10
|
+
* The Sharp package's public one-file-in/one-file-out upload primitive.
|
|
11
|
+
* `options.kind` selects which legacy task to run: the main file (optionally
|
|
12
|
+
* cropped, with `resizeOptions` re-applied to the crop output), or one named
|
|
13
|
+
* legacy image size. Never writes to storage.
|
|
14
|
+
*/ export function createTransformFile({ sharpDependency }) {
|
|
15
|
+
return async function transformFile({ file, options, req }) {
|
|
16
|
+
if (options.kind === 'main') {
|
|
17
|
+
return transformMain({
|
|
18
|
+
file,
|
|
19
|
+
options,
|
|
20
|
+
req,
|
|
21
|
+
sharpDependency
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return transformSize({
|
|
25
|
+
file,
|
|
26
|
+
options,
|
|
27
|
+
req,
|
|
28
|
+
sharpDependency
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function transformMain({ file, options, req, sharpDependency }) {
|
|
33
|
+
const { collectionUpload, crop } = options;
|
|
34
|
+
const { constructorOptions, formatOptions, resizeOptions, trimOptions, withMetadata } = collectionUpload;
|
|
35
|
+
const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type);
|
|
36
|
+
const fileHasAdjustments = Boolean(resizeOptions || formatOptions || trimOptions || constructorOptions);
|
|
37
|
+
if (crop) {
|
|
38
|
+
return transformCrop({
|
|
39
|
+
crop,
|
|
40
|
+
file,
|
|
41
|
+
fileIsAnimatedType,
|
|
42
|
+
req,
|
|
43
|
+
resizeOptions,
|
|
44
|
+
sharpDependency,
|
|
45
|
+
withMetadata
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (!fileIsAnimatedType && !fileHasAdjustments) {
|
|
49
|
+
return {
|
|
50
|
+
status: 'continue'
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const sharpOptions = {
|
|
54
|
+
...constructorOptions
|
|
55
|
+
};
|
|
56
|
+
if (fileIsAnimatedType) {
|
|
57
|
+
sharpOptions.animated = true;
|
|
58
|
+
}
|
|
59
|
+
// pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
|
60
|
+
let sharpFile = (await createSharpFromFile({
|
|
61
|
+
file,
|
|
62
|
+
options: sharpOptions,
|
|
63
|
+
sharpDependency
|
|
64
|
+
})).rotate();
|
|
65
|
+
if (resizeOptions) {
|
|
66
|
+
sharpFile = sharpFile.resize(resizeOptions);
|
|
67
|
+
}
|
|
68
|
+
if (formatOptions) {
|
|
69
|
+
sharpFile = sharpFile.toFormat(formatOptions.format, formatOptions.options);
|
|
70
|
+
}
|
|
71
|
+
if (trimOptions) {
|
|
72
|
+
sharpFile = sharpFile.trim(trimOptions);
|
|
73
|
+
}
|
|
74
|
+
sharpFile = await optionallyAppendMetadata({
|
|
75
|
+
req,
|
|
76
|
+
sharpFile,
|
|
77
|
+
withMetadata
|
|
78
|
+
});
|
|
79
|
+
const { data: outputData } = await sharpFile.toBuffer({
|
|
80
|
+
resolveWithObject: true
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
file: new File([
|
|
84
|
+
outputData
|
|
85
|
+
], file.name, {
|
|
86
|
+
type: file.type
|
|
87
|
+
}),
|
|
88
|
+
status: 'continue'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function transformCrop({ crop, file, fileIsAnimatedType, req, resizeOptions, sharpDependency, withMetadata }) {
|
|
92
|
+
const { cropData, heightInPixels, originalDimensions, widthInPixels } = crop;
|
|
93
|
+
const sharpOptions = fileIsAnimatedType ? {
|
|
94
|
+
animated: true
|
|
95
|
+
} : {};
|
|
96
|
+
const newWidth = Number(widthInPixels);
|
|
97
|
+
const newHeight = Number(heightInPixels);
|
|
98
|
+
const dimensionsChanged = originalDimensions.width !== newWidth || originalDimensions.height !== newHeight;
|
|
99
|
+
let croppedFile = file;
|
|
100
|
+
if (dimensionsChanged) {
|
|
101
|
+
const formattedCropData = {
|
|
102
|
+
height: newHeight,
|
|
103
|
+
left: percentToPixel(cropData.x, originalDimensions.width),
|
|
104
|
+
top: percentToPixel(cropData.y, originalDimensions.height),
|
|
105
|
+
width: newWidth
|
|
106
|
+
};
|
|
107
|
+
let cropped = (await createSharpFromFile({
|
|
108
|
+
file,
|
|
109
|
+
options: sharpOptions,
|
|
110
|
+
sharpDependency
|
|
111
|
+
})).extract(formattedCropData);
|
|
112
|
+
cropped = await optionallyAppendMetadata({
|
|
113
|
+
req,
|
|
114
|
+
sharpFile: cropped,
|
|
115
|
+
withMetadata
|
|
116
|
+
});
|
|
117
|
+
const { data } = await cropped.toBuffer({
|
|
118
|
+
resolveWithObject: true
|
|
119
|
+
});
|
|
120
|
+
croppedFile = new File([
|
|
121
|
+
data
|
|
122
|
+
], file.name, {
|
|
123
|
+
type: file.type
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (resizeOptions && !resizeOptions.withoutEnlargement) {
|
|
127
|
+
const sharpFile = await createSharpFromFile({
|
|
128
|
+
file: croppedFile,
|
|
129
|
+
sharpDependency
|
|
130
|
+
});
|
|
131
|
+
const { data: resizedBuffer } = await sharpFile.resize({
|
|
132
|
+
fit: resizeOptions.fit || 'cover',
|
|
133
|
+
height: resizeOptions.height,
|
|
134
|
+
position: resizeOptions.position || 'center',
|
|
135
|
+
width: resizeOptions.width
|
|
136
|
+
}).toBuffer({
|
|
137
|
+
resolveWithObject: true
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
file: new File([
|
|
141
|
+
resizedBuffer
|
|
142
|
+
], file.name, {
|
|
143
|
+
type: file.type
|
|
144
|
+
}),
|
|
145
|
+
status: 'continue'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
file: croppedFile,
|
|
150
|
+
status: 'continue'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function transformSize({ file, options, req, sharpDependency }) {
|
|
154
|
+
const { collectionUpload, focalPoint, imageResizeConfig, originalDimensions } = options;
|
|
155
|
+
const { withMetadata } = collectionUpload;
|
|
156
|
+
const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type);
|
|
157
|
+
const sharpOptions = fileIsAnimatedType ? {
|
|
158
|
+
animated: true
|
|
159
|
+
} : {};
|
|
160
|
+
const sharpBase = (await createSharpFromFile({
|
|
161
|
+
file,
|
|
162
|
+
options: sharpOptions,
|
|
163
|
+
sharpDependency
|
|
164
|
+
})).rotate();
|
|
165
|
+
const originalImageMeta = await sharpBase.metadata();
|
|
166
|
+
let adjustedDimensions = {
|
|
167
|
+
...originalDimensions
|
|
168
|
+
};
|
|
169
|
+
if ([
|
|
170
|
+
5,
|
|
171
|
+
6,
|
|
172
|
+
7,
|
|
173
|
+
8
|
|
174
|
+
].includes(originalImageMeta.orientation)) {
|
|
175
|
+
adjustedDimensions = {
|
|
176
|
+
height: originalDimensions.width,
|
|
177
|
+
width: originalDimensions.height
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
let resized = sharpBase.clone();
|
|
181
|
+
if (focalPoint) {
|
|
182
|
+
let { height: resizeHeight, width: resizeWidth } = imageResizeConfig;
|
|
183
|
+
const originalAspectRatio = adjustedDimensions.width / adjustedDimensions.height;
|
|
184
|
+
if (resizeHeight && !resizeWidth) {
|
|
185
|
+
resizeWidth = Math.round(resizeHeight * originalAspectRatio);
|
|
186
|
+
}
|
|
187
|
+
if (resizeWidth && !resizeHeight) {
|
|
188
|
+
resizeHeight = Math.round(resizeWidth / originalAspectRatio);
|
|
189
|
+
}
|
|
190
|
+
if (!resizeHeight) {
|
|
191
|
+
resizeHeight = originalImageMeta.height;
|
|
192
|
+
}
|
|
193
|
+
if (!resizeWidth) {
|
|
194
|
+
resizeWidth = originalImageMeta.width;
|
|
195
|
+
}
|
|
196
|
+
const resizeAspectRatio = resizeWidth / resizeHeight;
|
|
197
|
+
const prioritizeHeight = resizeAspectRatio < originalAspectRatio;
|
|
198
|
+
resized = resized.resize({
|
|
199
|
+
fastShrinkOnLoad: false,
|
|
200
|
+
height: prioritizeHeight ? resizeHeight : undefined,
|
|
201
|
+
width: prioritizeHeight ? undefined : resizeWidth
|
|
202
|
+
});
|
|
203
|
+
const metadataAppended = await optionallyAppendMetadata({
|
|
204
|
+
req,
|
|
205
|
+
sharpFile: resized,
|
|
206
|
+
withMetadata
|
|
207
|
+
});
|
|
208
|
+
const { info } = await metadataAppended.toBuffer({
|
|
209
|
+
resolveWithObject: true
|
|
210
|
+
});
|
|
211
|
+
const resizedHeight = originalImageMeta.pages ? info.height / originalImageMeta.pages : info.height;
|
|
212
|
+
const resizedWidth = info.width;
|
|
213
|
+
const halfResizeX = resizeWidth / 2;
|
|
214
|
+
const xFocalCenter = resizedWidth * (focalPoint.x / 100);
|
|
215
|
+
let leftBound = xFocalCenter - halfResizeX;
|
|
216
|
+
if (xFocalCenter + halfResizeX > resizedWidth) {
|
|
217
|
+
leftBound = resizedWidth - resizeWidth;
|
|
218
|
+
}
|
|
219
|
+
if (leftBound < 0) {
|
|
220
|
+
leftBound = 0;
|
|
221
|
+
}
|
|
222
|
+
const halfResizeY = resizeHeight / 2;
|
|
223
|
+
const yFocalCenter = resizedHeight * (focalPoint.y / 100);
|
|
224
|
+
let topBound = yFocalCenter - halfResizeY;
|
|
225
|
+
if (yFocalCenter + halfResizeY > resizedHeight) {
|
|
226
|
+
topBound = resizedHeight - resizeHeight;
|
|
227
|
+
}
|
|
228
|
+
if (topBound < 0) {
|
|
229
|
+
topBound = 0;
|
|
230
|
+
}
|
|
231
|
+
resized = resized.extract({
|
|
232
|
+
height: resizeHeight,
|
|
233
|
+
left: Math.floor(leftBound),
|
|
234
|
+
top: Math.floor(topBound),
|
|
235
|
+
width: resizeWidth
|
|
236
|
+
});
|
|
237
|
+
} else {
|
|
238
|
+
resized = resized.resize(imageResizeConfig);
|
|
239
|
+
}
|
|
240
|
+
if (imageResizeConfig.formatOptions) {
|
|
241
|
+
resized = resized.toFormat(imageResizeConfig.formatOptions.format, imageResizeConfig.formatOptions.options);
|
|
242
|
+
}
|
|
243
|
+
if (imageResizeConfig.trimOptions) {
|
|
244
|
+
resized = resized.trim(imageResizeConfig.trimOptions);
|
|
245
|
+
}
|
|
246
|
+
resized = await optionallyAppendMetadata({
|
|
247
|
+
req,
|
|
248
|
+
sharpFile: resized,
|
|
249
|
+
withMetadata
|
|
250
|
+
});
|
|
251
|
+
const { data: outputData } = await resized.toBuffer({
|
|
252
|
+
resolveWithObject: true
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
file: new File([
|
|
256
|
+
outputData
|
|
257
|
+
], file.name, {
|
|
258
|
+
type: file.type
|
|
259
|
+
}),
|
|
260
|
+
status: 'continue'
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
//# sourceMappingURL=transformFile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transformFile.ts"],"sourcesContent":["import type { PayloadRequest, TransformFileArgs, TransformFileResult } from 'payload'\nimport type { ResizeOptions, SharpOptions } from 'sharp'\n\nimport type { SharpDependency, SharpUploadTaskOptions } from './types.js'\n\nimport { createSharpFromFile } from './createSharpFromFile.js'\nimport { optionallyAppendMetadata } from './optionallyAppendMetadata.js'\n\nconst ANIMATED_MIME_TYPES = ['image/avif', 'image/gif', 'image/webp']\n\nconst percentToPixel = (value: number, dimension: number) => Math.floor((value / 100) * dimension)\n\n/**\n * The Sharp package's public one-file-in/one-file-out upload primitive.\n * `options.kind` selects which legacy task to run: the main file (optionally\n * cropped, with `resizeOptions` re-applied to the crop output), or one named\n * legacy image size. Never writes to storage.\n */\nexport function createTransformFile({ sharpDependency }: { sharpDependency: SharpDependency }) {\n return async function transformFile({\n file,\n options,\n req,\n }: TransformFileArgs<SharpUploadTaskOptions>): Promise<TransformFileResult> {\n if (options.kind === 'main') {\n return transformMain({ file, options, req, sharpDependency })\n }\n\n return transformSize({ file, options, req, sharpDependency })\n }\n}\n\nasync function transformMain({\n file,\n options,\n req,\n sharpDependency,\n}: {\n file: File\n options: Extract<SharpUploadTaskOptions, { kind: 'main' }>\n req: PayloadRequest\n sharpDependency: SharpDependency\n}): Promise<TransformFileResult> {\n const { collectionUpload, crop } = options\n const { constructorOptions, formatOptions, resizeOptions, trimOptions, withMetadata } =\n collectionUpload\n\n const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type)\n const fileHasAdjustments = Boolean(\n resizeOptions || formatOptions || trimOptions || constructorOptions,\n )\n\n if (crop) {\n return transformCrop({\n crop,\n file,\n fileIsAnimatedType,\n req,\n resizeOptions,\n sharpDependency,\n withMetadata,\n })\n }\n\n if (!fileIsAnimatedType && !fileHasAdjustments) {\n return { status: 'continue' }\n }\n\n const sharpOptions: SharpOptions = { ...constructorOptions }\n\n if (fileIsAnimatedType) {\n sharpOptions.animated = true\n }\n\n // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081\n let sharpFile = (\n await createSharpFromFile({ file, options: sharpOptions, sharpDependency })\n ).rotate()\n\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 sharpFile = await optionallyAppendMetadata({ req, sharpFile, withMetadata })\n const { data: outputData } = await sharpFile.toBuffer({ resolveWithObject: true })\n\n return {\n file: new File([outputData], file.name, { type: file.type }),\n status: 'continue',\n }\n}\n\nasync function transformCrop({\n crop,\n file,\n fileIsAnimatedType,\n req,\n resizeOptions,\n sharpDependency,\n withMetadata,\n}: {\n crop: NonNullable<Extract<SharpUploadTaskOptions, { kind: 'main' }>['crop']>\n file: File\n fileIsAnimatedType: boolean\n req: PayloadRequest\n resizeOptions: ResizeOptions | undefined\n sharpDependency: SharpDependency\n withMetadata: SharpUploadTaskOptions['collectionUpload']['withMetadata']\n}): Promise<TransformFileResult> {\n const { cropData, heightInPixels, originalDimensions, widthInPixels } = crop\n\n const sharpOptions: SharpOptions = fileIsAnimatedType ? { animated: true } : {}\n\n const newWidth = Number(widthInPixels)\n const newHeight = Number(heightInPixels)\n const dimensionsChanged =\n originalDimensions.width !== newWidth || originalDimensions.height !== newHeight\n\n let croppedFile = file\n\n if (dimensionsChanged) {\n const formattedCropData = {\n height: newHeight,\n left: percentToPixel(cropData.x, originalDimensions.width),\n top: percentToPixel(cropData.y, originalDimensions.height),\n width: newWidth,\n }\n\n let cropped = (\n await createSharpFromFile({ file, options: sharpOptions, sharpDependency })\n ).extract(formattedCropData)\n cropped = await optionallyAppendMetadata({ req, sharpFile: cropped, withMetadata })\n const { data } = await cropped.toBuffer({ resolveWithObject: true })\n croppedFile = new File([data], file.name, { type: file.type })\n }\n\n if (resizeOptions && !resizeOptions.withoutEnlargement) {\n const sharpFile = await createSharpFromFile({ file: croppedFile, sharpDependency })\n const { data: resizedBuffer } = await sharpFile\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 return {\n file: new File([resizedBuffer], file.name, { type: file.type }),\n status: 'continue',\n }\n }\n\n return {\n file: croppedFile,\n status: 'continue',\n }\n}\n\nasync function transformSize({\n file,\n options,\n req,\n sharpDependency,\n}: {\n file: File\n options: Extract<SharpUploadTaskOptions, { kind: 'size' }>\n req: PayloadRequest\n sharpDependency: SharpDependency\n}): Promise<TransformFileResult> {\n const { collectionUpload, focalPoint, imageResizeConfig, originalDimensions } = options\n const { withMetadata } = collectionUpload\n\n const fileIsAnimatedType = ANIMATED_MIME_TYPES.includes(file.type)\n const sharpOptions: SharpOptions = fileIsAnimatedType ? { animated: true } : {}\n\n const sharpBase = (\n await createSharpFromFile({ file, options: sharpOptions, sharpDependency })\n ).rotate()\n const originalImageMeta = await sharpBase.metadata()\n\n let adjustedDimensions = { ...originalDimensions }\n\n if ([5, 6, 7, 8].includes(originalImageMeta.orientation!)) {\n adjustedDimensions = {\n height: originalDimensions.width,\n width: originalDimensions.height,\n }\n }\n\n let resized = sharpBase.clone()\n\n if (focalPoint) {\n let { height: resizeHeight, width: resizeWidth } = imageResizeConfig\n\n const originalAspectRatio = adjustedDimensions.width / adjustedDimensions.height\n\n if (resizeHeight && !resizeWidth) {\n resizeWidth = Math.round(resizeHeight * originalAspectRatio)\n }\n if (resizeWidth && !resizeHeight) {\n resizeHeight = Math.round(resizeWidth / originalAspectRatio)\n }\n if (!resizeHeight) {\n resizeHeight = originalImageMeta.height\n }\n if (!resizeWidth) {\n resizeWidth = originalImageMeta.width\n }\n\n const resizeAspectRatio = resizeWidth / resizeHeight\n const prioritizeHeight = resizeAspectRatio < originalAspectRatio\n\n resized = resized.resize({\n fastShrinkOnLoad: false,\n height: prioritizeHeight ? resizeHeight : undefined,\n width: prioritizeHeight ? undefined : resizeWidth,\n })\n\n const metadataAppended = await optionallyAppendMetadata({\n req,\n sharpFile: resized,\n withMetadata,\n })\n const { info } = await metadataAppended.toBuffer({ resolveWithObject: true })\n\n const resizedHeight = originalImageMeta.pages\n ? info.height / originalImageMeta.pages\n : info.height\n const resizedWidth = info.width\n\n const halfResizeX = resizeWidth / 2\n const xFocalCenter = resizedWidth * (focalPoint.x / 100)\n let leftBound = xFocalCenter - halfResizeX\n if (xFocalCenter + halfResizeX > resizedWidth) {\n leftBound = resizedWidth - resizeWidth\n }\n if (leftBound < 0) {\n leftBound = 0\n }\n\n const halfResizeY = resizeHeight / 2\n const yFocalCenter = resizedHeight * (focalPoint.y / 100)\n let topBound = yFocalCenter - halfResizeY\n if (yFocalCenter + halfResizeY > resizedHeight) {\n topBound = resizedHeight - resizeHeight\n }\n if (topBound < 0) {\n topBound = 0\n }\n\n resized = resized.extract({\n height: resizeHeight,\n left: Math.floor(leftBound),\n top: Math.floor(topBound),\n width: resizeWidth,\n })\n } else {\n resized = resized.resize(imageResizeConfig)\n }\n\n if (imageResizeConfig.formatOptions) {\n resized = resized.toFormat(\n imageResizeConfig.formatOptions.format,\n imageResizeConfig.formatOptions.options,\n )\n }\n if (imageResizeConfig.trimOptions) {\n resized = resized.trim(imageResizeConfig.trimOptions)\n }\n\n resized = await optionallyAppendMetadata({ req, sharpFile: resized, withMetadata })\n const { data: outputData } = await resized.toBuffer({ resolveWithObject: true })\n\n return {\n file: new File([outputData], file.name, { type: file.type }),\n status: 'continue',\n }\n}\n"],"names":["createSharpFromFile","optionallyAppendMetadata","ANIMATED_MIME_TYPES","percentToPixel","value","dimension","Math","floor","createTransformFile","sharpDependency","transformFile","file","options","req","kind","transformMain","transformSize","collectionUpload","crop","constructorOptions","formatOptions","resizeOptions","trimOptions","withMetadata","fileIsAnimatedType","includes","type","fileHasAdjustments","Boolean","transformCrop","status","sharpOptions","animated","sharpFile","rotate","resize","toFormat","format","trim","data","outputData","toBuffer","resolveWithObject","File","name","cropData","heightInPixels","originalDimensions","widthInPixels","newWidth","Number","newHeight","dimensionsChanged","width","height","croppedFile","formattedCropData","left","x","top","y","cropped","extract","withoutEnlargement","resizedBuffer","fit","position","focalPoint","imageResizeConfig","sharpBase","originalImageMeta","metadata","adjustedDimensions","orientation","resized","clone","resizeHeight","resizeWidth","originalAspectRatio","round","resizeAspectRatio","prioritizeHeight","fastShrinkOnLoad","undefined","metadataAppended","info","resizedHeight","pages","resizedWidth","halfResizeX","xFocalCenter","leftBound","halfResizeY","yFocalCenter","topBound"],"mappings":"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"}
|