@humaan/payload-storage-imagekit 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,8 @@ import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
3
3
  import path from 'node:path';
4
4
  import { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js';
5
5
  import { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js';
6
- import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isVideoOrPdfMimeType } from './getAdminThumbnailURL.js';
6
+ import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isPdfMimeType, isVideoMimeType } from './getAdminThumbnailURL.js';
7
+ import { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js';
7
8
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
8
9
  const PRIVATE_COLLECTION_DESCRIPTION = 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.';
9
10
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
@@ -51,6 +52,7 @@ const getStoredImageKitURL = (doc)=>{
51
52
  const storedURL = doc?.imagekit?.url;
52
53
  return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined;
53
54
  };
55
+ const hasStoredImageKitURL = (doc)=>!!getStoredImageKitURL(doc);
54
56
  const findUploadDocumentByFilename = async ({ collection, filename, prefix, req })=>{
55
57
  const constraints = [
56
58
  {
@@ -90,24 +92,19 @@ const applyClientUploadContextToData = (data, clientUploadContext, buildThumbnai
90
92
  data.thumbnailURL = buildThumbnailURL(data);
91
93
  return data;
92
94
  };
95
+ const getClientUploadMimeType = (clientUploadContext)=>{
96
+ if (!isImageKitClientUploadContext(clientUploadContext)) {
97
+ return undefined;
98
+ }
99
+ return typeof clientUploadContext.mimeType === 'string' && clientUploadContext.mimeType.trim().length > 0 ? clientUploadContext.mimeType : undefined;
100
+ };
93
101
  const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFolder, signedUrlExpiresIn, urlEndpoint })=>{
94
102
  const fetchUploadedFile = async (fileURL)=>{
95
103
  const response = await fetch(fileURL);
96
104
  if (!response.ok) {
97
105
  throw new Error(`imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`);
98
106
  }
99
- const fileBuffer = await response.arrayBuffer();
100
- const headers = new Headers();
101
- const contentType = response.headers.get('Content-Type');
102
- if (contentType) {
103
- headers.set('Content-Type', contentType);
104
- }
105
- headers.set('Content-Length', String(fileBuffer.byteLength));
106
- return new Response(fileBuffer, {
107
- headers,
108
- status: 200,
109
- statusText: response.statusText
110
- });
107
+ return response;
111
108
  };
112
109
  return ({ collection })=>{
113
110
  const isPrivate = privateSlugs.has(collection.slug);
@@ -134,6 +131,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
134
131
  name: 'imagekit',
135
132
  type: 'group',
136
133
  admin: {
134
+ condition: (data)=>hasStoredImageKitURL(data),
137
135
  disableListColumn: true,
138
136
  disableListFilter: true
139
137
  },
@@ -150,13 +148,17 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
150
148
  type: 'text',
151
149
  admin: {
152
150
  readOnly: true
153
- }
151
+ },
152
+ label: 'URL'
154
153
  },
155
154
  {
156
155
  name: 'isPrivate',
157
156
  type: 'checkbox',
158
157
  admin: {
159
- description: 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',
158
+ condition: (_data, siblingData)=>isDocumentPrivate({
159
+ imagekit: siblingData
160
+ }, isPrivate),
161
+ description: "Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.",
160
162
  readOnly: true
161
163
  },
162
164
  label: 'Private file'
@@ -200,7 +202,8 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
200
202
  }
201
203
  }
202
204
  ],
203
- interfaceName: 'imagekitFields'
205
+ interfaceName: 'imagekitFields',
206
+ label: 'ImageKit'
204
207
  }
205
208
  ],
206
209
  generateURL: ({ filename })=>client.helper.buildSrc({
@@ -252,13 +255,23 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
252
255
  const hasClientUploadContext = 'clientUploadContext' in args.params;
253
256
  if (hasClientUploadContext) {
254
257
  const clientUploadContext = args.params.clientUploadContext;
258
+ const clientUploadMimeType = getClientUploadMimeType(clientUploadContext);
259
+ if (clientUploadMimeType && isVideoMimeType(clientUploadMimeType)) {
260
+ const headers = new Headers();
261
+ headers.set('Content-Length', '0');
262
+ headers.set('Content-Type', clientUploadMimeType);
263
+ return new Response(null, {
264
+ headers,
265
+ status: 200
266
+ });
267
+ }
255
268
  const clientUploadURL = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.url === 'string' && clientUploadContext.url.length > 0 ? clientUploadContext.url : null;
256
269
  const filePath = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.filePath === 'string' && clientUploadContext.filePath.length > 0 ? clientUploadContext.filePath : getFilePath(rootFolder, args.params.collection, args.params.filename);
257
- const imagekitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
270
+ const imageKitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
258
271
  src: filePath,
259
272
  urlEndpoint
260
273
  });
261
- return fetchUploadedFile(imagekitURL);
274
+ return fetchUploadedFile(imageKitURL);
262
275
  }
