@humaan/payload-storage-imagekit 2.8.2 → 2.9.1

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/README.md CHANGED
@@ -243,19 +243,19 @@ upload: {
243
243
  The adapter streams Payload temporary files to ImageKit for server-side uploads,
244
244
  rather than loading those files into a second buffer.
245
245
 
246
- ## Folder Migration
246
+ ## Folder and account migration
247
247
 
248
- Use `migrateFolder` to copy uploads from one ImageKit root folder to another (e.g. staging to production).
248
+ Use `migrateFolder` to copy referenced originals between folders or ImageKit accounts (e.g. staging to production). It updates the supplied Payload database in place and retains source assets.
249
249
 
250
250
  Behavior:
251
251
 
252
- - Copies files one-by-one per collection
253
- - Creates destination folders automatically if they do not exist
254
- - Optional database update via Payload local API
255
- - Dry-run is enabled by default (`dryRun: true`)
256
- - Resolves each source asset by the stored `imagekit.fileId`, then copies the ImageKit-returned file path into the destination folder. This avoids mismatches between Payload's `filename` field and ImageKit's normalized file names.
257
- - Reruns are idempotent, including dry-runs: if a document's stored `imagekit.fileId` already resolves to a file in the destination collection folder, that document is skipped.
258
- - Handles [private files](#private-files): mark a collection `{ private: true }` and migrated documents are stored with `imagekit.isPrivate: true`. The fast server-side copy preserves the source file's ImageKit privacy; when the copy falls back to re-uploading from the source URL, the source URL is signed (so a private source can be read) and the file is re-uploaded with `isPrivateFile: true`. Because ImageKit privacy is fixed at upload, migrating assumes the destination collection's privacy matches the source — copying a public file into a `{ private: true }` collection keeps the file public on ImageKit.
252
+ - Scans documents in pages and skips assets already in the destination account's collection folder.
253
+ - Resolves stored file IDs in the source account and only transfers assets under the configured source collection folder.
254
+ - Dry-run is enabled by default and checks source assets and destination conflicts without migration writes.
255
+ - Transfers original bytes using URL uploads (`orig-true`), signing private source URLs with the source credentials.
256
+ - Tags uploads so reruns can recover even if an upload succeeded but its response or the database update failed.
257
+ - Reports unrelated destination filename conflicts instead of overwriting files.
258
+ - Updates ImageKit references, filenames, prefixes, legacy object keys and thumbnails together. Source privacy is preserved unless explicitly overridden per collection.
259
259
 
260
260
  Example:
261
261
 
@@ -266,39 +266,72 @@ import { getPayload } from 'payload'
266
266
 
267
267
  const payload = await getPayload({ config })
268
268
 
269
- const results = await migrateFolder({
270
- collections: { media: true },
271
- dryRun: true,
272
- from: process.env.IMAGEKIT_FOLDER!,
273
- payload,
274
- privateKey: process.env.IMAGEKIT_PRIVATE_KEY!,
275
- to: process.env.IMAGEKIT_FOLDER_MIGRATION_DESTINATION!,
276
- urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT!,
277
- })
278
-
279
- console.log(results)
280
- await payload.destroy()
269
+ try {
270
+ const results = await migrateFolder({
271
+ collections: { media: true },
272
+ dryRun: process.env.DRY_RUN !== 'false',
273
+ payload,
274
+ from: {
275
+ folder: process.env.IMAGEKIT_MIGRATION_FROM_FOLDER!,
276
+ privateKey: process.env.IMAGEKIT_MIGRATION_FROM_PRIVATE_KEY!,
277
+ urlEndpoint: process.env.IMAGEKIT_MIGRATION_FROM_ENDPOINT!,
278
+ },
279
+ to: {
280
+ folder: process.env.IMAGEKIT_MIGRATION_TO_FOLDER!,
281
+ privateKey: process.env.IMAGEKIT_MIGRATION_TO_PRIVATE_KEY!,
282
+ urlEndpoint: process.env.IMAGEKIT_MIGRATION_TO_ENDPOINT!,
283
+ },
284
+ })
285
+ console.log(results)
286
+ if (results.some(({ failed }) => failed > 0)) process.exitCode = 1
287
+ } finally {
288
+ await payload.destroy()
289
+ }
281
290
  ```
282
291
 
283
292
  `migrateFolder(options)` options:
284
293
 
285
- - `collections` (required): `Partial<Record<UploadCollectionSlug, true | { private?: boolean }>>`
286
- - `from` (required): source root folder, e.g. `/payload-uploads-staging`
287
- - `to` (required): destination root folder, e.g. `/payload-uploads-production`
288
- - `privateKey` (required): ImageKit private API key
289
- - `urlEndpoint` (required): ImageKit URL endpoint
294
+ - `collections` (required): `Partial<Record<UploadCollectionSlug, true | { private?: boolean }>>`. `true` preserves source privacy; an explicit `private` boolean sets destination privacy on transferred files.
295
+ - `from` / `to` (required): `{ folder, privateKey, urlEndpoint }`. Folder roots include collection subfolders. Use the same credentials for same-account migrations; identical folders in different accounts are supported. No public key is needed.
290
296
  - `dryRun` (optional, default `true`): when `true`, logs what would happen without writes
291
- - `payload` (optional): when provided and `dryRun` is `false`, updates `url` + `imagekit.*` fields
292
- - `signedUrlExpiresIn` (optional, default `900`): expiry in seconds for the signed URL used to read a private source file during the re-upload fallback
297
+ - `payload` (required): the database containing the records to update. Boot it with the destination storage configuration, particularly for private delivery.
298
+ - `signedUrlExpiresIn` (optional, default `900`): private source URL expiry in seconds.
299
+
300
+ Each collection returns `{ collectionSlug, planned, migrated, reused, skipped, failed, errors }`.
301
+ `reused` counts recovered uploads (also included in `migrated` after a live database update).
302
+ Per-document failures are reported in `errors`; configuration/query errors throw. There are no background jobs.
303
+
304
+ **Updating wrappers for 2.9:** replace string `from`/`to` and top-level credentials with the objects
305
+ above, always supply `payload`, and use the result counts instead of `status`/`jobId`.
306
+ `from.folder` now filters source assets; the old implementation only logged it.
307
+ The storage plugin's configuration is unchanged.
308
+
309
+ For the production → staging → production database-cloning workflow, run against the cloned
310
+ production database with staging as `from` and production as `to`. Existing production assets are
311
+ skipped; staging additions are transferred. Set normal app storage settings to the destination for
312
+ future uploads.
313
+
314
+ Transfers use a tagged URL upload in both same-account and cross-account cases, rather than a
315
+ native server-side copy. Keep migration tags intact until all records have been updated. A source
316
+ version change between attempts becomes a conflict rather than reusing stale content. Nested source
317
+ paths are flattened into the destination collection folder; collisions are reported. File version
318
+ history and account-specific custom metadata schemas are not transferred. Run one migration per
319
+ destination at a time; concurrent runs may report a conflict recoverable on a subsequent run.
320
+
321
+ On MongoDB, migration updates use the database adapter directly so the old `_objectKey` can be
322
+ removed in the same write as the new storage reference. Writing `null` would collide with the
323
+ legacy sparse unique index. These metadata updates bypass Payload validation and change hooks,
324
+ including cache revalidation; refresh application caches after the migration. Already uploaded
325
+ files from an interrupted 2.9.0 migration can be recovered by rerunning with this fix.
293
326
 
294
327
  Run a migration script:
295
328
 
296
329
  ```bash
297
- # dry-run (default)
298
- pnpx tsx --env-file=../.env imagekit-migration.ts
330
+ # From the plugin repository root; dry-run is the default
331
+ DRY_RUN=true pnpm dlx tsx --env-file=.env.local dev/migration/imagekit-migration.ts
299
332
 
300
333
  # live run
301
- DRY_RUN=false pnpx tsx --env-file=../.env imagekit-migration.ts
334
+ DRY_RUN=false pnpm dlx tsx --env-file=.env.local dev/migration/imagekit-migration.ts
302
335
  ```
303
336
 
304
337
  ## Notes
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export type PayloadStorageImageKitConfig = {
41
41
  };
42
42
  export declare const imagekitStorage: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
43
43
  export { migrateFolder } from './migrate.js';
44
- export type { MigrateFolderOptions, MigrationJobResult } from './migrate.js';
44
+ export type { MigrateFolderOptions, MigrationJobResult, MigrationLocation } from './migrate.js';
45
45
  export declare const payloadStorageImagekit: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
46
46
  export declare const payloadStorageImageKit: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
47
47
  export type PayloadStorageImagekitConfig = PayloadStorageImageKitConfig;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n CollectionOptions,\n} from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionConfig,\n CollectionSlug,\n Config,\n Field,\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 {\n buildStoragePathData,\n buildUploadStoragePathData,\n getStorageFilePath,\n initClientUploads,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { openAsBlob } from 'node:fs'\nimport path from 'node:path'\nimport { APIError } from 'payload'\nimport {\n getRangeRequestInfo,\n isXmlMimeType,\n UPLOAD_CONTENT_SECURITY_POLICY,\n} from 'payload/internal'\nimport { validateMimeType } from 'payload/shared'\n\nimport type { VerifiedImageKitUpload } from './clientUploadVerification.js'\n\nimport { imageKitUploadAuthPath, isImageKitReceiptContext } from './clientUploads.js'\nimport {\n createImageKitClientUploadVerifier,\n getClientUploadStorageFilePath,\n} from './clientUploadVerification.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/**\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\nconst XML_FAMILY_MIME_TYPES = ['application/xml', 'image/svg+xml', 'text/xml']\n\nconst collectionCanStoreXml = (collection: CollectionConfig): boolean => {\n const upload = collection.upload\n\n if (!upload || upload === true) {\n return Boolean(upload)\n }\n\n // Payload skips MIME and content restrictions when this escape hatch is enabled.\n if (upload.allowRestrictedFileTypes) {\n return true\n }\n\n const allowedMimeTypes = upload.mimeTypes\n\n return (\n !allowedMimeTypes?.length ||\n allowedMimeTypes.some((mimeType) => mimeType.includes('*') || isXmlMimeType(mimeType)) ||\n XML_FAMILY_MIME_TYPES.some((mimeType) => validateMimeType(mimeType, allowedMimeTypes))\n )\n}\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?: ClientUploadsConfig\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 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 applyVerifiedClientUploadToData = <T extends UploadDocumentData>(\n data: T,\n uploaded: VerifiedImageKitUpload,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate: boolean,\n): T => {\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.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 verifyClientUpload,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: ClientUploadsConfig\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n verifyClientUpload: ReturnType<typeof createImageKitClientUploadVerifier>\n}): Adapter => {\n const fetchUploadedFile = async ({\n fileSize,\n fileURL,\n mimeType,\n range,\n }: {\n fileSize: number\n fileURL: string\n mimeType: string\n range?: null | string\n }): Promise<Response> => {\n const rangeResult = getRangeRequestInfo({ fileSize, rangeHeader: range ?? null })\n\n if (rangeResult.type === 'invalid') {\n return new Response(null, {\n headers: new Headers(rangeResult.headers),\n status: rangeResult.status,\n })\n }\n\n const normalizedRange =\n rangeResult.type === 'partial'\n ? `bytes=${rangeResult.rangeStart}-${rangeResult.rangeEnd}`\n : undefined\n // Payload cancels image probes after reading a bounded prefix. Next.js memoizes\n // GET responses by teeing their streams; cancelling one branch can then wait\n // forever for its unread cached sibling. An explicit signal opts out of that\n // memoization, while no-store also excludes these reads from the data cache.\n const response = await fetch(fileURL, {\n cache: 'no-store',\n ...(normalizedRange ? { headers: { Range: normalizedRange } } : {}),\n signal: new AbortController().signal,\n })\n\n if (!response.ok || response.status !== rangeResult.status) {\n void response.body?.cancel().catch(() => undefined)\n throw new Error(\n `imagekitStorage: ImageKit returned an unexpected file response (${response.status}).`,\n )\n }\n\n const upstreamLength = response.headers.get('Content-Length')\n const expectedLength = rangeResult.headers['Content-Length']\n const upstreamRange = response.headers.get('Content-Range')\n const expectedRange =\n rangeResult.type === 'partial' ? rangeResult.headers['Content-Range'] : undefined\n\n if (\n (upstreamLength !== null && upstreamLength !== expectedLength) ||\n (expectedRange && upstreamRange !== expectedRange)\n ) {\n void response.body?.cancel().catch(() => undefined)\n throw new Error('imagekitStorage: ImageKit returned mismatched file content metadata.')\n }\n\n const headers = new Headers(rangeResult.headers)\n headers.set('Content-Type', mimeType)\n if (isXmlMimeType(mimeType)) {\n headers.set('Content-Security-Policy', UPLOAD_CONTENT_SECURITY_POLICY)\n }\n\n return new Response(response.body, {\n headers,\n status: response.status,\n })\n }\n\n return ({ collection, prefix = getCollectionFolder(rootFolder, collection.slug) }) => {\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 // Presentational wrapper: tucks the ImageKit details into a closed panel without\n // changing the `imagekit.*` data paths.\n type: 'collapsible',\n admin: {\n condition: (data) => hasStoredImageKitURL(data),\n initCollapsed: true,\n },\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n 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 const source = (data || {}) as { _objectKey?: string; prefix?: string }\n const documentPrefix = path.posix.join(\n source.prefix || prefix,\n source._objectKey || '',\n )\n const storageFilePath = buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix: documentPrefix,\n filename,\n }).storageFilePath\n\n return buildSignedURL(`/${storageFilePath}`)\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: false,\n },\n ],\n label: 'Storage: ImageKit',\n },\n ],\n generateURL: ({ filename, prefix: documentPrefix = '' }) =>\n client.helper.buildSrc({\n src: `/${buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix: documentPrefix,\n filename,\n }).storageFilePath}`,\n urlEndpoint,\n }),\n handleDelete: async ({ doc, storageFilePath }) => {\n const storedFileId = (doc as ImageKitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId || (await findImageKitFileId(client, `/${storageFilePath.replace(/^\\/+/, '')}`))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file, storageFilePath }) => {\n if (clientUploadContext) {\n const uploaded = await verifyClientUpload({\n collectionSlug: collection.slug,\n context: clientUploadContext,\n })\n\n return applyVerifiedClientUploadToData(data, uploaded, buildThumbnailURL, isPrivate)\n }\n\n // ImageKit's `toFile()` consumes async iterables before starting the request. A file-backed\n // Blob keeps temporary-file uploads lazy so FormData can stream them from disk instead.\n const uploadSource = file.tempFilePath\n ? await openAsBlob(file.tempFilePath, { type: file.mimeType })\n : file.buffer\n const uploadFile = await toFile(uploadSource, file.filename, { type: file.mimeType })\n const uploadFolder = path.posix.dirname(`/${storageFilePath.replace(/^\\/+/, '')}`)\n const uploadFilename = path.posix.basename(storageFilePath)\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: uploadFilename,\n folder: uploadFolder,\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 || uploadFilename,\n })\n\n return data\n },\n requiresClientUploadReceipt: true,\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 (!isImageKitReceiptContext(clientUploadContext)) {\n throw new APIError('Invalid ImageKit client upload context.', 400)\n }\n\n const uploadedFile = await verifyClientUpload({\n collectionSlug: collection.slug,\n context: clientUploadContext,\n })\n const expectedFilePath = getClientUploadStorageFilePath({\n collectionPrefix: prefix,\n context: clientUploadContext,\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 return fetchUploadedFile({\n fileSize: uploadedFile.size,\n fileURL: verifiedFileURL,\n mimeType: uploadedFile.mimeType,\n range: req.headers.get('Range'),\n })\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 const storageFilePath = await getStorageFilePath({\n collection,\n collectionPrefix: prefix,\n doc: doc || undefined,\n filename: args.params.filename,\n prefixQueryParam: args.params.prefix,\n req,\n })\n const imageKitPath = `/${storageFilePath.replace(/^\\/+/, '')}`\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n\n // XML-family uploads (including SVG) must pass through Payload so its CSP is\n // present even when ImageKit uses a custom, same-origin delivery domain.\n if (typeof mimeType === 'string' && isXmlMimeType(mimeType)) {\n const filesize = (doc as { filesize?: number } | null)?.filesize\n\n if (typeof filesize !== 'number' || !Number.isSafeInteger(filesize) || filesize < 0) {\n throw new APIError('Stored ImageKit file metadata is incomplete.', 500)\n }\n\n const fileURL = isDocumentPrivate(doc, isPrivate)\n ? buildSignedURL(imageKitPath, ORIGINAL_FILE_TRANSFORMATION)\n : client.helper.buildSrc({\n src: imageKitPath,\n transformation: [{ raw: ORIGINAL_FILE_TRANSFORMATION }],\n urlEndpoint,\n })\n\n return fetchUploadedFile({\n fileSize: filesize,\n fileURL,\n mimeType,\n range: req.headers.get('Range'),\n })\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 let signSrc = imageKitPath\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 imageKitPath,\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 rootFolder: 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 prefix: getCollectionFolder(rootFolder, collectionSlug),\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 const configuredSlugs = new Set(Object.keys(collections))\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 if (disablePayloadAccessControl) {\n const xmlCollectionSlugs = (config.collections || [])\n .filter(\n (collection) =>\n configuredSlugs.has(collection.slug) && collectionCanStoreXml(collection),\n )\n .map((collection) => collection.slug)\n\n if (xmlCollectionSlugs.length > 0) {\n throw new Error(\n `imagekitStorage: \\`disablePayloadAccessControl\\` cannot be used with collections that can store XML-family files (${xmlCollectionSlugs.join(', ')}). XML-family files require Payload's file route so its Content Security Policy is applied. Configure a MIME allow list that excludes SVG/XML, or keep Payload access control enabled.`,\n )\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 collectionSecurity = Object.fromEntries(\n (config.collections || [])\n .filter((collection) => configuredSlugs.has(collection.slug))\n .map((collection) => [\n collection.slug,\n {\n collection,\n isPrivate: privateSlugs.has(collection.slug),\n prefix: getCollectionFolder(rootFolder, collection.slug),\n },\n ]),\n )\n const verifyClientUpload = createImageKitClientUploadVerifier({\n client: imageKitClient,\n collections: collectionSecurity,\n })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imageKitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n verifyClientUpload,\n })\n\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 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: baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n async ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n const context = req.file.clientUploadContext\n const requestMimeType = req.file.mimetype.split(';', 1)[0]?.trim().toLowerCase()\n if (\n !isImageKitReceiptContext(context) ||\n context.filename !== data.filename ||\n context.filename !== req.file.name ||\n // Payload can clear req.file.size when preparing an in-memory image.\n // generateFileData preserves the original size on the document.\n context.expectedSize !== data.filesize\n ) {\n throw new APIError('ImageKit client upload reference does not match this request.', 400)\n }\n\n const uploaded = await verifyClientUpload({\n collectionSlug: collection.slug,\n context,\n })\n // Payload keeps the browser's MIME unless it fetched the file content, in which\n // case it uses the verified ImageKit-detected type the static handler served.\n if (\n requestMimeType !== context.expectedMimeType &&\n requestMimeType !== uploaded.mimeType\n ) {\n throw new APIError('ImageKit client upload reference does not match this request.', 400)\n }\n // A provider file can only be claimed once, including through legacy receipts.\n // The unique file ID index also rejects concurrent claims.\n const existing = await req.payload.db.findOne({\n collection: collection.slug,\n req,\n where: {\n 'imagekit.fileId': {\n equals: uploaded.fileId,\n },\n },\n })\n if (existing) {\n throw new APIError('This ImageKit client upload receipt has already been used.', 400)\n }\n\n data.filename = uploaded.name\n data.filesize = uploaded.size\n data.mimeType = uploaded.mimeType\n\n return applyVerifiedClientUploadToData(\n data,\n uploaded,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n beforeOperation: [\n ...(collection.hooks?.beforeOperation || []),\n ({ args, operation, req }) => {\n if (\n (operation !== 'create' && operation !== 'update') ||\n req.context?.skipCloudStorage ||\n !req.file ||\n req.file.clientUploadContext ||\n !('data' in args) ||\n !isRecord(args.data) ||\n typeof args.data.prefix !== 'string'\n ) {\n return args\n }\n\n // Payload checks filename uniqueness before cloud storage's beforeChange\n // normalizes the prefix. Use the persisted form during that lookup too.\n args.data.prefix = buildUploadStoragePathData({\n collectionPrefix: getCollectionFolder(rootFolder, collection.slug),\n docPrefix: args.data.prefix,\n filename: req.file.name,\n }).sanitizedDocPrefix\n return args\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 }\n\n const uploadCollections = Object.fromEntries(\n Object.keys(collections).map((collectionSlug) => [\n collectionSlug,\n {\n isPrivate: privateSlugs.has(collectionSlug),\n prefix: getCollectionFolder(rootFolder, collectionSlug),\n },\n ]),\n ) as Partial<\n Record<UploadCollectionSlug, { isPrivate: boolean; prefix: string }>\n >\n\n initClientUploads({\n clientHandler: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n collections: uploadCollections,\n config: configWithCropDisabled,\n enabled: enabled !== false && Boolean(clientUploads),\n serverHandler: createImageKitUploadAuthHandler({\n access: typeof clientUploads === 'object' ? clientUploads.access : undefined,\n collections: uploadCollections,\n privateKey,\n publicKey,\n verifyClientUpload,\n }),\n serverHandlerPath: imageKitUploadAuthPath,\n })\n\n const output = cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n rootFolder,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n\n // Make the provider identity unique at persistence time to reject concurrent receipt replays.\n // Keep the legacy object key index for existing uploads.\n output.collections = (output.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const originalPrefixField = (config.collections || [])\n .find((original) => original.slug === collection.slug)\n ?.fields.find((field) => 'name' in field && field.name === 'prefix')\n\n const withUniqueIdentity = (field: Field): Field => {\n if (\n field.type === 'text' &&\n 'name' in field &&\n field.name === 'prefix' &&\n (!originalPrefixField ||\n !('defaultValue' in originalPrefixField) ||\n originalPrefixField.defaultValue == null)\n ) {\n // Cloud storage's collection-prefix default is applied on reads of old\n // documents that have no stored prefix. Its URL hook then appends that\n // prefix, but Payload's file access check cannot find it in the DB.\n // Upload hooks still persist the collection prefix for new files.\n return {\n ...field,\n defaultValue: ({ req }) =>\n req.file && typeof field.defaultValue === 'string' ? field.defaultValue : '',\n }\n }\n if ('name' in field && field.name === '_objectKey') {\n return { ...field, unique: true } as Field\n }\n if (field.type === 'group' && 'name' in field && field.name === 'imagekit') {\n return {\n ...field,\n fields: field.fields.map((metadataField) =>\n 'name' in metadataField && metadataField.name === 'fileId'\n ? { ...metadataField, unique: true }\n : metadataField,\n ),\n }\n }\n // The imagekit group sits inside the adapter's presentational \"Storage: ImageKit\" panel.\n if (field.type === 'collapsible') {\n return { ...field, fields: field.fields.map(withUniqueIdentity) }\n }\n return field\n }\n\n return {\n ...collection,\n fields: collection.fields.map(withUniqueIdentity),\n }\n })\n\n return output\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","buildStoragePathData","buildUploadStoragePathData","getStorageFilePath","initClientUploads","openAsBlob","path","APIError","getRangeRequestInfo","isXmlMimeType","UPLOAD_CONTENT_SECURITY_POLICY","validateMimeType","imageKitUploadAuthPath","isImageKitReceiptContext","createImageKitClientUploadVerifier","getClientUploadStorageFilePath","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isPdfMimeType","isVideoMimeType","getImageKitThumbnailSrc","getVideoPosterSrc","DEFAULT_SIGNED_URL_EXPIRES_IN","ORIGINAL_FILE_TRANSFORMATION","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","XML_FAMILY_MIME_TYPES","collectionCanStoreXml","collection","upload","Boolean","allowRestrictedFileTypes","allowedMimeTypes","mimeTypes","length","some","mimeType","includes","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","undefined","isRecord","getStoredImageKitURL","storedURL","url","hasStoredImageKitURL","findUploadDocumentByFilename","filename","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyVerifiedClientUploadToData","data","uploaded","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","verifyClientUpload","fetchUploadedFile","fileSize","fileURL","range","rangeResult","rangeHeader","Response","headers","Headers","status","normalizedRange","rangeStart","rangeEnd","response","fetch","cache","Range","signal","AbortController","ok","body","cancel","catch","upstreamLength","get","expectedLength","upstreamRange","expectedRange","set","slug","has","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","condition","initCollapsed","disableListColumn","disableListFilter","hidden","readOnly","label","_data","siblingData","description","hooks","afterRead","source","documentPrefix","_objectKey","storageFilePath","collectionPrefix","docPrefix","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","clientUploadContext","file","context","uploadSource","tempFilePath","buffer","uploadFile","uploadFolder","uploadFilename","fileName","useUniqueFileName","isPrivateFile","requiresClientUploadReceipt","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","uploadedFile","expectedFilePath","verifiedFileURL","size","prefixQueryParam","imageKitPath","filesize","Number","isSafeInteger","signSrc","redirect","imageKitURL","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","configuredSlugs","xmlCollectionSlugs","routes","api","imageKitClient","collectionSecurity","fromEntries","configWithCropDisabled","isCollectionPrivate","collectionOptions","collectionFocalPoint","existingDescription","baseAdmin","beforeChange","requestMimeType","mimetype","split","toLowerCase","expectedSize","expectedMimeType","existing","beforeOperation","operation","skipCloudStorage","sanitizedDocPrefix","adminThumbnail","crop","uploadCollections","clientHandler","serverHandler","access","serverHandlerPath","output","originalPrefixField","original","field","withUniqueIdentity","defaultValue","unique","metadataField","migrateFolder","payloadStorageImagekit","payloadStorageImageKit"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n Adapter,\n ClientUploadsConfig,\n CollectionOptions,\n} from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionConfig,\n CollectionSlug,\n Config,\n Field,\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 {\n buildStoragePathData,\n buildUploadStoragePathData,\n getStorageFilePath,\n initClientUploads,\n} from '@payloadcms/plugin-cloud-storage/utilities'\nimport { openAsBlob } from 'node:fs'\nimport path from 'node:path'\nimport { APIError } from 'payload'\nimport {\n getRangeRequestInfo,\n isXmlMimeType,\n UPLOAD_CONTENT_SECURITY_POLICY,\n} from 'payload/internal'\nimport { validateMimeType } from 'payload/shared'\n\nimport type { VerifiedImageKitUpload } from './clientUploadVerification.js'\n\nimport { imageKitUploadAuthPath, isImageKitReceiptContext } from './clientUploads.js'\nimport {\n createImageKitClientUploadVerifier,\n getClientUploadStorageFilePath,\n} from './clientUploadVerification.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/**\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\nconst XML_FAMILY_MIME_TYPES = ['application/xml', 'image/svg+xml', 'text/xml']\n\nconst collectionCanStoreXml = (collection: CollectionConfig): boolean => {\n const upload = collection.upload\n\n if (!upload || upload === true) {\n return Boolean(upload)\n }\n\n // Payload skips MIME and content restrictions when this escape hatch is enabled.\n if (upload.allowRestrictedFileTypes) {\n return true\n }\n\n const allowedMimeTypes = upload.mimeTypes\n\n return (\n !allowedMimeTypes?.length ||\n allowedMimeTypes.some((mimeType) => mimeType.includes('*') || isXmlMimeType(mimeType)) ||\n XML_FAMILY_MIME_TYPES.some((mimeType) => validateMimeType(mimeType, allowedMimeTypes))\n )\n}\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?: ClientUploadsConfig\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 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 applyVerifiedClientUploadToData = <T extends UploadDocumentData>(\n data: T,\n uploaded: VerifiedImageKitUpload,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate: boolean,\n): T => {\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.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 verifyClientUpload,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: ClientUploadsConfig\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n verifyClientUpload: ReturnType<typeof createImageKitClientUploadVerifier>\n}): Adapter => {\n const fetchUploadedFile = async ({\n fileSize,\n fileURL,\n mimeType,\n range,\n }: {\n fileSize: number\n fileURL: string\n mimeType: string\n range?: null | string\n }): Promise<Response> => {\n const rangeResult = getRangeRequestInfo({ fileSize, rangeHeader: range ?? null })\n\n if (rangeResult.type === 'invalid') {\n return new Response(null, {\n headers: new Headers(rangeResult.headers),\n status: rangeResult.status,\n })\n }\n\n const normalizedRange =\n rangeResult.type === 'partial'\n ? `bytes=${rangeResult.rangeStart}-${rangeResult.rangeEnd}`\n : undefined\n // Payload cancels image probes after reading a bounded prefix. Next.js memoizes\n // GET responses by teeing their streams; cancelling one branch can then wait\n // forever for its unread cached sibling. An explicit signal opts out of that\n // memoization, while no-store also excludes these reads from the data cache.\n const response = await fetch(fileURL, {\n cache: 'no-store',\n ...(normalizedRange ? { headers: { Range: normalizedRange } } : {}),\n signal: new AbortController().signal,\n })\n\n if (!response.ok || response.status !== rangeResult.status) {\n void response.body?.cancel().catch(() => undefined)\n throw new Error(\n `imagekitStorage: ImageKit returned an unexpected file response (${response.status}).`,\n )\n }\n\n const upstreamLength = response.headers.get('Content-Length')\n const expectedLength = rangeResult.headers['Content-Length']\n const upstreamRange = response.headers.get('Content-Range')\n const expectedRange =\n rangeResult.type === 'partial' ? rangeResult.headers['Content-Range'] : undefined\n\n if (\n (upstreamLength !== null && upstreamLength !== expectedLength) ||\n (expectedRange && upstreamRange !== expectedRange)\n ) {\n void response.body?.cancel().catch(() => undefined)\n throw new Error('imagekitStorage: ImageKit returned mismatched file content metadata.')\n }\n\n const headers = new Headers(rangeResult.headers)\n headers.set('Content-Type', mimeType)\n if (isXmlMimeType(mimeType)) {\n headers.set('Content-Security-Policy', UPLOAD_CONTENT_SECURITY_POLICY)\n }\n\n return new Response(response.body, {\n headers,\n status: response.status,\n })\n }\n\n return ({ collection, prefix = getCollectionFolder(rootFolder, collection.slug) }) => {\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 // Presentational wrapper: tucks the ImageKit details into a closed panel without\n // changing the `imagekit.*` data paths.\n type: 'collapsible',\n admin: {\n condition: (data) => hasStoredImageKitURL(data),\n initCollapsed: true,\n },\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n 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 const source = (data || {}) as { _objectKey?: string; prefix?: string }\n const documentPrefix = path.posix.join(\n source.prefix || prefix,\n source._objectKey || '',\n )\n const storageFilePath = buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix: documentPrefix,\n filename,\n }).storageFilePath\n\n return buildSignedURL(`/${storageFilePath}`)\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: false,\n },\n ],\n label: 'Storage: ImageKit',\n },\n ],\n generateURL: ({ filename, prefix: documentPrefix = '' }) =>\n client.helper.buildSrc({\n src: `/${buildStoragePathData({\n collectionPrefix: prefix,\n docPrefix: documentPrefix,\n filename,\n }).storageFilePath}`,\n urlEndpoint,\n }),\n handleDelete: async ({ doc, storageFilePath }) => {\n const storedFileId = (doc as ImageKitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId || (await findImageKitFileId(client, `/${storageFilePath.replace(/^\\/+/, '')}`))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file, storageFilePath }) => {\n if (clientUploadContext) {\n const uploaded = await verifyClientUpload({\n collectionSlug: collection.slug,\n context: clientUploadContext,\n })\n\n return applyVerifiedClientUploadToData(data, uploaded, buildThumbnailURL, isPrivate)\n }\n\n // ImageKit's `toFile()` consumes async iterables before starting the request. A file-backed\n // Blob keeps temporary-file uploads lazy so FormData can stream them from disk instead.\n const uploadSource = file.tempFilePath\n ? await openAsBlob(file.tempFilePath, { type: file.mimeType })\n : file.buffer\n const uploadFile = await toFile(uploadSource, file.filename, { type: file.mimeType })\n const uploadFolder = path.posix.dirname(`/${storageFilePath.replace(/^\\/+/, '')}`)\n const uploadFilename = path.posix.basename(storageFilePath)\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: uploadFilename,\n folder: uploadFolder,\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 || uploadFilename,\n })\n\n return data\n },\n requiresClientUploadReceipt: true,\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 (!isImageKitReceiptContext(clientUploadContext)) {\n throw new APIError('Invalid ImageKit client upload context.', 400)\n }\n\n const uploadedFile = await verifyClientUpload({\n collectionSlug: collection.slug,\n context: clientUploadContext,\n })\n const expectedFilePath = getClientUploadStorageFilePath({\n collectionPrefix: prefix,\n context: clientUploadContext,\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 return fetchUploadedFile({\n fileSize: uploadedFile.size,\n fileURL: verifiedFileURL,\n mimeType: uploadedFile.mimeType,\n range: req.headers.get('Range'),\n })\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 const storageFilePath = await getStorageFilePath({\n collection,\n collectionPrefix: prefix,\n doc: doc || undefined,\n filename: args.params.filename,\n prefixQueryParam: args.params.prefix,\n req,\n })\n const imageKitPath = `/${storageFilePath.replace(/^\\/+/, '')}`\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n\n // XML-family uploads (including SVG) must pass through Payload so its CSP is\n // present even when ImageKit uses a custom, same-origin delivery domain.\n if (typeof mimeType === 'string' && isXmlMimeType(mimeType)) {\n const filesize = (doc as { filesize?: number } | null)?.filesize\n\n if (typeof filesize !== 'number' || !Number.isSafeInteger(filesize) || filesize < 0) {\n throw new APIError('Stored ImageKit file metadata is incomplete.', 500)\n }\n\n const fileURL = isDocumentPrivate(doc, isPrivate)\n ? buildSignedURL(imageKitPath, ORIGINAL_FILE_TRANSFORMATION)\n : client.helper.buildSrc({\n src: imageKitPath,\n transformation: [{ raw: ORIGINAL_FILE_TRANSFORMATION }],\n urlEndpoint,\n })\n\n return fetchUploadedFile({\n fileSize: filesize,\n fileURL,\n mimeType,\n range: req.headers.get('Range'),\n })\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 let signSrc = imageKitPath\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 imageKitPath,\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 rootFolder: 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 prefix: getCollectionFolder(rootFolder, collectionSlug),\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 const configuredSlugs = new Set(Object.keys(collections))\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 if (disablePayloadAccessControl) {\n const xmlCollectionSlugs = (config.collections || [])\n .filter(\n (collection) =>\n configuredSlugs.has(collection.slug) && collectionCanStoreXml(collection),\n )\n .map((collection) => collection.slug)\n\n if (xmlCollectionSlugs.length > 0) {\n throw new Error(\n `imagekitStorage: \\`disablePayloadAccessControl\\` cannot be used with collections that can store XML-family files (${xmlCollectionSlugs.join(', ')}). XML-family files require Payload's file route so its Content Security Policy is applied. Configure a MIME allow list that excludes SVG/XML, or keep Payload access control enabled.`,\n )\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 collectionSecurity = Object.fromEntries(\n (config.collections || [])\n .filter((collection) => configuredSlugs.has(collection.slug))\n .map((collection) => [\n collection.slug,\n {\n collection,\n isPrivate: privateSlugs.has(collection.slug),\n prefix: getCollectionFolder(rootFolder, collection.slug),\n },\n ]),\n )\n const verifyClientUpload = createImageKitClientUploadVerifier({\n client: imageKitClient,\n collections: collectionSecurity,\n })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imageKitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n verifyClientUpload,\n })\n\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 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: baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n async ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n const context = req.file.clientUploadContext\n const requestMimeType = req.file.mimetype.split(';', 1)[0]?.trim().toLowerCase()\n if (\n !isImageKitReceiptContext(context) ||\n context.filename !== data.filename ||\n context.filename !== req.file.name ||\n // Payload can clear req.file.size when preparing an in-memory image.\n // generateFileData preserves the original size on the document.\n context.expectedSize !== data.filesize\n ) {\n throw new APIError('ImageKit client upload reference does not match this request.', 400)\n }\n\n const uploaded = await verifyClientUpload({\n collectionSlug: collection.slug,\n context,\n })\n // Payload keeps the browser's MIME unless it fetched the file content, in which\n // case it uses the verified ImageKit-detected type the static handler served.\n if (\n requestMimeType !== context.expectedMimeType &&\n requestMimeType !== uploaded.mimeType\n ) {\n throw new APIError('ImageKit client upload reference does not match this request.', 400)\n }\n // A provider file can only be claimed once, including through legacy receipts.\n // The unique file ID index also rejects concurrent claims.\n const existing = await req.payload.db.findOne({\n collection: collection.slug,\n req,\n where: {\n 'imagekit.fileId': {\n equals: uploaded.fileId,\n },\n },\n })\n if (existing) {\n throw new APIError('This ImageKit client upload receipt has already been used.', 400)\n }\n\n data.filename = uploaded.name\n data.filesize = uploaded.size\n data.mimeType = uploaded.mimeType\n\n return applyVerifiedClientUploadToData(\n data,\n uploaded,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n beforeOperation: [\n ...(collection.hooks?.beforeOperation || []),\n ({ args, operation, req }) => {\n if (\n (operation !== 'create' && operation !== 'update') ||\n req.context?.skipCloudStorage ||\n !req.file ||\n req.file.clientUploadContext ||\n !('data' in args) ||\n !isRecord(args.data) ||\n typeof args.data.prefix !== 'string'\n ) {\n return args\n }\n\n // Payload checks filename uniqueness before cloud storage's beforeChange\n // normalizes the prefix. Use the persisted form during that lookup too.\n args.data.prefix = buildUploadStoragePathData({\n collectionPrefix: getCollectionFolder(rootFolder, collection.slug),\n docPrefix: args.data.prefix,\n filename: req.file.name,\n }).sanitizedDocPrefix\n return args\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 }\n\n const uploadCollections = Object.fromEntries(\n Object.keys(collections).map((collectionSlug) => [\n collectionSlug,\n {\n isPrivate: privateSlugs.has(collectionSlug),\n prefix: getCollectionFolder(rootFolder, collectionSlug),\n },\n ]),\n ) as Partial<\n Record<UploadCollectionSlug, { isPrivate: boolean; prefix: string }>\n >\n\n initClientUploads({\n clientHandler: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n collections: uploadCollections,\n config: configWithCropDisabled,\n enabled: enabled !== false && Boolean(clientUploads),\n serverHandler: createImageKitUploadAuthHandler({\n access: typeof clientUploads === 'object' ? clientUploads.access : undefined,\n collections: uploadCollections,\n privateKey,\n publicKey,\n verifyClientUpload,\n }),\n serverHandlerPath: imageKitUploadAuthPath,\n })\n\n const output = cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n rootFolder,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n\n // Make the provider identity unique at persistence time to reject concurrent receipt replays.\n // Keep the legacy object key index for existing uploads.\n output.collections = (output.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const originalPrefixField = (config.collections || [])\n .find((original) => original.slug === collection.slug)\n ?.fields.find((field) => 'name' in field && field.name === 'prefix')\n\n const withUniqueIdentity = (field: Field): Field => {\n if (\n field.type === 'text' &&\n 'name' in field &&\n field.name === 'prefix' &&\n (!originalPrefixField ||\n !('defaultValue' in originalPrefixField) ||\n originalPrefixField.defaultValue == null)\n ) {\n // Cloud storage's collection-prefix default is applied on reads of old\n // documents that have no stored prefix. Its URL hook then appends that\n // prefix, but Payload's file access check cannot find it in the DB.\n // Upload hooks still persist the collection prefix for new files.\n return {\n ...field,\n defaultValue: ({ req }) =>\n req.file && typeof field.defaultValue === 'string' ? field.defaultValue : '',\n }\n }\n if ('name' in field && field.name === '_objectKey') {\n return { ...field, unique: true } as Field\n }\n if (field.type === 'group' && 'name' in field && field.name === 'imagekit') {\n return {\n ...field,\n fields: field.fields.map((metadataField) =>\n 'name' in metadataField && metadataField.name === 'fileId'\n ? { ...metadataField, unique: true }\n : metadataField,\n ),\n }\n }\n // The imagekit group sits inside the adapter's presentational \"Storage: ImageKit\" panel.\n if (field.type === 'collapsible') {\n return { ...field, fields: field.fields.map(withUniqueIdentity) }\n }\n return field\n }\n\n return {\n ...collection,\n fields: collection.fields.map(withUniqueIdentity),\n }\n })\n\n return output\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult, MigrationLocation } 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","buildStoragePathData","buildUploadStoragePathData","getStorageFilePath","initClientUploads","openAsBlob","path","APIError","getRangeRequestInfo","isXmlMimeType","UPLOAD_CONTENT_SECURITY_POLICY","validateMimeType","imageKitUploadAuthPath","isImageKitReceiptContext","createImageKitClientUploadVerifier","getClientUploadStorageFilePath","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isPdfMimeType","isVideoMimeType","getImageKitThumbnailSrc","getVideoPosterSrc","DEFAULT_SIGNED_URL_EXPIRES_IN","ORIGINAL_FILE_TRANSFORMATION","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","XML_FAMILY_MIME_TYPES","collectionCanStoreXml","collection","upload","Boolean","allowRestrictedFileTypes","allowedMimeTypes","mimeTypes","length","some","mimeType","includes","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","undefined","isRecord","getStoredImageKitURL","storedURL","url","hasStoredImageKitURL","findUploadDocumentByFilename","filename","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyVerifiedClientUploadToData","data","uploaded","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","verifyClientUpload","fetchUploadedFile","fileSize","fileURL","range","rangeResult","rangeHeader","Response","headers","Headers","status","normalizedRange","rangeStart","rangeEnd","response","fetch","cache","Range","signal","AbortController","ok","body","cancel","catch","upstreamLength","get","expectedLength","upstreamRange","expectedRange","set","slug","has","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","condition","initCollapsed","disableListColumn","disableListFilter","hidden","readOnly","label","_data","siblingData","description","hooks","afterRead","source","documentPrefix","_objectKey","storageFilePath","collectionPrefix","docPrefix","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","clientUploadContext","file","context","uploadSource","tempFilePath","buffer","uploadFile","uploadFolder","uploadFilename","fileName","useUniqueFileName","isPrivateFile","requiresClientUploadReceipt","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","uploadedFile","expectedFilePath","verifiedFileURL","size","prefixQueryParam","imageKitPath","filesize","Number","isSafeInteger","signSrc","redirect","imageKitURL","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","configuredSlugs","xmlCollectionSlugs","routes","api","imageKitClient","collectionSecurity","fromEntries","configWithCropDisabled","isCollectionPrivate","collectionOptions","collectionFocalPoint","existingDescription","baseAdmin","beforeChange","requestMimeType","mimetype","split","toLowerCase","expectedSize","expectedMimeType","existing","beforeOperation","operation","skipCloudStorage","sanitizedDocPrefix","adminThumbnail","crop","uploadCollections","clientHandler","serverHandler","access","serverHandlerPath","output","originalPrefixField","original","field","withUniqueIdentity","defaultValue","unique","metadataField","migrateFolder","payloadStorageImagekit","payloadStorageImageKit"],"mappings":"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"}
