@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.
Files changed (75) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +105 -0
  3. package/dist/canResizeImage.d.ts +2 -0
  4. package/dist/canResizeImage.d.ts.map +1 -0
  5. package/dist/canResizeImage.js +12 -0
  6. package/dist/canResizeImage.js.map +1 -0
  7. package/dist/createSharpFromFile.d.ts +8 -0
  8. package/dist/createSharpFromFile.d.ts.map +1 -0
  9. package/dist/createSharpFromFile.js +31 -0
  10. package/dist/createSharpFromFile.js.map +1 -0
  11. package/dist/createSharpFromFile.spec.js +120 -0
  12. package/dist/createSharpFromFile.spec.js.map +1 -0
  13. package/dist/getImageResizeAction.d.ts +24 -0
  14. package/dist/getImageResizeAction.d.ts.map +1 -0
  15. package/dist/getImageResizeAction.js +56 -0
  16. package/dist/getImageResizeAction.js.map +1 -0
  17. package/dist/handleRequest.d.ts +14 -0
  18. package/dist/handleRequest.d.ts.map +1 -0
  19. package/dist/handleRequest.js +143 -0
  20. package/dist/handleRequest.js.map +1 -0
  21. package/dist/handleRequest.spec.js +188 -0
  22. package/dist/handleRequest.spec.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +3 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/initSharpCollections.d.ts +14 -0
  28. package/dist/initSharpCollections.d.ts.map +1 -0
  29. package/dist/initSharpCollections.js +84 -0
  30. package/dist/initSharpCollections.js.map +1 -0
  31. package/dist/initSharpCollections.spec.js +185 -0
  32. package/dist/initSharpCollections.spec.js.map +1 -0
  33. package/dist/mapWithBoundedConcurrency.d.ts +7 -0
  34. package/dist/mapWithBoundedConcurrency.d.ts.map +1 -0
  35. package/dist/mapWithBoundedConcurrency.js +26 -0
  36. package/dist/mapWithBoundedConcurrency.js.map +1 -0
  37. package/dist/mapWithBoundedConcurrency.spec.js +35 -0
  38. package/dist/mapWithBoundedConcurrency.spec.js.map +1 -0
  39. package/dist/optionallyAppendMetadata.d.ts +9 -0
  40. package/dist/optionallyAppendMetadata.d.ts.map +1 -0
  41. package/dist/optionallyAppendMetadata.js +17 -0
  42. package/dist/optionallyAppendMetadata.js.map +1 -0
  43. package/dist/parseDynamicResize.d.ts +17 -0
  44. package/dist/parseDynamicResize.d.ts.map +1 -0
  45. package/dist/parseDynamicResize.js +117 -0
  46. package/dist/parseDynamicResize.js.map +1 -0
  47. package/dist/parseDynamicResize.spec.js +123 -0
  48. package/dist/parseDynamicResize.spec.js.map +1 -0
  49. package/dist/prepareLegacyUpload.d.ts +14 -0
  50. package/dist/prepareLegacyUpload.d.ts.map +1 -0
  51. package/dist/prepareLegacyUpload.js +147 -0
  52. package/dist/prepareLegacyUpload.js.map +1 -0
  53. package/dist/prepareLegacyUpload.spec.js +95 -0
  54. package/dist/prepareLegacyUpload.spec.js.map +1 -0
  55. package/dist/sanitizeResizeConfig.d.ts +11 -0
  56. package/dist/sanitizeResizeConfig.d.ts.map +1 -0
  57. package/dist/sanitizeResizeConfig.js +20 -0
  58. package/dist/sanitizeResizeConfig.js.map +1 -0
  59. package/dist/sharpTransformer.d.ts +10 -0
  60. package/dist/sharpTransformer.d.ts.map +1 -0
  61. package/dist/sharpTransformer.js +122 -0
  62. package/dist/sharpTransformer.js.map +1 -0
  63. package/dist/sharpTransformer.spec.js +117 -0
  64. package/dist/sharpTransformer.spec.js.map +1 -0
  65. package/dist/transformFile.d.ts +12 -0
  66. package/dist/transformFile.d.ts.map +1 -0
  67. package/dist/transformFile.js +264 -0
  68. package/dist/transformFile.js.map +1 -0
  69. package/dist/transformFile.spec.js +259 -0
  70. package/dist/transformFile.spec.js.map +1 -0
  71. package/dist/types.d.ts +150 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +7 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +56 -0