263
276
  const doc = args.doc || await findUploadDocumentByFilename({
264
277
  collection: args.params.collection,
@@ -273,12 +286,14 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
273
286
  let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename);
274
287
  // A `tr` request for a video/PDF is a thumbnail request (the plain transform
275
288
  // can't render a preview); serve ImageKit's generated thumbnail instead.
276
- if (tr && isVideoOrPdfMimeType(mimeType)) {
277
- signSrc = `${signSrc}/ik-thumbnail.jpg`;
289
+ if (tr && isVideoMimeType(mimeType)) {
290
+ signSrc = getVideoPosterSrc(signSrc);
291
+ } else if (tr && isPdfMimeType(mimeType)) {
292
+ signSrc = getImageKitThumbnailSrc(signSrc);
278
293
  }
279
294
  return Response.redirect(buildSignedURL(signSrc, tr), 302);
280
295
  }
281
- const imagekitURL = client.helper.buildSrc({
296
+ const imageKitURL = client.helper.buildSrc({
282
297
  src: getStoredImageKitURL(doc) || getFilePath(rootFolder, args.params.collection, args.params.filename),
283
298
  transformation: tr ? [
284
299
  {
@@ -287,7 +302,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
287
302
  ] : undefined,
288
303
  urlEndpoint
289
304
  });
290
- return Response.redirect(imagekitURL, 302);
305
+ return Response.redirect(imageKitURL, 302);
291
306
  }
292
307
  };
293
308
  };
@@ -305,7 +320,7 @@ const toCloudStorageCollections = (collections, adapter, privateSlugs, disablePa
305
320
  return mappedCollections;
306
321
  };
307
322
  export const imagekitStorage = (pluginOptions)=>(config)=>{
308
- const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
323
+ const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, focalPoint, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
309
324
  if (!privateKey?.trim()) {
310
325
  throw new Error('imagekitStorage: `privateKey` is required.');
311
326
  }
@@ -325,12 +340,12 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
325
340
  const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
326
341
  const apiRoute = config.routes?.api ?? '/api';
327
342
  const rootFolder = normalizeFolder(folder);
328
- const imagekitClient = new ImageKit({
343
+ const imageKitClient = new ImageKit({
329
344
  privateKey
330
345
  });
331
346
  const adapter = imagekitAdapter({
332
347
  apiRoute,
333
- client: imagekitClient,
348
+ client: imageKitClient,
334
349
  clientUploads,
335
350
  privateSlugs,
336
351
  rootFolder,
@@ -347,6 +362,8 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
347
362
  const isCollectionPrivate = privateSlugs.has(collection.slug);
348
363
  const buildThumbnailURL = (doc)=>isDocumentPrivate(doc, isCollectionPrivate) ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute) : getAdminThumbnailURL(doc);
349
364
  const beforeDocumentControls = collection.admin?.components?.edit?.beforeDocumentControls || [];
365
+ const collectionOptions = collections[collection.slug];
366
+ const collectionFocalPoint = collectionOptions !== true && typeof collectionOptions === 'object' && typeof collectionOptions.focalPoint === 'boolean' ? collectionOptions.focalPoint : undefined;
350
367
  // Surface a "this is a private collection" note in the list and edit views.
351
368
  // Preserve an existing string description by appending; leave custom
352
369
  // component descriptions untouched.
@@ -392,6 +409,17 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
392
409
  ]
393
410
  },
394
411
  upload: {
412
+ // Focal points are opt-in for configured collections. Plugin-level
413
+ // and collection-level options can override this default; a
414
+ // collection's own `upload.focalPoint` still wins because it is
415
+ // spread in afterwards.
416
+ focalPoint: false,
417
+ ...typeof focalPoint === 'boolean' ? {
418
+ focalPoint
419
+ } : {},
420
+ ...typeof collectionFocalPoint === 'boolean' ? {
421
+ focalPoint: collectionFocalPoint
422
+ } : {},
395
423
  ...typeof collection.upload === 'object' ? collection.upload : {},
396
424
  adminThumbnail: ({ doc })=>buildThumbnailURL(doc),
397
425
  crop: false
@@ -402,7 +430,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
402
430
  ...config.endpoints || [],
403
431
  {
404
432
  handler: createImageKitUploadAuthHandler({
405
- client: imagekitClient,
433
+ client: imageKitClient,
406
434
  collections
407
435
  }),
408
436
  method: 'get',
@@ -419,5 +447,6 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
419
447
  export { migrateFolder } from './migrate.js';
420
448
  // Backward-compatible alias
421
449
  export const payloadStorageImagekit = imagekitStorage;
450
+ export const payloadStorageImageKit = imagekitStorage;
422
451
 
423
452
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isVideoOrPdfMimeType,\n} from './getAdminThumbnailURL.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImagekitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImagekitReference\n\nexport type PayloadStorageImagekitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImagekitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n const fileBuffer = await response.arrayBuffer()\n const headers = new Headers()\n const contentType = response.headers.get('Content-Type')\n\n if (contentType) {\n headers.set('Content-Type', contentType)\n }\n\n headers.set('Content-Length', String(fileBuffer.byteLength))\n\n return new Response(fileBuffer, {\n headers,\n status: 200,\n statusText: response.statusText,\n })\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n description:\n 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImagekitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imagekitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imagekitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoOrPdfMimeType(mimeType)) {\n signSrc = `${signSrc}/ik-thumbnail.jpg`\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imagekitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imagekitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImagekitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imagekitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imagekitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imagekitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isVideoOrPdfMimeType","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","fileBuffer","arrayBuffer","headers","Headers","contentType","get","set","String","byteLength","Response","statusText","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","disableListColumn","disableListFilter","hidden","readOnly","description","label","condition","_data","siblingData","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","mimeType","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","clientUploadURL","imagekitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imagekitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isPdfMimeType,\n isVideoMimeType,\n} from './getAdminThumbnailURL.js'\nimport { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Override Payload's native focal-point picker (`upload.focalPoint`) for this\n * collection.\n */\n focalPoint?: boolean\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImageKitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImageKitReference\n\nexport type PayloadStorageImageKitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n /**\n * Broad default for Payload's native focal-point picker (`upload.focalPoint`)\n * on every configured collection. Focal points are disabled by default for\n * configured collections, so prefer collection-level `focalPoint: true` for\n * image collections. A collection's own `upload.focalPoint` still takes\n * precedence.\n */\n focalPoint?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImageKitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst hasStoredImageKitURL = (doc: unknown): boolean => !!getStoredImageKitURL(doc)\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst getClientUploadMimeType = (clientUploadContext: unknown): string | undefined => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return undefined\n }\n\n return typeof clientUploadContext.mimeType === 'string' &&\n clientUploadContext.mimeType.trim().length > 0\n ? clientUploadContext.mimeType\n : undefined\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n return response\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n condition: (data) => hasStoredImageKitURL(data),\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n label: 'URL',\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description:\n \"Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.\",\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n label: 'ImageKit',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImageKitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadMimeType = getClientUploadMimeType(clientUploadContext)\n\n if (clientUploadMimeType && isVideoMimeType(clientUploadMimeType)) {\n const headers = new Headers()\n\n headers.set('Content-Length', '0')\n headers.set('Content-Type', clientUploadMimeType)\n\n return new Response(null, {\n headers,\n status: 200,\n })\n }\n\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imageKitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imageKitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoMimeType(mimeType)) {\n signSrc = getVideoPosterSrc(signSrc)\n } else if (tr && isPdfMimeType(mimeType)) {\n signSrc = getImageKitThumbnailSrc(signSrc)\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imageKitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imageKitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImageKitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n focalPoint,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imageKitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imageKitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n const collectionOptions = collections[collection.slug]\n const collectionFocalPoint =\n collectionOptions !== true &&\n typeof collectionOptions === 'object' &&\n typeof collectionOptions.focalPoint === 'boolean'\n ? collectionOptions.focalPoint\n : undefined\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n // Focal points are opt-in for configured collections. Plugin-level\n // and collection-level options can override this default; a\n // collection's own `upload.focalPoint` still wins because it is\n // spread in afterwards.\n focalPoint: false,\n ...(typeof focalPoint === 'boolean' ? { focalPoint } : {}),\n ...(typeof collectionFocalPoint === 'boolean'\n ? { focalPoint: collectionFocalPoint }\n : {}),\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imageKitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\nexport const payloadStorageImageKit = imagekitStorage\n\nexport type PayloadStorageImagekitConfig = PayloadStorageImageKitConfig\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isPdfMimeType","isVideoMimeType","getImageKitThumbnailSrc","getVideoPosterSrc","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","hasStoredImageKitURL","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","getClientUploadMimeType","mimeType","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","condition","disableListColumn","disableListFilter","hidden","readOnly","label","_data","siblingData","description","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","get","hasClientUploadContext","params","clientUploadMimeType","headers","Headers","set","Response","clientUploadURL","imageKitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imageKitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","collectionOptions","collectionFocalPoint","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit","payloadStorageImageKit"],"mappings":"AAUA,OAAOA,YAAYC,MAAM,QAAQ,mBAAkB;AACnD,SAASC,kBAAkB,QAAQ,mCAAkC;AACrE,OAAOC,UAAU,YAAW;AAE5B,SAASC,yBAAyB,EAAEC,6BAA6B,QAAQ,qBAAoB;AAC7F,SAASC,+BAA+B,QAAQ,iDAAgD;AAChG,SACEC,oBAAoB,EACpBC,2BAA2B,EAC3BC,aAAa,EACbC,eAAe,QACV,4BAA2B;AAClC,SAASC,uBAAuB,EAAEC,iBAAiB,QAAQ,uBAAsB;AAiBjF,MAAMC,gCAAgC;AAEtC,MAAMC,iCACJ;AAEF,MAAMC,4BAA4B,CAACC,QACjCA,UAAU,QAAQ,OAAOA,UAAU,YAAYA,OAAOC,YAAY;AAEpE;;;;;CAKC,GACD,MAAMC,oBAAoB,CAACC,KAAcC;IACvC,MAAMC,SAAUF,KAA4DG,UAAUC;IAEtF,OAAO,OAAOF,WAAW,YAAYA,SAASD;AAChD;AAwCA,MAAMI,kBAAkB,CAACC;IACvB,MAAMC,aAAaD,OAAOE,IAAI,GAAGC,OAAO,CAAC,cAAc;IAEvD,IAAI,CAACF,YAAY;QACf,MAAM,IAAIG,MAAM;IAClB;IAEA,OAAO,CAAC,CAAC,EAAEH,YAAY;AACzB;AAEA,MAAMI,sBAAsB,CAACC,YAAoBC,iBAC/C7B,KAAK8B,KAAK,CAACC,IAAI,CAACH,YAAYC;AAE9B,MAAMG,cAAc,CAACJ,YAAoBC,gBAAwBI,WAC/DjC,KAAK8B,KAAK,CAACC,IAAI,CAACJ,oBAAoBC,YAAYC,iBAAiBI;AAEnE,MAAMC,qBAAqB,OACzBf,UACAgB;IAEA,MAAMC,aAAapC,KAAK8B,KAAK,CAACO,OAAO,CAACF;IACtC,MAAMG,OAAOtC,KAAK8B,KAAK,CAACS,QAAQ,CAACJ;IAEjC,MAAMK,QAAQ;IAEd,IAAK,IAAIC,OAAO,IAAKA,QAAQD,MAAO;QAClC,MAAME,SAAS,MAAMvB,SAASuB,MAAM,CAACC,IAAI,CAAC;YACxCC,MAAM;YACNJ;YACAxC,MAAMoC,eAAe,MAAM,MAAM,GAAGA,WAAW,CAAC,CAAC;YACjDK;QACF;QAEA,MAAMI,QAAQH,OAAOI,IAAI,CACvB,CAACC,QAAU,YAAYA,SAASA,MAAMZ,QAAQ,KAAKA,YAAYY,MAAMT,IAAI,KAAKA;QAGhF,IAAIO,SAAS,YAAYA,OAAO;YAC9B,OAAOA,MAAMG,MAAM;QACrB;QAEA,IAAIN,OAAOO,MAAM,GAAGT,OAAO;YACzB;QACF;IACF;IAEA,OAAOU;AACT;AAEA,MAAMC,WAAW,CAACtC,QAChB,CAAC,CAACA,SAAS,OAAOA,UAAU;AAE9B,MAAMuC,uBAAuB,CAACpC;IAC5B,MAAMqC,YAAarC,KAAuCG,UAAUmC;IAEpE,OAAO,OAAOD,cAAc,YAAYA,UAAU7B,IAAI,GAAGyB,MAAM,GAAG,IAAII,YAAYH;AACpF;AAEA,MAAMK,uBAAuB,CAACvC,MAA0B,CAAC,CAACoC,qBAAqBpC;AAE/E,MAAMwC,+BAA+B,OAAO,EAC1CC,UAAU,EACVxB,QAAQ,EACRyB,MAAM,EACNC,GAAG,EAMJ;IACC,MAAMC,cAAuB;QAC3B;YACE3B,UAAU;gBACR4B,QAAQ5B;YACV;QACF;KACD;IAED,IAAI,OAAOyB,WAAW,UAAU;QAC9BE,YAAYE,IAAI,CAAC;YACfJ,QAAQ;gBACNG,QAAQH;YACV;QACF;IACF;IAEA,OAAOC,IAAII,OAAO,CAACC,EAAE,CAACC,OAAO,CAAC;QAC5BR;QACAE;QACAO,OAAO;YACLC,KAAKP;QACP;IACF;AACF;AAEA,MAAMQ,iCAAiC,CACrCC,MACAC,qBACAC,oBAAyCnE,oBAAoB,EAC7DgB,YAAY,KAAK;IAEjB,IAAI,CAAClB,8BAA8BoE,sBAAsB;QACvD,OAAOD;IACT;IAEAA,KAAKlD,QAAQ,GAAG;QACd6B,QAAQsB,oBAAoBtB,MAAM;QAClC,+EAA+E;QAC/E,wDAAwD;QACxD5B;QACAoD,cAAcF,oBAAoBE,YAAY;QAC9ClB,KAAKgB,oBAAoBhB,GAAG;IAC9B;IAEA,sFAAsF;IACtFe,KAAKI,YAAY,GAAGF,kBAAkBF;IAEtC,OAAOA;AACT;AAEA,MAAMK,0BAA0B,CAACJ;IAC/B,IAAI,CAACpE,8BAA8BoE,sBAAsB;QACvD,OAAOpB;IACT;IAEA,OAAO,OAAOoB,oBAAoBK,QAAQ,KAAK,YAC7CL,oBAAoBK,QAAQ,CAACnD,IAAI,GAAGyB,MAAM,GAAG,IAC3CqB,oBAAoBK,QAAQ,GAC5BzB;AACN;AAEA,MAAM0B,kBAAkB,CAAC,EACvBC,QAAQ,EACRC,MAAM,EACNC,aAAa,EACbC,YAAY,EACZpD,UAAU,EACVqD,kBAAkB,EAClBC,WAAW,EASZ;IACC,MAAMC,oBAAoB,OAAOC;QAC/B,MAAMC,WAAW,MAAMC,MAAMF;QAE7B,IAAI,CAACC,SAASE,EAAE,EAAE;YAChB,MAAM,IAAI7D,MACR,CAAC,8DAA8D,EAAE2D,SAASG,MAAM,CAAC,EAAE,CAAC;QAExF;QAEA,OAAOH;IACT;IAEA,OAAO,CAAC,EAAE5B,UAAU,EAAE;QACpB,MAAMrC,YAAY4D,aAAaS,GAAG,CAAChC,WAAWiC,IAAI;QAElD,mFAAmF;QACnF,gFAAgF;QAChF,6EAA6E;QAC7E,MAAMC,iBAAiB,CAACC,KAAaC,KACnCf,OAAOgB,MAAM,CAACC,QAAQ,CAAC;gBACrBC,WAAWf;gBACXgB,QAAQ;gBACRL;gBACAM,gBAAgBL,KAAK;oBAAC;wBAAEM,KAAKN;oBAAG;iBAAE,GAAG3C;gBACrCgC;YACF;QAEF,MAAMX,oBAAoB,CAACvD,MACzBD,kBAAkBC,KAAKI,aACnBf,4BAA4BW,KAAKyC,WAAWiC,IAAI,EAAEb,YAClDzE,qBAAqBY;QAE3B,OAAO;YACLsB,MAAM;YACNyC;YACAqB,QAAQ;gBACN;oBACE9D,MAAM;oBACNM,MAAM;oBACNyD,OAAO;wBACLC,WAAW,CAACjC,OAASd,qBAAqBc;wBAC1CkC,mBAAmB;wBACnBC,mBAAmB;oBACrB;oBACAJ,QAAQ;wBACN;4BACE9D,MAAM;4BACNM,MAAM;4BACNyD,OAAO;gCACLI,QAAQ;4BACV;wBACF;wBACA;4BACEnE,MAAM;4BACNM,MAAM;4BACNyD,OAAO;gCACLK,UAAU;4BACZ;4BACAC,OAAO;wBACT;wBACA;4BACErE,MAAM;4BACNM,MAAM;4BACNyD,OAAO;gCACLC,WAAW,CAACM,OAAOC,cACjB9F,kBAAkB;wCAAEI,UAAU0F;oCAAY,GAAGzF;gCAC/C0F,aACE;gCACFJ,UAAU;4BACZ;4BACAC,OAAO;wBACT;wBACA;4BACErE,MAAM;4BACNM,MAAM;4BACNyD,OAAO;gCACL,mFAAmF;gCACnFC,WAAW,CAACM,OAAOC,cACjB9F,kBAAkB;wCAAEI,UAAU0F;oCAAY,GAAGzF;gCAC/C0F,aAAa,CAAC,kFAAkF,EAAE7B,mBAAmB,kKAAkK,CAAC;gCACxRyB,UAAU;4BACZ;4BACAK,OAAO;gCACL,mFAAmF;gCACnFC,WAAW;oCACT,CAAC,EAAE3C,IAAI,EAAEwC,WAAW,EAAE;wCACpB,IAAI,CAAC9F,kBAAkB;4CAAEI,UAAU0F;wCAAY,GAAGzF,YAAY;4CAC5D,OAAO8B;wCACT;wCAEA,MAAMjB,WAAYoC,MAA4CpC;wCAE9D,IAAI,CAACA,UAAU;4CACb,OAAOiB;wCACT;wCAEA,OAAOyC,eAAe3D,YAAYJ,YAAY6B,WAAWiC,IAAI,EAAEzD;oCACjE;iCACD;4BACH;4BACA0E,OAAO;4BACPM,SAAS;wBACX;wBACA;4BACE3E,MAAM;4BACNM,MAAM;4BACNyD,OAAO;gCACLI,QAAQ;4BACV;wBACF;qBACD;oBACDS,eAAe;oBACfP,OAAO;gBACT;aACD;YACDQ,aAAa,CAAC,EAAElF,QAAQ,EAAE,GACxB6C,OAAOgB,MAAM,CAACC,QAAQ,CAAC;oBACrBH,KAAK5D,YAAYJ,YAAY6B,WAAWiC,IAAI,EAAEzD;oBAC9CiD;gBACF;YACFkC,cAAc,OAAO,EAAEpG,GAAG,EAAEiB,QAAQ,EAAE;gBACpC,MAAMoF,eAAe,AAACrG,IAAuCG,QAAQ,EAAE6B;gBACvE,MAAMA,SACJqE,gBACC,MAAMnF,mBAAmB4C,QAAQ9C,YAAYJ,YAAY6B,WAAWiC,IAAI,EAAEzD;gBAE7E,IAAI,CAACe,QAAQ;oBACX;gBACF;gBAEA,MAAM8B,OAAOwC,KAAK,CAACC,MAAM,CAACvE;YAC5B;YACAwE,cAAc,OAAO,EAAElD,mBAAmB,EAAED,IAAI,EAAEoD,IAAI,EAAE;gBACtD,IAAIvH,8BAA8BoE,sBAAsB;oBACtD,OAAOF,+BACLC,MACAC,qBACAC,mBACAnD;gBAEJ;gBAEA,MAAMsG,aAAa,MAAM5H,OAAO2H,KAAKE,MAAM,EAAEF,KAAKxF,QAAQ,EAAE;oBAAEW,MAAM6E,KAAK9C,QAAQ;gBAAC;gBAElF,MAAMiD,WAAW,MAAM9C,OAAOwC,KAAK,CAACO,MAAM,CAAC;oBACzCJ,MAAMC;oBACNI,UAAUL,KAAKxF,QAAQ;oBACvBX,QAAQK,oBAAoBC,YAAY6B,WAAWiC,IAAI;oBACvDqC,mBAAmB;oBACnB,GAAI3G,YAAY;wBAAE4G,eAAe;oBAAK,IAAI,CAAC,CAAC;gBAC9C;gBAEA3D,KAAKlD,QAAQ,GAAG;oBACd6B,QAAQ4E,SAAS5E,MAAM;oBACvB5B;oBACAoD,cAAcoD,SAASpD,YAAY;oBACnClB,KAAKsE,SAAStE,GAAG;gBACnB;gBAEA,0EAA0E;gBAC1E,iFAAiF;gBACjF,sEAAsE;gBACtEe,KAAKI,YAAY,GAAGF,kBAAkB;oBACpC,GAAGF,IAAI;oBACPpC,UAAU,AAACoC,KAA+BpC,QAAQ,IAAIwF,KAAKxF,QAAQ;gBACrE;gBAEA,OAAOoC;YACT;YACA4D,eAAe,OAAOtE,KAAKuE;gBACzB,MAAMC,aAAa,IAAIC,IAAIzE,IAAIL,GAAG,IAAI;gBACtC,MAAMuC,KAAKsC,WAAWE,YAAY,CAACC,GAAG,CAAC;gBACvC,MAAMC,yBAAyB,yBAAyBL,KAAKM,MAAM;gBAEnE,IAAID,wBAAwB;oBAC1B,MAAMjE,sBAAsB4D,KAAKM,MAAM,CAAClE,mBAAmB;oBAC3D,MAAMmE,uBAAuB/D,wBAAwBJ;oBAErD,IAAImE,wBAAwBlI,gBAAgBkI,uBAAuB;wBACjE,MAAMC,UAAU,IAAIC;wBAEpBD,QAAQE,GAAG,CAAC,kBAAkB;wBAC9BF,QAAQE,GAAG,CAAC,gBAAgBH;wBAE5B,OAAO,IAAII,SAAS,MAAM;4BACxBH;4BACAlD,QAAQ;wBACV;oBACF;oBAEA,MAAMsD,kBACJ5I,8BAA8BoE,wBAC9B,OAAOA,oBAAoBhB,GAAG,KAAK,YACnCgB,oBAAoBhB,GAAG,CAACL,MAAM,GAAG,IAC7BqB,oBAAoBhB,GAAG,GACvB;oBACN,MAAMnB,WACJjC,8BAA8BoE,wBAC9B,OAAOA,oBAAoBnC,QAAQ,KAAK,YACxCmC,oBAAoBnC,QAAQ,CAACc,MAAM,GAAG,IAClCqB,oBAAoBnC,QAAQ,GAC5BH,YAAYJ,YAAYsG,KAAKM,MAAM,CAAC/E,UAAU,EAAEyE,KAAKM,MAAM,CAACvG,QAAQ;oBAC1E,MAAM8G,cAAc3H,YAChBuE,eAAexD,UAAU0D,MACzBiD,mBACAhE,OAAOgB,MAAM,CAACC,QAAQ,CAAC;wBACrBH,KAAKzD;wBACL+C;oBACF;oBAEJ,OAAOC,kBAAkB4D;gBAC3B;gBAEA,MAAM/H,MACJkH,KAAKlH,GAAG,IACP,MAAMwC,6BAA6B;oBAClCC,YAAYyE,KAAKM,MAAM,CAAC/E,UAAU;oBAClCxB,UAAUiG,KAAKM,MAAM,CAACvG,QAAQ;oBAC9ByB,QAAQwE,KAAKM,MAAM,CAAC9E,MAAM;oBAC1BC;gBACF;gBAEF,2EAA2E;gBAC3E,+EAA+E;gBAC/E,IAAI5C,kBAAkBC,KAAKI,YAAY;oBACrC,MAAMuD,WAAY3D,KAAsC2D;oBACxD,IAAIqE,UAAUhH,YAAYJ,YAAYsG,KAAKM,MAAM,CAAC/E,UAAU,EAAEyE,KAAKM,MAAM,CAACvG,QAAQ;oBAElF,6EAA6E;oBAC7E,yEAAyE;oBACzE,IAAI4D,MAAMtF,gBAAgBoE,WAAW;wBACnCqE,UAAUvI,kBAAkBuI;oBAC9B,OAAO,IAAInD,MAAMvF,cAAcqE,WAAW;wBACxCqE,UAAUxI,wBAAwBwI;oBACpC;oBAEA,OAAOH,SAASI,QAAQ,CAACtD,eAAeqD,SAASnD,KAAK;gBACxD;gBAEA,MAAMkD,cAAcjE,OAAOgB,MAAM,CAACC,QAAQ,CAAC;oBACzCH,KACExC,qBAAqBpC,QACrBgB,YAAYJ,YAAYsG,KAAKM,MAAM,CAAC/E,UAAU,EAAEyE,KAAKM,MAAM,CAACvG,QAAQ;oBACtEiE,gBAAgBL,KAAK;wBAAC;4BAAEM,KAAKN;wBAAG;qBAAE,GAAG3C;oBACrCgC;gBACF;gBAEA,OAAO2D,SAASI,QAAQ,CAACF,aAAa;YACxC;QACF;IACF;AACF;AAEA,MAAMG,4BAA4B,CAChCC,aACAC,SACApE,cACAqE;IAEA,MAAMC,oBAAwE,CAAC;IAE/E,KAAK,MAAMzH,kBAAkB0H,OAAOC,IAAI,CAACL,aAAc;QACrDG,iBAAiB,CAACzH,eAAe,GAAG;YAClCuH;YACAK,qBAAqB;YACrB,oFAAoF;YACpFJ,6BAA6BrE,aAAaS,GAAG,CAAC5D,kBAC1CqB,YACAmG;QACN;IACF;IAEA,OAAOC;AACT;AAEA,OAAO,MAAMI,kBACX,CAACC,gBACD,CAACC;QACC,MAAM,EACJC,kBAAkB,EAClB9E,aAAa,EACboE,WAAW,EACXE,2BAA2B,EAC3BS,OAAO,EACPC,UAAU,EACVzI,MAAM,EACN0I,UAAU,EACVC,SAAS,EACThF,oBAAoBiF,wBAAwB,EAC5ChF,WAAW,EACZ,GAAGyE;QAEJ,IAAI,CAACK,YAAYxI,QAAQ;YACvB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAACuI,WAAWzI,QAAQ;YACtB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAACwD,aAAa1D,QAAQ;YACxB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAACyH,eAAeI,OAAOC,IAAI,CAACL,aAAalG,MAAM,KAAK,GAAG;YACzD,MAAM,IAAIvB,MAAM;QAClB;QAEA,MAAMsD,eAAe,IAAImF,IACvBZ,OAAOa,OAAO,CAACjB,aACZkB,MAAM,CAAC,CAAC,GAAGxJ,MAAM,GAAKD,0BAA0BC,QAChDyJ,GAAG,CAAC,CAAC,CAAC5E,KAAK,GAAKA;QAGrB,IAAI2D,+BAA+BrE,aAAauF,IAAI,GAAG,GAAG;YACxD,MAAM,IAAI7I,MACR;QAEJ;QAEA,MAAMuD,qBACJ,OAAOiF,6BAA6B,YAAYA,2BAA2B,IACvEA,2BACAxJ;QACN,MAAMmE,WAAW+E,OAAOY,MAAM,EAAEC,OAAO;QAEvC,MAAM7I,aAAaP,gBAAgBC;QAEnC,MAAMoJ,iBAAiB,IAAI7K,SAAS;YAAEmK;QAAW;QACjD,MAAMZ,UAAUxE,gBAAgB;YAC9BC;YACAC,QAAQ4F;YACR3F;YACAC;YACApD;YACAqD;YACAC;QACF;QAEA,MAAMyF,kBAAkB,IAAIR,IAAIZ,OAAOC,IAAI,CAACL;QAC5C,MAAMyB,yBAAiC;YACrC,GAAGhB,MAAM;YACTT,aAAa,AAACS,CAAAA,OAAOT,WAAW,IAAI,EAAE,AAAD,EAAGmB,GAAG,CAAC,CAAC7G;gBAC3C,IAAI,CAACkH,gBAAgBlF,GAAG,CAAChC,WAAWiC,IAAI,GAAG;oBACzC,OAAOjC;gBACT;gBAEA,MAAMoH,sBAAsB7F,aAAaS,GAAG,CAAChC,WAAWiC,IAAI;gBAC5D,MAAMnB,oBAAoB,CAACvD,MACzBD,kBAAkBC,KAAK6J,uBACnBxK,4BAA4BW,KAAKyC,WAAWiC,IAAI,EAAEb,YAClDzE,qBAAqBY;gBAE3B,MAAM8J,yBACJrH,WAAW4C,KAAK,EAAE0E,YAAYC,MAAMF,0BAA0B,EAAE;gBAClE,MAAMG,oBAAoB9B,WAAW,CAAC1F,WAAWiC,IAAI,CAAC;gBACtD,MAAMwF,uBACJD,sBAAsB,QACtB,OAAOA,sBAAsB,YAC7B,OAAOA,kBAAkBlB,UAAU,KAAK,YACpCkB,kBAAkBlB,UAAU,GAC5B7G;gBAEN,4EAA4E;gBAC5E,qEAAqE;gBACrE,oCAAoC;gBACpC,MAAMiI,sBAAsB1H,WAAW4C,KAAK,EAAES;gBAC9C,MAAMsE,YAAYP,sBACd;oBACE,GAAIpH,WAAW4C,KAAK,IAAI,CAAC,CAAC;oBAC1BS,aACE,OAAOqE,wBAAwB,YAAYA,oBAAoB3J,IAAI,GAAGyB,MAAM,GAAG,IAC3E,GAAGkI,oBAAoB,CAAC,EAAExK,gCAAgC,GACzDwK,uBAAuBxK;gBAChC,IACA8C,WAAW4C,KAAK;gBAEpB,OAAO;oBACL,GAAG5C,UAAU;oBACb4C,OAAOtB,gBACH;wBACE,GAAIqG,aAAa,CAAC,CAAC;wBACnBL,YAAY;4BACV,GAAItH,WAAW4C,KAAK,EAAE0E,cAAc,CAAC,CAAC;4BACtCC,MAAM;gCACJ,GAAIvH,WAAW4C,KAAK,EAAE0E,YAAYC,QAAQ,CAAC,CAAC;gCAC5CF,wBAAwB;uCACnBA;oCACH;wCACEO,aAAa;4CACXC,UAAUrL,0BAA0BwD,WAAWiC,IAAI;4CACnD7D,gBAAgB4B,WAAWiC,IAAI;4CAC/BpE,QAAQK,oBAAoBC,YAAY6B,WAAWiC,IAAI;4CACvDtE,WAAWyJ;4CACXZ;wCACF;wCACAjK,MAAM;oCACR;iCACD;4BACH;wBACF;oBACF,IACAoL;oBACJrE,OAAO;wBACL,GAAItD,WAAWsD,KAAK,IAAI,CAAC,CAAC;wBAC1BwE,cAAc;+BACR9H,WAAWsD,KAAK,EAAEwE,gBAAgB,EAAE;4BACxC,CAAC,EAAElH,IAAI,EAAEV,GAAG,EAAE;gCACZ,IAAI,CAACR,SAASkB,SAAS,CAACV,IAAI8D,IAAI,EAAEnD,qBAAqB;oCACrD,OAAOD;gCACT;gCAEA,OAAOD,+BACLC,MACAV,IAAI8D,IAAI,CAACnD,mBAAmB,EAC5BC,mBACAsG;4BAEJ;yBACD;oBACH;oBACAhD,QAAQ;wBACN,mEAAmE;wBACnE,4DAA4D;wBAC5D,gEAAgE;wBAChE,wBAAwB;wBACxBkC,YAAY;wBACZ,GAAI,OAAOA,eAAe,YAAY;4BAAEA;wBAAW,IAAI,CAAC,CAAC;wBACzD,GAAI,OAAOmB,yBAAyB,YAChC;4BAAEnB,YAAYmB;wBAAqB,IACnC,CAAC,CAAC;wBACN,GAAI,OAAOzH,WAAWoE,MAAM,KAAK,WAAWpE,WAAWoE,MAAM,GAAG,CAAC,CAAC;wBAClE2D,gBAAgB,CAAC,EAAExK,GAAG,EAAE,GAAKuD,kBAAkBvD;wBAC/CyK,MAAM;oBACR;gBACF;YACF;YACAC,WAAW3G,gBACP;mBACM6E,OAAO8B,SAAS,IAAI,EAAE;gBAC1B;oBACEC,SAASxL,gCAAgC;wBACvC2E,QAAQ4F;wBACRvB;oBACF;oBACAyC,QAAQ;oBACR5L,MAAM,GAAGC,0BAA0B,gBAAgB;gBACrD;aACD,GACD2J,OAAO8B,SAAS;QACtB;QAEA,OAAO3L,mBAAmB;YACxB8J;YACAV,aAAaD,0BACXC,aACAC,SACApE,cACAqE;YAEFS;QACF,GAAGc;IACL,EAAC;AAEH,SAASiB,aAAa,QAAQ,eAAc;AAG5C,4BAA4B;AAC5B,OAAO,MAAMC,yBAAyBpC,gBAAe;AACrD,OAAO,MAAMqC,yBAAyBrC,gBAAe"}
package/dist/migrate.d.ts CHANGED
@@ -21,8 +21,8 @@ export type MigrateFolderOptions = {
21
21
  */
22
22
  from: string;
23
23
  /**
24
- * Optional Payload instance. When provided, the stored `imagekit.*` and `url` fields
25
- * in the database will be updated to reflect the new file locations after copying.
24
+ * Optional Payload instance. When provided, the stored `imagekit.*` fields in the
25
+ * database will be updated to reflect the new ImageKit asset after copying.
26
26
  */
27
27
  payload?: Payload;
28
28
  /**
@@ -40,8 +40,8 @@ export type MigrateFolderOptions = {
40
40
  */
41
41
  to: string;
42
42
  /**
43
- * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to
44
- * reconstruct canonical `url` values when updating the database.
43
+ * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign
44
+ * private source URLs when the migrator falls back to re-uploading from the source URL.
45
45
  */
46
46
  urlEndpoint: string;
47
47
  };
package/dist/migrate.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import ImageKit from '@imagekit/nodejs';
2
2
  import path from 'node:path';
3
- import { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js';
4
3
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
5
4
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
6
5
  const normalizeFolder = (folder)=>{
@@ -11,7 +10,7 @@ const normalizeFolder = (folder)=>{
11
10
  return `/${normalized}`;
12
11
  };
13
12
  const getCollectionFolder = (rootFolder, collectionSlug)=>path.posix.join(rootFolder, collectionSlug);
14
- const getFilePath = (rootFolder, collectionSlug, filename)=>path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename);
13
+ const isFileInFolder = (filePath, folderPath)=>filePath === folderPath || filePath.startsWith(`${folderPath}/`);
15
14
  const ensureFolderExists = async (client, folderPath)=>{
16
15
  const normalized = normalizeFolder(folderPath);
17
16
  const segments = normalized.split('/').filter(Boolean);
@@ -84,7 +83,19 @@ const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUr
84
83
  url: uploaded.url
85
84
  };
86
85
  };
87
- const getUploadedImagekitFields = async ({ client, fileId })=>{
86
+ const getSourceImageKitFields = async ({ client, fileId })=>{
87
+ const file = await client.files.get(fileId);
88
+ if (!file.fileId || !file.filePath || !file.url) {
89
+ throw new Error(`migrateFolder: missing ImageKit source file details for "${fileId}".`);
90
+ }
91
+ return {
92
+ fileId: file.fileId,
93
+ fileName: file.name || path.posix.basename(file.filePath),
94
+ filePath: file.filePath,
95
+ url: file.url
96
+ };
97
+ };
98
+ const getUploadedImageKitFields = async ({ client, fileId })=>{
88
99
  const file = await client.files.get(fileId);
89
100
  if (!file.fileId || !file.url) {
90
101
  throw new Error(`migrateFolder: missing ImageKit file details for "${fileId}".`);
@@ -122,7 +133,6 @@ export const migrateFolder = async (options)=>{
122
133
  }
123
134
  const privateSlugs = new Set(Object.entries(collections).filter(([, value])=>isPrivateCollectionConfig(value)).map(([slug])=>slug));
124
135
  const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
125
- const apiRoute = payload?.config.routes?.api ?? '/api';
126
136
  const fromFolder = normalizeFolder(from);
127
137
  const toFolder = normalizeFolder(to);
128
138
  if (fromFolder === toFolder) {
@@ -169,27 +179,36 @@ export const migrateFolder = async (options)=>{
169
179
  for(let i = 0; i < found.docs.length; i++){
170
180
  const doc = found.docs[i];
171
181
  const filename = doc.filename;
172
- const existingImagekit = doc.imagekit;
182
+ const existingImageKit = doc.imagekit;
183
+ const displayName = filename || existingImageKit?.fileId || doc.id;
173
184
  console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`);
174
- // Skip items that don't have ImageKit data or filename
175
- if (!filename || !existingImagekit?.fileId || !existingImagekit?.url) {
185
+ // Skip items that don't have an ImageKit file ID to migrate from.
186
+ if (!existingImageKit?.fileId) {
176
187
  skippedCount += 1;
177
- console.log(`[migrateFolder] Skipping document ${doc.id} - missing filename or ImageKit data`);
188
+ console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`);
178
189
  continue;
179
190
  }
180
- if (dryRun) {
181
- console.log(`[migrateFolder] [dry-run] Would upload: ${toFolder}/${slug}/${filename}`);
182
- console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`);
183
- } else {
184
- try {
185
- const sourceFilePath = getFilePath(fromFolder, slug, filename);
186
- const destinationFolder = getCollectionFolder(toFolder, slug);
187
- const destinationFilePath = getFilePath(toFolder, slug, filename);
191
+ try {
192
+ const destinationFolder = getCollectionFolder(toFolder, slug);
193
+ const source = await getSourceImageKitFields({
194
+ client,
195
+ fileId: existingImageKit.fileId
196
+ });
197
+ if (isFileInFolder(source.filePath, destinationFolder)) {
198
+ skippedCount += 1;
199
+ console.log(`[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`);
200
+ continue;
201
+ }
202
+ if (dryRun) {
203
+ console.log(`[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`);
204
+ console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`);
205
+ } else {
206
+ const destinationFilePath = path.posix.join(destinationFolder, source.fileName);
188
207
  let uploaded;
189
208
  try {
190
209
  const copyResponse = await client.files.copy({
191
210
  destinationPath: destinationFolder,
192
- sourceFilePath
211
+ sourceFilePath: source.filePath
193
212
  }).asResponse();
194
213
  let copiedFileId = await readFileIdFromCopyResponse(copyResponse);
195
214
  if (!copiedFileId) {
@@ -199,9 +218,9 @@ export const migrateFolder = async (options)=>{
199
218
  });
200
219
  }
201
220
  if (!copiedFileId) {
202
- throw new Error(`migrateFolder: copy request completed for "${sourceFilePath}", but the destination asset at "${destinationFilePath}" was not discoverable via ImageKit after retrying.`);
221
+ throw new Error(`migrateFolder: copy request completed for ImageKit file ID "${source.fileId}" from "${source.filePath}", but the destination asset at "${destinationFilePath}" was not discoverable via ImageKit after retrying.`);
203
222
  }
204
- uploaded = await getUploadedImagekitFields({
223
+ uploaded = await getUploadedImageKitFields({
205
224
  client,
206
225
  fileId: copiedFileId
207
226
  });
@@ -209,37 +228,23 @@ export const migrateFolder = async (options)=>{
209
228
  if (!isImageKitFileNotFoundError(error)) {
210
229
  throw error;
211
230
  }
212
- console.warn(`[migrateFolder] Source file not found at ${sourceFilePath}. Falling back to stored ImageKit URL for "${filename}".`);
231
+ console.warn(`[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for "${source.fileName}".`);
213
232
  // A private source file 401s on its plain URL, so sign it before ImageKit
214
233
  // fetches it, and re-upload as private so the copy keeps its privacy.
215
234
  const sourceUrl = isCollectionPrivate ? client.helper.buildSrc({
216
235
  expiresIn: signedUrlExpiresIn,
217
236
  signed: true,
218
- src: existingImagekit.url,
237
+ src: source.url,
219
238
  urlEndpoint
220
- }) : existingImagekit.url;
239
+ }) : source.url;
221
240
  uploaded = await uploadFileFromURL({
222
241
  client,
223
- fileName: filename,
242
+ fileName: source.fileName,
224
243
  folder: destinationFolder,
225
244
  isPrivate: isCollectionPrivate,
226
245
  sourceUrl
227
246
  });
228
247
  }
229
- const canonicalUrl = client.helper.buildSrc({
230
- src: destinationFilePath,
231
- urlEndpoint
232
- });
233
- const mimeType = doc.mimeType;
234
- const adminThumbnailURL = isCollectionPrivate ? getPrivateAdminThumbnailURL({
235
- filename,
236
- mimeType
237
- }, slug, apiRoute) : getAdminThumbnailURL({
238
- imagekit: {
239
- url: uploaded.url
240
- },
241
- mimeType
242
- });
243
248
  // Update the document with new ImageKit data
244
249
  await payload.update({
245
250
  id: doc.id,
@@ -250,17 +255,15 @@ export const migrateFolder = async (options)=>{
250
255
  isPrivate: isCollectionPrivate,
251
256
  thumbnailUrl: uploaded.thumbnailUrl,
252
257
  url: uploaded.url
253
- },
254
- thumbnailURL: adminThumbnailURL || canonicalUrl,
255
- url: canonicalUrl
258
+ }
256
259
  }
257
260
  });
258
261
  updatedCount += 1;
259
- console.log(`[migrateFolder] Content migrated! ${filename} (${doc.id})`);
260
- } catch (error) {
261
- errorCount += 1;
262
- console.error(`[migrateFolder] Failed to migrate document "${doc.id}" for file "${filename}":`, error);
262
+ console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`);
263
263
  }
264
+ } catch (error) {
265
+ errorCount += 1;
266
+ console.error(`[migrateFolder] Failed to migrate document "${doc.id}" for file "${displayName}":`, error);
264
267
  }
265
268
  }
266
269
  results.push({