@humaan/payload-storage-imagekit 2.8.1 → 2.9.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 +65 -33
- package/dist/clientUploadVerification.js +3 -1
- package/dist/clientUploadVerification.js.map +1 -1
- package/dist/endpoints/createImageKitUploadAuthHandler.d.ts +10 -0
- package/dist/endpoints/createImageKitUploadAuthHandler.js +36 -1
- package/dist/endpoints/createImageKitUploadAuthHandler.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +30 -45
- package/dist/migrate.js +220 -241
- package/dist/migrate.js.map +1 -1
- package/package.json +16 -16
package/dist/migrate.js
CHANGED
|
@@ -1,283 +1,262 @@
|
|
|
1
1
|
/* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */ import ImageKit from '@imagekit/nodejs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!normalized) {
|
|
8
|
-
throw new Error('migrateFolder: folder path cannot be empty.');
|
|
4
|
+
import { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js';
|
|
5
|
+
const normalizeLocation = (location, label)=>{
|
|
6
|
+
if (!location?.privateKey?.trim()) {
|
|
7
|
+
throw new Error(`migrateFolder: \`${label}.privateKey\` is required.`);
|
|
9
8
|
}
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
const getCollectionFolder = (rootFolder, collectionSlug)=>path.posix.join(rootFolder, collectionSlug);
|
|
13
|
-
const isFileInFolder = (filePath, folderPath)=>filePath === folderPath || filePath.startsWith(`${folderPath}/`);
|
|
14
|
-
const ensureFolderExists = async (client, folderPath)=>{
|
|
15
|
-
const normalized = normalizeFolder(folderPath);
|
|
16
|
-
const segments = normalized.split('/').filter(Boolean);
|
|
17
|
-
let parentFolderPath = '/';
|
|
18
|
-
for (const folderName of segments){
|
|
19
|
-
try {
|
|
20
|
-
await client.folders.create({
|
|
21
|
-
folderName,
|
|
22
|
-
parentFolderPath
|
|
23
|
-
});
|
|
24
|
-
} catch (error) {
|
|
25
|
-
const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : '';
|
|
26
|
-
if (!/already exists/i.test(message)) {
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
parentFolderPath = path.posix.join(parentFolderPath, folderName);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const findImageKitFileId = async (imagekit, filePath, options)=>{
|
|
34
|
-
const parentPath = path.posix.dirname(filePath);
|
|
35
|
-
const name = path.posix.basename(filePath);
|
|
36
|
-
const retries = options?.retries ?? 0;
|
|
37
|
-
const retryDelayMs = options?.retryDelayMs ?? 250;
|
|
38
|
-
const limit = 100;
|
|
39
|
-
for(let attempt = 0; attempt <= retries; attempt++){
|
|
40
|
-
for(let skip = 0;; skip += limit){
|
|
41
|
-
const assets = await imagekit.assets.list({
|
|
42
|
-
type: 'file',
|
|
43
|
-
limit,
|
|
44
|
-
path: parentPath === '/' ? '/' : `${parentPath}/`,
|
|
45
|
-
skip
|
|
46
|
-
});
|
|
47
|
-
const found = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath && asset.name === name);
|
|
48
|
-
if (found && 'fileId' in found) {
|
|
49
|
-
return found.fileId;
|
|
50
|
-
}
|
|
51
|
-
if (assets.length < limit) {
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (attempt < retries) {
|
|
56
|
-
await new Promise((resolve)=>{
|
|
57
|
-
setTimeout(resolve, retryDelayMs);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
9
|
+
if (!location.urlEndpoint?.trim()) {
|
|
10
|
+
throw new Error(`migrateFolder: \`${label}.urlEndpoint\` is required.`);
|
|
60
11
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
67
|
-
const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUrl })=>{
|
|
68
|
-
const uploaded = await client.files.upload({
|
|
69
|
-
file: sourceUrl,
|
|
70
|
-
fileName,
|
|
71
|
-
folder,
|
|
72
|
-
useUniqueFileName: false,
|
|
73
|
-
...isPrivate ? {
|
|
74
|
-
isPrivateFile: true
|
|
75
|
-
} : {}
|
|
76
|
-
});
|
|
77
|
-
if (!uploaded.fileId || !uploaded.url) {
|
|
78
|
-
throw new Error(`migrateFolder: upload from URL did not return file details for "${fileName}".`);
|
|
12
|
+
const endpoint = new URL(location.urlEndpoint);
|
|
13
|
+
if (![
|
|
14
|
+
'http:',
|
|
15
|
+
'https:'
|
|
16
|
+
].includes(endpoint.protocol) || endpoint.search || endpoint.hash) {
|
|
17
|
+
throw new Error(`migrateFolder: \`${label}.urlEndpoint\` must be an HTTP(S) URL without a query or fragment.`);
|
|
79
18
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
url: uploaded.url
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
const getSourceImageKitFields = async ({ client, fileId })=>{
|
|
87
|
-
const file = await client.files.get(fileId);
|
|
88
|
-
if (!file.fileId || !file.filePath || !file.url) {
|
|
89
|
-
throw new Error(`migrateFolder: missing ImageKit source file details for "${fileId}".`);
|
|
19
|
+
const folder = location.folder?.trim().replace(/^\/+|\/+$/g, '');
|
|
20
|
+
if (!folder || folder.split('/').some((segment)=>!segment || segment === '.' || segment === '..')) {
|
|
21
|
+
throw new Error(`migrateFolder: \`${label}.folder\` must be a non-empty folder without dot segments.`);
|
|
90
22
|
}
|
|
91
23
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
url: file.url
|
|
24
|
+
...location,
|
|
25
|
+
folder: `/${folder}`,
|
|
26
|
+
urlEndpoint: endpoint.href.replace(/\/$/, '')
|
|
96
27
|
};
|
|
97
28
|
};
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
if (!file.fileId || !file.url) {
|
|
101
|
-
throw new Error(`migrateFolder: missing ImageKit file details for "${fileId}".`);
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
fileId: file.fileId,
|
|
105
|
-
thumbnailUrl: file.thumbnail,
|
|
106
|
-
url: file.url
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
const readFileIdFromCopyResponse = async (response)=>{
|
|
29
|
+
const isInFolder = (filePath, folder)=>filePath.startsWith(`${folder}/`);
|
|
30
|
+
const getAsset = async (client, fileId)=>{
|
|
110
31
|
try {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
32
|
+
const file = await client.files.get(fileId);
|
|
33
|
+
if (!file.fileId || !file.filePath || !file.url) {
|
|
34
|
+
throw new Error(`ImageKit returned incomplete file details for "${fileId}".`);
|
|
114
35
|
}
|
|
115
|
-
|
|
116
|
-
|
|
36
|
+
return {
|
|
37
|
+
...file,
|
|
38
|
+
name: file.name || path.posix.basename(file.filePath),
|
|
39
|
+
fileId: file.fileId,
|
|
40
|
+
filePath: file.filePath,
|
|
41
|
+
url: file.url
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Authentication errors, rate limits and outages are not evidence of a missing file.
|
|
45
|
+
if (error instanceof ImageKit.NotFoundError) {
|
|
46
|
+
return undefined;
|
|
117
47
|
}
|
|
118
|
-
|
|
119
|
-
// Ignore empty or non-JSON responses and fall back to asset discovery.
|
|
48
|
+
throw error;
|
|
120
49
|
}
|
|
121
|
-
return undefined;
|
|
122
50
|
};
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
51
|
+
const findDestination = async (client, filePath)=>{
|
|
52
|
+
const limit = 100;
|
|
53
|
+
for(let skip = 0;; skip += limit){
|
|
54
|
+
const assets = await client.assets.list({
|
|
55
|
+
type: 'file',
|
|
56
|
+
limit,
|
|
57
|
+
path: `${path.posix.dirname(filePath)}/`,
|
|
58
|
+
skip
|
|
59
|
+
});
|
|
60
|
+
const match = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath);
|
|
61
|
+
if (match && 'fileId' in match && match.fileId) {
|
|
62
|
+
return getAsset(client, match.fileId);
|
|
63
|
+
}
|
|
64
|
+
if (assets.length < limit) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
127
67
|
}
|
|
128
|
-
|
|
129
|
-
|
|
68
|
+
};
|
|
69
|
+
const transferTag = (source, filePath, documentID, isPrivate)=>{
|
|
70
|
+
const identity = JSON.stringify([
|
|
71
|
+
source.fileId,
|
|
72
|
+
source.versionInfo?.id || source.updatedAt,
|
|
73
|
+
source.url,
|
|
74
|
+
filePath,
|
|
75
|
+
documentID,
|
|
76
|
+
isPrivate
|
|
77
|
+
]);
|
|
78
|
+
return `payload-migration-${createHash('sha256').update(identity).digest('hex')}`;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Copy referenced originals between ImageKit folders/accounts and update Payload in place.
|
|
82
|
+
* Transfers are tagged at upload time and never overwrite an existing destination file.
|
|
83
|
+
* Reruns recover the upload/database-update gap without relying on filename equality.
|
|
84
|
+
*/ export const migrateFolder = async (options)=>{
|
|
85
|
+
const { collections, dryRun = true, payload, signedUrlExpiresIn = 900 } = options;
|
|
86
|
+
const from = normalizeLocation(options.from, 'from');
|
|
87
|
+
const to = normalizeLocation(options.to, 'to');
|
|
88
|
+
if (!payload) {
|
|
89
|
+
throw new Error('migrateFolder: `payload` is required.');
|
|
130
90
|
}
|
|
131
|
-
|
|
91
|
+
const slugs = Object.keys(collections || {});
|
|
92
|
+
if (!slugs.length) {
|
|
132
93
|
throw new Error('migrateFolder: `collections` must include at least one upload collection.');
|
|
133
94
|
}
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (fromFolder === toFolder) {
|
|
139
|
-
throw new Error('migrateFolder: `from` and `to` cannot be the same folder.');
|
|
95
|
+
for (const slug of slugs){
|
|
96
|
+
if (!payload.collections[slug]?.config.upload) {
|
|
97
|
+
throw new Error(`migrateFolder: "${slug}" is not an upload collection.`);
|
|
98
|
+
}
|
|
140
99
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
});
|
|
144
|
-
console.log(`[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`);
|
|
145
|
-
if (dryRun) {
|
|
146
|
-
console.log(`[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`);
|
|
147
|
-
} else {
|
|
148
|
-
console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`);
|
|
149
|
-
await ensureFolderExists(client, toFolder);
|
|
100
|
+
if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0) {
|
|
101
|
+
throw new Error('migrateFolder: `signedUrlExpiresIn` must be positive.');
|
|
150
102
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
103
|
+
// A single transfer path for both cases gives uploads atomic provenance tags, including
|
|
104
|
+
// when an upload succeeds remotely but its response is lost. Native copy cannot add a tag.
|
|
105
|
+
const sourceClient = new ImageKit({
|
|
106
|
+
maxRetries: 0,
|
|
107
|
+
privateKey: from.privateKey
|
|
108
|
+
});
|
|
109
|
+
const destinationClient = new ImageKit({
|
|
110
|
+
maxRetries: 0,
|
|
111
|
+
privateKey: to.privateKey
|
|
112
|
+
});
|
|
153
113
|
const results = [];
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
114
|
+
for (const slug of slugs){
|
|
115
|
+
const result = {
|
|
116
|
+
collectionSlug: slug,
|
|
117
|
+
errors: [],
|
|
118
|
+
failed: 0,
|
|
119
|
+
migrated: 0,
|
|
120
|
+
planned: 0,
|
|
121
|
+
reused: 0,
|
|
122
|
+
skipped: 0
|
|
123
|
+
};
|
|
124
|
+
const sourceFolder = path.posix.join(from.folder, slug);
|
|
125
|
+
const destinationFolder = path.posix.join(to.folder, slug);
|
|
126
|
+
const collectionOptions = collections[slug];
|
|
127
|
+
const plannedPaths = new Map();
|
|
128
|
+
let page = 1;
|
|
129
|
+
let hasNextPage;
|
|
130
|
+
do {
|
|
168
131
|
const found = await payload.find({
|
|
169
132
|
collection: slug,
|
|
170
133
|
depth: 0,
|
|
171
|
-
limit:
|
|
172
|
-
|
|
134
|
+
limit: 100,
|
|
135
|
+
page,
|
|
136
|
+
sort: 'id'
|
|
173
137
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const filename = doc.filename;
|
|
182
|
-
const existingImageKit = doc.imagekit;
|
|
183
|
-
const displayName = filename || existingImageKit?.fileId || doc.id;
|
|
184
|
-
console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`);
|
|
185
|
-
// Skip items that don't have an ImageKit file ID to migrate from.
|
|
186
|
-
if (!existingImageKit?.fileId) {
|
|
187
|
-
skippedCount += 1;
|
|
188
|
-
console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`);
|
|
138
|
+
hasNextPage = found.hasNextPage;
|
|
139
|
+
page += 1;
|
|
140
|
+
for (const doc of found.docs){
|
|
141
|
+
const reference = doc.imagekit;
|
|
142
|
+
if (!reference?.fileId) {
|
|
143
|
+
result.skipped += 1;
|
|
144
|
+
console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: no ImageKit file ID.`);
|
|
189
145
|
continue;
|
|
190
146
|
}
|
|
191
147
|
try {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
148
|
+
const atDestination = await getAsset(destinationClient, reference.fileId);
|
|
149
|
+
if (atDestination && isInFolder(atDestination.filePath, destinationFolder)) {
|
|
150
|
+
result.skipped += 1;
|
|
151
|
+
console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: already at destination.`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const source = await getAsset(sourceClient, reference.fileId);
|
|
155
|
+
if (!source) {
|
|
156
|
+
throw new Error(`File "${reference.fileId}" was not found in the source account.`);
|
|
157
|
+
}
|
|
158
|
+
if (!isInFolder(source.filePath, sourceFolder)) {
|
|
159
|
+
result.skipped += 1;
|
|
160
|
+
console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: outside ${sourceFolder}.`);
|
|
200
161
|
continue;
|
|
201
162
|
}
|
|
163
|
+
const isPrivate = collectionOptions !== true && typeof collectionOptions?.private === 'boolean' ? collectionOptions.private : source.isPrivateFile === true;
|
|
164
|
+
const destinationPath = path.posix.join(destinationFolder, source.name);
|
|
165
|
+
const tag = transferTag(source, destinationPath, doc.id, isPrivate);
|
|
166
|
+
const previousTransfer = plannedPaths.get(destinationPath);
|
|
167
|
+
if (previousTransfer && previousTransfer !== tag) {
|
|
168
|
+
throw new Error(`Destination conflict at "${destinationPath}": multiple source records target this path.`);
|
|
169
|
+
}
|
|
170
|
+
plannedPaths.set(destinationPath, tag);
|
|
171
|
+
let destination = await findDestination(destinationClient, destinationPath);
|
|
172
|
+
if (destination && (!destination.tags?.includes(tag) || destination.isPrivateFile === true !== isPrivate)) {
|
|
173
|
+
throw new Error(`Destination conflict at "${destinationPath}": existing asset is not this transfer.`);
|
|
174
|
+
}
|
|
175
|
+
if (destination) {
|
|
176
|
+
result.reused += 1;
|
|
177
|
+
}
|
|
202
178
|
if (dryRun) {
|
|
203
|
-
|
|
204
|
-
console.log(`[migrateFolder] [dry-run] Would
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
destinationPath: destinationFolder,
|
|
211
|
-
sourceFilePath: source.filePath
|
|
212
|
-
}).asResponse();
|
|
213
|
-
let copiedFileId = await readFileIdFromCopyResponse(copyResponse);
|
|
214
|
-
if (!copiedFileId) {
|
|
215
|
-
copiedFileId = await findImageKitFileId(client, destinationFilePath, {
|
|
216
|
-
retries: 20,
|
|
217
|
-
retryDelayMs: 1000
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
if (!copiedFileId) {
|
|
221
|
-
throw new Error(`migrateFolder: copy request completed for ImageKit file ID "${source.fileId}" from "${source.filePath}", but the destination asset at "${destinationFilePath}" was not discoverable via ImageKit after retrying.`);
|
|
222
|
-
}
|
|
223
|
-
uploaded = await getUploadedImageKitFields({
|
|
224
|
-
client,
|
|
225
|
-
fileId: copiedFileId
|
|
226
|
-
});
|
|
227
|
-
} catch (error) {
|
|
228
|
-
if (!isImageKitFileNotFoundError(error)) {
|
|
229
|
-
throw error;
|
|
230
|
-
}
|
|
231
|
-
console.warn(`[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for "${source.fileName}".`);
|
|
232
|
-
// A private source file 401s on its plain URL, so sign it before ImageKit
|
|
233
|
-
// fetches it, and re-upload as private so the copy keeps its privacy.
|
|
234
|
-
const sourceUrl = isCollectionPrivate ? client.helper.buildSrc({
|
|
179
|
+
result.planned += 1;
|
|
180
|
+
console.log(`[migrateFolder] [dry-run] Would ${destination ? 'reuse' : 'upload'} ${slug}/${doc.id}: ${source.filePath} -> ${destinationPath}`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!destination) {
|
|
184
|
+
const sourceURL = sourceClient.helper.buildSrc({
|
|
185
|
+
...source.isPrivateFile ? {
|
|
235
186
|
expiresIn: signedUrlExpiresIn,
|
|
236
|
-
signed: true
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
fileName: source.fileName,
|
|
243
|
-
folder: destinationFolder,
|
|
244
|
-
isPrivate: isCollectionPrivate,
|
|
245
|
-
sourceUrl
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
// Update the document with new ImageKit data
|
|
249
|
-
await payload.update({
|
|
250
|
-
id: doc.id,
|
|
251
|
-
collection: slug,
|
|
252
|
-
data: {
|
|
253
|
-
imagekit: {
|
|
254
|
-
fileId: uploaded.fileId,
|
|
255
|
-
isPrivate: isCollectionPrivate,
|
|
256
|
-
thumbnailUrl: uploaded.thumbnailUrl,
|
|
257
|
-
url: uploaded.url
|
|
187
|
+
signed: true
|
|
188
|
+
} : {},
|
|
189
|
+
src: source.filePath,
|
|
190
|
+
transformation: [
|
|
191
|
+
{
|
|
192
|
+
raw: 'orig-true'
|
|
258
193
|
}
|
|
259
|
-
|
|
194
|
+
],
|
|
195
|
+
urlEndpoint: from.urlEndpoint
|
|
196
|
+
});
|
|
197
|
+
const uploaded = await destinationClient.files.upload({
|
|
198
|
+
file: sourceURL,
|
|
199
|
+
fileName: source.name,
|
|
200
|
+
folder: destinationFolder,
|
|
201
|
+
isPrivateFile: isPrivate,
|
|
202
|
+
overwriteFile: false,
|
|
203
|
+
tags: [
|
|
204
|
+
tag
|
|
205
|
+
],
|
|
206
|
+
useUniqueFileName: false
|
|
260
207
|
});
|
|
261
|
-
|
|
262
|
-
|
|
208
|
+
if (!uploaded.fileId) {
|
|
209
|
+
throw new Error(`Upload of "${source.fileId}" did not return a file ID.`);
|
|
210
|
+
}
|
|
211
|
+
destination = await getAsset(destinationClient, uploaded.fileId);
|
|
212
|
+
if (!destination || destination.filePath !== destinationPath || !destination.tags?.includes(tag) || destination.isPrivateFile === true !== isPrivate) {
|
|
213
|
+
throw new Error(`Upload of "${source.fileId}" returned unexpected destination details.`);
|
|
214
|
+
}
|
|
263
215
|
}
|
|
216
|
+
const imagekit = {
|
|
217
|
+
fileId: destination.fileId,
|
|
218
|
+
isPrivate,
|
|
219
|
+
thumbnailUrl: destination.thumbnail || null,
|
|
220
|
+
url: destinationClient.helper.buildSrc({
|
|
221
|
+
src: destination.filePath,
|
|
222
|
+
urlEndpoint: to.urlEndpoint
|
|
223
|
+
})
|
|
224
|
+
};
|
|
225
|
+
const updated = {
|
|
226
|
+
...doc,
|
|
227
|
+
filename: destination.name,
|
|
228
|
+
imagekit
|
|
229
|
+
};
|
|
230
|
+
await payload.update({
|
|
231
|
+
id: doc.id,
|
|
232
|
+
collection: slug,
|
|
233
|
+
context: {
|
|
234
|
+
skipCloudStorage: true
|
|
235
|
+
},
|
|
236
|
+
data: {
|
|
237
|
+
_objectKey: null,
|
|
238
|
+
filename: destination.name,
|
|
239
|
+
imagekit,
|
|
240
|
+
prefix: destinationFolder.replace(/^\//, ''),
|
|
241
|
+
thumbnailURL: isPrivate ? getPrivateAdminThumbnailURL(updated, slug, payload.config.routes.api) : getAdminThumbnailURL(updated)
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
result.migrated += 1;
|
|
245
|
+
console.log(`[migrateFolder] Migrated ${slug}/${doc.id} to ${destinationPath}.`);
|
|
264
246
|
} catch (error) {
|
|
265
|
-
|
|
266
|
-
|
|
247
|
+
const message = error instanceof Error ? error.message : 'Unknown migration error';
|
|
248
|
+
result.failed += 1;
|
|
249
|
+
result.errors.push({
|
|
250
|
+
documentID: doc.id,
|
|
251
|
+
message
|
|
252
|
+
});
|
|
253
|
+
console.error(`[migrateFolder] Failed ${slug}/${doc.id}: ${message}`);
|
|
267
254
|
}
|
|
268
255
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
status: dryRun ? 'Pending' : 'Completed'
|
|
273
|
-
});
|
|
274
|
-
console.log(`[migrateFolder] Collection "${slug}" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`);
|
|
275
|
-
}
|
|
276
|
-
console.log('[migrateFolder] Document-by-document migration complete.');
|
|
277
|
-
} else {
|
|
278
|
-
console.log('[migrateFolder] No Payload instance provided - skipping migration.');
|
|
256
|
+
}while (hasNextPage)
|
|
257
|
+
results.push(result);
|
|
258
|
+
console.log(`[migrateFolder] ${slug}: planned=${result.planned}, migrated=${result.migrated}, reused=${result.reused}, skipped=${result.skipped}, failed=${result.failed}.`);
|
|
279
259
|
}
|
|
280
|
-
console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`);
|
|
281
260
|
return results;
|
|
282
261
|
};
|
|
283
262
|
|
package/dist/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["/* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */\n\nimport type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` fields in the\n * database will be updated to reflect the new ImageKit asset after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to sign\n * private source URLs when the migrator falls back to re-uploading from the source URL.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst isFileInFolder = (filePath: string, folderPath: string): boolean =>\n filePath === folderPath || filePath.startsWith(`${folderPath}/`)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getSourceImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{\n fileId: string\n fileName: string\n filePath: string\n url: string\n}> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit source file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n fileName: file.name || path.posix.basename(file.filePath),\n filePath: file.filePath,\n url: file.url,\n }\n}\n\nconst getUploadedImageKitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImageKit = (doc as { imagekit?: { fileId?: string } }).imagekit\n const displayName = filename || existingImageKit?.fileId || doc.id\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have an ImageKit file ID to migrate from.\n if (!existingImageKit?.fileId) {\n skippedCount += 1\n console.log(`[migrateFolder] Skipping document ${doc.id} - missing ImageKit file ID`)\n continue\n }\n\n try {\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const source = await getSourceImageKitFields({\n client,\n fileId: existingImageKit.fileId,\n })\n\n if (isFileInFolder(source.filePath, destinationFolder)) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - ImageKit file ID ${source.fileId} is already in ${destinationFolder}`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would copy ImageKit file ID ${source.fileId} from ${source.filePath} to ${destinationFolder}`,\n )\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n const destinationFilePath = path.posix.join(destinationFolder, source.fileName)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath: source.filePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `migrateFolder: copy request completed for ImageKit file ID \"${source.fileId}\" from \"${source.filePath}\", but the destination asset at \"${destinationFilePath}\" was not discoverable via ImageKit after retrying.`,\n )\n }\n\n uploaded = await getUploadedImageKitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found for ImageKit file ID ${source.fileId}. Falling back to resolved ImageKit URL for \"${source.fileName}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: source.url,\n urlEndpoint,\n })\n : source.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: source.fileName,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${displayName} (${doc.id})`)\n }\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${displayName}\":`,\n error,\n )\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","isFileInFolder","filePath","folderPath","startsWith","ensureFolderExists","client","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getSourceImageKitFields","get","getUploadedImageKitFields","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","filename","existingImageKit","displayName","id","destinationFolder","source","destinationFilePath","copyResponse","copy","destinationPath","sourceFilePath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","update","data","push","jobId","status"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["/* eslint-disable no-console -- Migration progress is intentionally reported to the invoking process. */\n\nimport type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport { createHash } from 'node:crypto'\nimport path from 'node:path'\n\nimport { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js'\n\nexport type MigrationLocation = {\n /** Root folder, with collection subfolders beneath it. */\n folder: string\n privateKey: string\n urlEndpoint: string\n}\n\nexport type MigrateFolderOptions = {\n /** `true` preserves source privacy; an explicit `private` value sets destination privacy. */\n collections: Partial<Record<UploadCollectionSlug, { private?: boolean } | true>>\n /** Reads and reports the same plan without uploading or updating documents. Defaults to true. */\n dryRun?: boolean\n from: MigrationLocation\n /** The database containing the records to update, including any already at the destination. */\n payload: Payload\n signedUrlExpiresIn?: number\n to: MigrationLocation\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n errors: { documentID: number | string; message: string }[]\n failed: number\n migrated: number\n planned: number\n /** Existing tagged transfers recovered after an interrupted run. Included in migrated when live. */\n reused: number\n skipped: number\n}\n\ntype Asset = {\n fileId: string\n filePath: string\n isPrivateFile?: boolean\n name: string\n tags?: null | string[]\n thumbnail?: string\n updatedAt?: string\n url: string\n versionInfo?: { id?: string }\n}\n\nconst normalizeLocation = (location: MigrationLocation, label: string): MigrationLocation => {\n if (!location?.privateKey?.trim()) {\n throw new Error(`migrateFolder: \\`${label}.privateKey\\` is required.`)\n }\n if (!location.urlEndpoint?.trim()) {\n throw new Error(`migrateFolder: \\`${label}.urlEndpoint\\` is required.`)\n }\n const endpoint = new URL(location.urlEndpoint)\n if (!['http:', 'https:'].includes(endpoint.protocol) || endpoint.search || endpoint.hash) {\n throw new Error(\n `migrateFolder: \\`${label}.urlEndpoint\\` must be an HTTP(S) URL without a query or fragment.`,\n )\n }\n const folder = location.folder?.trim().replace(/^\\/+|\\/+$/g, '')\n if (\n !folder ||\n folder.split('/').some((segment) => !segment || segment === '.' || segment === '..')\n ) {\n throw new Error(\n `migrateFolder: \\`${label}.folder\\` must be a non-empty folder without dot segments.`,\n )\n }\n return { ...location, folder: `/${folder}`, urlEndpoint: endpoint.href.replace(/\\/$/, '') }\n}\n\nconst isInFolder = (filePath: string, folder: string): boolean => filePath.startsWith(`${folder}/`)\n\nconst getAsset = async (client: ImageKit, fileId: string): Promise<Asset | undefined> => {\n try {\n const file = await client.files.get(fileId)\n if (!file.fileId || !file.filePath || !file.url) {\n throw new Error(`ImageKit returned incomplete file details for \"${fileId}\".`)\n }\n return {\n ...file,\n name: file.name || path.posix.basename(file.filePath),\n fileId: file.fileId,\n filePath: file.filePath,\n url: file.url,\n }\n } catch (error) {\n // Authentication errors, rate limits and outages are not evidence of a missing file.\n if (error instanceof ImageKit.NotFoundError) {\n return undefined\n }\n throw error\n }\n}\n\nconst findDestination = async (client: ImageKit, filePath: string): Promise<Asset | undefined> => {\n const limit = 100\n for (let skip = 0; ; skip += limit) {\n const assets = await client.assets.list({\n type: 'file',\n limit,\n path: `${path.posix.dirname(filePath)}/`,\n skip,\n })\n const match = assets.find((asset) => 'fileId' in asset && asset.filePath === filePath)\n if (match && 'fileId' in match && match.fileId) {\n return getAsset(client, match.fileId)\n }\n if (assets.length < limit) {\n return undefined\n }\n }\n}\n\nconst transferTag = (\n source: Asset,\n filePath: string,\n documentID: number | string,\n isPrivate: boolean,\n): string => {\n const identity = JSON.stringify([\n source.fileId,\n source.versionInfo?.id || source.updatedAt,\n source.url,\n filePath,\n documentID,\n isPrivate,\n ])\n return `payload-migration-${createHash('sha256').update(identity).digest('hex')}`\n}\n\n/**\n * Copy referenced originals between ImageKit folders/accounts and update Payload in place.\n * Transfers are tagged at upload time and never overwrite an existing destination file.\n * Reruns recover the upload/database-update gap without relying on filename equality.\n */\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const { collections, dryRun = true, payload, signedUrlExpiresIn = 900 } = options\n const from = normalizeLocation(options.from, 'from')\n const to = normalizeLocation(options.to, 'to')\n if (!payload) {\n throw new Error('migrateFolder: `payload` is required.')\n }\n const slugs = Object.keys(collections || {})\n if (!slugs.length) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n for (const slug of slugs) {\n if (!payload.collections[slug]?.config.upload) {\n throw new Error(`migrateFolder: \"${slug}\" is not an upload collection.`)\n }\n }\n if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0) {\n throw new Error('migrateFolder: `signedUrlExpiresIn` must be positive.')\n }\n\n // A single transfer path for both cases gives uploads atomic provenance tags, including\n // when an upload succeeds remotely but its response is lost. Native copy cannot add a tag.\n const sourceClient = new ImageKit({ maxRetries: 0, privateKey: from.privateKey })\n const destinationClient = new ImageKit({ maxRetries: 0, privateKey: to.privateKey })\n const results: MigrationJobResult[] = []\n\n for (const slug of slugs) {\n const result: MigrationJobResult = {\n collectionSlug: slug,\n errors: [],\n failed: 0,\n migrated: 0,\n planned: 0,\n reused: 0,\n skipped: 0,\n }\n const sourceFolder = path.posix.join(from.folder, slug)\n const destinationFolder = path.posix.join(to.folder, slug)\n const collectionOptions = collections[slug]\n const plannedPaths = new Map<string, string>()\n let page = 1\n let hasNextPage: boolean\n\n do {\n const found = await payload.find({ collection: slug, depth: 0, limit: 100, page, sort: 'id' })\n hasNextPage = found.hasNextPage\n page += 1\n for (const doc of found.docs) {\n const reference = (doc as { imagekit?: { fileId?: string } }).imagekit\n if (!reference?.fileId) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: no ImageKit file ID.`)\n continue\n }\n try {\n const atDestination = await getAsset(destinationClient, reference.fileId)\n if (atDestination && isInFolder(atDestination.filePath, destinationFolder)) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: already at destination.`)\n continue\n }\n const source = await getAsset(sourceClient, reference.fileId)\n if (!source) {\n throw new Error(`File \"${reference.fileId}\" was not found in the source account.`)\n }\n if (!isInFolder(source.filePath, sourceFolder)) {\n result.skipped += 1\n console.log(`[migrateFolder] Skipping ${slug}/${doc.id}: outside ${sourceFolder}.`)\n continue\n }\n\n const isPrivate =\n collectionOptions !== true && typeof collectionOptions?.private === 'boolean'\n ? collectionOptions.private\n : source.isPrivateFile === true\n const destinationPath = path.posix.join(destinationFolder, source.name)\n const tag = transferTag(source, destinationPath, doc.id, isPrivate)\n const previousTransfer = plannedPaths.get(destinationPath)\n if (previousTransfer && previousTransfer !== tag) {\n throw new Error(\n `Destination conflict at \"${destinationPath}\": multiple source records target this path.`,\n )\n }\n plannedPaths.set(destinationPath, tag)\n let destination = await findDestination(destinationClient, destinationPath)\n if (\n destination &&\n (!destination.tags?.includes(tag) || (destination.isPrivateFile === true) !== isPrivate)\n ) {\n throw new Error(\n `Destination conflict at \"${destinationPath}\": existing asset is not this transfer.`,\n )\n }\n if (destination) {\n result.reused += 1\n }\n if (dryRun) {\n result.planned += 1\n console.log(\n `[migrateFolder] [dry-run] Would ${destination ? 'reuse' : 'upload'} ${slug}/${doc.id}: ${source.filePath} -> ${destinationPath}`,\n )\n continue\n }\n\n if (!destination) {\n const sourceURL = sourceClient.helper.buildSrc({\n ...(source.isPrivateFile ? { expiresIn: signedUrlExpiresIn, signed: true } : {}),\n src: source.filePath,\n transformation: [{ raw: 'orig-true' }],\n urlEndpoint: from.urlEndpoint,\n })\n const uploaded = await destinationClient.files.upload({\n file: sourceURL,\n fileName: source.name,\n folder: destinationFolder,\n isPrivateFile: isPrivate,\n overwriteFile: false,\n tags: [tag],\n useUniqueFileName: false,\n })\n if (!uploaded.fileId) {\n throw new Error(`Upload of \"${source.fileId}\" did not return a file ID.`)\n }\n destination = await getAsset(destinationClient, uploaded.fileId)\n if (\n !destination ||\n destination.filePath !== destinationPath ||\n !destination.tags?.includes(tag) ||\n (destination.isPrivateFile === true) !== isPrivate\n ) {\n throw new Error(\n `Upload of \"${source.fileId}\" returned unexpected destination details.`,\n )\n }\n }\n\n const imagekit = {\n fileId: destination.fileId,\n isPrivate,\n thumbnailUrl: destination.thumbnail || null,\n url: destinationClient.helper.buildSrc({\n src: destination.filePath,\n urlEndpoint: to.urlEndpoint,\n }),\n }\n const updated = { ...doc, filename: destination.name, imagekit }\n await payload.update({\n id: doc.id,\n collection: slug,\n context: { skipCloudStorage: true },\n data: {\n _objectKey: null,\n filename: destination.name,\n imagekit,\n prefix: destinationFolder.replace(/^\\//, ''),\n thumbnailURL: isPrivate\n ? getPrivateAdminThumbnailURL(updated, slug, payload.config.routes.api)\n : getAdminThumbnailURL(updated),\n },\n })\n result.migrated += 1\n console.log(`[migrateFolder] Migrated ${slug}/${doc.id} to ${destinationPath}.`)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown migration error'\n result.failed += 1\n result.errors.push({ documentID: doc.id, message })\n console.error(`[migrateFolder] Failed ${slug}/${doc.id}: ${message}`)\n }\n }\n } while (hasNextPage)\n\n results.push(result)\n console.log(\n `[migrateFolder] ${slug}: planned=${result.planned}, migrated=${result.migrated}, reused=${result.reused}, skipped=${result.skipped}, failed=${result.failed}.`,\n )\n }\n return results\n}\n"],"names":["ImageKit","createHash","path","getAdminThumbnailURL","getPrivateAdminThumbnailURL","normalizeLocation","location","label","privateKey","trim","Error","urlEndpoint","endpoint","URL","includes","protocol","search","hash","folder","replace","split","some","segment","href","isInFolder","filePath","startsWith","getAsset","client","fileId","file","files","get","url","name","posix","basename","error","NotFoundError","undefined","findDestination","limit","skip","assets","list","type","dirname","match","find","asset","length","transferTag","source","documentID","isPrivate","identity","JSON","stringify","versionInfo","id","updatedAt","update","digest","migrateFolder","options","collections","dryRun","payload","signedUrlExpiresIn","from","to","slugs","Object","keys","slug","config","upload","Number","isFinite","sourceClient","maxRetries","destinationClient","results","result","collectionSlug","errors","failed","migrated","planned","reused","skipped","sourceFolder","join","destinationFolder","collectionOptions","plannedPaths","Map","page","hasNextPage","found","collection","depth","sort","doc","docs","reference","imagekit","console","log","atDestination","private","isPrivateFile","destinationPath","tag","previousTransfer","set","destination","tags","sourceURL","helper","buildSrc","expiresIn","signed","src","transformation","raw","uploaded","fileName","overwriteFile","useUniqueFileName","thumbnailUrl","thumbnail","updated","filename","context","skipCloudStorage","data","_objectKey","prefix","thumbnailURL","routes","api","message","push"],"mappings":"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"}
|