package/dist/migrate.d.ts CHANGED
@@ -1,54 +1,39 @@
1
1
  import type { Payload, UploadCollectionSlug } from 'payload';
2
- type ImageKitCollectionConfig = {
3
- private?: boolean;
4
- } | true;
5
- type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>;
2
+ export type MigrationLocation = {
3
+ /** Root folder, with collection subfolders beneath it. */
4
+ folder: string;
5
+ privateKey: string;
6
+ urlEndpoint: string;
7
+ };
6
8
  export type MigrateFolderOptions = {
7
- /**
8
- * The Payload upload collections to migrate. Keys are collection slugs; the value is
9
- * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.
10
- */
11
- collections: ImageKitCollectionMap;
12
- /**
13
- * Dry-run mode. When `true`, migration logs what would happen without writing
14
- * to ImageKit or Payload. Defaults to `true`.
15
- * @default true
16
- */
9
+ /** `true` preserves source privacy; an explicit `private` value sets destination privacy. */
10
+ collections: Partial<Record<UploadCollectionSlug, {
11
+ private?: boolean;
12
+ } | true>>;
13
+ /** Reads and reports the same plan without uploading or updating documents. Defaults to true. */
17
14
  dryRun?: boolean;
18
- /**
19
- * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder
20
- * (`/staging/media`) will be copied into `to`.
21
- */
22
- from: string;
23
- /**
24
- * Optional Payload instance. When provided, the stored `imagekit.*` fields in the
25
- * database will be updated to reflect the new ImageKit asset after copying.
26
- */
27
- payload?: Payload;
28
- /**
29
- * ImageKit private API key.
30
- */
31
- privateKey: string;
32
- /**
33
- * Expiry (in seconds) for the signed URL used to read a private source file when the
34
- * fast server-side copy is not possible and the migrator falls back to re-uploading
35
- * from the source URL. Defaults to 900 (15 minutes).
36
- */
15
+ from: MigrationLocation;
16
+ /** The database containing the records to update, including any already at the destination. */
17
+ payload: Payload;
37
18
  signedUrlExpiresIn?: number;
38
- /**
39
- * The destination ImageKit root folder (e.g. `/production`).
40
- */
41
- to: string;
42
- /**
43
- * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign
44
- * private source URLs when the migrator falls back to re-uploading from the source URL.
45
- */
46
- urlEndpoint: string;
19
+ to: MigrationLocation;
47
20
  };
