@humaan/payload-storage-imagekit 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -8
- package/dist/clientUploads.d.ts +3 -0
- package/dist/clientUploads.js.map +1 -1
- package/dist/components/ImageKitUploadHandler.js +3 -0
- package/dist/components/ImageKitUploadHandler.js.map +1 -1
- package/dist/getAdminThumbnailURL.d.ts +2 -0
- package/dist/getAdminThumbnailURL.js +30 -15
- package/dist/getAdminThumbnailURL.js.map +1 -1
- package/dist/imageTransforms.d.ts +37 -0
- package/dist/imageTransforms.js +117 -0
- package/dist/imageTransforms.js.map +1 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +55 -26
- package/dist/index.js.map +1 -1
- package/dist/migrate.js +8 -8
- package/dist/migrate.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -9,8 +9,10 @@ This plugin integrates `@payloadcms/plugin-cloud-storage` with ImageKit and prov
|
|
|
9
9
|
- Required root folder with per-collection subfolders
|
|
10
10
|
- Stored ImageKit references (`imagekit.fileId`, `imagekit.url`, `imagekit.thumbnailUrl`, `imagekit.isPrivate`)
|
|
11
11
|
- `crop: false` for configured upload collections
|
|
12
|
+
- Opt-in `focalPoint` toggle for configured upload collections
|
|
12
13
|
- Admin thumbnails using ImageKit transforms (`tr=w-300,h-300`)
|
|
13
14
|
- Built-in thumbnail path for videos and PDFs in admin previews
|
|
15
|
+
- Browser-safe ImageKit transform helpers for frontend image rendering
|
|
14
16
|
- Migration utility for copying collection files between ImageKit folders
|
|
15
17
|
|
|
16
18
|
## Installation
|
|
@@ -38,7 +40,7 @@ export default buildConfig({
|
|
|
38
40
|
clientUploads: true,
|
|
39
41
|
folder: '/payload-uploads',
|
|
40
42
|
collections: {
|
|
41
|
-
media: true,
|
|
43
|
+
media: { focalPoint: true },
|
|
42
44
|
},
|
|
43
45
|
privateKey: process.env.IMAGEKIT_PRIVATE_KEY || '',
|
|
44
46
|
publicKey: process.env.IMAGEKIT_PUBLIC_KEY || '',
|
|
@@ -52,7 +54,7 @@ export default buildConfig({
|
|
|
52
54
|
|
|
53
55
|
`imagekitStorage(options)` options:
|
|
54
56
|
|
|
55
|
-
- `collections` (required): `Partial<Record<UploadCollectionSlug, true | { private?: boolean }>>`. Use `true` for a normal (public) collection, or `{ private: true }` to deliver the collection's files as ImageKit [private files](#private-files).
|
|
57
|
+
- `collections` (required): `Partial<Record<UploadCollectionSlug, true | { focalPoint?: boolean; private?: boolean }>>`. Use `true` for a normal (public) collection, `{ focalPoint: true }` to enable Payload's native focal-point picker for that collection, or `{ private: true }` to deliver the collection's files as ImageKit [private files](#private-files).
|
|
56
58
|
- `folder` (required): root upload folder in ImageKit, e.g. `/payload-uploads`
|
|
57
59
|
- `privateKey` (required): ImageKit private API key
|
|
58
60
|
- `publicKey` (required): ImageKit public key
|
|
@@ -61,6 +63,7 @@ export default buildConfig({
|
|
|
61
63
|
Duplicate filenames still follow Payload's normal safe-name behavior, so a second `pug.jpg` becomes `pug-1.jpg` instead of overwriting the first asset in ImageKit.
|
|
62
64
|
- `signedUrlExpiresIn` (optional): expiry in seconds for the signed URLs generated for [private](#private-files) collections. Defaults to `900` (15 minutes). Signed URLs are generated fresh on every request, so a short window is safe.
|
|
63
65
|
- `enabled` (optional): enable / disable plugin
|
|
66
|
+
- `focalPoint` (optional): broad default for Payload's native focal-point picker (`upload.focalPoint`) on every configured collection. Focal points are disabled by default for configured collections, so prefer the per-collection `collections.<slug>.focalPoint` option for mixed upload collections, e.g. enable it for `media` and leave it off for `documents`. When `true`, editors can place a focal point and `focalX`/`focalY` (0–100) are stored on each doc for focal-aware cropping at render time. A collection's own `upload.focalPoint` still takes precedence.
|
|
64
67
|
- `alwaysInsertFields` (optional): pass-through to cloud storage plugin
|
|
65
68
|
- `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin
|
|
66
69
|
|
|
@@ -68,7 +71,62 @@ export default buildConfig({
|
|
|
68
71
|
|
|
69
72
|
- `imagekitStorage` (primary)
|
|
70
73
|
- `migrateFolder`
|
|
71
|
-
- `
|
|
74
|
+
- `payloadStorageImageKit` (alias of `imagekitStorage`)
|
|
75
|
+
- `@humaan/payload-storage-imagekit/image`:
|
|
76
|
+
- `buildImageKitTransformation`
|
|
77
|
+
- `withImageKitTransformation`
|
|
78
|
+
- `computeFocalCropRect`
|
|
79
|
+
- `getImageKitVideoSrc`
|
|
80
|
+
- `getImageKitThumbnailSrc`
|
|
81
|
+
- `getVideoPosterSrc`
|
|
82
|
+
- `srcParamsPayloadImageKit` (alias of `withImageKitTransformation`)
|
|
83
|
+
|
|
84
|
+
## Frontend Image Transforms
|
|
85
|
+
|
|
86
|
+
Use the browser-safe `image` export in app image components to build ImageKit URLs from Payload upload data without importing the server plugin entry.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import {
|
|
90
|
+
withImageKitTransformation,
|
|
91
|
+
} from '@humaan/payload-storage-imagekit/image'
|
|
92
|
+
|
|
93
|
+
const src = withImageKitTransformation(media.imagekit.url, {
|
|
94
|
+
focalX: media.focalX,
|
|
95
|
+
focalY: media.focalY,
|
|
96
|
+
height: 800,
|
|
97
|
+
originalHeight: media.height,
|
|
98
|
+
originalWidth: media.width,
|
|
99
|
+
quality: 80,
|
|
100
|
+
width: 1200,
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Provide `focalX`/`focalY` with the original image dimensions when you want the helper to compute a clamped `cm-extract` crop before resizing. This avoids ImageKit rejecting out-of-bounds coordinate crops.
|
|
105
|
+
|
|
106
|
+
Use `getImageKitVideoSrc` before adding video transforms when the stored ImageKit asset URL may not include a `.mp4` or `.mov` extension. It appends ImageKit's `/ik-video.mp4` optimization hint only when the URL lacks a supported video extension.
|
|
107
|
+
|
|
108
|
+
Use `getVideoPosterSrc` when you need ImageKit's generated `ik-thumbnail.jpg` URL for a video poster. It also applies the `/ik-video.mp4` hint first when the source URL lacks a `.mp4` or `.mov` extension. Use `getImageKitThumbnailSrc` for non-video thumbnail URLs such as PDF previews.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import {
|
|
112
|
+
getImageKitVideoSrc,
|
|
113
|
+
getVideoPosterSrc,
|
|
114
|
+
withImageKitTransformation,
|
|
115
|
+
} from '@humaan/payload-storage-imagekit/image'
|
|
116
|
+
|
|
117
|
+
const video = withImageKitTransformation(getImageKitVideoSrc(media.imagekit.url), {
|
|
118
|
+
height: 720,
|
|
119
|
+
quality: 80,
|
|
120
|
+
vc: 'h264',
|
|
121
|
+
width: 1280,
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const poster = withImageKitTransformation(getVideoPosterSrc(video), {
|
|
125
|
+
height: 720,
|
|
126
|
+
quality: 80,
|
|
127
|
+
width: 1280,
|
|
128
|
+
})
|
|
129
|
+
```
|
|
72
130
|
|
|
73
131
|
## Private Files
|
|
74
132
|
|
|
@@ -93,11 +151,11 @@ imagekitStorage({
|
|
|
93
151
|
How it works:
|
|
94
152
|
|
|
95
153
|
- **Upload:** files in a private collection are uploaded with `isPrivateFile: true` (both server-side and client-side uploads).
|
|
96
|
-
- **Per-document status:** each document records its actual ImageKit privacy at upload time in `imagekit.isPrivate` (
|
|
154
|
+
- **Per-document status:** each document records its actual ImageKit privacy at upload time in `imagekit.isPrivate` (hidden from the admin form). A file's privacy on ImageKit is fixed when it is uploaded, so this stored flag — not the collection's current setting — is the **source of truth** for delivery. This means existing files keep working if you later flip a collection's `private` setting. (Documents uploaded before this flag existed fall back to the collection setting.)
|
|
97
155
|
- **Delivery:** files are served through Payload's own file route (`/api/<collection>/file/<filename>`), which enforces the collection's `access.read` control and then redirects to a freshly signed, expiring ImageKit URL. Nothing signed is ever persisted to the database.
|
|
98
|
-
- **Admin thumbnails** are routed through the same Payload file route so they sign on demand and respect access control (including ImageKit's `ik-thumbnail.jpg` previews for videos and PDFs).
|
|
99
|
-
- **Admin indicators:** private collections get a note in their admin description (visible in the list and edit views)
|
|
100
|
-
- **Signed
|
|
156
|
+
- **Admin thumbnails** are routed through the same Payload file route so they sign on demand and respect access control (including ImageKit's `ik-thumbnail.jpg` previews for videos and PDFs, with `/ik-video.mp4` applied first for videos without a `.mp4` or `.mov` extension).
|
|
157
|
+
- **Admin indicators:** private collections get a note in their admin description (visible in the list and edit views). The ImageKit metadata group is hidden while uploading and shown after a file has been stored, with `imagekit.url` visible as a read-only field and `imagekit.isPrivate` shown as read-only when the stored file is private.
|
|
158
|
+
- **Signed URLs:** because the stored `imagekit.url` is the unsigned URL (which 401s for private files), private documents also expose a virtual `imagekit.signedUrl` field — a freshly signed, time-limited URL computed on each read (never persisted). Use it through Payload's API to reach the file directly on ImageKit. The access-control-gated alternative is the document's top-level `url` (the Payload file route).
|
|
101
159
|
- **Access control is required.** Because signing happens at request time behind Payload access control, `disablePayloadAccessControl` cannot be combined with private collections — doing so throws at startup. Set per-collection `access.read` rules to control who can read each file.
|
|
102
160
|
|
|
103
161
|
## Folder Migration
|
|
@@ -165,7 +223,8 @@ DRY_RUN=false pnpx tsx --env-file=../.env imagekit-migration.ts
|
|
|
165
223
|
- Deletion prefers stored `imagekit.fileId`, with path lookup fallback.
|
|
166
224
|
- Admin thumbnail behavior:
|
|
167
225
|
- Images: `<imagekit.url>?tr=w-300,h-300`
|
|
168
|
-
- Videos
|
|
226
|
+
- Videos: `<imagekit.url>/ik-thumbnail.jpg?tr=w-300,h-300` or `<imagekit.url>/ik-video.mp4/ik-thumbnail.jpg?tr=w-300,h-300` when the video URL has no `.mp4` or `.mov` extension
|
|
227
|
+
- PDFs: `<imagekit.url>/ik-thumbnail.jpg?tr=w-300,h-300`
|
|
169
228
|
|
|
170
229
|
## Development
|
|
171
230
|
|
package/dist/clientUploads.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export declare const getImageKitUploadAuthPath: (collectionSlug: string) => `/${
|
|
|
3
3
|
export type ImageKitClientUploadContext = {
|
|
4
4
|
fileId?: string;
|
|
5
5
|
filePath?: string;
|
|
6
|
+
fileType?: string;
|
|
7
|
+
mimeType?: string;
|
|
6
8
|
name?: string;
|
|
9
|
+
size?: number;
|
|
7
10
|
thumbnailUrl?: string;
|
|
8
11
|
url?: string;
|
|
9
12
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["export const imageKitUploadAuthBasePath = '/imagekit-upload-auth'\n\nexport const getImageKitUploadAuthPath = (collectionSlug: string): `/${string}` =>\n `${imageKitUploadAuthBasePath}/${collectionSlug}`\n\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n name?: string\n thumbnailUrl?: string\n url?: string\n}\n\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport const isImageKitClientUploadContext = (\n value: unknown,\n): value is ImageKitClientUploadContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value\n}\n"],"names":["imageKitUploadAuthBasePath","getImageKitUploadAuthPath","collectionSlug","isImageKitClientUploadContext","value"],"mappings":"AAAA,OAAO,MAAMA,6BAA6B,wBAAuB;AAEjE,OAAO,MAAMC,4BAA4B,CAACC,iBACxC,GAAGF,2BAA2B,CAAC,EAAEE,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["export const imageKitUploadAuthBasePath = '/imagekit-upload-auth'\n\nexport const getImageKitUploadAuthPath = (collectionSlug: string): `/${string}` =>\n `${imageKitUploadAuthBasePath}/${collectionSlug}`\n\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n fileType?: string\n mimeType?: string\n name?: string\n size?: number\n thumbnailUrl?: string\n url?: string\n}\n\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport const isImageKitClientUploadContext = (\n value: unknown,\n): value is ImageKitClientUploadContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value\n}\n"],"names":["imageKitUploadAuthBasePath","getImageKitUploadAuthPath","collectionSlug","isImageKitClientUploadContext","value"],"mappings":"AAAA,OAAO,MAAMA,6BAA6B,wBAAuB;AAEjE,OAAO,MAAMC,4BAA4B,CAACC,iBACxC,GAAGF,2BAA2B,CAAC,EAAEE,gBAAgB,CAAA;AAqBnD,OAAO,MAAMC,gCAAgC,CAC3CC;IAEA,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,OAAO,YAAYA,SAAS,kBAAkBA,SAAS,SAASA;AAClE,EAAC"}
|
|
@@ -63,6 +63,9 @@ export const ImageKitUploadHandler = ({ authPath, collectionSlug, folder, isPriv
|
|
|
63
63
|
name: uploadedFilename,
|
|
64
64
|
fileId: uploaded.fileId,
|
|
65
65
|
filePath: uploaded.filePath,
|
|
66
|
+
fileType: uploaded.fileType,
|
|
67
|
+
mimeType: file.type,
|
|
68
|
+
size: typeof uploaded.size === 'number' ? uploaded.size : file.size,
|
|
66
69
|
thumbnailUrl: uploaded.thumbnailUrl,
|
|
67
70
|
url: uploaded.url
|
|
68
71
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useUploadHandlers } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect } from 'react'\n\nimport type { ImageKitClientUploadContext, ImageKitUploadHandlerProps } from '../clientUploads.js'\n\ntype ImageKitAuthResponse = {\n expire: number\n fileName?: string\n signature: string\n token: string\n}\n\ntype ImageKitUploadResponse = {\n message?: string\n name?: string\n} & ImageKitClientUploadContext\n\nconst uploadAPIURL = 'https://upload.imagekit.io/api/v1/files/upload'\n\nconst readErrorMessage = async (response: Response, fallback: string): Promise<string> => {\n try {\n const result = (await response.json()) as { message?: string }\n\n if (typeof result.message === 'string' && result.message.trim().length > 0) {\n return result.message\n }\n } catch {\n // Ignore non-JSON responses and fall back to the default message.\n }\n\n return fallback\n}\n\nexport const ImageKitUploadHandler = ({\n authPath,\n collectionSlug,\n folder,\n isPrivate,\n publicKey,\n}: ImageKitUploadHandlerProps) => {\n const { config } = useConfig()\n const { setUploadHandler } = useUploadHandlers()\n\n useEffect(() => {\n const authURL = formatAdminURL({\n apiRoute: config.routes.api,\n path: authPath,\n })\n\n setUploadHandler({\n collectionSlug,\n handler: async ({ file, updateFilename }) => {\n const authRequestURL = new URL(authURL, window.location.origin)\n\n authRequestURL.searchParams.set('filename', file.name)\n\n const authResponse = await fetch(authRequestURL, {\n credentials: 'include',\n })\n\n if (!authResponse.ok) {\n throw new Error(\n await readErrorMessage(authResponse, 'Failed to authenticate direct ImageKit upload.'),\n )\n }\n\n const auth = (await authResponse.json()) as ImageKitAuthResponse\n const safeFilename =\n typeof auth.fileName === 'string' && auth.fileName.length > 0 ? auth.fileName : file.name\n const formData = new FormData()\n\n formData.set('expire', String(auth.expire))\n formData.set('file', file)\n formData.set('fileName', safeFilename)\n formData.set('folder', folder)\n formData.set('publicKey', publicKey)\n formData.set('signature', auth.signature)\n formData.set('token', auth.token)\n formData.set('useUniqueFileName', 'false')\n\n if (isPrivate) {\n // ImageKit's upload signature only covers `token` + `expire`, so adding\n // this flag does not invalidate the signed auth parameters.\n formData.set('isPrivateFile', 'true')\n }\n\n const uploadResponse = await fetch(uploadAPIURL, {\n body: formData,\n method: 'POST',\n })\n\n if (!uploadResponse.ok) {\n throw new Error(\n await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.'),\n )\n }\n\n const uploaded = (await uploadResponse.json()) as ImageKitUploadResponse\n const uploadedFilename =\n typeof uploaded.name === 'string' && uploaded.name.length > 0\n ? uploaded.name\n : safeFilename\n\n updateFilename(uploadedFilename)\n\n return {\n name: uploadedFilename,\n fileId: uploaded.fileId,\n filePath: uploaded.filePath,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n },\n })\n }, [authPath, collectionSlug, config.routes.api, folder, isPrivate, publicKey, setUploadHandler])\n\n return null\n}\n"],"names":["useConfig","useUploadHandlers","formatAdminURL","useEffect","uploadAPIURL","readErrorMessage","response","fallback","result","json","message","trim","length","ImageKitUploadHandler","authPath","collectionSlug","folder","isPrivate","publicKey","config","setUploadHandler","authURL","apiRoute","routes","api","path","handler","file","updateFilename","authRequestURL","URL","window","location","origin","searchParams","set","name","authResponse","fetch","credentials","ok","Error","auth","safeFilename","fileName","formData","FormData","String","expire","signature","token","uploadResponse","body","method","uploaded","uploadedFilename","fileId","filePath","thumbnailUrl","url"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,iBAAiB,QAAQ,iBAAgB;AAC7D,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,SAAS,QAAQ,QAAO;
|
|
1
|
+
{"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useUploadHandlers } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect } from 'react'\n\nimport type { ImageKitClientUploadContext, ImageKitUploadHandlerProps } from '../clientUploads.js'\n\ntype ImageKitAuthResponse = {\n expire: number\n fileName?: string\n signature: string\n token: string\n}\n\ntype ImageKitUploadResponse = {\n fileType?: string\n message?: string\n name?: string\n size?: number\n} & ImageKitClientUploadContext\n\nconst uploadAPIURL = 'https://upload.imagekit.io/api/v1/files/upload'\n\nconst readErrorMessage = async (response: Response, fallback: string): Promise<string> => {\n try {\n const result = (await response.json()) as { message?: string }\n\n if (typeof result.message === 'string' && result.message.trim().length > 0) {\n return result.message\n }\n } catch {\n // Ignore non-JSON responses and fall back to the default message.\n }\n\n return fallback\n}\n\nexport const ImageKitUploadHandler = ({\n authPath,\n collectionSlug,\n folder,\n isPrivate,\n publicKey,\n}: ImageKitUploadHandlerProps) => {\n const { config } = useConfig()\n const { setUploadHandler } = useUploadHandlers()\n\n useEffect(() => {\n const authURL = formatAdminURL({\n apiRoute: config.routes.api,\n path: authPath,\n })\n\n setUploadHandler({\n collectionSlug,\n handler: async ({ file, updateFilename }) => {\n const authRequestURL = new URL(authURL, window.location.origin)\n\n authRequestURL.searchParams.set('filename', file.name)\n\n const authResponse = await fetch(authRequestURL, {\n credentials: 'include',\n })\n\n if (!authResponse.ok) {\n throw new Error(\n await readErrorMessage(authResponse, 'Failed to authenticate direct ImageKit upload.'),\n )\n }\n\n const auth = (await authResponse.json()) as ImageKitAuthResponse\n const safeFilename =\n typeof auth.fileName === 'string' && auth.fileName.length > 0 ? auth.fileName : file.name\n const formData = new FormData()\n\n formData.set('expire', String(auth.expire))\n formData.set('file', file)\n formData.set('fileName', safeFilename)\n formData.set('folder', folder)\n formData.set('publicKey', publicKey)\n formData.set('signature', auth.signature)\n formData.set('token', auth.token)\n formData.set('useUniqueFileName', 'false')\n\n if (isPrivate) {\n // ImageKit's upload signature only covers `token` + `expire`, so adding\n // this flag does not invalidate the signed auth parameters.\n formData.set('isPrivateFile', 'true')\n }\n\n const uploadResponse = await fetch(uploadAPIURL, {\n body: formData,\n method: 'POST',\n })\n\n if (!uploadResponse.ok) {\n throw new Error(\n await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.'),\n )\n }\n\n const uploaded = (await uploadResponse.json()) as ImageKitUploadResponse\n const uploadedFilename =\n typeof uploaded.name === 'string' && uploaded.name.length > 0\n ? uploaded.name\n : safeFilename\n\n updateFilename(uploadedFilename)\n\n return {\n name: uploadedFilename,\n fileId: uploaded.fileId,\n filePath: uploaded.filePath,\n fileType: uploaded.fileType,\n mimeType: file.type,\n size: typeof uploaded.size === 'number' ? uploaded.size : file.size,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n },\n })\n }, [authPath, collectionSlug, config.routes.api, folder, isPrivate, publicKey, setUploadHandler])\n\n return null\n}\n"],"names":["useConfig","useUploadHandlers","formatAdminURL","useEffect","uploadAPIURL","readErrorMessage","response","fallback","result","json","message","trim","length","ImageKitUploadHandler","authPath","collectionSlug","folder","isPrivate","publicKey","config","setUploadHandler","authURL","apiRoute","routes","api","path","handler","file","updateFilename","authRequestURL","URL","window","location","origin","searchParams","set","name","authResponse","fetch","credentials","ok","Error","auth","safeFilename","fileName","formData","FormData","String","expire","signature","token","uploadResponse","body","method","uploaded","uploadedFilename","fileId","filePath","fileType","mimeType","type","size","thumbnailUrl","url"],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const isVideoMimeType: (mimeType?: string) => boolean;
|
|
2
|
+
export declare const isPdfMimeType: (mimeType?: string) => boolean;
|
|
1
3
|
export declare const isVideoOrPdfMimeType: (mimeType?: string) => boolean;
|
|
2
4
|
export declare const getAdminThumbnailURL: (doc: unknown) => string;
|
|
3
5
|
/**
|
|
@@ -1,25 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { getImageKitThumbnailSrc, getVideoPosterSrc, withImageKitTransformation } from './imageTransforms.js';
|
|
2
|
+
const getThumbnailTransform = (file)=>{
|
|
3
|
+
if (!isVideoOrPdfMimeType(file.mimeType) && typeof file.focalX === 'number' && typeof file.focalY === 'number' && typeof file.width === 'number' && typeof file.height === 'number') {
|
|
4
|
+
return {
|
|
5
|
+
focalX: file.focalX,
|
|
6
|
+
focalY: file.focalY,
|
|
7
|
+
height: 300,
|
|
8
|
+
originalHeight: file.height,
|
|
9
|
+
originalWidth: file.width,
|
|
10
|
+
width: 300
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
height: 300,
|
|
15
|
+
width: 300
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export const isVideoMimeType = (mimeType)=>Boolean(mimeType?.startsWith('video'));
|
|
19
|
+
export const isPdfMimeType = (mimeType)=>{
|
|
3
20
|
const isPDF = mimeType === 'application/pdf' || mimeType?.endsWith('/pdf');
|
|
4
|
-
return Boolean(
|
|
21
|
+
return Boolean(isPDF);
|
|
5
22
|
};
|
|
23
|
+
export const isVideoOrPdfMimeType = (mimeType)=>isVideoMimeType(mimeType) || isPdfMimeType(mimeType);
|
|
6
24
|
export const getAdminThumbnailURL = (doc)=>{
|
|
7
25
|
const file = doc;
|
|
8
|
-
const baseURL = file.imagekit?.url;
|
|
26
|
+
const baseURL = file.imagekit?.url || file.imagekit?.thumbnailUrl;
|
|
9
27
|
if (!baseURL) {
|
|
10
28
|
return '';
|
|
11
29
|
}
|
|
12
30
|
let thumbnailBaseURL = baseURL;
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} catch {
|
|
18
|
-
const normalizedBaseURL = baseURL.split('?')[0].split('#')[0];
|
|
19
|
-
thumbnailBaseURL = `${normalizedBaseURL}/ik-thumbnail.jpg`;
|
|
20
|
-
}
|
|
31
|
+
if (isVideoMimeType(file.mimeType)) {
|
|
32
|
+
thumbnailBaseURL = getVideoPosterSrc(baseURL);
|
|
33
|
+
} else if (isPdfMimeType(file.mimeType)) {
|
|
34
|
+
thumbnailBaseURL = getImageKitThumbnailSrc(baseURL);
|
|
21
35
|
}
|
|
22
|
-
return
|
|
36
|
+
return withImageKitTransformation(thumbnailBaseURL, getThumbnailTransform(file));
|
|
23
37
|
};
|
|
24
38
|
/**
|
|
25
39
|
* Admin thumbnail URL for a private file.
|
|
@@ -30,12 +44,13 @@ export const getAdminThumbnailURL = (doc)=>{
|
|
|
30
44
|
* signs the ImageKit URL on demand (including the `/ik-thumbnail.jpg` segment for
|
|
31
45
|
* videos and PDFs, derived from the document's mimeType).
|
|
32
46
|
*/ export const getPrivateAdminThumbnailURL = (doc, collectionSlug, apiRoute)=>{
|
|
33
|
-
const
|
|
47
|
+
const file = doc;
|
|
48
|
+
const filename = file.filename;
|
|
34
49
|
if (!filename) {
|
|
35
50
|
return '';
|
|
36
51
|
}
|
|
37
52
|
const base = `${apiRoute.replace(/\/+$/, '')}/${collectionSlug}/file/${encodeURIComponent(filename)}`;
|
|
38
|
-
return
|
|
53
|
+
return withImageKitTransformation(base, getThumbnailTransform(file));
|
|
39
54
|
};
|
|
40
55
|
|
|
41
56
|
//# sourceMappingURL=getAdminThumbnailURL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/getAdminThumbnailURL.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../src/getAdminThumbnailURL.ts"],"sourcesContent":["import {\n getImageKitThumbnailSrc,\n getVideoPosterSrc,\n withImageKitTransformation,\n} from './imageTransforms.js'\n\ntype ImageKitReference = {\n imagekit?: {\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadPreviewDocument = {\n filename?: string\n focalX?: null | number\n focalY?: null | number\n height?: null | number\n mimeType?: string\n width?: null | number\n} & ImageKitReference\n\nconst getThumbnailTransform = (file: UploadPreviewDocument) => {\n if (\n !isVideoOrPdfMimeType(file.mimeType) &&\n typeof file.focalX === 'number' &&\n typeof file.focalY === 'number' &&\n typeof file.width === 'number' &&\n typeof file.height === 'number'\n ) {\n return {\n focalX: file.focalX,\n focalY: file.focalY,\n height: 300,\n originalHeight: file.height,\n originalWidth: file.width,\n width: 300,\n }\n }\n\n return {\n height: 300,\n width: 300,\n }\n}\n\nexport const isVideoMimeType = (mimeType?: string): boolean =>\n Boolean(mimeType?.startsWith('video'))\n\nexport const isPdfMimeType = (mimeType?: string): boolean => {\n const isPDF = mimeType === 'application/pdf' || mimeType?.endsWith('/pdf')\n\n return Boolean(isPDF)\n}\n\nexport const isVideoOrPdfMimeType = (mimeType?: string): boolean =>\n isVideoMimeType(mimeType) || isPdfMimeType(mimeType)\n\nexport const getAdminThumbnailURL = (doc: unknown): string => {\n const file = doc as UploadPreviewDocument\n const baseURL = file.imagekit?.url || file.imagekit?.thumbnailUrl\n\n if (!baseURL) {\n return ''\n }\n\n let thumbnailBaseURL = baseURL\n\n if (isVideoMimeType(file.mimeType)) {\n thumbnailBaseURL = getVideoPosterSrc(baseURL)\n } else if (isPdfMimeType(file.mimeType)) {\n thumbnailBaseURL = getImageKitThumbnailSrc(baseURL)\n }\n\n return withImageKitTransformation(thumbnailBaseURL, getThumbnailTransform(file))\n}\n\n/**\n * Admin thumbnail URL for a private file.\n *\n * Private files 401 on their plain ImageKit URL, so the preview must be served\n * through Payload's own file route (`<apiRoute>/<collection>/file/<filename>`).\n * That route enforces collection access control and the adapter's `staticHandler`\n * signs the ImageKit URL on demand (including the `/ik-thumbnail.jpg` segment for\n * videos and PDFs, derived from the document's mimeType).\n */\nexport const getPrivateAdminThumbnailURL = (\n doc: unknown,\n collectionSlug: string,\n apiRoute: string,\n): string => {\n const file = doc as UploadPreviewDocument\n const filename = file.filename\n\n if (!filename) {\n return ''\n }\n\n const base = `${apiRoute.replace(/\\/+$/, '')}/${collectionSlug}/file/${encodeURIComponent(filename)}`\n\n return withImageKitTransformation(base, getThumbnailTransform(file))\n}\n"],"names":["getImageKitThumbnailSrc","getVideoPosterSrc","withImageKitTransformation","getThumbnailTransform","file","isVideoOrPdfMimeType","mimeType","focalX","focalY","width","height","originalHeight","originalWidth","isVideoMimeType","Boolean","startsWith","isPdfMimeType","isPDF","endsWith","getAdminThumbnailURL","doc","baseURL","imagekit","url","thumbnailUrl","thumbnailBaseURL","getPrivateAdminThumbnailURL","collectionSlug","apiRoute","filename","base","replace","encodeURIComponent"],"mappings":"AAAA,SACEA,uBAAuB,EACvBC,iBAAiB,EACjBC,0BAA0B,QACrB,uBAAsB;AAkB7B,MAAMC,wBAAwB,CAACC;IAC7B,IACE,CAACC,qBAAqBD,KAAKE,QAAQ,KACnC,OAAOF,KAAKG,MAAM,KAAK,YACvB,OAAOH,KAAKI,MAAM,KAAK,YACvB,OAAOJ,KAAKK,KAAK,KAAK,YACtB,OAAOL,KAAKM,MAAM,KAAK,UACvB;QACA,OAAO;YACLH,QAAQH,KAAKG,MAAM;YACnBC,QAAQJ,KAAKI,MAAM;YACnBE,QAAQ;YACRC,gBAAgBP,KAAKM,MAAM;YAC3BE,eAAeR,KAAKK,KAAK;YACzBA,OAAO;QACT;IACF;IAEA,OAAO;QACLC,QAAQ;QACRD,OAAO;IACT;AACF;AAEA,OAAO,MAAMI,kBAAkB,CAACP,WAC9BQ,QAAQR,UAAUS,WAAW,UAAS;AAExC,OAAO,MAAMC,gBAAgB,CAACV;IAC5B,MAAMW,QAAQX,aAAa,qBAAqBA,UAAUY,SAAS;IAEnE,OAAOJ,QAAQG;AACjB,EAAC;AAED,OAAO,MAAMZ,uBAAuB,CAACC,WACnCO,gBAAgBP,aAAaU,cAAcV,UAAS;AAEtD,OAAO,MAAMa,uBAAuB,CAACC;IACnC,MAAMhB,OAAOgB;IACb,MAAMC,UAAUjB,KAAKkB,QAAQ,EAAEC,OAAOnB,KAAKkB,QAAQ,EAAEE;IAErD,IAAI,CAACH,SAAS;QACZ,OAAO;IACT;IAEA,IAAII,mBAAmBJ;IAEvB,IAAIR,gBAAgBT,KAAKE,QAAQ,GAAG;QAClCmB,mBAAmBxB,kBAAkBoB;IACvC,OAAO,IAAIL,cAAcZ,KAAKE,QAAQ,GAAG;QACvCmB,mBAAmBzB,wBAAwBqB;IAC7C;IAEA,OAAOnB,2BAA2BuB,kBAAkBtB,sBAAsBC;AAC5E,EAAC;AAED;;;;;;;;CAQC,GACD,OAAO,MAAMsB,8BAA8B,CACzCN,KACAO,gBACAC;IAEA,MAAMxB,OAAOgB;IACb,MAAMS,WAAWzB,KAAKyB,QAAQ;IAE9B,IAAI,CAACA,UAAU;QACb,OAAO;IACT;IAEA,MAAMC,OAAO,GAAGF,SAASG,OAAO,CAAC,QAAQ,IAAI,CAAC,EAAEJ,eAAe,MAAM,EAAEK,mBAAmBH,WAAW;IAErG,OAAO3B,2BAA2B4B,MAAM3B,sBAAsBC;AAChE,EAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ImageTransformationOptions = {
|
|
2
|
+
dpr?: number;
|
|
3
|
+
fit?: 'contain' | 'cover';
|
|
4
|
+
/** Focal point X as a percentage (0-100) of the original image width. */
|
|
5
|
+
focalX?: null | number;
|
|
6
|
+
/** Focal point Y as a percentage (0-100) of the original image height. */
|
|
7
|
+
focalY?: null | number;
|
|
8
|
+
format?: string;
|
|
9
|
+
height?: number;
|
|
10
|
+
lossless?: boolean;
|
|
11
|
+
/** The original uploaded image height in pixels, required to compute a custom focal crop. */
|
|
12
|
+
originalHeight?: null | number;
|
|
13
|
+
/** The original uploaded image width in pixels, required to compute a custom focal crop. */
|
|
14
|
+
originalWidth?: null | number;
|
|
15
|
+
quality?: number;
|
|
16
|
+
vc?: string;
|
|
17
|
+
width?: number;
|
|
18
|
+
};
|
|
19
|
+
export type CropRect = {
|
|
20
|
+
height: number;
|
|
21
|
+
width: number;
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const getImageKitVideoSrc: (src: string) => string;
|
|
26
|
+
export declare const getImageKitThumbnailSrc: (src: string) => string;
|
|
27
|
+
export declare const getVideoPosterSrc: (src: string) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Compute the largest target-aspect rectangle that fits inside the source image,
|
|
30
|
+
* centred on a focal point and clamped so ImageKit's `cm-extract` never receives
|
|
31
|
+
* an out-of-bounds crop.
|
|
32
|
+
*/
|
|
33
|
+
export declare const computeFocalCropRect: (originalWidth: number, originalHeight: number, targetWidth: number, targetHeight: number, focalX: number, focalY: number) => CropRect;
|
|
34
|
+
export declare const buildImageKitTransformation: (params: ImageTransformationOptions) => string;
|
|
35
|
+
export declare const withImageKitTransformation: (src: string, params: ImageTransformationOptions) => string;
|
|
36
|
+
export declare const srcParamsPayloadImageKit: (src: string, params: ImageTransformationOptions) => string;
|
|
37
|
+
export declare const srcParamsPayloadImagekit: (src: string, params: ImageTransformationOptions) => string;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const hasImageKitVideoExtension = (pathname)=>/\.(?:mov|mp4)$/i.test(pathname.replace(/\/$/, ''));
|
|
2
|
+
export const getImageKitVideoSrc = (src)=>{
|
|
3
|
+
try {
|
|
4
|
+
const url = new URL(src);
|
|
5
|
+
if (hasImageKitVideoExtension(url.pathname)) {
|
|
6
|
+
return src;
|
|
7
|
+
}
|
|
8
|
+
url.pathname = `${url.pathname.replace(/\/$/, '')}/ik-video.mp4`;
|
|
9
|
+
return url.toString();
|
|
10
|
+
} catch {
|
|
11
|
+
const hashIndex = src.indexOf('#');
|
|
12
|
+
const beforeHash = hashIndex === -1 ? src : src.slice(0, hashIndex);
|
|
13
|
+
const hash = hashIndex === -1 ? '' : src.slice(hashIndex);
|
|
14
|
+
const queryIndex = beforeHash.indexOf('?');
|
|
15
|
+
const pathname = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
|
|
16
|
+
const search = queryIndex === -1 ? '' : beforeHash.slice(queryIndex);
|
|
17
|
+
if (hasImageKitVideoExtension(pathname)) {
|
|
18
|
+
return src;
|
|
19
|
+
}
|
|
20
|
+
return `${pathname.replace(/\/$/, '')}/ik-video.mp4${search}${hash}`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const getImageKitThumbnailSrc = (src)=>{
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(src);
|
|
26
|
+
url.pathname = `${url.pathname.replace(/\/$/, '')}/ik-thumbnail.jpg`;
|
|
27
|
+
url.search = '';
|
|
28
|
+
url.hash = '';
|
|
29
|
+
return url.toString();
|
|
30
|
+
} catch {
|
|
31
|
+
const normalizedSrc = src.split('?')[0].split('#')[0].replace(/\/$/, '');
|
|
32
|
+
return `${normalizedSrc}/ik-thumbnail.jpg`;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
export const getVideoPosterSrc = (src)=>getImageKitThumbnailSrc(getImageKitVideoSrc(src));
|
|
36
|
+
const isPositiveNumber = (value)=>typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
37
|
+
const clamp = (value, min, max)=>Math.min(Math.max(value, min), max);
|
|
38
|
+
/**
|
|
39
|
+
* Compute the largest target-aspect rectangle that fits inside the source image,
|
|
40
|
+
* centred on a focal point and clamped so ImageKit's `cm-extract` never receives
|
|
41
|
+
* an out-of-bounds crop.
|
|
42
|
+
*/ export const computeFocalCropRect = (originalWidth, originalHeight, targetWidth, targetHeight, focalX, focalY)=>{
|
|
43
|
+
const targetAspect = targetWidth / targetHeight;
|
|
44
|
+
let cropWidth;
|
|
45
|
+
let cropHeight;
|
|
46
|
+
if (originalWidth / originalHeight > targetAspect) {
|
|
47
|
+
cropHeight = originalHeight;
|
|
48
|
+
cropWidth = Math.round(originalHeight * targetAspect);
|
|
49
|
+
} else {
|
|
50
|
+
cropWidth = originalWidth;
|
|
51
|
+
cropHeight = Math.round(originalWidth / targetAspect);
|
|
52
|
+
}
|
|
53
|
+
cropWidth = Math.min(cropWidth, originalWidth);
|
|
54
|
+
cropHeight = Math.min(cropHeight, originalHeight);
|
|
55
|
+
const centreX = clamp(focalX, 0, 100) / 100 * originalWidth;
|
|
56
|
+
const centreY = clamp(focalY, 0, 100) / 100 * originalHeight;
|
|
57
|
+
const x = clamp(Math.round(centreX - cropWidth / 2), 0, originalWidth - cropWidth);
|
|
58
|
+
const y = clamp(Math.round(centreY - cropHeight / 2), 0, originalHeight - cropHeight);
|
|
59
|
+
return {
|
|
60
|
+
height: cropHeight,
|
|
61
|
+
width: cropWidth,
|
|
62
|
+
x,
|
|
63
|
+
y
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const getCustomCropRect = (params)=>{
|
|
67
|
+
if (params.fit !== 'contain' && isPositiveNumber(params.width) && isPositiveNumber(params.height) && isPositiveNumber(params.originalWidth) && isPositiveNumber(params.originalHeight) && typeof params.focalX === 'number' && Number.isFinite(params.focalX) && typeof params.focalY === 'number' && Number.isFinite(params.focalY)) {
|
|
68
|
+
return computeFocalCropRect(params.originalWidth, params.originalHeight, params.width, params.height, params.focalX, params.focalY);
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
export const buildImageKitTransformation = (params)=>{
|
|
73
|
+
const transformParams = [];
|
|
74
|
+
if (params.width) {
|
|
75
|
+
transformParams.push(`w-${params.width}`);
|
|
76
|
+
}
|
|
77
|
+
if (params.height) {
|
|
78
|
+
transformParams.push(`h-${params.height}`);
|
|
79
|
+
}
|
|
80
|
+
if (params.fit === 'contain') {
|
|
81
|
+
transformParams.push('c-at_max');
|
|
82
|
+
}
|
|
83
|
+
if (params.dpr) {
|
|
84
|
+
transformParams.push(`dpr-${params.dpr}`);
|
|
85
|
+
}
|
|
86
|
+
if (params.format) {
|
|
87
|
+
transformParams.push(`f-${params.format}`);
|
|
88
|
+
}
|
|
89
|
+
if (params.vc) {
|
|
90
|
+
transformParams.push(`vc-${params.vc}`);
|
|
91
|
+
}
|
|
92
|
+
if (params.lossless) {
|
|
93
|
+
transformParams.push('lo-true', 'q-100');
|
|
94
|
+
} else if (params.quality) {
|
|
95
|
+
transformParams.push(`q-${params.quality}`);
|
|
96
|
+
}
|
|
97
|
+
const customCrop = getCustomCropRect(params);
|
|
98
|
+
if (customCrop) {
|
|
99
|
+
return [
|
|
100
|
+
`cm-extract,x-${customCrop.x},y-${customCrop.y},w-${customCrop.width},h-${customCrop.height}`,
|
|
101
|
+
transformParams.join(',')
|
|
102
|
+
].filter(Boolean).join(':');
|
|
103
|
+
}
|
|
104
|
+
return transformParams.join(',');
|
|
105
|
+
};
|
|
106
|
+
export const withImageKitTransformation = (src, params)=>{
|
|
107
|
+
const transformation = buildImageKitTransformation(params);
|
|
108
|
+
if (!transformation) {
|
|
109
|
+
return src;
|
|
110
|
+
}
|
|
111
|
+
return `${src}${src.includes('?') ? '&' : '?'}tr=${transformation}`;
|
|
112
|
+
};
|
|
113
|
+
export const srcParamsPayloadImageKit = withImageKitTransformation;
|
|
114
|
+
// Backward-compatible alias for the helper name used in older project templates.
|
|
115
|
+
export const srcParamsPayloadImagekit = srcParamsPayloadImageKit;
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=imageTransforms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/imageTransforms.ts"],"sourcesContent":["export type ImageTransformationOptions = {\n dpr?: number\n fit?: 'contain' | 'cover'\n /** Focal point X as a percentage (0-100) of the original image width. */\n focalX?: null | number\n /** Focal point Y as a percentage (0-100) of the original image height. */\n focalY?: null | number\n format?: string\n height?: number\n lossless?: boolean\n /** The original uploaded image height in pixels, required to compute a custom focal crop. */\n originalHeight?: null | number\n /** The original uploaded image width in pixels, required to compute a custom focal crop. */\n originalWidth?: null | number\n quality?: number\n vc?: string\n width?: number\n}\n\nexport type CropRect = {\n height: number\n width: number\n x: number\n y: number\n}\n\nconst hasImageKitVideoExtension = (pathname: string): boolean =>\n /\\.(?:mov|mp4)$/i.test(pathname.replace(/\\/$/, ''))\n\nexport const getImageKitVideoSrc = (src: string): string => {\n try {\n const url = new URL(src)\n\n if (hasImageKitVideoExtension(url.pathname)) {\n return src\n }\n\n url.pathname = `${url.pathname.replace(/\\/$/, '')}/ik-video.mp4`\n\n return url.toString()\n } catch {\n const hashIndex = src.indexOf('#')\n const beforeHash = hashIndex === -1 ? src : src.slice(0, hashIndex)\n const hash = hashIndex === -1 ? '' : src.slice(hashIndex)\n const queryIndex = beforeHash.indexOf('?')\n const pathname = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex)\n const search = queryIndex === -1 ? '' : beforeHash.slice(queryIndex)\n\n if (hasImageKitVideoExtension(pathname)) {\n return src\n }\n\n return `${pathname.replace(/\\/$/, '')}/ik-video.mp4${search}${hash}`\n }\n}\n\nexport const getImageKitThumbnailSrc = (src: string): string => {\n try {\n const url = new URL(src)\n url.pathname = `${url.pathname.replace(/\\/$/, '')}/ik-thumbnail.jpg`\n url.search = ''\n url.hash = ''\n\n return url.toString()\n } catch {\n const normalizedSrc = src.split('?')[0].split('#')[0].replace(/\\/$/, '')\n\n return `${normalizedSrc}/ik-thumbnail.jpg`\n }\n}\n\nexport const getVideoPosterSrc = (src: string): string =>\n getImageKitThumbnailSrc(getImageKitVideoSrc(src))\n\nconst isPositiveNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.min(Math.max(value, min), max)\n\n/**\n * Compute the largest target-aspect rectangle that fits inside the source image,\n * centred on a focal point and clamped so ImageKit's `cm-extract` never receives\n * an out-of-bounds crop.\n */\nexport const computeFocalCropRect = (\n originalWidth: number,\n originalHeight: number,\n targetWidth: number,\n targetHeight: number,\n focalX: number,\n focalY: number,\n): CropRect => {\n const targetAspect = targetWidth / targetHeight\n let cropWidth: number\n let cropHeight: number\n\n if (originalWidth / originalHeight > targetAspect) {\n cropHeight = originalHeight\n cropWidth = Math.round(originalHeight * targetAspect)\n } else {\n cropWidth = originalWidth\n cropHeight = Math.round(originalWidth / targetAspect)\n }\n\n cropWidth = Math.min(cropWidth, originalWidth)\n cropHeight = Math.min(cropHeight, originalHeight)\n\n const centreX = (clamp(focalX, 0, 100) / 100) * originalWidth\n const centreY = (clamp(focalY, 0, 100) / 100) * originalHeight\n const x = clamp(Math.round(centreX - cropWidth / 2), 0, originalWidth - cropWidth)\n const y = clamp(Math.round(centreY - cropHeight / 2), 0, originalHeight - cropHeight)\n\n return { height: cropHeight, width: cropWidth, x, y }\n}\n\nconst getCustomCropRect = (params: ImageTransformationOptions): CropRect | null => {\n if (\n params.fit !== 'contain' &&\n isPositiveNumber(params.width) &&\n isPositiveNumber(params.height) &&\n isPositiveNumber(params.originalWidth) &&\n isPositiveNumber(params.originalHeight) &&\n typeof params.focalX === 'number' &&\n Number.isFinite(params.focalX) &&\n typeof params.focalY === 'number' &&\n Number.isFinite(params.focalY)\n ) {\n return computeFocalCropRect(\n params.originalWidth,\n params.originalHeight,\n params.width,\n params.height,\n params.focalX,\n params.focalY,\n )\n }\n\n return null\n}\n\nexport const buildImageKitTransformation = (params: ImageTransformationOptions): string => {\n const transformParams: string[] = []\n\n if (params.width) {\n transformParams.push(`w-${params.width}`)\n }\n\n if (params.height) {\n transformParams.push(`h-${params.height}`)\n }\n\n if (params.fit === 'contain') {\n transformParams.push('c-at_max')\n }\n\n if (params.dpr) {\n transformParams.push(`dpr-${params.dpr}`)\n }\n\n if (params.format) {\n transformParams.push(`f-${params.format}`)\n }\n\n if (params.vc) {\n transformParams.push(`vc-${params.vc}`)\n }\n\n if (params.lossless) {\n transformParams.push('lo-true', 'q-100')\n } else if (params.quality) {\n transformParams.push(`q-${params.quality}`)\n }\n\n const customCrop = getCustomCropRect(params)\n\n if (customCrop) {\n return [\n `cm-extract,x-${customCrop.x},y-${customCrop.y},w-${customCrop.width},h-${customCrop.height}`,\n transformParams.join(','),\n ]\n .filter(Boolean)\n .join(':')\n }\n\n return transformParams.join(',')\n}\n\nexport const withImageKitTransformation = (\n src: string,\n params: ImageTransformationOptions,\n): string => {\n const transformation = buildImageKitTransformation(params)\n\n if (!transformation) {\n return src\n }\n\n return `${src}${src.includes('?') ? '&' : '?'}tr=${transformation}`\n}\n\nexport const srcParamsPayloadImageKit = withImageKitTransformation\n\n// Backward-compatible alias for the helper name used in older project templates.\nexport const srcParamsPayloadImagekit = srcParamsPayloadImageKit\n"],"names":["hasImageKitVideoExtension","pathname","test","replace","getImageKitVideoSrc","src","url","URL","toString","hashIndex","indexOf","beforeHash","slice","hash","queryIndex","search","getImageKitThumbnailSrc","normalizedSrc","split","getVideoPosterSrc","isPositiveNumber","value","Number","isFinite","clamp","min","max","Math","computeFocalCropRect","originalWidth","originalHeight","targetWidth","targetHeight","focalX","focalY","targetAspect","cropWidth","cropHeight","round","centreX","centreY","x","y","height","width","getCustomCropRect","params","fit","buildImageKitTransformation","transformParams","push","dpr","format","vc","lossless","quality","customCrop","join","filter","Boolean","withImageKitTransformation","transformation","includes","srcParamsPayloadImageKit","srcParamsPayloadImagekit"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Config, UploadCollectionSlug } from 'payload';
|
|
2
2
|
export type ImageKitCollectionOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Override Payload's native focal-point picker (`upload.focalPoint`) for this
|
|
5
|
+
* collection.
|
|
6
|
+
*/
|
|
7
|
+
focalPoint?: boolean;
|
|
3
8
|
/**
|
|
4
9
|
* Upload files in this collection as ImageKit private files (`isPrivateFile: true`)
|
|
5
10
|
* and deliver them only through short-lived signed URLs, behind Payload access control.
|
|
@@ -8,12 +13,20 @@ export type ImageKitCollectionOptions = {
|
|
|
8
13
|
};
|
|
9
14
|
type ImageKitCollectionConfig = ImageKitCollectionOptions | true;
|
|
10
15
|
type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>;
|
|
11
|
-
export type
|
|
16
|
+
export type PayloadStorageImageKitConfig = {
|
|
12
17
|
alwaysInsertFields?: boolean;
|
|
13
18
|
clientUploads?: boolean;
|
|
14
19
|
collections: ImageKitCollectionMap;
|
|
15
20
|
disablePayloadAccessControl?: true;
|
|
16
21
|
enabled?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Broad default for Payload's native focal-point picker (`upload.focalPoint`)
|
|
24
|
+
* on every configured collection. Focal points are disabled by default for
|
|
25
|
+
* configured collections, so prefer collection-level `focalPoint: true` for
|
|
26
|
+
* image collections. A collection's own `upload.focalPoint` still takes
|
|
27
|
+
* precedence.
|
|
28
|
+
*/
|
|
29
|
+
focalPoint?: boolean;
|
|
17
30
|
folder: string;
|
|
18
31
|
privateKey: string;
|
|
19
32
|
publicKey: string;
|
|
@@ -25,7 +38,9 @@ export type PayloadStorageImagekitConfig = {
|
|
|
25
38
|
signedUrlExpiresIn?: number;
|
|
26
39
|
urlEndpoint: string;
|
|
27
40
|
};
|
|
28
|
-
export declare const imagekitStorage: (pluginOptions:
|
|
41
|
+
export declare const imagekitStorage: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
|
|
29
42
|
export { migrateFolder } from './migrate.js';
|
|
30
43
|
export type { MigrateFolderOptions, MigrationJobResult } from './migrate.js';
|
|
31
|
-
export declare const payloadStorageImagekit: (pluginOptions:
|
|
44
|
+
export declare const payloadStorageImagekit: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
|
|
45
|
+
export declare const payloadStorageImageKit: (pluginOptions: PayloadStorageImageKitConfig) => (config: Config) => Config;
|
|
46
|
+
export type PayloadStorageImagekitConfig = PayloadStorageImageKitConfig;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,8 @@ import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js';
|
|
5
5
|
import { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js';
|
|
6
|
-
import { getAdminThumbnailURL, getPrivateAdminThumbnailURL,
|
|
6
|
+
import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isPdfMimeType, isVideoMimeType } from './getAdminThumbnailURL.js';
|
|
7
|
+
import { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js';
|
|
7
8
|
const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
|
|
8
9
|
const PRIVATE_COLLECTION_DESCRIPTION = 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.';
|
|
9
10
|
const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
|
|
@@ -51,6 +52,7 @@ const getStoredImageKitURL = (doc)=>{
|
|
|
51
52
|
const storedURL = doc?.imagekit?.url;
|
|
52
53
|
return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined;
|
|
53
54
|
};
|
|
55
|
+
const hasStoredImageKitURL = (doc)=>!!getStoredImageKitURL(doc);
|
|
54
56
|
const findUploadDocumentByFilename = async ({ collection, filename, prefix, req })=>{
|
|
55
57
|
const constraints = [
|
|
56
58
|
{
|
|
@@ -90,24 +92,19 @@ const applyClientUploadContextToData = (data, clientUploadContext, buildThumbnai
|
|
|
90
92
|
data.thumbnailURL = buildThumbnailURL(data);
|
|
91
93
|
return data;
|
|
92
94
|
};
|
|
95
|
+
const getClientUploadMimeType = (clientUploadContext)=>{
|
|
96
|
+
if (!isImageKitClientUploadContext(clientUploadContext)) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return typeof clientUploadContext.mimeType === 'string' && clientUploadContext.mimeType.trim().length > 0 ? clientUploadContext.mimeType : undefined;
|
|
100
|
+
};
|
|
93
101
|
const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFolder, signedUrlExpiresIn, urlEndpoint })=>{
|
|
94
102
|
const fetchUploadedFile = async (fileURL)=>{
|
|
95
103
|
const response = await fetch(fileURL);
|
|
96
104
|
if (!response.ok) {
|
|
97
105
|
throw new Error(`imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`);
|
|
98
106
|
}
|
|
99
|
-
|
|
100
|
-
const headers = new Headers();
|
|
101
|
-
const contentType = response.headers.get('Content-Type');
|
|
102
|
-
if (contentType) {
|
|
103
|
-
headers.set('Content-Type', contentType);
|
|
104
|
-
}
|
|
105
|
-
headers.set('Content-Length', String(fileBuffer.byteLength));
|
|
106
|
-
return new Response(fileBuffer, {
|
|
107
|
-
headers,
|
|
108
|
-
status: 200,
|
|
109
|
-
statusText: response.statusText
|
|
110
|
-
});
|
|
107
|
+
return response;
|
|
111
108
|
};
|
|
112
109
|
return ({ collection })=>{
|
|
113
110
|
const isPrivate = privateSlugs.has(collection.slug);
|
|
@@ -134,6 +131,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
134
131
|
name: 'imagekit',
|
|
135
132
|
type: 'group',
|
|
136
133
|
admin: {
|
|
134
|
+
condition: (data)=>hasStoredImageKitURL(data),
|
|
137
135
|
disableListColumn: true,
|
|
138
136
|
disableListFilter: true
|
|
139
137
|
},
|
|
@@ -150,13 +148,17 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
150
148
|
type: 'text',
|
|
151
149
|
admin: {
|
|
152
150
|
readOnly: true
|
|
153
|
-
}
|
|
151
|
+
},
|
|
152
|
+
label: 'URL'
|
|
154
153
|
},
|
|
155
154
|
{
|
|
156
155
|
name: 'isPrivate',
|
|
157
156
|
type: 'checkbox',
|
|
158
157
|
admin: {
|
|
159
|
-
|
|
158
|
+
condition: (_data, siblingData)=>isDocumentPrivate({
|
|
159
|
+
imagekit: siblingData
|
|
160
|
+
}, isPrivate),
|
|
161
|
+
description: "Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.",
|
|
160
162
|
readOnly: true
|
|
161
163
|
},
|
|
162
164
|
label: 'Private file'
|
|
@@ -200,7 +202,8 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
],
|
|
203
|
-
interfaceName: 'imagekitFields'
|
|
205
|
+
interfaceName: 'imagekitFields',
|
|
206
|
+
label: 'ImageKit'
|
|
204
207
|
}
|
|
205
208
|
],
|
|
206
209
|
generateURL: ({ filename })=>client.helper.buildSrc({
|
|
@@ -252,13 +255,23 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
252
255
|
const hasClientUploadContext = 'clientUploadContext' in args.params;
|
|
253
256
|
if (hasClientUploadContext) {
|
|
254
257
|
const clientUploadContext = args.params.clientUploadContext;
|
|
258
|
+
const clientUploadMimeType = getClientUploadMimeType(clientUploadContext);
|
|
259
|
+
if (clientUploadMimeType && isVideoMimeType(clientUploadMimeType)) {
|
|
260
|
+
const headers = new Headers();
|
|
261
|
+
headers.set('Content-Length', '0');
|
|
262
|
+
headers.set('Content-Type', clientUploadMimeType);
|
|
263
|
+
return new Response(null, {
|
|
264
|
+
headers,
|
|
265
|
+
status: 200
|
|
266
|
+
});
|
|
267
|
+
}
|
|
255
268
|
const clientUploadURL = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.url === 'string' && clientUploadContext.url.length > 0 ? clientUploadContext.url : null;
|
|
256
269
|
const filePath = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.filePath === 'string' && clientUploadContext.filePath.length > 0 ? clientUploadContext.filePath : getFilePath(rootFolder, args.params.collection, args.params.filename);
|
|
257
|
-
const
|
|
270
|
+
const imageKitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
|
|
258
271
|
src: filePath,
|
|
259
272
|
urlEndpoint
|
|
260
273
|
});
|
|
261
|
-
return fetchUploadedFile(
|
|
274
|
+
return fetchUploadedFile(imageKitURL);
|
|
262
275
|
}
|
|
263
276
|
const doc = args.doc || await findUploadDocumentByFilename({
|
|
264
277
|
collection: args.params.collection,
|
|
@@ -273,12 +286,14 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
273
286
|
let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename);
|
|
274
287
|
// A `tr` request for a video/PDF is a thumbnail request (the plain transform
|
|
275
288
|
// can't render a preview); serve ImageKit's generated thumbnail instead.
|
|
276
|
-
if (tr &&
|
|
277
|
-
signSrc =
|
|
289
|
+
if (tr && isVideoMimeType(mimeType)) {
|
|
290
|
+
signSrc = getVideoPosterSrc(signSrc);
|
|
291
|
+
} else if (tr && isPdfMimeType(mimeType)) {
|
|
292
|
+
signSrc = getImageKitThumbnailSrc(signSrc);
|
|
278
293
|
}
|
|
279
294
|
return Response.redirect(buildSignedURL(signSrc, tr), 302);
|
|
280
295
|
}
|
|
281
|
-
const
|
|
296
|
+
const imageKitURL = client.helper.buildSrc({
|
|
282
297
|
src: getStoredImageKitURL(doc) || getFilePath(rootFolder, args.params.collection, args.params.filename),
|
|
283
298
|
transformation: tr ? [
|
|
284
299
|
{
|
|
@@ -287,7 +302,7 @@ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFo
|
|
|
287
302
|
] : undefined,
|
|
288
303
|
urlEndpoint
|
|
289
304
|
});
|
|
290
|
-
return Response.redirect(
|
|
305
|
+
return Response.redirect(imageKitURL, 302);
|
|
291
306
|
}
|
|
292
307
|
};
|
|
293
308
|
};
|
|
@@ -305,7 +320,7 @@ const toCloudStorageCollections = (collections, adapter, privateSlugs, disablePa
|
|
|
305
320
|
return mappedCollections;
|
|
306
321
|
};
|
|
307
322
|
export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
308
|
-
const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
|
|
323
|
+
const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, focalPoint, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
|
|
309
324
|
if (!privateKey?.trim()) {
|
|
310
325
|
throw new Error('imagekitStorage: `privateKey` is required.');
|
|
311
326
|
}
|
|
@@ -325,12 +340,12 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
|
325
340
|
const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
|
|
326
341
|
const apiRoute = config.routes?.api ?? '/api';
|
|
327
342
|
const rootFolder = normalizeFolder(folder);
|
|
328
|
-
const
|
|
343
|
+
const imageKitClient = new ImageKit({
|
|
329
344
|
privateKey
|
|
330
345
|
});
|
|
331
346
|
const adapter = imagekitAdapter({
|
|
332
347
|
apiRoute,
|
|
333
|
-
client:
|
|
348
|
+
client: imageKitClient,
|
|
334
349
|
clientUploads,
|
|
335
350
|
privateSlugs,
|
|
336
351
|
rootFolder,
|
|
@@ -347,6 +362,8 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
|
347
362
|
const isCollectionPrivate = privateSlugs.has(collection.slug);
|
|
348
363
|
const buildThumbnailURL = (doc)=>isDocumentPrivate(doc, isCollectionPrivate) ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute) : getAdminThumbnailURL(doc);
|
|
349
364
|
const beforeDocumentControls = collection.admin?.components?.edit?.beforeDocumentControls || [];
|
|
365
|
+
const collectionOptions = collections[collection.slug];
|
|
366
|
+
const collectionFocalPoint = collectionOptions !== true && typeof collectionOptions === 'object' && typeof collectionOptions.focalPoint === 'boolean' ? collectionOptions.focalPoint : undefined;
|
|
350
367
|
// Surface a "this is a private collection" note in the list and edit views.
|
|
351
368
|
// Preserve an existing string description by appending; leave custom
|
|
352
369
|
// component descriptions untouched.
|
|
@@ -392,6 +409,17 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
|
392
409
|
]
|
|
393
410
|
},
|
|
394
411
|
upload: {
|
|
412
|
+
// Focal points are opt-in for configured collections. Plugin-level
|
|
413
|
+
// and collection-level options can override this default; a
|
|
414
|
+
// collection's own `upload.focalPoint` still wins because it is
|
|
415
|
+
// spread in afterwards.
|
|
416
|
+
focalPoint: false,
|
|
417
|
+
...typeof focalPoint === 'boolean' ? {
|
|
418
|
+
focalPoint
|
|
419
|
+
} : {},
|
|
420
|
+
...typeof collectionFocalPoint === 'boolean' ? {
|
|
421
|
+
focalPoint: collectionFocalPoint
|
|
422
|
+
} : {},
|
|
395
423
|
...typeof collection.upload === 'object' ? collection.upload : {},
|
|
396
424
|
adminThumbnail: ({ doc })=>buildThumbnailURL(doc),
|
|
397
425
|
crop: false
|
|
@@ -402,7 +430,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
|
402
430
|
...config.endpoints || [],
|
|
403
431
|
{
|
|
404
432
|
handler: createImageKitUploadAuthHandler({
|
|
405
|
-
client:
|
|
433
|
+
client: imageKitClient,
|
|
406
434
|
collections
|
|
407
435
|
}),
|
|
408
436
|
method: 'get',
|
|
@@ -419,5 +447,6 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
|
|
|
419
447
|
export { migrateFolder } from './migrate.js';
|
|
420
448
|
// Backward-compatible alias
|
|
421
449
|
export const payloadStorageImagekit = imagekitStorage;
|
|
450
|
+
export const payloadStorageImageKit = imagekitStorage;
|
|
422
451
|
|
|
423
452
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isVideoOrPdfMimeType,\n} from './getAdminThumbnailURL.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImagekitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImagekitReference\n\nexport type PayloadStorageImagekitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImagekitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n const fileBuffer = await response.arrayBuffer()\n const headers = new Headers()\n const contentType = response.headers.get('Content-Type')\n\n if (contentType) {\n headers.set('Content-Type', contentType)\n }\n\n headers.set('Content-Length', String(fileBuffer.byteLength))\n\n return new Response(fileBuffer, {\n headers,\n status: 200,\n statusText: response.statusText,\n })\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n description:\n 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImagekitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imagekitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imagekitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoOrPdfMimeType(mimeType)) {\n signSrc = `${signSrc}/ik-thumbnail.jpg`\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imagekitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imagekitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImagekitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imagekitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imagekitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imagekitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isVideoOrPdfMimeType","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","fileBuffer","arrayBuffer","headers","Headers","contentType","get","set","String","byteLength","Response","statusText","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","disableListColumn","disableListFilter","hidden","readOnly","description","label","condition","_data","siblingData","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","mimeType","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","clientUploadURL","imagekitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imagekitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit"],"mappings":"AAUA,OAAOA,YAAYC,MAAM,QAAQ,mBAAkB;AACnD,SAASC,kBAAkB,QAAQ,mCAAkC;AACrE,OAAOC,UAAU,YAAW;AAE5B,SAASC,yBAAyB,EAAEC,6BAA6B,QAAQ,qBAAoB;AAC7F,SAASC,+BAA+B,QAAQ,iDAAgD;AAChG,SACEC,oBAAoB,EACpBC,2BAA2B,EAC3BC,oBAAoB,QACf,4BAA2B;AAYlC,MAAMC,gCAAgC;AAEtC,MAAMC,iCACJ;AAEF,MAAMC,4BAA4B,CAACC,QACjCA,UAAU,QAAQ,OAAOA,UAAU,YAAYA,OAAOC,YAAY;AAEpE;;;;;CAKC,GACD,MAAMC,oBAAoB,CAACC,KAAcC;IACvC,MAAMC,SAAUF,KAA4DG,UAAUC;IAEtF,OAAO,OAAOF,WAAW,YAAYA,SAASD;AAChD;AAgCA,MAAMI,kBAAkB,CAACC;IACvB,MAAMC,aAAaD,OAAOE,IAAI,GAAGC,OAAO,CAAC,cAAc;IAEvD,IAAI,CAACF,YAAY;QACf,MAAM,IAAIG,MAAM;IAClB;IAEA,OAAO,CAAC,CAAC,EAAEH,YAAY;AACzB;AAEA,MAAMI,sBAAsB,CAACC,YAAoBC,iBAC/C1B,KAAK2B,KAAK,CAACC,IAAI,CAACH,YAAYC;AAE9B,MAAMG,cAAc,CAACJ,YAAoBC,gBAAwBI,WAC/D9B,KAAK2B,KAAK,CAACC,IAAI,CAACJ,oBAAoBC,YAAYC,iBAAiBI;AAEnE,MAAMC,qBAAqB,OACzBf,UACAgB;IAEA,MAAMC,aAAajC,KAAK2B,KAAK,CAACO,OAAO,CAACF;IACtC,MAAMG,OAAOnC,KAAK2B,KAAK,CAACS,QAAQ,CAACJ;IAEjC,MAAMK,QAAQ;IAEd,IAAK,IAAIC,OAAO,IAAKA,QAAQD,MAAO;QAClC,MAAME,SAAS,MAAMvB,SAASuB,MAAM,CAACC,IAAI,CAAC;YACxCC,MAAM;YACNJ;YACArC,MAAMiC,eAAe,MAAM,MAAM,GAAGA,WAAW,CAAC,CAAC;YACjDK;QACF;QAEA,MAAMI,QAAQH,OAAOI,IAAI,CACvB,CAACC,QAAU,YAAYA,SAASA,MAAMZ,QAAQ,KAAKA,YAAYY,MAAMT,IAAI,KAAKA;QAGhF,IAAIO,SAAS,YAAYA,OAAO;YAC9B,OAAOA,MAAMG,MAAM;QACrB;QAEA,IAAIN,OAAOO,MAAM,GAAGT,OAAO;YACzB;QACF;IACF;IAEA,OAAOU;AACT;AAEA,MAAMC,WAAW,CAACtC,QAChB,CAAC,CAACA,SAAS,OAAOA,UAAU;AAE9B,MAAMuC,uBAAuB,CAACpC;IAC5B,MAAMqC,YAAarC,KAAuCG,UAAUmC;IAEpE,OAAO,OAAOD,cAAc,YAAYA,UAAU7B,IAAI,GAAGyB,MAAM,GAAG,IAAII,YAAYH;AACpF;AAEA,MAAMK,+BAA+B,OAAO,EAC1CC,UAAU,EACVvB,QAAQ,EACRwB,MAAM,EACNC,GAAG,EAMJ;IACC,MAAMC,cAAuB;QAC3B;YACE1B,UAAU;gBACR2B,QAAQ3B;YACV;QACF;KACD;IAED,IAAI,OAAOwB,WAAW,UAAU;QAC9BE,YAAYE,IAAI,CAAC;YACfJ,QAAQ;gBACNG,QAAQH;YACV;QACF;IACF;IAEA,OAAOC,IAAII,OAAO,CAACC,EAAE,CAACC,OAAO,CAAC;QAC5BR;QACAE;QACAO,OAAO;YACLC,KAAKP;QACP;IACF;AACF;AAEA,MAAMQ,iCAAiC,CACrCC,MACAC,qBACAC,oBAAyC/D,oBAAoB,EAC7Da,YAAY,KAAK;IAEjB,IAAI,CAACf,8BAA8BgE,sBAAsB;QACvD,OAAOD;IACT;IAEAA,KAAKjD,QAAQ,GAAG;QACd6B,QAAQqB,oBAAoBrB,MAAM;QAClC,+EAA+E;QAC/E,wDAAwD;QACxD5B;QACAmD,cAAcF,oBAAoBE,YAAY;QAC9CjB,KAAKe,oBAAoBf,GAAG;IAC9B;IAEA,sFAAsF;IACtFc,KAAKI,YAAY,GAAGF,kBAAkBF;IAEtC,OAAOA;AACT;AAEA,MAAMK,kBAAkB,CAAC,EACvBC,QAAQ,EACRC,MAAM,EACNC,aAAa,EACbC,YAAY,EACZjD,UAAU,EACVkD,kBAAkB,EAClBC,WAAW,EASZ;IACC,MAAMC,oBAAoB,OAAOC;QAC/B,MAAMC,WAAW,MAAMC,MAAMF;QAE7B,IAAI,CAACC,SAASE,EAAE,EAAE;YAChB,MAAM,IAAI1D,MACR,CAAC,8DAA8D,EAAEwD,SAASG,MAAM,CAAC,EAAE,CAAC;QAExF;QAEA,MAAMC,aAAa,MAAMJ,SAASK,WAAW;QAC7C,MAAMC,UAAU,IAAIC;QACpB,MAAMC,cAAcR,SAASM,OAAO,CAACG,GAAG,CAAC;QAEzC,IAAID,aAAa;YACfF,QAAQI,GAAG,CAAC,gBAAgBF;QAC9B;QAEAF,QAAQI,GAAG,CAAC,kBAAkBC,OAAOP,WAAWQ,UAAU;QAE1D,OAAO,IAAIC,SAAST,YAAY;YAC9BE;YACAH,QAAQ;YACRW,YAAYd,SAASc,UAAU;QACjC;IACF;IAEA,OAAO,CAAC,EAAExC,UAAU,EAAE;QACpB,MAAMpC,YAAYyD,aAAaoB,GAAG,CAACzC,WAAW0C,IAAI;QAElD,mFAAmF;QACnF,gFAAgF;QAChF,6EAA6E;QAC7E,MAAMC,iBAAiB,CAACC,KAAaC,KACnC1B,OAAO2B,MAAM,CAACC,QAAQ,CAAC;gBACrBC,WAAW1B;gBACX2B,QAAQ;gBACRL;gBACAM,gBAAgBL,KAAK;oBAAC;wBAAEM,KAAKN;oBAAG;iBAAE,GAAGnD;gBACrC6B;YACF;QAEF,MAAMT,oBAAoB,CAACtD,MACzBD,kBAAkBC,KAAKI,aACnBZ,4BAA4BQ,KAAKwC,WAAW0C,IAAI,EAAExB,YAClDnE,qBAAqBS;QAE3B,OAAO;YACLsB,MAAM;YACNsC;YACAgC,QAAQ;gBACN;oBACEtE,MAAM;oBACNM,MAAM;oBACNiE,OAAO;wBACLC,mBAAmB;wBACnBC,mBAAmB;oBACrB;oBACAH,QAAQ;wBACN;4BACEtE,MAAM;4BACNM,MAAM;4BACNiE,OAAO;gCACLG,QAAQ;4BACV;wBACF;wBACA;4BACE1E,MAAM;4BACNM,MAAM;4BACNiE,OAAO;gCACLI,UAAU;4BACZ;wBACF;wBACA;4BACE3E,MAAM;4BACNM,MAAM;4BACNiE,OAAO;gCACLK,aACE;gCACFD,UAAU;4BACZ;4BACAE,OAAO;wBACT;wBACA;4BACE7E,MAAM;4BACNM,MAAM;4BACNiE,OAAO;gCACL,mFAAmF;gCACnFO,WAAW,CAACC,OAAOC,cACjBvG,kBAAkB;wCAAEI,UAAUmG;oCAAY,GAAGlG;gCAC/C8F,aAAa,CAAC,kFAAkF,EAAEpC,mBAAmB,kKAAkK,CAAC;gCACxRmC,UAAU;4BACZ;4BACAM,OAAO;gCACL,mFAAmF;gCACnFC,WAAW;oCACT,CAAC,EAAEpD,IAAI,EAAEkD,WAAW,EAAE;wCACpB,IAAI,CAACvG,kBAAkB;4CAAEI,UAAUmG;wCAAY,GAAGlG,YAAY;4CAC5D,OAAO8B;wCACT;wCAEA,MAAMjB,WAAYmC,MAA4CnC;wCAE9D,IAAI,CAACA,UAAU;4CACb,OAAOiB;wCACT;wCAEA,OAAOiD,eAAenE,YAAYJ,YAAY4B,WAAW0C,IAAI,EAAEjE;oCACjE;iCACD;4BACH;4BACAkF,OAAO;4BACPM,SAAS;wBACX;wBACA;4BACEnF,MAAM;4BACNM,MAAM;4BACNiE,OAAO;gCACLG,QAAQ;4BACV;wBACF;qBACD;oBACDU,eAAe;gBACjB;aACD;YACDC,aAAa,CAAC,EAAE1F,QAAQ,EAAE,GACxB0C,OAAO2B,MAAM,CAACC,QAAQ,CAAC;oBACrBH,KAAKpE,YAAYJ,YAAY4B,WAAW0C,IAAI,EAAEjE;oBAC9C8C;gBACF;YACF6C,cAAc,OAAO,EAAE5G,GAAG,EAAEiB,QAAQ,EAAE;gBACpC,MAAM4F,eAAe,AAAC7G,IAAuCG,QAAQ,EAAE6B;gBACvE,MAAMA,SACJ6E,gBACC,MAAM3F,mBAAmByC,QAAQ3C,YAAYJ,YAAY4B,WAAW0C,IAAI,EAAEjE;gBAE7E,IAAI,CAACe,QAAQ;oBACX;gBACF;gBAEA,MAAM2B,OAAOmD,KAAK,CAACC,MAAM,CAAC/E;YAC5B;YACAgF,cAAc,OAAO,EAAE3D,mBAAmB,EAAED,IAAI,EAAE6D,IAAI,EAAE;gBACtD,IAAI5H,8BAA8BgE,sBAAsB;oBACtD,OAAOF,+BACLC,MACAC,qBACAC,mBACAlD;gBAEJ;gBAEA,MAAM8G,aAAa,MAAMjI,OAAOgI,KAAKE,MAAM,EAAEF,KAAKhG,QAAQ,EAAE;oBAAEW,MAAMqF,KAAKG,QAAQ;gBAAC;gBAElF,MAAMC,WAAW,MAAM1D,OAAOmD,KAAK,CAACQ,MAAM,CAAC;oBACzCL,MAAMC;oBACNK,UAAUN,KAAKhG,QAAQ;oBACvBX,QAAQK,oBAAoBC,YAAY4B,WAAW0C,IAAI;oBACvDsC,mBAAmB;oBACnB,GAAIpH,YAAY;wBAAEqH,eAAe;oBAAK,IAAI,CAAC,CAAC;gBAC9C;gBAEArE,KAAKjD,QAAQ,GAAG;oBACd6B,QAAQqF,SAASrF,MAAM;oBACvB5B;oBACAmD,cAAc8D,SAAS9D,YAAY;oBACnCjB,KAAK+E,SAAS/E,GAAG;gBACnB;gBAEA,0EAA0E;gBAC1E,iFAAiF;gBACjF,sEAAsE;gBACtEc,KAAKI,YAAY,GAAGF,kBAAkB;oBACpC,GAAGF,IAAI;oBACPnC,UAAU,AAACmC,KAA+BnC,QAAQ,IAAIgG,KAAKhG,QAAQ;gBACrE;gBAEA,OAAOmC;YACT;YACAsE,eAAe,OAAOhF,KAAKiF;gBACzB,MAAMC,aAAa,IAAIC,IAAInF,IAAIJ,GAAG,IAAI;gBACtC,MAAM+C,KAAKuC,WAAWE,YAAY,CAACnD,GAAG,CAAC;gBACvC,MAAMoD,yBAAyB,yBAAyBJ,KAAKK,MAAM;gBAEnE,IAAID,wBAAwB;oBAC1B,MAAM1E,sBAAsBsE,KAAKK,MAAM,CAAC3E,mBAAmB;oBAC3D,MAAM4E,kBACJ5I,8BAA8BgE,wBAC9B,OAAOA,oBAAoBf,GAAG,KAAK,YACnCe,oBAAoBf,GAAG,CAACL,MAAM,GAAG,IAC7BoB,oBAAoBf,GAAG,GACvB;oBACN,MAAMnB,WACJ9B,8BAA8BgE,wBAC9B,OAAOA,oBAAoBlC,QAAQ,KAAK,YACxCkC,oBAAoBlC,QAAQ,CAACc,MAAM,GAAG,IAClCoB,oBAAoBlC,QAAQ,GAC5BH,YAAYJ,YAAY+G,KAAKK,MAAM,CAACxF,UAAU,EAAEmF,KAAKK,MAAM,CAAC/G,QAAQ;oBAC1E,MAAMiH,cAAc9H,YAChB+E,eAAehE,UAAUkE,MACzB4C,mBACAtE,OAAO2B,MAAM,CAACC,QAAQ,CAAC;wBACrBH,KAAKjE;wBACL4C;oBACF;oBAEJ,OAAOC,kBAAkBkE;gBAC3B;gBAEA,MAAMlI,MACJ2H,KAAK3H,GAAG,IACP,MAAMuC,6BAA6B;oBAClCC,YAAYmF,KAAKK,MAAM,CAACxF,UAAU;oBAClCvB,UAAU0G,KAAKK,MAAM,CAAC/G,QAAQ;oBAC9BwB,QAAQkF,KAAKK,MAAM,CAACvF,MAAM;oBAC1BC;gBACF;gBAEF,2EAA2E;gBAC3E,+EAA+E;gBAC/E,IAAI3C,kBAAkBC,KAAKI,YAAY;oBACrC,MAAMgH,WAAYpH,KAAsCoH;oBACxD,IAAIe,UAAUnH,YAAYJ,YAAY+G,KAAKK,MAAM,CAACxF,UAAU,EAAEmF,KAAKK,MAAM,CAAC/G,QAAQ;oBAElF,6EAA6E;oBAC7E,yEAAyE;oBACzE,IAAIoE,MAAM5F,qBAAqB2H,WAAW;wBACxCe,UAAU,GAAGA,QAAQ,iBAAiB,CAAC;oBACzC;oBAEA,OAAOpD,SAASqD,QAAQ,CAACjD,eAAegD,SAAS9C,KAAK;gBACxD;gBAEA,MAAM6C,cAAcvE,OAAO2B,MAAM,CAACC,QAAQ,CAAC;oBACzCH,KACEhD,qBAAqBpC,QACrBgB,YAAYJ,YAAY+G,KAAKK,MAAM,CAACxF,UAAU,EAAEmF,KAAKK,MAAM,CAAC/G,QAAQ;oBACtEyE,gBAAgBL,KAAK;wBAAC;4BAAEM,KAAKN;wBAAG;qBAAE,GAAGnD;oBACrC6B;gBACF;gBAEA,OAAOgB,SAASqD,QAAQ,CAACF,aAAa;YACxC;QACF;IACF;AACF;AAEA,MAAMG,4BAA4B,CAChCC,aACAC,SACA1E,cACA2E;IAEA,MAAMC,oBAAwE,CAAC;IAE/E,KAAK,MAAM5H,kBAAkB6H,OAAOC,IAAI,CAACL,aAAc;QACrDG,iBAAiB,CAAC5H,eAAe,GAAG;YAClC0H;YACAK,qBAAqB;YACrB,oFAAoF;YACpFJ,6BAA6B3E,aAAaoB,GAAG,CAACpE,kBAC1CqB,YACAsG;QACN;IACF;IAEA,OAAOC;AACT;AAEA,OAAO,MAAMI,kBACX,CAACC,gBACD,CAACC;QACC,MAAM,EACJC,kBAAkB,EAClBpF,aAAa,EACb0E,WAAW,EACXE,2BAA2B,EAC3BS,OAAO,EACP3I,MAAM,EACN4I,UAAU,EACVC,SAAS,EACTrF,oBAAoBsF,wBAAwB,EAC5CrF,WAAW,EACZ,GAAG+E;QAEJ,IAAI,CAACI,YAAY1I,QAAQ;YACvB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAACyI,WAAW3I,QAAQ;YACtB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAACqD,aAAavD,QAAQ;YACxB,MAAM,IAAIE,MAAM;QAClB;QAEA,IAAI,CAAC4H,eAAeI,OAAOC,IAAI,CAACL,aAAarG,MAAM,KAAK,GAAG;YACzD,MAAM,IAAIvB,MAAM;QAClB;QAEA,MAAMmD,eAAe,IAAIwF,IACvBX,OAAOY,OAAO,CAAChB,aACZiB,MAAM,CAAC,CAAC,GAAG1J,MAAM,GAAKD,0BAA0BC,QAChD2J,GAAG,CAAC,CAAC,CAACtE,KAAK,GAAKA;QAGrB,IAAIsD,+BAA+B3E,aAAa4F,IAAI,GAAG,GAAG;YACxD,MAAM,IAAI/I,MACR;QAEJ;QAEA,MAAMoD,qBACJ,OAAOsF,6BAA6B,YAAYA,2BAA2B,IACvEA,2BACA1J;QACN,MAAMgE,WAAWqF,OAAOW,MAAM,EAAEC,OAAO;QAEvC,MAAM/I,aAAaP,gBAAgBC;QAEnC,MAAMsJ,iBAAiB,IAAI5K,SAAS;YAAEkK;QAAW;QACjD,MAAMX,UAAU9E,gBAAgB;YAC9BC;YACAC,QAAQiG;YACRhG;YACAC;YACAjD;YACAkD;YACAC;QACF;QAEA,MAAM8F,kBAAkB,IAAIR,IAAIX,OAAOC,IAAI,CAACL;QAC5C,MAAMwB,yBAAiC;YACrC,GAAGf,MAAM;YACTT,aAAa,AAACS,CAAAA,OAAOT,WAAW,IAAI,EAAE,AAAD,EAAGkB,GAAG,CAAC,CAAChH;gBAC3C,IAAI,CAACqH,gBAAgB5E,GAAG,CAACzC,WAAW0C,IAAI,GAAG;oBACzC,OAAO1C;gBACT;gBAEA,MAAMuH,sBAAsBlG,aAAaoB,GAAG,CAACzC,WAAW0C,IAAI;gBAC5D,MAAM5B,oBAAoB,CAACtD,MACzBD,kBAAkBC,KAAK+J,uBACnBvK,4BAA4BQ,KAAKwC,WAAW0C,IAAI,EAAExB,YAClDnE,qBAAqBS;gBAE3B,MAAMgK,yBACJxH,WAAWqD,KAAK,EAAEoE,YAAYC,MAAMF,0BAA0B,EAAE;gBAElE,4EAA4E;gBAC5E,qEAAqE;gBACrE,oCAAoC;gBACpC,MAAMG,sBAAsB3H,WAAWqD,KAAK,EAAEK;gBAC9C,MAAMkE,YAAYL,sBACd;oBACE,GAAIvH,WAAWqD,KAAK,IAAI,CAAC,CAAC;oBAC1BK,aACE,OAAOiE,wBAAwB,YAAYA,oBAAoB3J,IAAI,GAAGyB,MAAM,GAAG,IAC3E,GAAGkI,oBAAoB,CAAC,EAAExK,gCAAgC,GACzDwK,uBAAuBxK;gBAChC,IACA6C,WAAWqD,KAAK;gBAEpB,OAAO;oBACL,GAAGrD,UAAU;oBACbqD,OAAOjC,gBACH;wBACE,GAAIwG,aAAa,CAAC,CAAC;wBACnBH,YAAY;4BACV,GAAIzH,WAAWqD,KAAK,EAAEoE,cAAc,CAAC,CAAC;4BACtCC,MAAM;gCACJ,GAAI1H,WAAWqD,KAAK,EAAEoE,YAAYC,QAAQ,CAAC,CAAC;gCAC5CF,wBAAwB;uCACnBA;oCACH;wCACEK,aAAa;4CACXC,UAAUlL,0BAA0BoD,WAAW0C,IAAI;4CACnDrE,gBAAgB2B,WAAW0C,IAAI;4CAC/B5E,QAAQK,oBAAoBC,YAAY4B,WAAW0C,IAAI;4CACvD9E,WAAW2J;4CACXZ;wCACF;wCACAhK,MAAM;oCACR;iCACD;4BACH;wBACF;oBACF,IACAiL;oBACJ7D,OAAO;wBACL,GAAI/D,WAAW+D,KAAK,IAAI,CAAC,CAAC;wBAC1BgE,cAAc;+BACR/H,WAAW+D,KAAK,EAAEgE,gBAAgB,EAAE;4BACxC,CAAC,EAAEnH,IAAI,EAAEV,GAAG,EAAE;gCACZ,IAAI,CAACP,SAASiB,SAAS,CAACV,IAAIuE,IAAI,EAAE5D,qBAAqB;oCACrD,OAAOD;gCACT;gCAEA,OAAOD,+BACLC,MACAV,IAAIuE,IAAI,CAAC5D,mBAAmB,EAC5BC,mBACAyG;4BAEJ;yBACD;oBACH;oBACAzC,QAAQ;wBACN,GAAI,OAAO9E,WAAW8E,MAAM,KAAK,WAAW9E,WAAW8E,MAAM,GAAG,CAAC,CAAC;wBAClEkD,gBAAgB,CAAC,EAAExK,GAAG,EAAE,GAAKsD,kBAAkBtD;wBAC/CyK,MAAM;oBACR;gBACF;YACF;YACAC,WAAW9G,gBACP;mBACMmF,OAAO2B,SAAS,IAAI,EAAE;gBAC1B;oBACEC,SAASrL,gCAAgC;wBACvCqE,QAAQiG;wBACRtB;oBACF;oBACAsC,QAAQ;oBACRzL,MAAM,GAAGC,0BAA0B,gBAAgB;gBACrD;aACD,GACD2J,OAAO2B,SAAS;QACtB;QAEA,OAAOxL,mBAAmB;YACxB8J;YACAV,aAAaD,0BACXC,aACAC,SACA1E,cACA2E;YAEFS;QACF,GAAGa;IACL,EAAC;AAEH,SAASe,aAAa,QAAQ,eAAc;AAG5C,4BAA4B;AAC5B,OAAO,MAAMC,yBAAyBjC,gBAAe"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isPdfMimeType,\n isVideoMimeType,\n} from './getAdminThumbnailURL.js'\nimport { getImageKitThumbnailSrc, getVideoPosterSrc } from './imageTransforms.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Override Payload's native focal-point picker (`upload.focalPoint`) for this\n * collection.\n */\n focalPoint?: boolean\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImageKitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImageKitReference\n\nexport type PayloadStorageImageKitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n /**\n * Broad default for Payload's native focal-point picker (`upload.focalPoint`)\n * on every configured collection. Focal points are disabled by default for\n * configured collections, so prefer collection-level `focalPoint: true` for\n * image collections. A collection's own `upload.focalPoint` still takes\n * precedence.\n */\n focalPoint?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImageKitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst hasStoredImageKitURL = (doc: unknown): boolean => !!getStoredImageKitURL(doc)\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst getClientUploadMimeType = (clientUploadContext: unknown): string | undefined => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return undefined\n }\n\n return typeof clientUploadContext.mimeType === 'string' &&\n clientUploadContext.mimeType.trim().length > 0\n ? clientUploadContext.mimeType\n : undefined\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n return response\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n condition: (data) => hasStoredImageKitURL(data),\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n label: 'URL',\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description:\n \"Indicates whether this file was uploaded to ImageKit as private. Private files are delivered through signed URLs. Changes to this collection's private option in payload.config only affect future uploads.\",\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n label: 'ImageKit',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImageKitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadMimeType = getClientUploadMimeType(clientUploadContext)\n\n if (clientUploadMimeType && isVideoMimeType(clientUploadMimeType)) {\n const headers = new Headers()\n\n headers.set('Content-Length', '0')\n headers.set('Content-Type', clientUploadMimeType)\n\n return new Response(null, {\n headers,\n status: 200,\n })\n }\n\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imageKitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imageKitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoMimeType(mimeType)) {\n signSrc = getVideoPosterSrc(signSrc)\n } else if (tr && isPdfMimeType(mimeType)) {\n signSrc = getImageKitThumbnailSrc(signSrc)\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imageKitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imageKitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImageKitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n focalPoint,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imageKitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imageKitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n const collectionOptions = collections[collection.slug]\n const collectionFocalPoint =\n collectionOptions !== true &&\n typeof collectionOptions === 'object' &&\n typeof collectionOptions.focalPoint === 'boolean'\n ? collectionOptions.focalPoint\n : undefined\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n // Focal points are opt-in for configured collections. Plugin-level\n // and collection-level options can override this default; a\n // collection's own `upload.focalPoint` still wins because it is\n // spread in afterwards.\n focalPoint: false,\n ...(typeof focalPoint === 'boolean' ? { focalPoint } : {}),\n ...(typeof collectionFocalPoint === 'boolean'\n ? { focalPoint: collectionFocalPoint }\n : {}),\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imageKitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\nexport const payloadStorageImageKit = imagekitStorage\n\nexport type PayloadStorageImagekitConfig = PayloadStorageImageKitConfig\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isPdfMimeType","isVideoMimeType","getImageKitThumbnailSrc","getVideoPosterSrc","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","hasStoredImageKitURL","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","getClientUploadMimeType","mimeType","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","condition","disableListColumn","disableListFilter","hidden","readOnly","label","_data","siblingData","description","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","get","hasClientUploadContext","params","clientUploadMimeType","headers","Headers","set","Response","clientUploadURL","imageKitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","focalPoint","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imageKitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","collectionOptions","collectionFocalPoint","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit","payloadStorageImageKit"],"mappings":"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"}
|
package/dist/migrate.js
CHANGED
|
@@ -83,7 +83,7 @@ const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUr
|
|
|
83
83
|
url: uploaded.url
|
|
84
84
|
};
|
|
85
85
|
};
|
|
86
|
-
const
|
|
86
|
+
const getSourceImageKitFields = async ({ client, fileId })=>{
|
|
87
87
|
const file = await client.files.get(fileId);
|
|
88
88
|
if (!file.fileId || !file.filePath || !file.url) {
|
|
89
89
|
throw new Error(`migrateFolder: missing ImageKit source file details for "${fileId}".`);
|
|
@@ -95,7 +95,7 @@ const getSourceImagekitFields = async ({ client, fileId })=>{
|
|
|
95
95
|
url: file.url
|
|
96
96
|
};
|
|
97
97
|
};
|
|
98
|
-
const
|
|
98
|
+
const getUploadedImageKitFields = async ({ client, fileId })=>{
|
|
99
99
|
const file = await client.files.get(fileId);
|
|
100
100
|
if (!file.fileId || !file.url) {
|
|
101
101
|
throw new Error(`migrateFolder: missing ImageKit file details for "${fileId}".`);
|
|
@@ -179,20 +179,20 @@ export const migrateFolder = async (options)=>{
|
|
|
179
179
|
for(let i = 0; i < found.docs.length; i++){
|
|
180
180
|
const doc = found.docs[i];
|
|
181
181
|
const filename = doc.filename;
|
|
182
|
-
const
|
|
183
|
-
const displayName = filename ||
|
|
182
|
+
const existingImageKit = doc.imagekit;
|
|
183
|
+
const displayName = filename || existingImageKit?.fileId || doc.id;
|
|
184
184
|
console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`);
|
|
185
185
|
// Skip items that don't have an ImageKit file ID to migrate from.
|
|
186
|
-
if (!
|
|
186
|
+
if (!existingImageKit?.fileId) {
|
|
187
187
|
skippedCount += 1;
|
|
188
188
|
console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`);
|
|
189
189
|
continue;
|
|
190
190
|
}
|
|
191
191
|
try {
|
|
192
192
|
const destinationFolder = getCollectionFolder(toFolder, slug);
|
|
193
|
-
const source = await
|
|
193
|
+
const source = await getSourceImageKitFields({
|
|
194
194
|
client,
|
|
195
|
-
fileId:
|
|
195
|
+
fileId: existingImageKit.fileId
|
|
196
196
|
});
|
|
197
197
|
if (isFileInFolder(source.filePath, destinationFolder)) {
|
|
198
198
|
skippedCount += 1;
|
|
@@ -220,7 +220,7 @@ export const migrateFolder = async (options)=>{
|
|
|
220
220
|
if (!copiedFileId) {
|
|
221
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
222
|
}
|
|
223
|
-
uploaded = await
|
|
223
|
+
uploaded = await getUploadedImageKitFields({
|
|
224
224
|
client,
|
|
225
225
|
fileId: copiedFileId
|
|
226
226
|
});
|
package/dist/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` fields in the\n * database will be updated to reflect the new ImageKit asset after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign\n * private source URLs when the migrator falls back to re-uploading from the source URL.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst isFileInFolder = (filePath: string, folderPath: string): boolean =>\n filePath === folderPath || filePath.startsWith(`${folderPath}/`)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getSourceImagekitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{\n fileId: string\n fileName: string\n filePath: string\n url: string\n}> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit source file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n fileName: file.name || path.posix.basename(file.filePath),\n filePath: file.filePath,\n url: file.url,\n }\n}\n\nconst getUploadedImagekitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImagekit = (doc as any).imagekit\n const displayName = filename || existingImagekit?.fileId || doc.id\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have an ImageKit file ID to migrate from.\n if (!existingImagekit?.fileId) {\n skippedCount += 1\n console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`)\n continue\n }\n\n try {\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const source = await getSourceImagekitFields({\n client,\n fileId: existingImagekit.fileId,\n })\n\n if (isFileInFolder(source.filePath, destinationFolder)) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`,\n )\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n const destinationFilePath = path.posix.join(destinationFolder, source.fileName)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath: source.filePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `migrateFolder: copy request completed for ImageKit file ID \"${source.fileId}\" from \"${source.filePath}\", but the destination asset at \"${destinationFilePath}\" was not discoverable via ImageKit after retrying.`,\n )\n }\n\n uploaded = await getUploadedImagekitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for \"${source.fileName}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: source.url,\n urlEndpoint,\n })\n : source.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: source.fileName,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`)\n }\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${displayName}\":`,\n error,\n )\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","isFileInFolder","filePath","folderPath","startsWith","ensureFolderExists","client","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getSourceImagekitFields","get","getUploadedImagekitFields","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","filename","existingImagekit","displayName","id","destinationFolder","source","destinationFilePath","copyResponse","copy","destinationPath","sourceFilePath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","update","data","push","jobId","status"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` fields in the\n * database will be updated to reflect the new ImageKit asset after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign\n * private source URLs when the migrator falls back to re-uploading from the source URL.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst isFileInFolder = (filePath: string, folderPath: string): boolean =>\n filePath === folderPath || filePath.startsWith(`${folderPath}/`)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getSourceImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{\n fileId: string\n fileName: string\n filePath: string\n url: string\n}> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit source file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n fileName: file.name || path.posix.basename(file.filePath),\n filePath: file.filePath,\n url: file.url,\n }\n}\n\nconst getUploadedImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImageKit = (doc as any).imagekit\n const displayName = filename || existingImageKit?.fileId || doc.id\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have an ImageKit file ID to migrate from.\n if (!existingImageKit?.fileId) {\n skippedCount += 1\n console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`)\n continue\n }\n\n try {\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const source = await getSourceImageKitFields({\n client,\n fileId: existingImageKit.fileId,\n })\n\n if (isFileInFolder(source.filePath, destinationFolder)) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`,\n )\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n const destinationFilePath = path.posix.join(destinationFolder, source.fileName)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath: source.filePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `migrateFolder: copy request completed for ImageKit file ID \"${source.fileId}\" from \"${source.filePath}\", but the destination asset at \"${destinationFilePath}\" was not discoverable via ImageKit after retrying.`,\n )\n }\n\n uploaded = await getUploadedImageKitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for \"${source.fileName}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: source.url,\n urlEndpoint,\n })\n : source.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: source.fileName,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`)\n }\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${displayName}\":`,\n error,\n )\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","isFileInFolder","filePath","folderPath","startsWith","ensureFolderExists","client","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getSourceImageKitFields","get","getUploadedImageKitFields","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","filename","existingImageKit","displayName","id","destinationFolder","source","destinationFilePath","copyResponse","copy","destinationPath","sourceFilePath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","update","data","push","jobId","status"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humaan/payload-storage-imagekit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "ImageKit storage adapter plugin for Payload CMS uploads",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"types": "./dist/exports/client.d.ts",
|
|
20
20
|
"default": "./dist/exports/client.js"
|
|
21
21
|
},
|
|
22
|
+
"./image": {
|
|
23
|
+
"import": "./dist/imageTransforms.js",
|
|
24
|
+
"types": "./dist/imageTransforms.d.ts",
|
|
25
|
+
"default": "./dist/imageTransforms.js"
|
|
26
|
+
},
|
|
22
27
|
"./rsc": {
|
|
23
28
|
"import": "./dist/exports/rsc.js",
|
|
24
29
|
"types": "./dist/exports/rsc.d.ts",
|