@humaan/payload-storage-imagekit 2.6.0 → 2.6.2

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
@@ -6,6 +6,17 @@ import { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploa
6
6
  import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isPdfMimeType, isVideoMimeType } from './getAdminThumbnailURL.js';
7
7
  import { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js';
8
8
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
9
+ // Payload's file-type detector samples this many bytes when validating binary uploads.
10
+ const FILE_TYPE_DETECTION_SIZE = 4100;
11
+ const FILE_TYPE_DETECTION_RANGE = `bytes=0-${FILE_TYPE_DETECTION_SIZE - 1}`;
12
+ /**
13
+ * ImageKit re-encodes some uploads and serves the smaller derivative from the plain
14
+ * delivery URL — a 1.9 MB MP4 can come back as 868 KB. That is exactly what we want
15
+ * for visitors, but not when reading the file back to verify a client upload: the
16
+ * bytes and the byte count would both belong to a file ImageKit's API never reported.
17
+ * `orig-true` asks for the untouched original and does not disable optimisation for
18
+ * anyone else.
19
+ */ const ORIGINAL_FILE_TRANSFORMATION = 'orig-true';
9
20
  const PRIVATE_COLLECTION_DESCRIPTION = 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.';
10
21
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
11
22
  /**
@@ -92,20 +103,46 @@ const applyClientUploadContextToData = (data, clientUploadContext, buildThumbnai
92
103
  data.thumbnailURL = buildThumbnailURL(data);
93
104
  return data;
94
105
  };
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
- };
101
106
  const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFolder, signedUrlExpiresIn, urlEndpoint })=>{
102
- const fetchUploadedFile = async (fileURL)=>{
103
- const response = await fetch(fileURL);
107
+ const fetchUploadedFile = async (fileURL, range)=>{
108
+ const response = range ? await fetch(fileURL, {
109
+ headers: {
110
+ Range: range
111
+ }
112
+ }) : await fetch(fileURL);
104
113
  if (!response.ok) {
105
114
  throw new Error(`imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`);
106
115
  }
107
116
  return response;
108
117
  };
118
+ const fetchUploadedFilePrefix = async ({ fileSize, fileURL, mimeType })=>{
119
+ const response = await fetchUploadedFile(fileURL, FILE_TYPE_DETECTION_RANGE);
120
+ const contentLength = Number(response.headers.get('Content-Length'));
121
+ const contentRange = response.headers.get('Content-Range');
122
+ const rangeMatch = contentRange?.match(/^bytes 0-(\d+)\/(\d+)$/);
123
+ const rangeEnd = rangeMatch ? Number(rangeMatch[1]) : Number.NaN;
124
+ const totalSize = rangeMatch ? Number(rangeMatch[2]) : Number.NaN;
125
+ const expectedLength = Math.min(fileSize, FILE_TYPE_DETECTION_SIZE);
126
+ if (response.status !== 206 || !rangeMatch || !Number.isSafeInteger(contentLength) || contentLength !== expectedLength || !Number.isSafeInteger(rangeEnd) || rangeEnd + 1 !== contentLength || totalSize !== fileSize) {
127
+ // Deliberately not awaited. Under the instrumented `fetch` that Next.js and
128
+ // Vercel install, this promise never settles, so awaiting it swallows the error
129
+ // and hangs the request until the function times out (5 minutes on Vercel)
130
+ // instead of surfacing a 500.
131
+ void response.body?.cancel().catch(()=>{});
132
+ throw new Error('imagekitStorage: ImageKit returned an invalid video range response.');
133
+ }
134
+ const filePrefix = await response.arrayBuffer();
135
+ if (filePrefix.byteLength !== contentLength) {
136
+ throw new Error('imagekitStorage: ImageKit returned an invalid video range body.');
137
+ }
138
+ return new Response(filePrefix, {
139
+ headers: {
140
+ 'Content-Length': String(filePrefix.byteLength),
141
+ 'Content-Type': mimeType
142
+ },
143
+ status: 200
144
+ });
145
+ };
109
146
  return ({ collection })=>{
110
147
  const isPrivate = privateSlugs.has(collection.slug);
111
148
  // Sign a relative ImageKit path against the configured urlEndpoint. We always sign
@@ -255,23 +292,49 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
255
292
  const hasClientUploadContext = 'clientUploadContext' in args.params;
256
293
  if (hasClientUploadContext) {
257
294
  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
- });
295
+ if (!isImageKitClientUploadContext(clientUploadContext)) {
296
+ throw new Error('imagekitStorage: client upload context is invalid.');
297
+ }
298
+ const expectedFilePath = getFilePath(rootFolder, args.params.collection, args.params.filename);
299
+ const fileId = clientUploadContext.fileId;
300
+ if (typeof fileId !== 'string' || !fileId) {
301
+ throw new Error('imagekitStorage: client upload is missing its file ID.');
302
+ }
303
+ const uploadedFile = await client.files.get(fileId);
304
+ const uploadedFileMime = uploadedFile.mime;
305
+ const uploadedFileSize = uploadedFile.size;
306
+ const metadataMatchesUpload = uploadedFile.fileId === fileId && uploadedFile.filePath === expectedFilePath && uploadedFile.name === args.params.filename && typeof uploadedFileMime === 'string' && typeof uploadedFile.url === 'string' && typeof uploadedFileSize === 'number' && Number.isSafeInteger(uploadedFileSize) && uploadedFileSize > 0 && Boolean(uploadedFile.isPrivateFile) === isPrivate && (typeof clientUploadContext.size !== 'number' || uploadedFileSize === clientUploadContext.size);
307
+ if (!metadataMatchesUpload || typeof uploadedFileMime !== 'string' || typeof uploadedFileSize !== 'number') {
308
+ throw new Error('imagekitStorage: client upload metadata did not match.');
267
309
  }
268
- const clientUploadURL = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.url === 'string' && clientUploadContext.url.length > 0 ? clientUploadContext.url : null;
269
- const filePath = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.filePath === 'string' && clientUploadContext.filePath.length > 0 ? clientUploadContext.filePath : getFilePath(rootFolder, args.params.collection, args.params.filename);
270
- const imageKitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
271
- src: filePath,
310
+ // Replace browser-provided values before hooks persist the upload context.
311
+ Object.assign(clientUploadContext, {
312
+ name: uploadedFile.name,
313
+ fileId: uploadedFile.fileId,
314
+ filePath: uploadedFile.filePath,
315
+ fileType: uploadedFile.fileType,
316
+ mimeType: uploadedFileMime,
317
+ size: uploadedFileSize,
318
+ thumbnailUrl: uploadedFile.thumbnail,
319
+ url: uploadedFile.url
320
+ });
321
+ const verifiedFileURL = isPrivate ? buildSignedURL(expectedFilePath, ORIGINAL_FILE_TRANSFORMATION) : client.helper.buildSrc({
322
+ src: expectedFilePath,
323
+ transformation: [
324
+ {
325
+ raw: ORIGINAL_FILE_TRANSFORMATION
326
+ }
327
+ ],
272
328
  urlEndpoint
273
329
  });
274
- return fetchUploadedFile(imageKitURL);
330
+ if (isVideoMimeType(uploadedFileMime)) {
331
+ return fetchUploadedFilePrefix({
332
+ fileSize: uploadedFileSize,
333
+ fileURL: verifiedFileURL,
334
+ mimeType: uploadedFileMime
335
+ });
336
+ }
337
+ return fetchUploadedFile(verifiedFileURL);
275
338
  }
276
339
  const doc = args.doc || await findUploadDocumentByFilename({
277
340
  collection: args.params.collection,
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 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"}
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// Payload's file-type detector samples this many bytes when validating binary uploads.\nconst FILE_TYPE_DETECTION_SIZE = 4100\nconst FILE_TYPE_DETECTION_RANGE = `bytes=0-${FILE_TYPE_DETECTION_SIZE - 1}`\n/**\n * ImageKit re-encodes some uploads and serves the smaller derivative from the plain\n * delivery URL — a 1.9 MB MP4 can come back as 868 KB. That is exactly what we want\n * for visitors, but not when reading the file back to verify a client upload: the\n * bytes and the byte count would both belong to a file ImageKit's API never reported.\n * `orig-true` asks for the untouched original and does not disable optimisation for\n * anyone else.\n */\nconst ORIGINAL_FILE_TRANSFORMATION = 'orig-true'\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 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, range?: string): Promise<Response> => {\n const response = range\n ? await fetch(fileURL, {\n headers: {\n Range: range,\n },\n })\n : 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 const fetchUploadedFilePrefix = async ({\n fileSize,\n fileURL,\n mimeType,\n }: {\n fileSize: number\n fileURL: string\n mimeType: string\n }): Promise<Response> => {\n const response = await fetchUploadedFile(fileURL, FILE_TYPE_DETECTION_RANGE)\n const contentLength = Number(response.headers.get('Content-Length'))\n const contentRange = response.headers.get('Content-Range')\n const rangeMatch = contentRange?.match(/^bytes 0-(\\d+)\\/(\\d+)$/)\n const rangeEnd = rangeMatch ? Number(rangeMatch[1]) : Number.NaN\n const totalSize = rangeMatch ? Number(rangeMatch[2]) : Number.NaN\n const expectedLength = Math.min(fileSize, FILE_TYPE_DETECTION_SIZE)\n\n if (\n response.status !== 206 ||\n !rangeMatch ||\n !Number.isSafeInteger(contentLength) ||\n contentLength !== expectedLength ||\n !Number.isSafeInteger(rangeEnd) ||\n rangeEnd + 1 !== contentLength ||\n totalSize !== fileSize\n ) {\n // Deliberately not awaited. Under the instrumented `fetch` that Next.js and\n // Vercel install, this promise never settles, so awaiting it swallows the error\n // and hangs the request until the function times out (5 minutes on Vercel)\n // instead of surfacing a 500.\n void response.body?.cancel().catch(() => {})\n throw new Error('imagekitStorage: ImageKit returned an invalid video range response.')\n }\n\n const filePrefix = await response.arrayBuffer()\n\n if (filePrefix.byteLength !== contentLength) {\n throw new Error('imagekitStorage: ImageKit returned an invalid video range body.')\n }\n\n return new Response(filePrefix, {\n headers: {\n 'Content-Length': String(filePrefix.byteLength),\n 'Content-Type': mimeType,\n },\n status: 200,\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 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\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n throw new Error('imagekitStorage: client upload context is invalid.')\n }\n\n const expectedFilePath = getFilePath(\n rootFolder,\n args.params.collection,\n args.params.filename,\n )\n const fileId = clientUploadContext.fileId\n\n if (typeof fileId !== 'string' || !fileId) {\n throw new Error('imagekitStorage: client upload is missing its file ID.')\n }\n\n const uploadedFile = await client.files.get(fileId)\n const uploadedFileMime = uploadedFile.mime\n const uploadedFileSize = uploadedFile.size\n const metadataMatchesUpload =\n uploadedFile.fileId === fileId &&\n uploadedFile.filePath === expectedFilePath &&\n uploadedFile.name === args.params.filename &&\n typeof uploadedFileMime === 'string' &&\n typeof uploadedFile.url === 'string' &&\n typeof uploadedFileSize === 'number' &&\n Number.isSafeInteger(uploadedFileSize) &&\n uploadedFileSize > 0 &&\n Boolean(uploadedFile.isPrivateFile) === isPrivate &&\n (typeof clientUploadContext.size !== 'number' ||\n uploadedFileSize === clientUploadContext.size)\n\n if (\n !metadataMatchesUpload ||\n typeof uploadedFileMime !== 'string' ||\n typeof uploadedFileSize !== 'number'\n ) {\n throw new Error('imagekitStorage: client upload metadata did not match.')\n }\n\n // Replace browser-provided values before hooks persist the upload context.\n Object.assign(clientUploadContext, {\n name: uploadedFile.name,\n fileId: uploadedFile.fileId,\n filePath: uploadedFile.filePath,\n fileType: uploadedFile.fileType,\n mimeType: uploadedFileMime,\n size: uploadedFileSize,\n thumbnailUrl: uploadedFile.thumbnail,\n url: uploadedFile.url,\n })\n\n const verifiedFileURL = isPrivate\n ? buildSignedURL(expectedFilePath, ORIGINAL_FILE_TRANSFORMATION)\n : client.helper.buildSrc({\n src: expectedFilePath,\n transformation: [{ raw: ORIGINAL_FILE_TRANSFORMATION }],\n urlEndpoint,\n })\n\n if (isVideoMimeType(uploadedFileMime)) {\n return fetchUploadedFilePrefix({\n fileSize: uploadedFileSize,\n fileURL: verifiedFileURL,\n mimeType: uploadedFileMime,\n })\n }\n\n return fetchUploadedFile(verifiedFileURL)\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","FILE_TYPE_DETECTION_SIZE","FILE_TYPE_DETECTION_RANGE","ORIGINAL_FILE_TRANSFORMATION","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","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","range","response","fetch","headers","Range","ok","status","fetchUploadedFilePrefix","fileSize","mimeType","contentLength","Number","get","contentRange","rangeMatch","match","rangeEnd","NaN","totalSize","expectedLength","Math","min","isSafeInteger","body","cancel","catch","filePrefix","arrayBuffer","byteLength","Response","String","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","hasClientUploadContext","params","expectedFilePath","uploadedFile","uploadedFileMime","mime","uploadedFileSize","size","metadataMatchesUpload","Boolean","Object","assign","fileType","thumbnail","verifiedFileURL","signSrc","redirect","imageKitURL","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","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":"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"}
package/dist/migrate.js CHANGED
@@ -1,4 +1,4 @@
1
- import ImageKit from '@imagekit/nodejs';
1
+ /* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */ import ImageKit from '@imagekit/nodejs';
2
2
  import path from 'node:path';
3
3
  const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
4
4
  const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` fields in the\n * database will be updated to reflect the new ImageKit asset after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign\n * private source URLs when the migrator falls back to re-uploading from the source URL.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst isFileInFolder = (filePath: string, folderPath: string): boolean =>\n filePath === folderPath || filePath.startsWith(`${folderPath}/`)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\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 if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getSourceImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{\n fileId: string\n fileName: string\n filePath: string\n url: string\n}> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit source file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n fileName: file.name || path.posix.basename(file.filePath),\n filePath: file.filePath,\n url: file.url,\n }\n}\n\nconst getUploadedImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `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 const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImageKit = (doc as any).imagekit\n const displayName = filename || existingImageKit?.fileId || doc.id\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have an ImageKit file ID to migrate from.\n if (!existingImageKit?.fileId) {\n skippedCount += 1\n console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`)\n continue\n }\n\n try {\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const source = await getSourceImageKitFields({\n client,\n fileId: existingImageKit.fileId,\n })\n\n if (isFileInFolder(source.filePath, destinationFolder)) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`,\n )\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n const destinationFilePath = path.posix.join(destinationFolder, source.fileName)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath: source.filePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `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.`,\n )\n }\n\n uploaded = await getUploadedImageKitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for \"${source.fileName}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: source.url,\n urlEndpoint,\n })\n : source.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: source.fileName,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`)\n }\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${displayName}\":`,\n error,\n )\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","isFileInFolder","filePath","folderPath","startsWith","ensureFolderExists","client","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getSourceImageKitFields","get","getUploadedImageKitFields","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","filename","existingImageKit","displayName","id","destinationFolder","source","destinationFilePath","copyResponse","copy","destinationPath","sourceFilePath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","update","data","push","jobId","status"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["/* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */\n\nimport type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` fields in the\n * database will be updated to reflect the new ImageKit asset after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign\n * private source URLs when the migrator falls back to re-uploading from the source URL.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst isFileInFolder = (filePath: string, folderPath: string): boolean =>\n filePath === folderPath || filePath.startsWith(`${folderPath}/`)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\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 if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getSourceImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{\n fileId: string\n fileName: string\n filePath: string\n url: string\n}> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit source file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n fileName: file.name || path.posix.basename(file.filePath),\n filePath: file.filePath,\n url: file.url,\n }\n}\n\nconst getUploadedImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `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 const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImageKit = (doc as { imagekit?: { fileId?: string } }).imagekit\n const displayName = filename || existingImageKit?.fileId || doc.id\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have an ImageKit file ID to migrate from.\n if (!existingImageKit?.fileId) {\n skippedCount += 1\n console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`)\n continue\n }\n\n try {\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const source = await getSourceImageKitFields({\n client,\n fileId: existingImageKit.fileId,\n })\n\n if (isFileInFolder(source.filePath, destinationFolder)) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`,\n )\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n const destinationFilePath = path.posix.join(destinationFolder, source.fileName)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath: source.filePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `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.`,\n )\n }\n\n uploaded = await getUploadedImageKitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for \"${source.fileName}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: source.url,\n urlEndpoint,\n })\n : source.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: source.fileName,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`)\n }\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${displayName}\":`,\n error,\n )\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","isFileInFolder","filePath","folderPath","startsWith","ensureFolderExists","client","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getSourceImageKitFields","get","getUploadedImageKitFields","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","filename","existingImageKit","displayName","id","destinationFolder","source","destinationFilePath","copyResponse","copy","destinationPath","sourceFilePath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","update","data","push","jobId","status"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humaan/payload-storage-imagekit",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "ImageKit storage adapter plugin for Payload CMS uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,41 +36,43 @@
36
36
  "dist"
37
37
  ],
38
38
  "devDependencies": {
39
- "@eslint/eslintrc": "^3.3.5",
40
- "@payloadcms/db-mongodb": "3.85.1",
41
- "@payloadcms/db-postgres": "3.85.1",
42
- "@payloadcms/db-sqlite": "3.85.1",
39
+ "@eslint/eslintrc": "^3.3.6",
40
+ "@payloadcms/db-mongodb": "3.87.1",
41
+ "@payloadcms/db-postgres": "3.87.1",
42
+ "@payloadcms/db-sqlite": "3.87.1",
43
43
  "@payloadcms/eslint-config": "3.28.0",
44
- "@payloadcms/next": "3.85.1",
45
- "@payloadcms/plugin-cloud-storage": "3.85.1",
46
- "@payloadcms/richtext-lexical": "3.85.1",
47
- "@payloadcms/ui": "3.85.1",
48
- "@playwright/test": "1.59.1",
49
- "@swc-node/register": "1.10.9",
50
- "@swc/cli": "0.6.0",
51
- "@types/node": "24.12.3",
52
- "@types/react": "19.2.17",
53
- "@types/react-dom": "19.2.3",
44
+ "@payloadcms/next": "3.87.1",
45
+ "@payloadcms/plugin-cloud-storage": "3.87.1",
46
+ "@payloadcms/richtext-lexical": "3.87.1",
47
+ "@payloadcms/ui": "3.87.1",
48
+ "@playwright/test": "1.62.1",
49
+ "@swc-node/register": "1.12.1",
50
+ "@swc/cli": "0.8.1",
51
+ "@types/node": "26.2.0",
52
+ "@types/react": "19.2.18",
53
+ "@types/react-dom": "19.2.4",
54
+ "conventional-changelog": "8.1.1",
55
+ "conventional-changelog-conventionalcommits": "10.2.1",
54
56
  "copyfiles": "2.4.1",
55
57
  "cross-env": "10.1.0",
56
58
  "eslint": "^9.39.4",
57
- "eslint-config-next": "16.2.9",
58
- "graphql": "^16.14.2",
59
- "mongodb-memory-server": "10.1.4",
60
- "next": "16.2.9",
61
- "open": "^10.2.0",
62
- "payload": "3.85.1",
63
- "prettier": "^3.9.3",
59
+ "eslint-config-next": "16.3.0",
60
+ "graphql": "^17.0.2",
61
+ "mongodb-memory-server": "11.2.0",
62
+ "next": "16.3.0",
63
+ "open": "^11.0.0",
64
+ "payload": "3.87.1",
65
+ "prettier": "^3.9.6",
64
66
  "qs-esm": "8.0.1",
65
- "react": "19.2.7",
66
- "react-dom": "19.2.7",
67
- "rimraf": "3.0.2",
68
- "sharp": "0.34.2",
69
- "sort-package-json": "^2.15.1",
67
+ "react": "19.2.8",
68
+ "react-dom": "19.2.8",
69
+ "rimraf": "6.1.3",
70
+ "sharp": "0.35.3",
71
+ "sort-package-json": "^4.0.0",
70
72
  "typescript": "6.0.3",
71
- "vite": "7.3.6",
72
- "vite-tsconfig-paths": "6.0.5",
73
- "vitest": "4.1.9"
73
+ "vite": "8.2.1",
74
+ "vite-tsconfig-paths": "6.1.1",
75
+ "vitest": "4.1.10"
74
76
  },
75
77
  "peerDependencies": {
76
78
  "@payloadcms/plugin-cloud-storage": ">=3.85.1 <4.0.0",
@@ -83,14 +85,14 @@
83
85
  "access": "public",
84
86
  "registry": "https://registry.npmjs.org/"
85
87
  },
86
- "registry": "https://registry.npmjs.org/",
87
88
  "dependencies": {
88
- "@imagekit/nodejs": "^7.8.1"
89
+ "@imagekit/nodejs": "^7.10.0"
89
90
  },
90
91
  "scripts": {
91
- "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
92
+ "build": "pnpm clean && pnpm copyfiles && pnpm build:types && pnpm build:swc",
92
93
  "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
93
94
  "build:types": "tsc --outDir dist --rootDir ./src",
95
+ "changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md",
94
96
  "clean": "rimraf {dist,*.tsbuildinfo}",
95
97
  "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
96
98
  "dev": "next dev dev --turbo",
@@ -101,6 +103,7 @@
101
103
  "generate:types": "pnpm dev:generate-types",
102
104
  "lint": "eslint",
103
105
  "lint:fix": "eslint ./src --fix",
106
+ "release:bump": "node scripts/bump-release.mjs",
104
107
  "test": "pnpm test:int && pnpm test:e2e",
105
108
  "test:e2e": "playwright test",
106
109
  "test:int": "vitest"