48
21
  export type MigrationJobResult = {
49
22
  collectionSlug: string;
50
- jobId: string;
51
- status: 'Completed' | 'Pending';
23
+ errors: {
24
+ documentID: number | string;
25
+ message: string;
26
+ }[];
27
+ failed: number;
28
+ migrated: number;
29
+ planned: number;
30
+ /** Existing tagged transfers recovered after an interrupted run. Included in migrated when live. */
31
+ reused: number;
32
+ skipped: number;
52
33
  };
34
+ /**
35
+ * Copy referenced originals between ImageKit folders/accounts and update Payload in place.
36
+ * Transfers are tagged at upload time and never overwrite an existing destination file.
37
+ * Reruns recover the upload/database-update gap without relying on filename equality.
38
+ */
53
39
  export declare const migrateFolder: (options: MigrateFolderOptions) => Promise<MigrationJobResult[]>;
54
- export {};
package/dist/migrate.js CHANGED
@@ -1,283 +1,282 @@
1
1
  /* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */ import ImageKit from '@imagekit/nodejs';
2
+ import { createHash } from 'node:crypto';
2
3
  import path from 'node:path';
3
- const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
4
- const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
5
- const normalizeFolder = (folder)=>{
6
- const normalized = folder.trim().replace(/^\/+|\/+$/g, '');
7
- if (!normalized) {
8
- throw new Error('migrateFolder: folder path cannot be empty.');
4
+ import { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js';
5
+ const normalizeLocation = (location, label)=>{
6
+ if (!location?.privateKey?.trim()) {
7
+ throw new Error(`migrateFolder: \`${label}.privateKey\` is required.`);
9
8
  }
10
- return `/${normalized}`;
11
- };
12
- const getCollectionFolder = (rootFolder, collectionSlug)=>path.posix.join(rootFolder, collectionSlug);
13
- const isFileInFolder = (filePath, folderPath)=>filePath === folderPath || filePath.startsWith(`${folderPath}/`);
14
- const ensureFolderExists = async (client, folderPath)=>{
15
- const normalized = normalizeFolder(folderPath);
16
- const segments = normalized.split('/').filter(Boolean);
17
- let parentFolderPath = '/';
18
- for (const folderName of segments){
19
- try {
20
- await client.folders.create({
21
- folderName,
22
- parentFolderPath
23
- });
24
- } catch (error) {
25
- const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : '';
26
- if (!/already exists/i.test(message)) {
27
- throw error;
28
- }
29
- }
30
- parentFolderPath = path.posix.join(parentFolderPath, folderName);
31
- }
32
- };
33
- const findImageKitFileId = async (imagekit, filePath, options)=>{
34
- const parentPath = path.posix.dirname(filePath);
35
- const name = path.posix.basename(filePath);
36
- const retries = options?.retries ?? 0;
37
- const retryDelayMs = options?.retryDelayMs ?? 250;
38
- const limit = 100;
39
- for(let attempt = 0; attempt <= retries; attempt++){
40
- for(let skip = 0;; skip += limit){
41
- const assets = await imagekit.assets.list({
42
- type: 'file',
43
- limit,
44
- path: parentPath === '/' ? '/' : `${parentPath}/`,
45
- skip
46
- });
47
- const found = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath && asset.name === name);
48
- if (found && 'fileId' in found) {
49
- return found.fileId;
50
- }
51
- if (assets.length < limit) {
52
- break;
53
- }
54
- }
55
- if (attempt < retries) {
56
- await new Promise((resolve)=>{
57
- setTimeout(resolve, retryDelayMs);
58
- });
59
- }
60
- }
61
- return undefined;
62
- };
63
- const isImageKitFileNotFoundError = (error)=>{
64
- const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : '';
65
- return /does not exist|not found|no such file/i.test(message);
66
- };
67
- const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUrl })=>{
68
- const uploaded = await client.files.upload({
69
- file: sourceUrl,
70
- fileName,
71
- folder,
72
- useUniqueFileName: false,
73
- ...isPrivate ? {
74
- isPrivateFile: true
75
- } : {}
76
- });
77
- if (!uploaded.fileId || !uploaded.url) {
78
- throw new Error(`migrateFolder: upload from URL did not return file details for "${fileName}".`);
9
+ if (!location.urlEndpoint?.trim()) {
10
+ throw new Error(`migrateFolder: \`${label}.urlEndpoint\` is required.`);
79
11
  }
80
- return {
81
- fileId: uploaded.fileId,
82
- thumbnailUrl: uploaded.thumbnailUrl,
83
- url: uploaded.url
84
- };
85
- };
86
- const getSourceImageKitFields = async ({ client, fileId })=>{
87
- const file = await client.files.get(fileId);
88
- if (!file.fileId || !file.filePath || !file.url) {
89
- throw new Error(`migrateFolder: missing ImageKit source file details for "${fileId}".`);
12
+ const endpoint = new URL(location.urlEndpoint);
13
+ if (![
14
+ 'http:',
15
+ 'https:'
16
+ ].includes(endpoint.protocol) || endpoint.search || endpoint.hash) {
17
+ throw new Error(`migrateFolder: \`${label}.urlEndpoint\` must be an HTTP(S) URL without a query or fragment.`);
90
18
  }
91
- return {
92
- fileId: file.fileId,
93
- fileName: file.name || path.posix.basename(file.filePath),
94
- filePath: file.filePath,
95
- url: file.url
96
- };
97
- };
98
- const getUploadedImageKitFields = async ({ client, fileId })=>{
99
- const file = await client.files.get(fileId);
100
- if (!file.fileId || !file.url) {
101
- throw new Error(`migrateFolder: missing ImageKit file details for "${fileId}".`);
19
+ const folder = location.folder?.trim().replace(/^\/+|\/+$/g, '');
20
+ if (!folder || folder.split('/').some((segment)=>!segment || segment === '.' || segment === '..')) {
21
+ throw new Error(`migrateFolder: \`${label}.folder\` must be a non-empty folder without dot segments.`);
102
22
  }
103
23
  return {
104
- fileId: file.fileId,
105
- thumbnailUrl: file.thumbnail,
106
- url: file.url
24
+ ...location,
25
+ folder: `/${folder}`,
26
+ urlEndpoint: endpoint.href.replace(/\/$/, '')
107
27
  };
108
28
  };
109
- const readFileIdFromCopyResponse = async (response)=>{
29
+ const isInFolder = (filePath, folder)=>filePath.startsWith(`${folder}/`);
30
+ const getAsset = async (client, fileId)=>{
110
31
  try {
111
- const result = await response.json();
112
- if (typeof result.fileId === 'string' && result.fileId.length > 0) {
113
- return result.fileId;
32
+ const file = await client.files.get(fileId);
33
+ if (!file.fileId || !file.filePath || !file.url) {
34
+ throw new Error(`ImageKit returned incomplete file details for "${fileId}".`);
114
35
  }
115
- if (typeof result.file_id === 'string' && result.file_id.length > 0) {
116
- return result.file_id;
36
+ return {
37
+ ...file,
38
+ name: file.name || path.posix.basename(file.filePath),
39
+ fileId: file.fileId,
40
+ filePath: file.filePath,
41
+ url: file.url
42
+ };
43
+ } catch (error) {
44
+ // Authentication errors, rate limits and outages are not evidence of a missing file.
45
+ if (error instanceof ImageKit.NotFoundError) {
46
+ return undefined;
117
47
  }
118
- } catch {
119
- // Ignore empty or non-JSON responses and fall back to asset discovery.
48
+ throw error;
120
49
  }
121
- return undefined;
122
50
  };
123
- export const migrateFolder = async (options)=>{
124
- const { collections, dryRun = true, from, payload, privateKey, signedUrlExpiresIn: signedUrlExpiresInOption, to, urlEndpoint } = options;
125
- if (!privateKey?.trim()) {
126
- throw new Error('migrateFolder: `privateKey` is required.');
51
+ const findDestination = async (client, filePath)=>{
52
+ const limit = 100;
53
+ for(let skip = 0;; skip += limit){
54
+ const assets = await client.assets.list({
55
+ type: 'file',
56
+ limit,
57
+ path: `${path.posix.dirname(filePath)}/`,
58
+ skip
59
+ });
60
+ const match = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath);
61
+ if (match && 'fileId' in match && match.fileId) {
62
+ return getAsset(client, match.fileId);
63
+ }
64
+ if (assets.length < limit) {
65
+ return undefined;
66
+ }
127
67
  }
128
- if (!urlEndpoint?.trim()) {
129
- throw new Error('migrateFolder: `urlEndpoint` is required.');
68
+ };
69
+ const transferTag = (source, filePath, documentID, isPrivate)=>{
70
+ const identity = JSON.stringify([
71
+ source.fileId,
72
+ source.versionInfo?.id || source.updatedAt,
73
+ source.url,
74
+ filePath,
75
+ documentID,
76
+ isPrivate
77
+ ]);
78
+ return `payload-migration-${createHash('sha256').update(identity).digest('hex')}`;
79
+ };
80
+ /**
81
+ * Copy referenced originals between ImageKit folders/accounts and update Payload in place.
82
+ * Transfers are tagged at upload time and never overwrite an existing destination file.
83
+ * Reruns recover the upload/database-update gap without relying on filename equality.
84
+ */ export const migrateFolder = async (options)=>{
85
+ const { collections, dryRun = true, payload, signedUrlExpiresIn = 900 } = options;
86
+ const from = normalizeLocation(options.from, 'from');
87
+ const to = normalizeLocation(options.to, 'to');
88
+ if (!payload) {
89
+ throw new Error('migrateFolder: `payload` is required.');
130
90
  }
131
- if (!collections || Object.keys(collections).length === 0) {
91
+ const slugs = Object.keys(collections || {});
92
+ if (!slugs.length) {
132
93
  throw new Error('migrateFolder: `collections` must include at least one upload collection.');
133
94
  }
134
- const privateSlugs = new Set(Object.entries(collections).filter(([, value])=>isPrivateCollectionConfig(value)).map(([slug])=>slug));
135
- const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
136
- const fromFolder = normalizeFolder(from);
137
- const toFolder = normalizeFolder(to);
138
- if (fromFolder === toFolder) {
139
- throw new Error('migrateFolder: `from` and `to` cannot be the same folder.');
95
+ for (const slug of slugs){
96
+ if (!payload.collections[slug]?.config.upload) {
97
+ throw new Error(`migrateFolder: "${slug}" is not an upload collection.`);
98
+ }
140
99
  }
141
- const client = new ImageKit({
142
- privateKey
143
- });
144
- console.log(`[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`);
145
- if (dryRun) {
146
- console.log(`[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`);
147
- } else {
148
- console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`);
149
- await ensureFolderExists(client, toFolder);
100
+ if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0) {
101
+ throw new Error('migrateFolder: `signedUrlExpiresIn` must be positive.');
150
102
  }
151
- // Always copy file-by-file per collection
152
- const collectionSlugs = Object.keys(collections);
103
+ // A single transfer path for both cases gives uploads atomic provenance tags, including
104
+ // when an upload succeeds remotely but its response is lost. Native copy cannot add a tag.
105
+ const sourceClient = new ImageKit({
106
+ maxRetries: 0,
107
+ privateKey: from.privateKey
108
+ });
109
+ const destinationClient = new ImageKit({
110
+ maxRetries: 0,
111
+ privateKey: to.privateKey
112
+ });
153
113
  const results = [];
154
- // Ensure destination folder structure exists
155
- if (!dryRun) {
156
- for (const slug of collectionSlugs){
157
- const destinationFolderPath = path.posix.join(toFolder, slug);
158
- console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`);
159
- await ensureFolderExists(client, destinationFolderPath);
160
- }
161
- }
162
- // Process documents directly like the old migration script
163
- if (payload) {
164
- console.log('[migrateFolder] Starting document-by-document migration.');
165
- for (const slug of collectionSlugs){
166
- const isCollectionPrivate = privateSlugs.has(slug);
167
- console.log(`[migrateFolder] Migrating collection "${slug}"${isCollectionPrivate ? ' (private)' : ''}...`);
114
+ for (const slug of slugs){
115
+ const result = {
116
+ collectionSlug: slug,
117
+ errors: [],
118
+ failed: 0,
119
+ migrated: 0,
120
+ planned: 0,
121
+ reused: 0,
122
+ skipped: 0
123
+ };
124
+ const sourceFolder = path.posix.join(from.folder, slug);
125
+ const destinationFolder = path.posix.join(to.folder, slug);
126
+ const collectionOptions = collections[slug];
127
+ const plannedPaths = new Map();
128
+ let page = 1;
129
+ let hasNextPage;
130
+ do {
168
131
  const found = await payload.find({
169
132
  collection: slug,
170
133
  depth: 0,
171
- limit: 0,
172
- pagination: false
134
+ limit: 100,
135
+ page,
136
+ sort: 'id'
173
137
  });
174
- console.log(`[migrateFolder] Processing ${found.docs.length} document(s) in collection "${slug}".`);
175
- let updatedCount = 0;
176
- let skippedCount = 0;
177
- let errorCount = 0;
178
- // Process each document one by one, like the old migration script
179
- for(let i = 0; i < found.docs.length; i++){
180
- const doc = found.docs[i];
181
- const filename = doc.filename;
182
- const existingImageKit = doc.imagekit;
183
- const displayName = filename || existingImageKit?.fileId || doc.id;
184
- console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`);
185
- // Skip items that don't have an ImageKit file ID to migrate from.
186
- if (!existingImageKit?.fileId) {
187
- skippedCount += 1;
188
- console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`);
138
+ hasNextPage = found.hasNextPage;
139
+ page += 1;
140
+ for (const doc of found.docs){
141
+ const reference = doc.imagekit;
142
+ if (!reference?.fileId) {
143
+ result.skipped += 1;
144
+ console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: no ImageKit file ID.`);
189
145
  continue;
190
146
  }
191
147
  try {
192
- const destinationFolder = getCollectionFolder(toFolder, slug);
193
- const source = await getSourceImageKitFields({
194
- client,
195
- fileId: existingImageKit.fileId
196
- });
197
- if (isFileInFolder(source.filePath, destinationFolder)) {
198
- skippedCount += 1;
199
- console.log(`[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`);
148
+ const atDestination = await getAsset(destinationClient, reference.fileId);
149
+ if (atDestination && isInFolder(atDestination.filePath, destinationFolder)) {
150
+ result.skipped += 1;
151
+ console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: already at destination.`);
152
+ continue;
153
+ }
154
+ const source = await getAsset(sourceClient, reference.fileId);
155
+ if (!source) {
156
+ throw new Error(`File "${reference.fileId}" was not found in the source account.`);
157
+ }
158
+ if (!isInFolder(source.filePath, sourceFolder)) {
159
+ result.skipped += 1;
160
+ console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: outside ${sourceFolder}.`);
200
161
  continue;
201
162
  }
163
+ const isPrivate = collectionOptions !== true && typeof collectionOptions?.private === 'boolean' ? collectionOptions.private : source.isPrivateFile === true;
164
+ const destinationPath = path.posix.join(destinationFolder, source.name);
165
+ const tag = transferTag(source, destinationPath, doc.id, isPrivate);
166
+ const previousTransfer = plannedPaths.get(destinationPath);
167
+ if (previousTransfer && previousTransfer !== tag) {
168
+ throw new Error(`Destination conflict at "${destinationPath}": multiple source records target this path.`);
169
+ }
170
+ plannedPaths.set(destinationPath, tag);
171
+ let destination = await findDestination(destinationClient, destinationPath);
172
+ if (destination && (!destination.tags?.includes(tag) || destination.isPrivateFile === true !== isPrivate)) {
173
+ throw new Error(`Destination conflict at "${destinationPath}": existing asset is not this transfer.`);
174
+ }
175
+ if (destination) {
176
+ result.reused += 1;
177
+ }
202
178
  if (dryRun) {
203
- console.log(`[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`);
204
- console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`);
205
- } else {
206
- const destinationFilePath = path.posix.join(destinationFolder, source.fileName);
207
- let uploaded;
208
- try {
209
- const copyResponse = await client.files.copy({
210
- destinationPath: destinationFolder,
211
- sourceFilePath: source.filePath
212
- }).asResponse();
213
- let copiedFileId = await readFileIdFromCopyResponse(copyResponse);
214
- if (!copiedFileId) {
215
- copiedFileId = await findImageKitFileId(client, destinationFilePath, {
216
- retries: 20,
217
- retryDelayMs: 1000
218
- });
219
- }
220
- if (!copiedFileId) {
221
- throw new Error(`migrateFolder: copy request completed for ImageKit file ID "${source.fileId}" from "${source.filePath}", but the destination asset at "${destinationFilePath}" was not discoverable via ImageKit after retrying.`);
222
- }
223
- uploaded = await getUploadedImageKitFields({
224
- client,
225
- fileId: copiedFileId
226
- });
227
- } catch (error) {
228
- if (!isImageKitFileNotFoundError(error)) {
229
- throw error;
230
- }
231
- console.warn(`[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for "${source.fileName}".`);
232
- // A private source file 401s on its plain URL, so sign it before ImageKit
233
- // fetches it, and re-upload as private so the copy keeps its privacy.
234
- const sourceUrl = isCollectionPrivate ? client.helper.buildSrc({
179
+ result.planned += 1;
180
+ console.log(`[migrateFolder] [dry-run] Would ${destination ? 'reuse' : 'upload'} ${slug}/${doc.id}: ${source.filePath} -> ${destinationPath}`);
181
+ continue;
182
+ }
183
+ if (!destination) {
184
+ const sourceURL = sourceClient.helper.buildSrc({
185
+ ...source.isPrivateFile ? {
235
186
  expiresIn: signedUrlExpiresIn,
236
- signed: true,
237
- src: source.url,
238
- urlEndpoint
239
- }) : source.url;
240
- uploaded = await uploadFileFromURL({
241
- client,
242
- fileName: source.fileName,
243
- folder: destinationFolder,
244
- isPrivate: isCollectionPrivate,
245
- sourceUrl
246
- });
187
+ signed: true
188
+ } : {},
189
+ src: source.filePath,
190
+ transformation: [
191
+ {
192
+ raw: 'orig-true'
193
+ }
194
+ ],
195
+ urlEndpoint: from.urlEndpoint
196
+ });
197
+ const uploaded = await destinationClient.files.upload({
198
+ file: sourceURL,
199
+ fileName: source.name,
200
+ folder: destinationFolder,
201
+ isPrivateFile: isPrivate,
202
+ overwriteFile: false,
203
+ tags: [
204
+ tag
205
+ ],
206
+ useUniqueFileName: false
207
+ });
208
+ if (!uploaded.fileId) {
209
+ throw new Error(`Upload of "${source.fileId}" did not return a file ID.`);
247
210
  }
248
- // Update the document with new ImageKit data
249
- await payload.update({
211
+ destination = await getAsset(destinationClient, uploaded.fileId);
212
+ if (!destination || destination.filePath !== destinationPath || !destination.tags?.includes(tag) || destination.isPrivateFile === true !== isPrivate) {
213
+ throw new Error(`Upload of "${source.fileId}" returned unexpected destination details.`);
214
+ }
215
+ }
216
+ const imagekit = {
217
+ fileId: destination.fileId,
218
+ isPrivate,
219
+ thumbnailUrl: destination.thumbnail || null,
220
+ url: destinationClient.helper.buildSrc({
221
+ src: destination.filePath,
222
+ urlEndpoint: to.urlEndpoint
223
+ })
224
+ };
225
+ const updated = {
226
+ ...doc,
227
+ filename: destination.name,
228
+ imagekit
229
+ };
230
+ const data = {
231
+ filename: destination.name,
232
+ imagekit,
233
+ prefix: destinationFolder.replace(/^\//, ''),
234
+ thumbnailURL: isPrivate ? getPrivateAdminThumbnailURL(updated, slug, payload.config.routes.api) : getAdminThumbnailURL(updated)
235
+ };
236
+ if (payload.db.name === 'mongoose') {
237
+ // A sparse unique MongoDB index permits missing keys, but only one null.
238
+ // Use the adapter directly: Local API merges the old key back into the
239
+ // update, which conflicts with $unset. Remove it and save the reference
240
+ // in one database write, so failed updates leave the source intact.
241
+ await payload.db.updateOne({
250
242
  id: doc.id,
251
243
  collection: slug,
252
244
  data: {
253
- imagekit: {
254
- fileId: uploaded.fileId,
255
- isPrivate: isCollectionPrivate,
256
- thumbnailUrl: uploaded.thumbnailUrl,
257
- url: uploaded.url
245
+ ...data,
246
+ $unset: {
247
+ _objectKey: 1
258
248
  }
259
249
  }
260
250
  });
261
- updatedCount += 1;
262
- console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`);
251
+ } else {
252
+ await payload.update({
253
+ id: doc.id,
254
+ collection: slug,
255
+ context: {
256
+ skipCloudStorage: true
257
+ },
258
+ data: {
259
+ ...data,
260
+ _objectKey: null
261
+ }
262
+ });
263
263
  }
264
+ result.migrated += 1;
265
+ console.log(`[migrateFolder] Migrated ${slug}/${doc.id} to ${destinationPath}.`);
264
266
  } catch (error) {
265
- errorCount += 1;
266
- console.error(`[migrateFolder] Failed to migrate document "${doc.id}" for file "${displayName}":`, error);
267
+ const message = error instanceof Error ? error.message : 'Unknown migration error';
268
+ result.failed += 1;
269
+ result.errors.push({
270
+ documentID: doc.id,
271
+ message
272
+ });
273
+ console.error(`[migrateFolder] Failed ${slug}/${doc.id}: ${message}`);
267
274
  }
268
275
  }
269
- results.push({
270
- collectionSlug: slug,
271
- jobId: `migrate:${slug}`,
272
- status: dryRun ? 'Pending' : 'Completed'
273
- });
274
- console.log(`[migrateFolder] Collection "${slug}" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`);
275
- }
276
- console.log('[migrateFolder] Document-by-document migration complete.');
277
- } else {
278
- console.log('[migrateFolder] No Payload instance provided - skipping migration.');
276
+ }while (hasNextPage)
277
+ results.push(result);
278
+ console.log(`[migrateFolder] ${slug}: planned=${result.planned}, migrated=${result.migrated}, reused=${result.reused}, skipped=${result.skipped}, failed=${result.failed}.`);
279
279
  }
280
- console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`);
281
280
  return results;
282
281
  };
283
282
 
@@ -1 +1 @@
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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"}
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 { createHash } from 'node:crypto'\nimport path from 'node:path'\n\nimport { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js'\n\nexport type MigrationLocation = {\n /** Root folder, with collection subfolders beneath it. */\n folder: string\n privateKey: string\n urlEndpoint: string\n}\n\nexport type MigrateFolderOptions = {\n /** `true` preserves source privacy; an explicit `private` value sets destination privacy. */\n collections: Partial<Record<UploadCollectionSlug, { private?: boolean } | true>>\n /** Reads and reports the same plan without uploading or updating documents. Defaults to true. */\n dryRun?: boolean\n from: MigrationLocation\n /** The database containing the records to update, including any already at the destination. */\n payload: Payload\n signedUrlExpiresIn?: number\n to: MigrationLocation\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n errors: { documentID: number | string; message: string }[]\n failed: number\n migrated: number\n planned: number\n /** Existing tagged transfers recovered after an interrupted run. Included in migrated when live. */\n reused: number\n skipped: number\n}\n\ntype Asset = {\n fileId: string\n filePath: string\n isPrivateFile?: boolean\n name: string\n tags?: null | string[]\n thumbnail?: string\n updatedAt?: string\n url: string\n versionInfo?: { id?: string }\n}\n\nconst normalizeLocation = (location: MigrationLocation, label: string): MigrationLocation => {\n if (!location?.privateKey?.trim()) {\n throw new Error(`migrateFolder: \\`${label}.privateKey\\` is required.`)\n }\n if (!location.urlEndpoint?.trim()) {\n throw new Error(`migrateFolder: \\`${label}.urlEndpoint\\` is required.`)\n }\n const endpoint = new URL(location.urlEndpoint)\n if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.search || endpoint.hash) {\n throw new Error(\n `migrateFolder: \\`${label}.urlEndpoint\\` must be an HTTP(S) URL without a query or fragment.`,\n )\n }\n const folder = location.folder?.trim().replace(/^\\/+|\\/+$/g, '')\n if (\n !folder ||\n folder.split('/').some((segment) => !segment || segment === '.' || segment === '..')\n ) {\n throw new Error(\n `migrateFolder: \\`${label}.folder\\` must be a non-empty folder without dot segments.`,\n )\n }\n return { ...location, folder: `/${folder}`, urlEndpoint: endpoint.href.replace(/\\/$/, '') }\n}\n\nconst isInFolder = (filePath: string, folder: string): boolean => filePath.startsWith(`${folder}/`)\n\nconst getAsset = async (client: ImageKit, fileId: string): Promise<Asset | undefined> => {\n try {\n const file = await client.files.get(fileId)\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`ImageKit returned incomplete file details for \"${fileId}\".`)\n }\n return {\n ...file,\n name: file.name || path.posix.basename(file.filePath),\n fileId: file.fileId,\n filePath: file.filePath,\n url: file.url,\n }\n } catch (error) {\n // Authentication errors, rate limits and outages are not evidence of a missing file.\n if (error instanceof ImageKit.NotFoundError) {\n return undefined\n }\n throw error\n }\n}\n\nconst findDestination = async (client: ImageKit, filePath: string): Promise<Asset | undefined> => {\n const limit = 100\n for (let skip = 0; ; skip += limit) {\n const assets = await client.assets.list({\n type: 'file',\n limit,\n path: `${path.posix.dirname(filePath)}/`,\n skip,\n })\n const match = assets.find((asset) => 'fileId' in asset && asset.filePath === filePath)\n if (match && 'fileId' in match && match.fileId) {\n return getAsset(client, match.fileId)\n }\n if (assets.length < limit) {\n return undefined\n }\n }\n}\n\nconst transferTag = (\n source: Asset,\n filePath: string,\n documentID: number | string,\n isPrivate: boolean,\n): string => {\n const identity = JSON.stringify([\n source.fileId,\n source.versionInfo?.id || source.updatedAt,\n source.url,\n filePath,\n documentID,\n isPrivate,\n ])\n return `payload-migration-${createHash('sha256').update(identity).digest('hex')}`\n}\n\n/**\n * Copy referenced originals between ImageKit folders/accounts and update Payload in place.\n * Transfers are tagged at upload time and never overwrite an existing destination file.\n * Reruns recover the upload/database-update gap without relying on filename equality.\n */\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const { collections, dryRun = true, payload, signedUrlExpiresIn = 900 } = options\n const from = normalizeLocation(options.from, 'from')\n const to = normalizeLocation(options.to, 'to')\n if (!payload) {\n throw new Error('migrateFolder: `payload` is required.')\n }\n const slugs = Object.keys(collections || {})\n if (!slugs.length) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n for (const slug of slugs) {\n if (!payload.collections[slug]?.config.upload) {\n throw new Error(`migrateFolder: \"${slug}\" is not an upload collection.`)\n }\n }\n if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0) {\n throw new Error('migrateFolder: `signedUrlExpiresIn` must be positive.')\n }\n\n // A single transfer path for both cases gives uploads atomic provenance tags, including\n // when an upload succeeds remotely but its response is lost. Native copy cannot add a tag.\n const sourceClient = new ImageKit({ maxRetries: 0, privateKey: from.privateKey })\n const destinationClient = new ImageKit({ maxRetries: 0, privateKey: to.privateKey })\n const results: MigrationJobResult[] = []\n\n for (const slug of slugs) {\n const result: MigrationJobResult = {\n collectionSlug: slug,\n errors: [],\n failed: 0,\n migrated: 0,\n planned: 0,\n reused: 0,\n skipped: 0,\n }\n const sourceFolder = path.posix.join(from.folder, slug)\n const destinationFolder = path.posix.join(to.folder, slug)\n const collectionOptions = collections[slug]\n const plannedPaths = new Map<string, string>()\n let page = 1\n let hasNextPage: boolean\n\n do {\n const found = await payload.find({ collection: slug, depth: 0, limit: 100, page, sort: 'id' })\n hasNextPage = found.hasNextPage\n page += 1\n for (const doc of found.docs) {\n const reference = (doc as { imagekit?: { fileId?: string } }).imagekit\n if (!reference?.fileId) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: no ImageKit file ID.`)\n continue\n }\n try {\n const atDestination = await getAsset(destinationClient, reference.fileId)\n if (atDestination && isInFolder(atDestination.filePath, destinationFolder)) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: already at destination.`)\n continue\n }\n const source = await getAsset(sourceClient, reference.fileId)\n if (!source) {\n throw new Error(`File \"${reference.fileId}\" was not found in the source account.`)\n }\n if (!isInFolder(source.filePath, sourceFolder)) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: outside ${sourceFolder}.`)\n continue\n }\n\n const isPrivate =\n collectionOptions !== true && typeof collectionOptions?.private === 'boolean'\n ? collectionOptions.private\n : source.isPrivateFile === true\n const destinationPath = path.posix.join(destinationFolder, source.name)\n const tag = transferTag(source, destinationPath, doc.id, isPrivate)\n const previousTransfer = plannedPaths.get(destinationPath)\n if (previousTransfer && previousTransfer !== tag) {\n throw new Error(\n `Destination conflict at \"${destinationPath}\": multiple source records target this path.`,\n )\n }\n plannedPaths.set(destinationPath, tag)\n let destination = await findDestination(destinationClient, destinationPath)\n if (\n destination &&\n (!destination.tags?.includes(tag) || (destination.isPrivateFile === true) !== isPrivate)\n ) {\n throw new Error(\n `Destination conflict at \"${destinationPath}\": existing asset is not this transfer.`,\n )\n }\n if (destination) {\n result.reused += 1\n }\n if (dryRun) {\n result.planned += 1\n console.log(\n `[migrateFolder] [dry-run] Would ${destination ? 'reuse' : 'upload'} ${slug}/${doc.id}: ${source.filePath} -> ${destinationPath}`,\n )\n continue\n }\n\n if (!destination) {\n const sourceURL = sourceClient.helper.buildSrc({\n ...(source.isPrivateFile ? { expiresIn: signedUrlExpiresIn, signed: true } : {}),\n src: source.filePath,\n transformation: [{ raw: 'orig-true' }],\n urlEndpoint: from.urlEndpoint,\n })\n const uploaded = await destinationClient.files.upload({\n file: sourceURL,\n fileName: source.name,\n folder: destinationFolder,\n isPrivateFile: isPrivate,\n overwriteFile: false,\n tags: [tag],\n useUniqueFileName: false,\n })\n if (!uploaded.fileId) {\n throw new Error(`Upload of \"${source.fileId}\" did not return a file ID.`)\n }\n destination = await getAsset(destinationClient, uploaded.fileId)\n if (\n !destination ||\n destination.filePath !== destinationPath ||\n !destination.tags?.includes(tag) ||\n (destination.isPrivateFile === true) !== isPrivate\n ) {\n throw new Error(\n `Upload of \"${source.fileId}\" returned unexpected destination details.`,\n )\n }\n }\n\n const imagekit = {\n fileId: destination.fileId,\n isPrivate,\n thumbnailUrl: destination.thumbnail || null,\n url: destinationClient.helper.buildSrc({\n src: destination.filePath,\n urlEndpoint: to.urlEndpoint,\n }),\n }\n const updated = { ...doc, filename: destination.name, imagekit }\n const data = {\n filename: destination.name,\n imagekit,\n prefix: destinationFolder.replace(/^\\//, ''),\n thumbnailURL: isPrivate\n ? getPrivateAdminThumbnailURL(updated, slug, payload.config.routes.api)\n : getAdminThumbnailURL(updated),\n }\n if (payload.db.name === 'mongoose') {\n // A sparse unique MongoDB index permits missing keys, but only one null.\n // Use the adapter directly: Local API merges the old key back into the\n // update, which conflicts with $unset. Remove it and save the reference\n // in one database write, so failed updates leave the source intact.\n await payload.db.updateOne({\n id: doc.id,\n collection: slug,\n data: { ...data, $unset: { _objectKey: 1 } },\n })\n } else {\n await payload.update({\n id: doc.id,\n collection: slug,\n context: { skipCloudStorage: true },\n data: { ...data, _objectKey: null },\n })\n }\n result.migrated += 1\n console.log(`[migrateFolder] Migrated ${slug}/${doc.id} to ${destinationPath}.`)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown migration error'\n result.failed += 1\n result.errors.push({ documentID: doc.id, message })\n console.error(`[migrateFolder] Failed ${slug}/${doc.id}: ${message}`)\n }\n }\n } while (hasNextPage)\n\n results.push(result)\n console.log(\n `[migrateFolder] ${slug}: planned=${result.planned}, migrated=${result.migrated}, reused=${result.reused}, skipped=${result.skipped}, failed=${result.failed}.`,\n )\n }\n return results\n}\n"],"names":["ImageKit","createHash","path","getAdminThumbnailURL","getPrivateAdminThumbnailURL","normalizeLocation","location","label","privateKey","trim","Error","urlEndpoint","endpoint","URL","includes","protocol","search","hash","folder","replace","split","some","segment","href","isInFolder","filePath","startsWith","getAsset","client","fileId","file","files","get","url","name","posix","basename","error","NotFoundError","undefined","findDestination","limit","skip","assets","list","type","dirname","match","find","asset","length","transferTag","source","documentID","isPrivate","identity","JSON","stringify","versionInfo","id","updatedAt","update","digest","migrateFolder","options","collections","dryRun","payload","signedUrlExpiresIn","from","to","slugs","Object","keys","slug","config","upload","Number","isFinite","sourceClient","maxRetries","destinationClient","results","result","collectionSlug","errors","failed","migrated","planned","reused","skipped","sourceFolder","join","destinationFolder","collectionOptions","plannedPaths","Map","page","hasNextPage","found","collection","depth","sort","doc","docs","reference","imagekit","console","log","atDestination","private","isPrivateFile","destinationPath","tag","previousTransfer","set","destination","tags","sourceURL","helper","buildSrc","expiresIn","signed","src","transformation","raw","uploaded","fileName","overwriteFile","useUniqueFileName","thumbnailUrl","thumbnail","updated","filename","data","prefix","thumbnailURL","routes","api","db","updateOne","$unset","_objectKey","context","skipCloudStorage","message","push"],"mappings":"AAAA,sGAAsG,GAItG,OAAOA,cAAc,mBAAkB;AACvC,SAASC,UAAU,QAAQ,cAAa;AACxC,OAAOC,UAAU,YAAW;AAE5B,SAASC,oBAAoB,EAAEC,2BAA2B,QAAQ,4BAA2B;AA4C7F,MAAMC,oBAAoB,CAACC,UAA6BC;IACtD,IAAI,CAACD,UAAUE,YAAYC,QAAQ;QACjC,MAAM,IAAIC,MAAM,CAAC,iBAAiB,EAAEH,MAAM,0BAA0B,CAAC;IACvE;IACA,IAAI,CAACD,SAASK,WAAW,EAAEF,QAAQ;QACjC,MAAM,IAAIC,MAAM,CAAC,iBAAiB,EAAEH,MAAM,2BAA2B,CAAC;IACxE;IACA,MAAMK,WAAW,IAAIC,IAAIP,SAASK,WAAW;IAC7C,IAAI,CAAC;QAAC;QAAS;KAAS,CAACG,QAAQ,CAACF,SAASG,QAAQ,KAAKH,SAASI,MAAM,IAAIJ,SAASK,IAAI,EAAE;QACxF,MAAM,IAAIP,MACR,CAAC,iBAAiB,EAAEH,MAAM,kEAAkE,CAAC;IAEjG;IACA,MAAMW,SAASZ,SAASY,MAAM,EAAET,OAAOU,QAAQ,cAAc;IAC7D,IACE,CAACD,UACDA,OAAOE,KAAK,CAAC,KAAKC,IAAI,CAAC,CAACC,UAAY,CAACA,WAAWA,YAAY,OAAOA,YAAY,OAC/E;QACA,MAAM,IAAIZ,MACR,CAAC,iBAAiB,EAAEH,MAAM,0DAA0D,CAAC;IAEzF;IACA,OAAO;QAAE,GAAGD,QAAQ;QAAEY,QAAQ,CAAC,CAAC,EAAEA,QAAQ;QAAEP,aAAaC,SAASW,IAAI,CAACJ,OAAO,CAAC,OAAO;IAAI;AAC5F;AAEA,MAAMK,aAAa,CAACC,UAAkBP,SAA4BO,SAASC,UAAU,CAAC,GAAGR,OAAO,CAAC,CAAC;AAElG,MAAMS,WAAW,OAAOC,QAAkBC;IACxC,IAAI;QACF,MAAMC,OAAO,MAAMF,OAAOG,KAAK,CAACC,GAAG,CAACH;QACpC,IAAI,CAACC,KAAKD,MAAM,IAAI,CAACC,KAAKL,QAAQ,IAAI,CAACK,KAAKG,GAAG,EAAE;YAC/C,MAAM,IAAIvB,MAAM,CAAC,+CAA+C,EAAEmB,OAAO,EAAE,CAAC;QAC9E;QACA,OAAO;YACL,GAAGC,IAAI;YACPI,MAAMJ,KAAKI,IAAI,IAAIhC,KAAKiC,KAAK,CAACC,QAAQ,CAACN,KAAKL,QAAQ;YACpDI,QAAQC,KAAKD,MAAM;YACnBJ,UAAUK,KAAKL,QAAQ;YACvBQ,KAAKH,KAAKG,GAAG;QACf;IACF,EAAE,OAAOI,OAAO;QACd,qFAAqF;QACrF,IAAIA,iBAAiBrC,SAASsC,aAAa,EAAE;YAC3C,OAAOC;QACT;QACA,MAAMF;IACR;AACF;AAEA,MAAMG,kBAAkB,OAAOZ,QAAkBH;IAC/C,MAAMgB,QAAQ;IACd,IAAK,IAAIC,OAAO,IAAKA,QAAQD,MAAO;QAClC,MAAME,SAAS,MAAMf,OAAOe,MAAM,CAACC,IAAI,CAAC;YACtCC,MAAM;YACNJ;YACAvC,MAAM,GAAGA,KAAKiC,KAAK,CAACW,OAAO,CAACrB,UAAU,CAAC,CAAC;YACxCiB;QACF;QACA,MAAMK,QAAQJ,OAAOK,IAAI,CAAC,CAACC,QAAU,YAAYA,SAASA,MAAMxB,QAAQ,KAAKA;QAC7E,IAAIsB,SAAS,YAAYA,SAASA,MAAMlB,MAAM,EAAE;YAC9C,OAAOF,SAASC,QAAQmB,MAAMlB,MAAM;QACtC;QACA,IAAIc,OAAOO,MAAM,GAAGT,OAAO;YACzB,OAAOF;QACT;IACF;AACF;AAEA,MAAMY,cAAc,CAClBC,QACA3B,UACA4B,YACAC;IAEA,MAAMC,WAAWC,KAAKC,SAAS,CAAC;QAC9BL,OAAOvB,MAAM;QACbuB,OAAOM,WAAW,EAAEC,MAAMP,OAAOQ,SAAS;QAC1CR,OAAOnB,GAAG;QACVR;QACA4B;QACAC;KACD;IACD,OAAO,CAAC,kBAAkB,EAAErD,WAAW,UAAU4D,MAAM,CAACN,UAAUO,MAAM,CAAC,QAAQ;AACnF;AAEA;;;;CAIC,GACD,OAAO,MAAMC,gBAAgB,OAC3BC;IAEA,MAAM,EAAEC,WAAW,EAAEC,SAAS,IAAI,EAAEC,OAAO,EAAEC,qBAAqB,GAAG,EAAE,GAAGJ;IAC1E,MAAMK,OAAOhE,kBAAkB2D,QAAQK,IAAI,EAAE;IAC7C,MAAMC,KAAKjE,kBAAkB2D,QAAQM,EAAE,EAAE;IACzC,IAAI,CAACH,SAAS;QACZ,MAAM,IAAIzD,MAAM;IAClB;IACA,MAAM6D,QAAQC,OAAOC,IAAI,CAACR,eAAe,CAAC;IAC1C,IAAI,CAACM,MAAMrB,MAAM,EAAE;QACjB,MAAM,IAAIxC,MAAM;IAClB;IACA,KAAK,MAAMgE,QAAQH,MAAO;QACxB,IAAI,CAACJ,QAAQF,WAAW,CAACS,KAAK,EAAEC,OAAOC,QAAQ;YAC7C,MAAM,IAAIlE,MAAM,CAAC,gBAAgB,EAAEgE,KAAK,8BAA8B,CAAC;QACzE;IACF;IACA,IAAI,CAACG,OAAOC,QAAQ,CAACV,uBAAuBA,sBAAsB,GAAG;QACnE,MAAM,IAAI1D,MAAM;IAClB;IAEA,wFAAwF;IACxF,2FAA2F;IAC3F,MAAMqE,eAAe,IAAI/E,SAAS;QAAEgF,YAAY;QAAGxE,YAAY6D,KAAK7D,UAAU;IAAC;IAC/E,MAAMyE,oBAAoB,IAAIjF,SAAS;QAAEgF,YAAY;QAAGxE,YAAY8D,GAAG9D,UAAU;IAAC;IAClF,MAAM0E,UAAgC,EAAE;IAExC,KAAK,MAAMR,QAAQH,MAAO;QACxB,MAAMY,SAA6B;YACjCC,gBAAgBV;YAChBW,QAAQ,EAAE;YACVC,QAAQ;YACRC,UAAU;YACVC,SAAS;YACTC,QAAQ;YACRC,SAAS;QACX;QACA,MAAMC,eAAezF,KAAKiC,KAAK,CAACyD,IAAI,CAACvB,KAAKnD,MAAM,EAAEwD;QAClD,MAAMmB,oBAAoB3F,KAAKiC,KAAK,CAACyD,IAAI,CAACtB,GAAGpD,MAAM,EAAEwD;QACrD,MAAMoB,oBAAoB7B,WAAW,CAACS,KAAK;QAC3C,MAAMqB,eAAe,IAAIC;QACzB,IAAIC,OAAO;QACX,IAAIC;QAEJ,GAAG;YACD,MAAMC,QAAQ,MAAMhC,QAAQnB,IAAI,CAAC;gBAAEoD,YAAY1B;gBAAM2B,OAAO;gBAAG5D,OAAO;gBAAKwD;gBAAMK,MAAM;YAAK;YAC5FJ,cAAcC,MAAMD,WAAW;YAC/BD,QAAQ;YACR,KAAK,MAAMM,OAAOJ,MAAMK,IAAI,CAAE;gBAC5B,MAAMC,YAAY,AAACF,IAA2CG,QAAQ;gBACtE,IAAI,CAACD,WAAW5E,QAAQ;oBACtBsD,OAAOO,OAAO,IAAI;oBAClBiB,QAAQC,GAAG,CAAC,CAAC,yBAAyB,EAAElC,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,sBAAsB,CAAC;oBAC9E;gBACF;gBACA,IAAI;oBACF,MAAMkD,gBAAgB,MAAMlF,SAASsD,mBAAmBwB,UAAU5E,MAAM;oBACxE,IAAIgF,iBAAiBrF,WAAWqF,cAAcpF,QAAQ,EAAEoE,oBAAoB;wBAC1EV,OAAOO,OAAO,IAAI;wBAClBiB,QAAQC,GAAG,CAAC,CAAC,yBAAyB,EAAElC,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,yBAAyB,CAAC;wBACjF;oBACF;oBACA,MAAMP,SAAS,MAAMzB,SAASoD,cAAc0B,UAAU5E,MAAM;oBAC5D,IAAI,CAACuB,QAAQ;wBACX,MAAM,IAAI1C,MAAM,CAAC,MAAM,EAAE+F,UAAU5E,MAAM,CAAC,sCAAsC,CAAC;oBACnF;oBACA,IAAI,CAACL,WAAW4B,OAAO3B,QAAQ,EAAEkE,eAAe;wBAC9CR,OAAOO,OAAO,IAAI;wBAClBiB,QAAQC,GAAG,CAAC,CAAC,yBAAyB,EAAElC,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,UAAU,EAAEgC,aAAa,CAAC,CAAC;wBAClF;oBACF;oBAEA,MAAMrC,YACJwC,sBAAsB,QAAQ,OAAOA,mBAAmBgB,YAAY,YAChEhB,kBAAkBgB,OAAO,GACzB1D,OAAO2D,aAAa,KAAK;oBAC/B,MAAMC,kBAAkB9G,KAAKiC,KAAK,CAACyD,IAAI,CAACC,mBAAmBzC,OAAOlB,IAAI;oBACtE,MAAM+E,MAAM9D,YAAYC,QAAQ4D,iBAAiBT,IAAI5C,EAAE,EAAEL;oBACzD,MAAM4D,mBAAmBnB,aAAa/D,GAAG,CAACgF;oBAC1C,IAAIE,oBAAoBA,qBAAqBD,KAAK;wBAChD,MAAM,IAAIvG,MACR,CAAC,yBAAyB,EAAEsG,gBAAgB,4CAA4C,CAAC;oBAE7F;oBACAjB,aAAaoB,GAAG,CAACH,iBAAiBC;oBAClC,IAAIG,cAAc,MAAM5E,gBAAgByC,mBAAmB+B;oBAC3D,IACEI,eACC,CAAA,CAACA,YAAYC,IAAI,EAAEvG,SAASmG,QAAQ,AAACG,YAAYL,aAAa,KAAK,SAAUzD,SAAQ,GACtF;wBACA,MAAM,IAAI5C,MACR,CAAC,yBAAyB,EAAEsG,gBAAgB,uCAAuC,CAAC;oBAExF;oBACA,IAAII,aAAa;wBACfjC,OAAOM,MAAM,IAAI;oBACnB;oBACA,IAAIvB,QAAQ;wBACViB,OAAOK,OAAO,IAAI;wBAClBmB,QAAQC,GAAG,CACT,CAAC,gCAAgC,EAAEQ,cAAc,UAAU,SAAS,CAAC,EAAE1C,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,EAAE,EAAEP,OAAO3B,QAAQ,CAAC,IAAI,EAAEuF,iBAAiB;wBAEnI;oBACF;oBAEA,IAAI,CAACI,aAAa;wBAChB,MAAME,YAAYvC,aAAawC,MAAM,CAACC,QAAQ,CAAC;4BAC7C,GAAIpE,OAAO2D,aAAa,GAAG;gCAAEU,WAAWrD;gCAAoBsD,QAAQ;4BAAK,IAAI,CAAC,CAAC;4BAC/EC,KAAKvE,OAAO3B,QAAQ;4BACpBmG,gBAAgB;gCAAC;oCAAEC,KAAK;gCAAY;6BAAE;4BACtClH,aAAa0D,KAAK1D,WAAW;wBAC/B;wBACA,MAAMmH,WAAW,MAAM7C,kBAAkBlD,KAAK,CAAC6C,MAAM,CAAC;4BACpD9C,MAAMwF;4BACNS,UAAU3E,OAAOlB,IAAI;4BACrBhB,QAAQ2E;4BACRkB,eAAezD;4BACf0E,eAAe;4BACfX,MAAM;gCAACJ;6BAAI;4BACXgB,mBAAmB;wBACrB;wBACA,IAAI,CAACH,SAASjG,MAAM,EAAE;4BACpB,MAAM,IAAInB,MAAM,CAAC,WAAW,EAAE0C,OAAOvB,MAAM,CAAC,2BAA2B,CAAC;wBAC1E;wBACAuF,cAAc,MAAMzF,SAASsD,mBAAmB6C,SAASjG,MAAM;wBAC/D,IACE,CAACuF,eACDA,YAAY3F,QAAQ,KAAKuF,mBACzB,CAACI,YAAYC,IAAI,EAAEvG,SAASmG,QAC5B,AAACG,YAAYL,aAAa,KAAK,SAAUzD,WACzC;4BACA,MAAM,IAAI5C,MACR,CAAC,WAAW,EAAE0C,OAAOvB,MAAM,CAAC,0CAA0C,CAAC;wBAE3E;oBACF;oBAEA,MAAM6E,WAAW;wBACf7E,QAAQuF,YAAYvF,MAAM;wBAC1ByB;wBACA4E,cAAcd,YAAYe,SAAS,IAAI;wBACvClG,KAAKgD,kBAAkBsC,MAAM,CAACC,QAAQ,CAAC;4BACrCG,KAAKP,YAAY3F,QAAQ;4BACzBd,aAAa2D,GAAG3D,WAAW;wBAC7B;oBACF;oBACA,MAAMyH,UAAU;wBAAE,GAAG7B,GAAG;wBAAE8B,UAAUjB,YAAYlF,IAAI;wBAAEwE;oBAAS;oBAC/D,MAAM4B,OAAO;wBACXD,UAAUjB,YAAYlF,IAAI;wBAC1BwE;wBACA6B,QAAQ1C,kBAAkB1E,OAAO,CAAC,OAAO;wBACzCqH,cAAclF,YACVlD,4BAA4BgI,SAAS1D,MAAMP,QAAQQ,MAAM,CAAC8D,MAAM,CAACC,GAAG,IACpEvI,qBAAqBiI;oBAC3B;oBACA,IAAIjE,QAAQwE,EAAE,CAACzG,IAAI,KAAK,YAAY;wBAClC,yEAAyE;wBACzE,uEAAuE;wBACvE,wEAAwE;wBACxE,oEAAoE;wBACpE,MAAMiC,QAAQwE,EAAE,CAACC,SAAS,CAAC;4BACzBjF,IAAI4C,IAAI5C,EAAE;4BACVyC,YAAY1B;4BACZ4D,MAAM;gCAAE,GAAGA,IAAI;gCAAEO,QAAQ;oCAAEC,YAAY;gCAAE;4BAAE;wBAC7C;oBACF,OAAO;wBACL,MAAM3E,QAAQN,MAAM,CAAC;4BACnBF,IAAI4C,IAAI5C,EAAE;4BACVyC,YAAY1B;4BACZqE,SAAS;gCAAEC,kBAAkB;4BAAK;4BAClCV,MAAM;gCAAE,GAAGA,IAAI;gCAAEQ,YAAY;4BAAK;wBACpC;oBACF;oBACA3D,OAAOI,QAAQ,IAAI;oBACnBoB,QAAQC,GAAG,CAAC,CAAC,yBAAyB,EAAElC,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,IAAI,EAAEqD,gBAAgB,CAAC,CAAC;gBACjF,EAAE,OAAO3E,OAAO;oBACd,MAAM4G,UAAU5G,iBAAiB3B,QAAQ2B,MAAM4G,OAAO,GAAG;oBACzD9D,OAAOG,MAAM,IAAI;oBACjBH,OAAOE,MAAM,CAAC6D,IAAI,CAAC;wBAAE7F,YAAYkD,IAAI5C,EAAE;wBAAEsF;oBAAQ;oBACjDtC,QAAQtE,KAAK,CAAC,CAAC,uBAAuB,EAAEqC,KAAK,CAAC,EAAE6B,IAAI5C,EAAE,CAAC,EAAE,EAAEsF,SAAS;gBACtE;YACF;QACF,QAAS/C,YAAY;QAErBhB,QAAQgE,IAAI,CAAC/D;QACbwB,QAAQC,GAAG,CACT,CAAC,gBAAgB,EAAElC,KAAK,UAAU,EAAES,OAAOK,OAAO,CAAC,WAAW,EAAEL,OAAOI,QAAQ,CAAC,SAAS,EAAEJ,OAAOM,MAAM,CAAC,UAAU,EAAEN,OAAOO,OAAO,CAAC,SAAS,EAAEP,OAAOG,MAAM,CAAC,CAAC,CAAC;IAEnK;IACA,OAAOJ;AACT,EAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humaan/payload-storage-imagekit",
3
- "version": "2.8.2",
3
+ "version": "2.9.1",
4
4
  "description": "ImageKit storage adapter plugin for Payload CMS uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",