@@ -0,0 +1,17 @@
1
+ export async function optionallyAppendMetadata({ req, sharpFile, withMetadata }) {
2
+ const metadata = await sharpFile.metadata();
3
+ if (withMetadata === true) {
4
+ return sharpFile.withMetadata();
5
+ } else if (typeof withMetadata === 'function') {
6
+ const useMetadata = await withMetadata({
7
+ metadata,
8
+ req
9
+ });
10
+ if (useMetadata) {
11
+ return sharpFile.withMetadata();
12
+ }
13
+ }
14
+ return sharpFile;
15
+ }
16
+
17
+ //# sourceMappingURL=optionallyAppendMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/optionallyAppendMetadata.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\nimport type { Sharp } from 'sharp'\n\nimport type { WithMetadata } from './types.js'\n\nexport async function optionallyAppendMetadata({\n req,\n sharpFile,\n withMetadata,\n}: {\n req: PayloadRequest\n sharpFile: Sharp\n withMetadata: undefined | WithMetadata\n}): Promise<Sharp> {\n const metadata = await sharpFile.metadata()\n\n if (withMetadata === true) {\n return sharpFile.withMetadata()\n } else if (typeof withMetadata === 'function') {\n const useMetadata = await withMetadata({ metadata, req })\n\n if (useMetadata) {\n return sharpFile.withMetadata()\n }\n }\n\n return sharpFile\n}\n"],"names":["optionallyAppendMetadata","req","sharpFile","withMetadata","metadata","useMetadata"],"mappings":"AAKA,OAAO,eAAeA,yBAAyB,EAC7CC,GAAG,EACHC,SAAS,EACTC,YAAY,EAKb;IACC,MAAMC,WAAW,MAAMF,UAAUE,QAAQ;IAEzC,IAAID,iBAAiB,MAAM;QACzB,OAAOD,UAAUC,YAAY;IAC/B,OAAO,IAAI,OAAOA,iBAAiB,YAAY;QAC7C,MAAME,cAAc,MAAMF,aAAa;YAAEC;YAAUH;QAAI;QAEvD,IAAII,aAAa;YACf,OAAOH,UAAUC,YAAY;QAC/B;IACF;IAEA,OAAOD;AACT"}
@@ -0,0 +1,17 @@
1
+ import type { DynamicResizeParseResult } from './types.js';
2
+ /**
3
+ * Strictly parses the v1 dynamic resize query parameters (`width`, `height`,
4
+ * `withoutEnlargement`). The presence of any of them routes the request —
5
+ * a malformed combination is `valid: false` (mapped to `400`), not treated as
6
+ * an ordinary read. Every other query key (`draft`, `depth`, `where`, `prefix`,
7
+ * …) is ignored.
8
+ */
9
+ export declare function parseDynamicResize({ limits, searchParams, }: {
10
+ limits: {
11
+ maxHeight: number;
12
+ maxPixels: number;
13
+ maxWidth: number;
14
+ };
15
+ searchParams: URLSearchParams;
16
+ }): DynamicResizeParseResult;
17
+ //# sourceMappingURL=parseDynamicResize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseDynamicResize.d.ts","sourceRoot":"","sources":["../src/parseDynamicResize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAc1D;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,YAAY,GACb,EAAE;IACD,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,YAAY,EAAE,eAAe,CAAA;CAC9B,GAAG,wBAAwB,CAiG3B"}
@@ -0,0 +1,117 @@
1
+ const POSITIVE_INTEGER_PATTERN = /^[1-9]\d*$/;
2
+ function parsePositiveInteger(value) {
3
+ if (!POSITIVE_INTEGER_PATTERN.test(value)) {
4
+ return undefined;
5
+ }
6
+ const parsed = Number(value);
7
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
8
+ }
9
+ /**
10
+ * Strictly parses the v1 dynamic resize query parameters (`width`, `height`,
11
+ * `withoutEnlargement`). The presence of any of them routes the request —
12
+ * a malformed combination is `valid: false` (mapped to `400`), not treated as
13
+ * an ordinary read. Every other query key (`draft`, `depth`, `where`, `prefix`,
14
+ * …) is ignored.
15
+ */ export function parseDynamicResize({ limits, searchParams }) {
16
+ const widthValues = searchParams.getAll('width');
17
+ const heightValues = searchParams.getAll('height');
18
+ const withoutEnlargementValues = searchParams.getAll('withoutEnlargement');
19
+ const isRouted = widthValues.length > 0 || heightValues.length > 0 || withoutEnlargementValues.length > 0;
20
+ if (!isRouted) {
21
+ return {
22
+ isRouted: false
23
+ };
24
+ }
25
+ if (widthValues.length > 1) {
26
+ return {
27
+ error: '`width` may only be specified once.',
28
+ isRouted: true,
29
+ valid: false
30
+ };
31
+ }
32
+ if (heightValues.length > 1) {
33
+ return {
34
+ error: '`height` may only be specified once.',
35
+ isRouted: true,
36
+ valid: false
37
+ };
38
+ }
39
+ if (withoutEnlargementValues.length > 1) {
40
+ return {
41
+ error: '`withoutEnlargement` may only be specified once.',
42
+ isRouted: true,
43
+ valid: false
44
+ };
45
+ }
46
+ if (widthValues.length === 0 && heightValues.length === 0) {
47
+ return {
48
+ error: 'At least one of `width` or `height` is required to resize.',
49
+ isRouted: true,
50
+ valid: false
51
+ };
52
+ }
53
+ let width;
54
+ if (widthValues.length === 1) {
55
+ width = parsePositiveInteger(widthValues[0]);
56
+ if (width === undefined) {
57
+ return {
58
+ error: '`width` must be a positive integer.',
59
+ isRouted: true,
60
+ valid: false
61
+ };
62
+ }
63
+ if (width > limits.maxWidth) {
64
+ return {
65
+ error: `\`width\` exceeds the configured maximum of ${limits.maxWidth}.`,
66
+ isRouted: true,
67
+ valid: false
68
+ };
69
+ }
70
+ }
71
+ let height;
72
+ if (heightValues.length === 1) {
73
+ height = parsePositiveInteger(heightValues[0]);
74
+ if (height === undefined) {
75
+ return {
76
+ error: '`height` must be a positive integer.',
77
+ isRouted: true,
78
+ valid: false
79
+ };
80
+ }
81
+ if (height > limits.maxHeight) {
82
+ return {
83
+ error: `\`height\` exceeds the configured maximum of ${limits.maxHeight}.`,
84
+ isRouted: true,
85
+ valid: false
86
+ };
87
+ }
88
+ }
89
+ if (width !== undefined && height !== undefined && width * height > limits.maxPixels) {
90
+ return {
91
+ error: `Requested dimensions exceed the configured maximum of ${limits.maxPixels} pixels.`,
92
+ isRouted: true,
93
+ valid: false
94
+ };
95
+ }
96
+ let withoutEnlargement;
97
+ if (withoutEnlargementValues.length === 1) {
98
+ const raw = withoutEnlargementValues[0];
99
+ if (raw !== 'true' && raw !== 'false') {
100
+ return {
101
+ error: '`withoutEnlargement` must be "true" or "false".',
102
+ isRouted: true,
103
+ valid: false
104
+ };
105
+ }
106
+ withoutEnlargement = raw === 'true';
107
+ }
108
+ return {
109
+ height,
110
+ isRouted: true,
111
+ valid: true,
112
+ width,
113
+ withoutEnlargement
114
+ };
115
+ }
116
+
117
+ //# sourceMappingURL=parseDynamicResize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parseDynamicResize.ts"],"sourcesContent":["import type { DynamicResizeParseResult } from './types.js'\n\nconst POSITIVE_INTEGER_PATTERN = /^[1-9]\\d*$/\n\nfunction parsePositiveInteger(value: string): number | undefined {\n if (!POSITIVE_INTEGER_PATTERN.test(value)) {\n return undefined\n }\n\n const parsed = Number(value)\n\n return Number.isSafeInteger(parsed) ? parsed : undefined\n}\n\n/**\n * Strictly parses the v1 dynamic resize query parameters (`width`, `height`,\n * `withoutEnlargement`). The presence of any of them routes the request —\n * a malformed combination is `valid: false` (mapped to `400`), not treated as\n * an ordinary read. Every other query key (`draft`, `depth`, `where`, `prefix`,\n * …) is ignored.\n */\nexport function parseDynamicResize({\n limits,\n searchParams,\n}: {\n limits: { maxHeight: number; maxPixels: number; maxWidth: number }\n searchParams: URLSearchParams\n}): DynamicResizeParseResult {\n const widthValues = searchParams.getAll('width')\n const heightValues = searchParams.getAll('height')\n const withoutEnlargementValues = searchParams.getAll('withoutEnlargement')\n\n const isRouted =\n widthValues.length > 0 || heightValues.length > 0 || withoutEnlargementValues.length > 0\n\n if (!isRouted) {\n return { isRouted: false }\n }\n\n if (widthValues.length > 1) {\n return { error: '`width` may only be specified once.', isRouted: true, valid: false }\n }\n\n if (heightValues.length > 1) {\n return { error: '`height` may only be specified once.', isRouted: true, valid: false }\n }\n\n if (withoutEnlargementValues.length > 1) {\n return {\n error: '`withoutEnlargement` may only be specified once.',\n isRouted: true,\n valid: false,\n }\n }\n\n if (widthValues.length === 0 && heightValues.length === 0) {\n return {\n error: 'At least one of `width` or `height` is required to resize.',\n isRouted: true,\n valid: false,\n }\n }\n\n let width: number | undefined\n\n if (widthValues.length === 1) {\n width = parsePositiveInteger(widthValues[0]!)\n\n if (width === undefined) {\n return { error: '`width` must be a positive integer.', isRouted: true, valid: false }\n }\n\n if (width > limits.maxWidth) {\n return {\n error: `\\`width\\` exceeds the configured maximum of ${limits.maxWidth}.`,\n isRouted: true,\n valid: false,\n }\n }\n }\n\n let height: number | undefined\n\n if (heightValues.length === 1) {\n height = parsePositiveInteger(heightValues[0]!)\n\n if (height === undefined) {\n return { error: '`height` must be a positive integer.', isRouted: true, valid: false }\n }\n\n if (height > limits.maxHeight) {\n return {\n error: `\\`height\\` exceeds the configured maximum of ${limits.maxHeight}.`,\n isRouted: true,\n valid: false,\n }\n }\n }\n\n if (width !== undefined && height !== undefined && width * height > limits.maxPixels) {\n return {\n error: `Requested dimensions exceed the configured maximum of ${limits.maxPixels} pixels.`,\n isRouted: true,\n valid: false,\n }\n }\n\n let withoutEnlargement: boolean | undefined\n\n if (withoutEnlargementValues.length === 1) {\n const raw = withoutEnlargementValues[0]\n\n if (raw !== 'true' && raw !== 'false') {\n return {\n error: '`withoutEnlargement` must be \"true\" or \"false\".',\n isRouted: true,\n valid: false,\n }\n }\n\n withoutEnlargement = raw === 'true'\n }\n\n return { height, isRouted: true, valid: true, width, withoutEnlargement }\n}\n"],"names":["POSITIVE_INTEGER_PATTERN","parsePositiveInteger","value","test","undefined","parsed","Number","isSafeInteger","parseDynamicResize","limits","searchParams","widthValues","getAll","heightValues","withoutEnlargementValues","isRouted","length","error","valid","width","maxWidth","height","maxHeight","maxPixels","withoutEnlargement","raw"],"mappings":"AAEA,MAAMA,2BAA2B;AAEjC,SAASC,qBAAqBC,KAAa;IACzC,IAAI,CAACF,yBAAyBG,IAAI,CAACD,QAAQ;QACzC,OAAOE;IACT;IAEA,MAAMC,SAASC,OAAOJ;IAEtB,OAAOI,OAAOC,aAAa,CAACF,UAAUA,SAASD;AACjD;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,mBAAmB,EACjCC,MAAM,EACNC,YAAY,EAIb;IACC,MAAMC,cAAcD,aAAaE,MAAM,CAAC;IACxC,MAAMC,eAAeH,aAAaE,MAAM,CAAC;IACzC,MAAME,2BAA2BJ,aAAaE,MAAM,CAAC;IAErD,MAAMG,WACJJ,YAAYK,MAAM,GAAG,KAAKH,aAAaG,MAAM,GAAG,KAAKF,yBAAyBE,MAAM,GAAG;IAEzF,IAAI,CAACD,UAAU;QACb,OAAO;YAAEA,UAAU;QAAM;IAC3B;IAEA,IAAIJ,YAAYK,MAAM,GAAG,GAAG;QAC1B,OAAO;YAAEC,OAAO;YAAuCF,UAAU;YAAMG,OAAO;QAAM;IACtF;IAEA,IAAIL,aAAaG,MAAM,GAAG,GAAG;QAC3B,OAAO;YAAEC,OAAO;YAAwCF,UAAU;YAAMG,OAAO;QAAM;IACvF;IAEA,IAAIJ,yBAAyBE,MAAM,GAAG,GAAG;QACvC,OAAO;YACLC,OAAO;YACPF,UAAU;YACVG,OAAO;QACT;IACF;IAEA,IAAIP,YAAYK,MAAM,KAAK,KAAKH,aAAaG,MAAM,KAAK,GAAG;QACzD,OAAO;YACLC,OAAO;YACPF,UAAU;YACVG,OAAO;QACT;IACF;IAEA,IAAIC;IAEJ,IAAIR,YAAYK,MAAM,KAAK,GAAG;QAC5BG,QAAQlB,qBAAqBU,WAAW,CAAC,EAAE;QAE3C,IAAIQ,UAAUf,WAAW;YACvB,OAAO;gBAAEa,OAAO;gBAAuCF,UAAU;gBAAMG,OAAO;YAAM;QACtF;QAEA,IAAIC,QAAQV,OAAOW,QAAQ,EAAE;YAC3B,OAAO;gBACLH,OAAO,CAAC,4CAA4C,EAAER,OAAOW,QAAQ,CAAC,CAAC,CAAC;gBACxEL,UAAU;gBACVG,OAAO;YACT;QACF;IACF;IAEA,IAAIG;IAEJ,IAAIR,aAAaG,MAAM,KAAK,GAAG;QAC7BK,SAASpB,qBAAqBY,YAAY,CAAC,EAAE;QAE7C,IAAIQ,WAAWjB,WAAW;YACxB,OAAO;gBAAEa,OAAO;gBAAwCF,UAAU;gBAAMG,OAAO;YAAM;QACvF;QAEA,IAAIG,SAASZ,OAAOa,SAAS,EAAE;YAC7B,OAAO;gBACLL,OAAO,CAAC,6CAA6C,EAAER,OAAOa,SAAS,CAAC,CAAC,CAAC;gBAC1EP,UAAU;gBACVG,OAAO;YACT;QACF;IACF;IAEA,IAAIC,UAAUf,aAAaiB,WAAWjB,aAAae,QAAQE,SAASZ,OAAOc,SAAS,EAAE;QACpF,OAAO;YACLN,OAAO,CAAC,sDAAsD,EAAER,OAAOc,SAAS,CAAC,QAAQ,CAAC;YAC1FR,UAAU;YACVG,OAAO;QACT;IACF;IAEA,IAAIM;IAEJ,IAAIV,yBAAyBE,MAAM,KAAK,GAAG;QACzC,MAAMS,MAAMX,wBAAwB,CAAC,EAAE;QAEvC,IAAIW,QAAQ,UAAUA,QAAQ,SAAS;YACrC,OAAO;gBACLR,OAAO;gBACPF,UAAU;gBACVG,OAAO;YACT;QACF;QAEAM,qBAAqBC,QAAQ;IAC/B;IAEA,OAAO;QAAEJ;QAAQN,UAAU;QAAMG,OAAO;QAAMC;QAAOK;IAAmB;AAC1E"}
@@ -0,0 +1,123 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseDynamicResize } from './parseDynamicResize.js';
3
+ const DEFAULT_LIMITS = {
4
+ maxHeight: 4096,
5
+ maxPixels: 16_777_216,
6
+ maxWidth: 4096
7
+ };
8
+ const parse = (query)=>parseDynamicResize({
9
+ limits: DEFAULT_LIMITS,
10
+ searchParams: new URLSearchParams(query)
11
+ });
12
+ describe('parseDynamicResize', ()=>{
13
+ it('should route a request containing only withoutEnlargement, but reject it as invalid', ()=>{
14
+ const result = parse('withoutEnlargement=true');
15
+ expect(result.isRouted).toBe(true);
16
+ expect(result).toMatchObject({
17
+ valid: false
18
+ });
19
+ });
20
+ it.each([
21
+ true,
22
+ false
23
+ ])('should parse a valid withoutEnlargement=%s alongside width', (withoutEnlargement)=>{
24
+ expect(parse(`width=500&withoutEnlargement=${withoutEnlargement}`)).toEqual({
25
+ height: undefined,
26
+ isRouted: true,
27
+ valid: true,
28
+ width: 500,
29
+ withoutEnlargement
30
+ });
31
+ });
32
+ it('should reject an invalid withoutEnlargement value', ()=>{
33
+ const result = parse('width=500&withoutEnlargement=yes');
34
+ expect(result).toMatchObject({
35
+ isRouted: true,
36
+ valid: false
37
+ });
38
+ });
39
+ it.each([
40
+ 'width=500&width=600',
41
+ 'height=500&height=600',
42
+ 'width=500&withoutEnlargement=true&withoutEnlargement=false'
43
+ ])('should reject a repeated parameter: %s', (query)=>{
44
+ expect(parse(query)).toMatchObject({
45
+ isRouted: true,
46
+ valid: false
47
+ });
48
+ });
49
+ it.each([
50
+ 'abc',
51
+ '12.5',
52
+ '-5',
53
+ '0',
54
+ '',
55
+ '5px',
56
+ ' 5'
57
+ ])('should reject a non-positive-integer width: %s', (value)=>{
58
+ const result = parse(`width=${encodeURIComponent(value)}`);
59
+ expect(result).toMatchObject({
60
+ isRouted: true,
61
+ valid: false
62
+ });
63
+ });
64
+ it('should reject an unsafe-integer width without throwing', ()=>{
65
+ const result = parse('width=99999999999999999999999999999999');
66
+ expect(result).toMatchObject({
67
+ isRouted: true,
68
+ valid: false
69
+ });
70
+ });
71
+ it('should reject a width exceeding the configured maxWidth', ()=>{
72
+ const result = parse('width=5000');
73
+ expect(result).toMatchObject({
74
+ isRouted: true,
75
+ valid: false
76
+ });
77
+ });
78
+ it('should reject a height exceeding the configured maxHeight', ()=>{
79
+ const result = parse('height=5000');
80
+ expect(result).toMatchObject({
81
+ isRouted: true,
82
+ valid: false
83
+ });
84
+ });
85
+ it('should accept a width exactly at the configured maxWidth', ()=>{
86
+ expect(parse('width=4096')).toMatchObject({
87
+ isRouted: true,
88
+ valid: true
89
+ });
90
+ });
91
+ it('should reject a width/height combination whose product exceeds maxPixels even when each dimension is individually within its own limit', ()=>{
92
+ const result = parseDynamicResize({
93
+ limits: {
94
+ maxHeight: 4096,
95
+ maxPixels: 1000,
96
+ maxWidth: 4096
97
+ },
98
+ searchParams: new URLSearchParams('width=100&height=100')
99
+ });
100
+ expect(result).toMatchObject({
101
+ isRouted: true,
102
+ valid: false
103
+ });
104
+ });
105
+ it('should accept a width/height combination within the configured maxPixels', ()=>{
106
+ const result = parseDynamicResize({
107
+ limits: {
108
+ maxHeight: 4096,
109
+ maxPixels: 1000,
110
+ maxWidth: 4096
111
+ },
112
+ searchParams: new URLSearchParams('width=10&height=10')
113
+ });
114
+ expect(result).toMatchObject({
115
+ height: 10,
116
+ isRouted: true,
117
+ valid: true,
118
+ width: 10
119
+ });
120
+ });
121
+ });
122
+
123
+ //# sourceMappingURL=parseDynamicResize.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parseDynamicResize.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { parseDynamicResize } from './parseDynamicResize.js'\n\nconst DEFAULT_LIMITS = { maxHeight: 4096, maxPixels: 16_777_216, maxWidth: 4096 }\n\nconst parse = (query: string) =>\n parseDynamicResize({ limits: DEFAULT_LIMITS, searchParams: new URLSearchParams(query) })\n\ndescribe('parseDynamicResize', () => {\n it('should route a request containing only withoutEnlargement, but reject it as invalid', () => {\n const result = parse('withoutEnlargement=true')\n expect(result.isRouted).toBe(true)\n expect(result).toMatchObject({ valid: false })\n })\n\n it.each([true, false])(\n 'should parse a valid withoutEnlargement=%s alongside width',\n (withoutEnlargement) => {\n expect(parse(`width=500&withoutEnlargement=${withoutEnlargement}`)).toEqual({\n height: undefined,\n isRouted: true,\n valid: true,\n width: 500,\n withoutEnlargement,\n })\n },\n )\n\n it('should reject an invalid withoutEnlargement value', () => {\n const result = parse('width=500&withoutEnlargement=yes')\n expect(result).toMatchObject({ isRouted: true, valid: false })\n })\n\n it.each([\n 'width=500&width=600',\n 'height=500&height=600',\n 'width=500&withoutEnlargement=true&withoutEnlargement=false',\n ])('should reject a repeated parameter: %s', (query) => {\n expect(parse(query)).toMatchObject({ isRouted: true, valid: false })\n })\n\n it.each(['abc', '12.5', '-5', '0', '', '5px', ' 5'])(\n 'should reject a non-positive-integer width: %s',\n (value) => {\n const result = parse(`width=${encodeURIComponent(value)}`)\n expect(result).toMatchObject({ isRouted: true, valid: false })\n },\n )\n\n it('should reject an unsafe-integer width without throwing', () => {\n const result = parse('width=99999999999999999999999999999999')\n expect(result).toMatchObject({ isRouted: true, valid: false })\n })\n\n it('should reject a width exceeding the configured maxWidth', () => {\n const result = parse('width=5000')\n expect(result).toMatchObject({ isRouted: true, valid: false })\n })\n\n it('should reject a height exceeding the configured maxHeight', () => {\n const result = parse('height=5000')\n expect(result).toMatchObject({ isRouted: true, valid: false })\n })\n\n it('should accept a width exactly at the configured maxWidth', () => {\n expect(parse('width=4096')).toMatchObject({ isRouted: true, valid: true })\n })\n\n it('should reject a width/height combination whose product exceeds maxPixels even when each dimension is individually within its own limit', () => {\n const result = parseDynamicResize({\n limits: { maxHeight: 4096, maxPixels: 1000, maxWidth: 4096 },\n searchParams: new URLSearchParams('width=100&height=100'),\n })\n expect(result).toMatchObject({ isRouted: true, valid: false })\n })\n\n it('should accept a width/height combination within the configured maxPixels', () => {\n const result = parseDynamicResize({\n limits: { maxHeight: 4096, maxPixels: 1000, maxWidth: 4096 },\n searchParams: new URLSearchParams('width=10&height=10'),\n })\n expect(result).toMatchObject({ height: 10, isRouted: true, valid: true, width: 10 })\n })\n})\n"],"names":["describe","expect","it","parseDynamicResize","DEFAULT_LIMITS","maxHeight","maxPixels","maxWidth","parse","query","limits","searchParams","URLSearchParams","result","isRouted","toBe","toMatchObject","valid","each","withoutEnlargement","toEqual","height","undefined","width","value","encodeURIComponent"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,kBAAkB,QAAQ,0BAAyB;AAE5D,MAAMC,iBAAiB;IAAEC,WAAW;IAAMC,WAAW;IAAYC,UAAU;AAAK;AAEhF,MAAMC,QAAQ,CAACC,QACbN,mBAAmB;QAAEO,QAAQN;QAAgBO,cAAc,IAAIC,gBAAgBH;IAAO;AAExFT,SAAS,sBAAsB;IAC7BE,GAAG,uFAAuF;QACxF,MAAMW,SAASL,MAAM;QACrBP,OAAOY,OAAOC,QAAQ,EAAEC,IAAI,CAAC;QAC7Bd,OAAOY,QAAQG,aAAa,CAAC;YAAEC,OAAO;QAAM;IAC9C;IAEAf,GAAGgB,IAAI,CAAC;QAAC;QAAM;KAAM,EACnB,8DACA,CAACC;QACClB,OAAOO,MAAM,CAAC,6BAA6B,EAAEW,oBAAoB,GAAGC,OAAO,CAAC;YAC1EC,QAAQC;YACRR,UAAU;YACVG,OAAO;YACPM,OAAO;YACPJ;QACF;IACF;IAGFjB,GAAG,qDAAqD;QACtD,MAAMW,SAASL,MAAM;QACrBP,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAEAf,GAAGgB,IAAI,CAAC;QACN;QACA;QACA;KACD,EAAE,0CAA0C,CAACT;QAC5CR,OAAOO,MAAMC,QAAQO,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IACpE;IAEAf,GAAGgB,IAAI,CAAC;QAAC;QAAO;QAAQ;QAAM;QAAK;QAAI;QAAO;KAAK,EACjD,kDACA,CAACM;QACC,MAAMX,SAASL,MAAM,CAAC,MAAM,EAAEiB,mBAAmBD,QAAQ;QACzDvB,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAGFf,GAAG,0DAA0D;QAC3D,MAAMW,SAASL,MAAM;QACrBP,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAEAf,GAAG,2DAA2D;QAC5D,MAAMW,SAASL,MAAM;QACrBP,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAEAf,GAAG,6DAA6D;QAC9D,MAAMW,SAASL,MAAM;QACrBP,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAEAf,GAAG,4DAA4D;QAC7DD,OAAOO,MAAM,eAAeQ,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAK;IAC1E;IAEAf,GAAG,0IAA0I;QAC3I,MAAMW,SAASV,mBAAmB;YAChCO,QAAQ;gBAAEL,WAAW;gBAAMC,WAAW;gBAAMC,UAAU;YAAK;YAC3DI,cAAc,IAAIC,gBAAgB;QACpC;QACAX,OAAOY,QAAQG,aAAa,CAAC;YAAEF,UAAU;YAAMG,OAAO;QAAM;IAC9D;IAEAf,GAAG,4EAA4E;QAC7E,MAAMW,SAASV,mBAAmB;YAChCO,QAAQ;gBAAEL,WAAW;gBAAMC,WAAW;gBAAMC,UAAU;YAAK;YAC3DI,cAAc,IAAIC,gBAAgB;QACpC;QACAX,OAAOY,QAAQG,aAAa,CAAC;YAAEK,QAAQ;YAAIP,UAAU;YAAMG,OAAO;YAAMM,OAAO;QAAG;IACpF;AACF"}
@@ -0,0 +1,14 @@
1
+ import type { UploadTransformerInternal } from 'payload/internal';
2
+ import type { SharpCollectionConfig, SharpDependency } from './types.js';
3
+ /**
4
+ * Computes the legacy upload tasks (the main file, optionally cropped, and every
5
+ * configured legacy image size) and hands each to the injected `transform` callback
6
+ * exactly once. Reads the Sharp-owned config from `sharpTransformer({ collections })`
7
+ * at factory-construction time — not from the sanitized collection, which only gets
8
+ * the narrowed projection `init()` writes back.
9
+ */
10
+ export declare function createPrepareLegacyUpload({ collections, sharpDependency, }: {
11
+ collections: Partial<Record<string, SharpCollectionConfig>>;
12
+ sharpDependency: SharpDependency;
13
+ }): NonNullable<UploadTransformerInternal['prepareUpload']>;
14
+ //# sourceMappingURL=prepareLegacyUpload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepareLegacyUpload.d.ts","sourceRoot":"","sources":["../src/prepareLegacyUpload.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgC,yBAAyB,EAAE,MAAM,kBAAkB,CAAA;AAI/F,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAA0B,MAAM,YAAY,CAAA;AAsBhG;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,WAAW,EACX,eAAe,GAChB,EAAE;IACD,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAA;IAC3D,eAAe,EAAE,eAAe,CAAA;CACjC,GAAG,WAAW,CAAC,yBAAyB,CAAC,eAAe,CAAC,CAAC,CA6G1D"}
@@ -0,0 +1,147 @@
1
+ import { isNumber } from 'payload/shared';
2
+ import { canResizeImage } from './canResizeImage.js';
3
+ import { createSharpFromFile } from './createSharpFromFile.js';
4
+ import { getImageResizeAction } from './getImageResizeAction.js';
5
+ import { mapWithBoundedConcurrency } from './mapWithBoundedConcurrency.js';
6
+ import { sanitizeResizeConfig } from './sanitizeResizeConfig.js';
7
+ /**
8
+ * Sharp throws on truncated/header-only files. Returns `undefined` instead —
9
+ * matching core's dependency-free probe — since unreadable dimensions shouldn't fail the upload.
10
+ */ async function tryProbe(file, sharpDependency) {
11
+ try {
12
+ const sharpFile = await createSharpFromFile({
13
+ file,
14
+ sharpDependency
15
+ });
16
+ const metadata = await sharpFile.metadata();
17
+ return metadata.width && metadata.height ? metadata : undefined;
18
+ } catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ /**
23
+ * Computes the legacy upload tasks (the main file, optionally cropped, and every
24
+ * configured legacy image size) and hands each to the injected `transform` callback
25
+ * exactly once. Reads the Sharp-owned config from `sharpTransformer({ collections })`
26
+ * at factory-construction time — not from the sanitized collection, which only gets
27
+ * the narrowed projection `init()` writes back.
28
+ */ export function createPrepareLegacyUpload({ collections, sharpDependency }) {
29
+ return async ({ collectionSlug, file, req, transform, uploadEdits })=>{
30
+ const collectionUpload = collections[collectionSlug] ?? {};
31
+ const fileSupportsResize = canResizeImage(file.type);
32
+ const cropData = uploadEdits && typeof uploadEdits === 'object' && 'crop' in uploadEdits ? uploadEdits.crop : undefined;
33
+ let originalDimensions;
34
+ if (fileSupportsResize) {
35
+ const originalMeta = await tryProbe(file, sharpDependency);
36
+ if (originalMeta) {
37
+ originalDimensions = {
38
+ height: originalMeta.height,
39
+ width: originalMeta.width
40
+ };
41
+ }
42
+ }
43
+ // Unreadable dimensions can't be cropped/resized — treat as non-resizable rather than failing the upload.
44
+ const canProcessAsImage = fileSupportsResize && originalDimensions !== undefined;
45
+ const crop = cropData && originalDimensions ? {
46
+ cropData,
47
+ heightInPixels: uploadEdits.heightInPixels,
48
+ originalDimensions,
49
+ widthInPixels: uploadEdits.widthInPixels
50
+ } : undefined;
51
+ const mainResultFile = await transform({
52
+ fieldPath: 'filename',
53
+ options: {
54
+ collectionUpload,
55
+ crop,
56
+ kind: 'main'
57
+ }
58
+ });
59
+ const mainResult = await describeResult({
60
+ fieldPath: 'filename',
61
+ fileSupportsResize: canProcessAsImage,
62
+ resultFile: mainResultFile,
63
+ sharpDependency
64
+ });
65
+ const results = [
66
+ mainResult
67
+ ];
68
+ // A cropped upload derives its sizes from the crop output, not the original.
69
+ const isCropped = Boolean(crop && mainResult.width && mainResult.height);
70
+ const sizeSourceFile = isCropped ? mainResultFile : undefined;
71
+ const sizeSourceDimensions = isCropped ? {
72
+ height: mainResult.height,
73
+ width: mainResult.width
74
+ } : originalDimensions;
75
+ // Same precedence `init()` applies: Sharp's setting, else the collection's own `focalPoint`.
76
+ const effectiveFocalPoint = collectionUpload.focalPoint ?? req.payload.collections[collectionSlug]?.config.upload?.focalPoint;
77
+ const focalPointEnabled = effectiveFocalPoint !== false;
78
+ const variants = collectionUpload.variants;
79
+ if (canProcessAsImage && Array.isArray(variants) && sizeSourceDimensions) {
80
+ const focalPoint = focalPointEnabled && uploadEdits?.focalPoint ? {
81
+ x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,
82
+ y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50
83
+ } : undefined;
84
+ const sizeResults = await mapWithBoundedConcurrency(variants, async (rawConfig)=>{
85
+ const imageResizeConfig = sanitizeResizeConfig(rawConfig);
86
+ const fieldPath = `variants.${imageResizeConfig.name}`;
87
+ const resizeAction = getImageResizeAction({
88
+ dimensions: sizeSourceDimensions,
89
+ hasFocalPoint: Boolean(focalPoint),
90
+ imageResizeConfig
91
+ });
92
+ if (resizeAction === 'omit') {
93
+ return {
94
+ fieldPath
95
+ };
96
+ }
97
+ const sizeResultFile = await transform({
98
+ fieldPath,
99
+ file: sizeSourceFile,
100
+ options: {
101
+ collectionUpload,
102
+ focalPoint: resizeAction === 'resizeWithFocalPoint' ? focalPoint : undefined,
103
+ imageResizeConfig,
104
+ kind: 'size',
105
+ originalDimensions: sizeSourceDimensions
106
+ }
107
+ });
108
+ return describeResult({
109
+ fieldPath,
110
+ fileSupportsResize: true,
111
+ resultFile: sizeResultFile,
112
+ sharpDependency
113
+ });
114
+ });
115
+ results.push(...sizeResults);
116
+ }
117
+ return results;
118
+ };
119
+ }
120
+ async function describeResult({ fieldPath, fileSupportsResize, resultFile, sharpDependency }) {
121
+ if (!fileSupportsResize) {
122
+ return {
123
+ fieldPath,
124
+ file: resultFile,
125
+ mimeType: resultFile.type
126
+ };
127
+ }
128
+ const meta = await tryProbe(resultFile, sharpDependency);
129
+ if (!meta) {
130
+ return {
131
+ fieldPath,
132
+ file: resultFile,
133
+ mimeType: resultFile.type
134
+ };
135
+ }
136
+ // `tryProbe` never sets `animated: true`, so `meta.height` is already the
137
+ // single-frame height — do not divide by page count here.
138
+ return {
139
+ fieldPath,
140
+ file: resultFile,
141
+ height: meta.height,
142
+ mimeType: resultFile.type,
143
+ width: meta.width
144
+ };
145
+ }
146
+
147
+ //# sourceMappingURL=prepareLegacyUpload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prepareLegacyUpload.ts"],"sourcesContent":["import type { FocalPoint, ProbedImageSize } from 'payload'\nimport type { PreparedUploadTransformation, UploadTransformerInternal } from 'payload/internal'\n\nimport { isNumber } from 'payload/shared'\n\nimport type { SharpCollectionConfig, SharpDependency, SharpUploadTaskOptions } from './types.js'\n\nimport { canResizeImage } from './canResizeImage.js'\nimport { createSharpFromFile } from './createSharpFromFile.js'\nimport { getImageResizeAction } from './getImageResizeAction.js'\nimport { mapWithBoundedConcurrency } from './mapWithBoundedConcurrency.js'\nimport { sanitizeResizeConfig } from './sanitizeResizeConfig.js'\n\n/**\n * Sharp throws on truncated/header-only files. Returns `undefined` instead —\n * matching core's dependency-free probe — since unreadable dimensions shouldn't fail the upload.\n */\nasync function tryProbe(file: File, sharpDependency: SharpDependency) {\n try {\n const sharpFile = await createSharpFromFile({ file, sharpDependency })\n const metadata = await sharpFile.metadata()\n return metadata.width && metadata.height ? metadata : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Computes the legacy upload tasks (the main file, optionally cropped, and every\n * configured legacy image size) and hands each to the injected `transform` callback\n * exactly once. Reads the Sharp-owned config from `sharpTransformer({ collections })`\n * at factory-construction time — not from the sanitized collection, which only gets\n * the narrowed projection `init()` writes back.\n */\nexport function createPrepareLegacyUpload({\n collections,\n sharpDependency,\n}: {\n collections: Partial<Record<string, SharpCollectionConfig>>\n sharpDependency: SharpDependency\n}): NonNullable<UploadTransformerInternal['prepareUpload']> {\n return async ({ collectionSlug, file, req, transform, uploadEdits }) => {\n const collectionUpload = collections[collectionSlug] ?? {}\n\n const fileSupportsResize = canResizeImage(file.type)\n\n const cropData =\n uploadEdits && typeof uploadEdits === 'object' && 'crop' in uploadEdits\n ? uploadEdits.crop\n : undefined\n\n let originalDimensions: ProbedImageSize | undefined\n\n if (fileSupportsResize) {\n const originalMeta = await tryProbe(file, sharpDependency)\n if (originalMeta) {\n originalDimensions = { height: originalMeta.height, width: originalMeta.width }\n }\n }\n\n // Unreadable dimensions can't be cropped/resized — treat as non-resizable rather than failing the upload.\n const canProcessAsImage = fileSupportsResize && originalDimensions !== undefined\n\n const crop =\n cropData && originalDimensions\n ? {\n cropData,\n heightInPixels: uploadEdits.heightInPixels!,\n originalDimensions,\n widthInPixels: uploadEdits.widthInPixels!,\n }\n : undefined\n\n const mainResultFile = await transform({\n fieldPath: 'filename',\n options: { collectionUpload, crop, kind: 'main' } satisfies SharpUploadTaskOptions,\n })\n\n const mainResult = await describeResult({\n fieldPath: 'filename',\n fileSupportsResize: canProcessAsImage,\n resultFile: mainResultFile,\n sharpDependency,\n })\n\n const results: PreparedUploadTransformation[] = [mainResult]\n\n // A cropped upload derives its sizes from the crop output, not the original.\n const isCropped = Boolean(crop && mainResult.width && mainResult.height)\n const sizeSourceFile = isCropped ? mainResultFile : undefined\n const sizeSourceDimensions: ProbedImageSize | undefined = isCropped\n ? { height: mainResult.height!, width: mainResult.width! }\n : originalDimensions\n\n // Same precedence `init()` applies: Sharp's setting, else the collection's own `focalPoint`.\n const effectiveFocalPoint =\n collectionUpload.focalPoint ??\n req.payload.collections[collectionSlug]?.config.upload?.focalPoint\n const focalPointEnabled = effectiveFocalPoint !== false\n const variants = collectionUpload.variants\n\n if (canProcessAsImage && Array.isArray(variants) && sizeSourceDimensions) {\n const focalPoint: FocalPoint | undefined =\n focalPointEnabled && uploadEdits?.focalPoint\n ? {\n x: isNumber(uploadEdits.focalPoint.x) ? Math.round(uploadEdits.focalPoint.x) : 50,\n y: isNumber(uploadEdits.focalPoint.y) ? Math.round(uploadEdits.focalPoint.y) : 50,\n }\n : undefined\n\n const sizeResults = await mapWithBoundedConcurrency(variants, async (rawConfig) => {\n const imageResizeConfig = sanitizeResizeConfig(rawConfig)\n const fieldPath = `variants.${imageResizeConfig.name}` as const\n\n const resizeAction = getImageResizeAction({\n dimensions: sizeSourceDimensions,\n hasFocalPoint: Boolean(focalPoint),\n imageResizeConfig,\n })\n\n if (resizeAction === 'omit') {\n return { fieldPath } satisfies PreparedUploadTransformation\n }\n\n const sizeResultFile = await transform({\n fieldPath,\n file: sizeSourceFile,\n options: {\n collectionUpload,\n focalPoint: resizeAction === 'resizeWithFocalPoint' ? focalPoint : undefined,\n imageResizeConfig,\n kind: 'size',\n originalDimensions: sizeSourceDimensions,\n } satisfies SharpUploadTaskOptions,\n })\n\n return describeResult({\n fieldPath,\n fileSupportsResize: true,\n resultFile: sizeResultFile,\n sharpDependency,\n })\n })\n\n results.push(...sizeResults)\n }\n\n return results\n }\n}\n\nasync function describeResult({\n fieldPath,\n fileSupportsResize,\n resultFile,\n sharpDependency,\n}: {\n fieldPath: PreparedUploadTransformation['fieldPath']\n fileSupportsResize: boolean\n resultFile: File\n sharpDependency: SharpDependency\n}): Promise<PreparedUploadTransformation> {\n if (!fileSupportsResize) {\n return { fieldPath, file: resultFile, mimeType: resultFile.type }\n }\n\n const meta = await tryProbe(resultFile, sharpDependency)\n\n if (!meta) {\n return { fieldPath, file: resultFile, mimeType: resultFile.type }\n }\n\n // `tryProbe` never sets `animated: true`, so `meta.height` is already the\n // single-frame height — do not divide by page count here.\n return {\n fieldPath,\n file: resultFile,\n height: meta.height,\n mimeType: resultFile.type,\n width: meta.width,\n }\n}\n"],"names":["isNumber","canResizeImage","createSharpFromFile","getImageResizeAction","mapWithBoundedConcurrency","sanitizeResizeConfig","tryProbe","file","sharpDependency","sharpFile","metadata","width","height","undefined","createPrepareLegacyUpload","collections","collectionSlug","req","transform","uploadEdits","collectionUpload","fileSupportsResize","type","cropData","crop","originalDimensions","originalMeta","canProcessAsImage","heightInPixels","widthInPixels","mainResultFile","fieldPath","options","kind","mainResult","describeResult","resultFile","results","isCropped","Boolean","sizeSourceFile","sizeSourceDimensions","effectiveFocalPoint","focalPoint","payload","config","upload","focalPointEnabled","variants","Array","isArray","x","Math","round","y","sizeResults","rawConfig","imageResizeConfig","name","resizeAction","dimensions","hasFocalPoint","sizeResultFile","push","mimeType","meta"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { createPrepareLegacyUpload } from './prepareLegacyUpload.js';
3
+ const sharpDependency = ()=>({
4
+ metadata: async ()=>({
5
+ height: 1000,
6
+ width: 1000
7
+ })
8
+ });
9
+ const makeReq = ({ focalPoint })=>({
10
+ payload: {
11
+ collections: {
12
+ media: {
13
+ config: {
14
+ upload: {
15
+ focalPoint
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ });
22
+ const runPrepareUpload = async ({ collectionFocalPoint, sharpConfig })=>{
23
+ const transform = vi.fn(async (_task)=>new File([
24
+ 'x'
25
+ ], 'image.png', {
26
+ type: 'image/png'
27
+ }));
28
+ await createPrepareLegacyUpload({
29
+ collections: {
30
+ media: sharpConfig
31
+ },
32
+ sharpDependency
33
+ })({
34
+ collectionSlug: 'media',
35
+ file: new File([
36
+ 'x'
37
+ ], 'image.png', {
38
+ type: 'image/png'
39
+ }),
40
+ req: makeReq({
41
+ focalPoint: collectionFocalPoint
42
+ }),
43
+ transform,
44
+ uploadEdits: {
45
+ focalPoint: {
46
+ x: 10,
47
+ y: 90
48
+ }
49
+ }
50
+ });
51
+ const sizeTask = transform.mock.calls.map(([task])=>task.options).find((options)=>options.kind === 'size');
52
+ return sizeTask?.kind === 'size' ? sizeTask.focalPoint : undefined;
53
+ };
54
+ const variants = [
55
+ {
56
+ name: 'card',
57
+ height: 200,
58
+ width: 400
59
+ }
60
+ ];
61
+ describe('createPrepareLegacyUpload', ()=>{
62
+ it.each([
63
+ {
64
+ collectionFocalPoint: false,
65
+ expected: undefined,
66
+ sharpFocalPoint: undefined
67
+ },
68
+ {
69
+ collectionFocalPoint: false,
70
+ expected: {
71
+ x: 10,
72
+ y: 90
73
+ },
74
+ sharpFocalPoint: true
75
+ },
76
+ {
77
+ collectionFocalPoint: undefined,
78
+ expected: {
79
+ x: 10,
80
+ y: 90
81
+ },
82
+ sharpFocalPoint: undefined
83
+ }
84
+ ])('should resolve the size focal point from Sharp ($sharpFocalPoint) over the collection ($collectionFocalPoint)', async ({ collectionFocalPoint, expected, sharpFocalPoint })=>{
85
+ expect(await runPrepareUpload({
86
+ collectionFocalPoint,
87
+ sharpConfig: {
88
+ focalPoint: sharpFocalPoint,
89
+ variants
90
+ }
91
+ })).toEqual(expected);
92
+ });
93
+ });
94
+
95
+ //# sourceMappingURL=prepareLegacyUpload.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prepareLegacyUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest, UploadEdits } from 'payload'\nimport type { UploadTransformTask } from 'payload/internal'\n\nimport { describe, expect, it, vi } from 'vitest'\n\nimport type { SharpCollectionConfig, SharpDependency, SharpUploadTaskOptions } from './types.js'\n\nimport { createPrepareLegacyUpload } from './prepareLegacyUpload.js'\n\nconst sharpDependency = (() => ({\n metadata: async () => ({ height: 1000, width: 1000 }),\n})) as unknown as SharpDependency\n\nconst makeReq = ({ focalPoint }: { focalPoint?: boolean }): PayloadRequest =>\n ({\n payload: { collections: { media: { config: { upload: { focalPoint } } } } },\n }) as unknown as PayloadRequest\n\nconst runPrepareUpload = async ({\n collectionFocalPoint,\n sharpConfig,\n}: {\n collectionFocalPoint?: boolean\n sharpConfig: SharpCollectionConfig\n}) => {\n const transform = vi.fn(\n async (_task: UploadTransformTask) => new File(['x'], 'image.png', { type: 'image/png' }),\n )\n\n await createPrepareLegacyUpload({ collections: { media: sharpConfig }, sharpDependency })({\n collectionSlug: 'media',\n file: new File(['x'], 'image.png', { type: 'image/png' }),\n req: makeReq({ focalPoint: collectionFocalPoint }),\n transform,\n uploadEdits: { focalPoint: { x: 10, y: 90 } } as UploadEdits,\n })\n\n const sizeTask = transform.mock.calls\n .map(([task]) => task.options as SharpUploadTaskOptions)\n .find((options) => options.kind === 'size')\n\n return sizeTask?.kind === 'size' ? sizeTask.focalPoint : undefined\n}\n\nconst variants = [{ name: 'card', height: 200, width: 400 }]\n\ndescribe('createPrepareLegacyUpload', () => {\n it.each([\n { collectionFocalPoint: false, expected: undefined, sharpFocalPoint: undefined },\n { collectionFocalPoint: false, expected: { x: 10, y: 90 }, sharpFocalPoint: true },\n { collectionFocalPoint: undefined, expected: { x: 10, y: 90 }, sharpFocalPoint: undefined },\n ])(\n 'should resolve the size focal point from Sharp ($sharpFocalPoint) over the collection ($collectionFocalPoint)',\n async ({ collectionFocalPoint, expected, sharpFocalPoint }) => {\n expect(\n await runPrepareUpload({\n collectionFocalPoint,\n sharpConfig: { focalPoint: sharpFocalPoint, variants },\n }),\n ).toEqual(expected)\n },\n )\n})\n"],"names":["describe","expect","it","vi","createPrepareLegacyUpload","sharpDependency","metadata","height","width","makeReq","focalPoint","payload","collections","media","config","upload","runPrepareUpload","collectionFocalPoint","sharpConfig","transform","fn","_task","File","type","collectionSlug","file","req","uploadEdits","x","y","sizeTask","mock","calls","map","task","options","find","kind","undefined","variants","name","each","expected","sharpFocalPoint","toEqual"],"mappings":"AAGA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAQ;AAIjD,SAASC,yBAAyB,QAAQ,2BAA0B;AAEpE,MAAMC,kBAAmB,IAAO,CAAA;QAC9BC,UAAU,UAAa,CAAA;gBAAEC,QAAQ;gBAAMC,OAAO;YAAK,CAAA;IACrD,CAAA;AAEA,MAAMC,UAAU,CAAC,EAAEC,UAAU,EAA4B,GACtD,CAAA;QACCC,SAAS;YAAEC,aAAa;gBAAEC,OAAO;oBAAEC,QAAQ;wBAAEC,QAAQ;4BAAEL;wBAAW;oBAAE;gBAAE;YAAE;QAAE;IAC5E,CAAA;AAEF,MAAMM,mBAAmB,OAAO,EAC9BC,oBAAoB,EACpBC,WAAW,EAIZ;IACC,MAAMC,YAAYhB,GAAGiB,EAAE,CACrB,OAAOC,QAA+B,IAAIC,KAAK;YAAC;SAAI,EAAE,aAAa;YAAEC,MAAM;QAAY;IAGzF,MAAMnB,0BAA0B;QAAEQ,aAAa;YAAEC,OAAOK;QAAY;QAAGb;IAAgB,GAAG;QACxFmB,gBAAgB;QAChBC,MAAM,IAAIH,KAAK;YAAC;SAAI,EAAE,aAAa;YAAEC,MAAM;QAAY;QACvDG,KAAKjB,QAAQ;YAAEC,YAAYO;QAAqB;QAChDE;QACAQ,aAAa;YAAEjB,YAAY;gBAAEkB,GAAG;gBAAIC,GAAG;YAAG;QAAE;IAC9C;IAEA,MAAMC,WAAWX,UAAUY,IAAI,CAACC,KAAK,CAClCC,GAAG,CAAC,CAAC,CAACC,KAAK,GAAKA,KAAKC,OAAO,EAC5BC,IAAI,CAAC,CAACD,UAAYA,QAAQE,IAAI,KAAK;IAEtC,OAAOP,UAAUO,SAAS,SAASP,SAASpB,UAAU,GAAG4B;AAC3D;AAEA,MAAMC,WAAW;IAAC;QAAEC,MAAM;QAAQjC,QAAQ;QAAKC,OAAO;IAAI;CAAE;AAE5DR,SAAS,6BAA6B;IACpCE,GAAGuC,IAAI,CAAC;QACN;YAAExB,sBAAsB;YAAOyB,UAAUJ;YAAWK,iBAAiBL;QAAU;QAC/E;YAAErB,sBAAsB;YAAOyB,UAAU;gBAAEd,GAAG;gBAAIC,GAAG;YAAG;YAAGc,iBAAiB;QAAK;QACjF;YAAE1B,sBAAsBqB;YAAWI,UAAU;gBAAEd,GAAG;gBAAIC,GAAG;YAAG;YAAGc,iBAAiBL;QAAU;KAC3F,EACC,iHACA,OAAO,EAAErB,oBAAoB,EAAEyB,QAAQ,EAAEC,eAAe,EAAE;QACxD1C,OACE,MAAMe,iBAAiB;YACrBC;YACAC,aAAa;gBAAER,YAAYiC;gBAAiBJ;YAAS;QACvD,IACAK,OAAO,CAACF;IACZ;AAEJ"}
@@ -0,0 +1,11 @@
1
+ import type { ImageSize } from 'payload';
2
+ /**
3
+ * Sanitize the resize config. If the resize config has the `withoutReduction`
4
+ * property set to true, the `fit` and `position` properties will be set to `contain`
5
+ * and `top left` respectively.
6
+ *
7
+ * @param resizeConfig - the resize config
8
+ * @returns a sanitized resize config
9
+ */
10
+ export declare const sanitizeResizeConfig: (resizeConfig: ImageSize) => ImageSize;
11
+ //# sourceMappingURL=sanitizeResizeConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeResizeConfig.d.ts","sourceRoot":"","sources":["../src/sanitizeResizeConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,iBAAkB,SAAS,KAAG,SAU9D,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sanitize the resize config. If the resize config has the `withoutReduction`
3
+ * property set to true, the `fit` and `position` properties will be set to `contain`
4
+ * and `top left` respectively.
5
+ *
6
+ * @param resizeConfig - the resize config
7
+ * @returns a sanitized resize config
8
+ */ export const sanitizeResizeConfig = (resizeConfig)=>{
9
+ if (resizeConfig.withoutReduction) {
10
+ return {
11
+ ...resizeConfig,
12
+ // Why fit `contain` should also be set to https://github.com/lovell/sharp/issues/3595
13
+ fit: resizeConfig?.fit || 'contain',
14
+ position: resizeConfig?.position || 'left top'
15
+ };
16
+ }
17
+ return resizeConfig;
18
+ };
19
+
20
+ //# sourceMappingURL=sanitizeResizeConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sanitizeResizeConfig.ts"],"sourcesContent":["import type { ImageSize } from 'payload'\n\n/**\n * Sanitize the resize config. If the resize config has the `withoutReduction`\n * property set to true, the `fit` and `position` properties will be set to `contain`\n * and `top left` respectively.\n *\n * @param resizeConfig - the resize config\n * @returns a sanitized resize config\n */\nexport const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => {\n if (resizeConfig.withoutReduction) {\n return {\n ...resizeConfig,\n // Why fit `contain` should also be set to https://github.com/lovell/sharp/issues/3595\n fit: resizeConfig?.fit || 'contain',\n position: resizeConfig?.position || 'left top',\n }\n }\n return resizeConfig\n}\n"],"names":["sanitizeResizeConfig","resizeConfig","withoutReduction","fit","position"],"mappings":"AAEA;;;;;;;CAOC,GACD,OAAO,MAAMA,uBAAuB,CAACC;IACnC,IAAIA,aAAaC,gBAAgB,EAAE;QACjC,OAAO;YACL,GAAGD,YAAY;YACf,sFAAsF;YACtFE,KAAKF,cAAcE,OAAO;YAC1BC,UAAUH,cAAcG,YAAY;QACtC;IACF;IACA,OAAOH;AACT,EAAC"}