@payloadcms/figma 0.0.1-alpha.59 → 0.0.1-alpha.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/control-plane.d.ts +7 -0
- package/dist/api/control-plane.js +18 -0
- package/dist/commands/deploy.js +46 -22
- package/dist/db-content-api/generated/content-api-types.d.ts +16 -11
- package/dist/db-content-api/index.js +4 -2
- package/dist/db-content-api/temp-utilities/sorting.d.ts +1 -1
- package/dist/db-content-api/temp-utilities/sorting.js +11 -7
- package/dist/db-content-api/utilities/data/index.js +17 -1
- package/dist/db-content-api/utilities/joins.d.ts +1 -1
- package/dist/db-content-api/utilities/joins.js +7 -14
- package/dist/db-content-api/utilities/meta/buildPathTypes.d.ts +4 -0
- package/dist/db-content-api/utilities/meta/buildPathTypes.js +53 -7
- package/dist/db-content-api/utilities/meta/normalizeLocaleInWhere.d.ts +10 -0
- package/dist/db-content-api/utilities/meta/normalizeLocaleInWhere.js +102 -0
- package/dist/db-content-api/utilities/where.js +3 -2
- package/dist/utils/adapters/nextjs.js +13 -8
- package/dist/utils/adapters/nitro.js +5 -0
- package/dist/utils/adapters/vite.js +1 -0
- package/dist/utils/deploy-adapter.d.ts +2 -0
- package/dist/utils/s3-upload.js +32 -7
- package/package.json +1 -1
|
@@ -28,6 +28,10 @@ export interface CreateTenantOptions {
|
|
|
28
28
|
* Options for creating a new deployment
|
|
29
29
|
*/
|
|
30
30
|
export interface CreateDeploymentOptions {
|
|
31
|
+
/** Pages keyed by route, each with its associated asset keys */
|
|
32
|
+
pages: Record<string, {
|
|
33
|
+
assets: string[];
|
|
34
|
+
}>;
|
|
31
35
|
/** List of static asset paths that need upload URLs */
|
|
32
36
|
staticAssets: string[];
|
|
33
37
|
}
|
|
@@ -39,6 +43,8 @@ export interface CreateDeploymentResponse {
|
|
|
39
43
|
codeUploadUrl: string;
|
|
40
44
|
/** Unique deployment ID */
|
|
41
45
|
deploymentId: string;
|
|
46
|
+
/** Map of page asset paths to their signed S3 upload URLs */
|
|
47
|
+
pageUploadUrls: Record<string, string>;
|
|
42
48
|
/** Map of static asset paths to their signed S3 upload URLs */
|
|
43
49
|
staticAssetUploadUrls: Record<string, string>;
|
|
44
50
|
}
|
|
@@ -47,6 +53,7 @@ export interface CreateDeploymentApiResponse {
|
|
|
47
53
|
meta: {
|
|
48
54
|
deployment_id: string;
|
|
49
55
|
lambda_zip_upload_url: string;
|
|
56
|
+
page_upload_urls: Record<string, string>;
|
|
50
57
|
static_asset_upload_urls: Record<string, string>;
|
|
51
58
|
};
|
|
52
59
|
status: number;
|
|
@@ -158,21 +158,38 @@ import * as log from '../utils/log.js';
|
|
|
158
158
|
const encodedPath = encodeURIComponent(assetPath);
|
|
159
159
|
staticAssetUploadUrls[assetPath] = `${mockBaseUrl}/${tenantId}/${deploymentId}/static/${encodedPath}?X-Amz-Signature=mock`;
|
|
160
160
|
}
|
|
161
|
+
// Generate mock signed URLs for page assets
|
|
162
|
+
const pageUploadUrls = {};
|
|
163
|
+
for (const pageData of Object.values(options.pages)){
|
|
164
|
+
for (const assetPath of pageData.assets){
|
|
165
|
+
const encodedPath = encodeURIComponent(assetPath);
|
|
166
|
+
pageUploadUrls[assetPath] = `${mockBaseUrl}/${tenantId}/${deploymentId}/pages/${encodedPath}?X-Amz-Signature=mock`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
161
169
|
// Generate mock signed URL for code zip
|
|
162
170
|
const codeUploadUrl = `${mockBaseUrl}/${tenantId}/${deploymentId}/lambda.zip?X-Amz-Signature=mock`;
|
|
163
171
|
return {
|
|
164
172
|
codeUploadUrl,
|
|
165
173
|
deploymentId,
|
|
174
|
+
pageUploadUrls,
|
|
166
175
|
staticAssetUploadUrls
|
|
167
176
|
};
|
|
168
177
|
}
|
|
169
178
|
const url = `${getControlPlaneBaseUrl()}/v1/cms/tenant/${tenantId}/deploy/create`;
|
|
170
179
|
log.debug(`Calling createDeployment API at ${url}`);
|
|
180
|
+
// Build pages payload: { route: { assets: [...] } }
|
|
181
|
+
const pages = {};
|
|
182
|
+
for (const [route, pageData] of Object.entries(options.pages)){
|
|
183
|
+
pages[route] = {
|
|
184
|
+
assets: pageData.assets
|
|
185
|
+
};
|
|
186
|
+
}
|
|
171
187
|
// REAL API IMPLEMENTATION
|
|
172
188
|
const response = await controlPlaneFetch({
|
|
173
189
|
context: 'create deployment',
|
|
174
190
|
options: {
|
|
175
191
|
body: JSON.stringify({
|
|
192
|
+
pages,
|
|
176
193
|
static_assets: options.staticAssets
|
|
177
194
|
}),
|
|
178
195
|
headers: {
|
|
@@ -188,6 +205,7 @@ import * as log from '../utils/log.js';
|
|
|
188
205
|
return {
|
|
189
206
|
codeUploadUrl: data.meta.lambda_zip_upload_url,
|
|
190
207
|
deploymentId: data.meta.deployment_id,
|
|
208
|
+
pageUploadUrls: data.meta.page_upload_urls,
|
|
191
209
|
staticAssetUploadUrls: data.meta.static_asset_upload_urls
|
|
192
210
|
};
|
|
193
211
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -197,14 +197,6 @@ import { loginCommand } from './login.js';
|
|
|
197
197
|
spinner.start('Collecting assets...');
|
|
198
198
|
const pages = await adapter.collectPages(projectPath);
|
|
199
199
|
const assets = await adapter.collectAssets(projectPath);
|
|
200
|
-
const allAssetKeys = [
|
|
201
|
-
...pages.uploadKeys,
|
|
202
|
-
...assets.uploadKeys
|
|
203
|
-
];
|
|
204
|
-
const combinedPathMap = {
|
|
205
|
-
...pages.pathMap,
|
|
206
|
-
...assets.pathMap
|
|
207
|
-
};
|
|
208
200
|
spinner.stop(pc.green(`✓ ${pages.routes.length} pages, ${assets.routes.length} assets detected`));
|
|
209
201
|
// Create deployment package (if server adapter)
|
|
210
202
|
let zipPath = null;
|
|
@@ -226,7 +218,7 @@ import { loginCommand } from './login.js';
|
|
|
226
218
|
if (zipPath) {
|
|
227
219
|
log.debug(`Lambda zip size: ${formatBytes(zipSize)}`);
|
|
228
220
|
}
|
|
229
|
-
log.debug(`Pages: ${pages.routes.length} routes`);
|
|
221
|
+
log.debug(`Pages: ${pages.routes.length} routes (${pages.uploadKeys.length} files)`);
|
|
230
222
|
log.debug(`Assets: ${assets.routes.length} files`);
|
|
231
223
|
// ===== DEPLOYMENT CONFIRMATION =====
|
|
232
224
|
if (!options.yes) {
|
|
@@ -234,19 +226,28 @@ import { loginCommand } from './login.js';
|
|
|
234
226
|
`${pc.cyan('Adapter:')} ${adapter.name}`,
|
|
235
227
|
`${pc.cyan('Build ID:')} ${buildInfo.buildId}`,
|
|
236
228
|
zipPath ? `${pc.cyan('Code size:')} ${formatBytes(zipSize)}` : `${pc.cyan('Code:')} No server`,
|
|
237
|
-
`${pc.cyan('Pages:')} ${pages.routes.length} routes`,
|
|
238
|
-
`${pc.cyan('Assets:')} ${assets.
|
|
229
|
+
`${pc.cyan('Pages:')} ${pages.routes.length} routes (${pages.uploadKeys.length} files)`,
|
|
230
|
+
`${pc.cyan('Assets:')} ${assets.uploadKeys.length} files`
|
|
239
231
|
].join('\n'), 'Details');
|
|
240
232
|
}
|
|
241
233
|
// ===== DEPLOYMENT EXECUTION =====
|
|
242
234
|
// Step 1: Create deployment
|
|
243
235
|
spinner.start('Creating deployment...');
|
|
236
|
+
// Build pages payload from route → asset keys mapping
|
|
237
|
+
const pagesPayload = {};
|
|
238
|
+
for (const [route, assetKeys] of Object.entries(pages.routeAssetMap)){
|
|
239
|
+
pagesPayload[route] = {
|
|
240
|
+
assets: assetKeys
|
|
241
|
+
};
|
|
242
|
+
}
|
|
244
243
|
const createResponse = await createDeployment(credential, tenantInstanceId, {
|
|
245
|
-
|
|
244
|
+
pages: pagesPayload,
|
|
245
|
+
staticAssets: assets.uploadKeys
|
|
246
246
|
});
|
|
247
247
|
const deploymentId = createResponse.deploymentId;
|
|
248
248
|
const codeUploadUrl = createResponse.codeUploadUrl;
|
|
249
249
|
const staticAssetUploadUrls = createResponse.staticAssetUploadUrls;
|
|
250
|
+
const pageUploadUrls = createResponse.pageUploadUrls;
|
|
250
251
|
log.debug(`Deployment ID: ${deploymentId}`);
|
|
251
252
|
// Step 2: Upload code (skip for static sites)
|
|
252
253
|
if (zipPath) {
|
|
@@ -254,23 +255,46 @@ import { loginCommand } from './login.js';
|
|
|
254
255
|
await uploadLambdaZip(zipPath, codeUploadUrl);
|
|
255
256
|
spinner.stop(pc.green(`✓ Code uploaded (${formatBytes(zipSize)})`));
|
|
256
257
|
}
|
|
257
|
-
// Step 3: Upload
|
|
258
|
-
|
|
258
|
+
// Step 3: Upload page assets
|
|
259
|
+
const totalUploadCount = pages.uploadKeys.length + assets.uploadKeys.length;
|
|
260
|
+
let totalUploaded = 0;
|
|
261
|
+
let totalFailed = 0;
|
|
262
|
+
let totalBytes = 0;
|
|
263
|
+
if (pages.uploadKeys.length > 0) {
|
|
264
|
+
spinner.start('Uploading pages...');
|
|
265
|
+
const pageResults = await uploadStaticAssets({
|
|
266
|
+
assetUrls: pageUploadUrls,
|
|
267
|
+
onProgress: (uploaded, total)=>{
|
|
268
|
+
spinner.message(`Uploading pages (${uploaded}/${total})...`);
|
|
269
|
+
},
|
|
270
|
+
pathMap: pages.pathMap,
|
|
271
|
+
projectPath
|
|
272
|
+
});
|
|
273
|
+
totalUploaded += pageResults.assetsUploaded;
|
|
274
|
+
totalFailed += pageResults.assetsFailed;
|
|
275
|
+
totalBytes += pageResults.totalBytesUploaded;
|
|
276
|
+
spinner.stop(pc.green(`✓ ${pageResults.assetsUploaded} page files uploaded`));
|
|
277
|
+
}
|
|
278
|
+
// Step 4: Upload static assets
|
|
279
|
+
if (assets.uploadKeys.length > 0) {
|
|
259
280
|
spinner.start('Uploading assets...');
|
|
260
|
-
const
|
|
281
|
+
const assetResults = await uploadStaticAssets({
|
|
261
282
|
assetUrls: staticAssetUploadUrls,
|
|
262
283
|
onProgress: (uploaded, total)=>{
|
|
263
284
|
spinner.message(`Uploading assets (${uploaded}/${total})...`);
|
|
264
285
|
},
|
|
265
|
-
pathMap:
|
|
286
|
+
pathMap: assets.pathMap,
|
|
266
287
|
projectPath
|
|
267
288
|
});
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
289
|
+
totalUploaded += assetResults.assetsUploaded;
|
|
290
|
+
totalFailed += assetResults.assetsFailed;
|
|
291
|
+
totalBytes += assetResults.totalBytesUploaded;
|
|
292
|
+
spinner.stop(pc.green(`✓ ${assetResults.assetsUploaded} asset files uploaded`));
|
|
293
|
+
}
|
|
294
|
+
if (totalFailed > 0) {
|
|
295
|
+
p.log.warning(`${totalFailed}/${totalUploadCount} files failed to upload. Deployment may be incomplete.`);
|
|
296
|
+
} else if (totalUploaded > 0) {
|
|
297
|
+
p.log.success(pc.green(`✓ All files uploaded (${totalUploaded} files, ${formatBytes(totalBytes)})`));
|
|
274
298
|
}
|
|
275
299
|
// Step 4: Perform deployment
|
|
276
300
|
spinner.start('Performing deployment...');
|
|
@@ -3310,6 +3310,8 @@ export type components = {
|
|
|
3310
3310
|
UniquePath: {
|
|
3311
3311
|
paths: string[];
|
|
3312
3312
|
};
|
|
3313
|
+
/** @enum {string} */
|
|
3314
|
+
PathTypeArray: 'array';
|
|
3313
3315
|
PathTypeRelationship: {
|
|
3314
3316
|
/** @enum {string} */
|
|
3315
3317
|
type: 'relationship';
|
|
@@ -3326,7 +3328,7 @@ export type components = {
|
|
|
3326
3328
|
/** @enum {string} */
|
|
3327
3329
|
PathTypeBlocks: 'blocks';
|
|
3328
3330
|
/** @example array */
|
|
3329
|
-
PathType: '
|
|
3331
|
+
PathType: components['schemas']['PathTypeArray'] | components['schemas']['PathTypeRelationship'] | components['schemas']['PathTypeJoin'] | components['schemas']['PathTypeBlocks'];
|
|
3330
3332
|
/**
|
|
3331
3333
|
* @example {
|
|
3332
3334
|
* "author.tagIds": "array",
|
|
@@ -3441,7 +3443,7 @@ export type components = {
|
|
|
3441
3443
|
* ]
|
|
3442
3444
|
*/
|
|
3443
3445
|
JoinClause: {
|
|
3444
|
-
collectionId: string;
|
|
3446
|
+
collectionId: string | string[];
|
|
3445
3447
|
on: string;
|
|
3446
3448
|
path: string;
|
|
3447
3449
|
count: boolean;
|
|
@@ -3461,9 +3463,7 @@ export type components = {
|
|
|
3461
3463
|
* }
|
|
3462
3464
|
*/
|
|
3463
3465
|
IncludeSelectClause: {
|
|
3464
|
-
[key: string]: true |
|
|
3465
|
-
[key: string]: components['schemas']['IncludeSelectClause'];
|
|
3466
|
-
};
|
|
3466
|
+
[key: string]: true | components['schemas']['IncludeSelectClause'];
|
|
3467
3467
|
};
|
|
3468
3468
|
/**
|
|
3469
3469
|
* @example {
|
|
@@ -3476,9 +3476,7 @@ export type components = {
|
|
|
3476
3476
|
* }
|
|
3477
3477
|
*/
|
|
3478
3478
|
ExcludeSelectClause: {
|
|
3479
|
-
[key: string]: false |
|
|
3480
|
-
[key: string]: components['schemas']['ExcludeSelectClause'];
|
|
3481
|
-
};
|
|
3479
|
+
[key: string]: false | components['schemas']['ExcludeSelectClause'];
|
|
3482
3480
|
};
|
|
3483
3481
|
/** @description Field selection - use include mode (true) or exclude mode (false), but not both */
|
|
3484
3482
|
SelectClause: components['schemas']['IncludeSelectClause'] | components['schemas']['ExcludeSelectClause'];
|
|
@@ -3599,9 +3597,9 @@ export type components = {
|
|
|
3599
3597
|
/** @example posts */
|
|
3600
3598
|
collection: string;
|
|
3601
3599
|
/** @example false */
|
|
3602
|
-
createOnMissing: false | {
|
|
3600
|
+
createOnMissing: false | (components['schemas']['DocumentData'] & {
|
|
3603
3601
|
id: string;
|
|
3604
|
-
};
|
|
3602
|
+
});
|
|
3605
3603
|
doc: components['schemas']['DataWithOperations'];
|
|
3606
3604
|
/** @example 10 */
|
|
3607
3605
|
limit?: number;
|
|
@@ -3772,8 +3770,15 @@ export type components = {
|
|
|
3772
3770
|
collection: string;
|
|
3773
3771
|
/** @example false */
|
|
3774
3772
|
createOnMissing: false | {
|
|
3775
|
-
|
|
3773
|
+
/** @example true */
|
|
3776
3774
|
latest: boolean;
|
|
3775
|
+
/** @example doc-key */
|
|
3776
|
+
parent: string;
|
|
3777
|
+
/** @example 2024-01-01T00:00:00Z */
|
|
3778
|
+
createdAt?: string | null;
|
|
3779
|
+
/** @example 2024-01-01T00:00:00Z */
|
|
3780
|
+
updatedAt?: string | null;
|
|
3781
|
+
version?: components['schemas']['DocumentData'] & unknown;
|
|
3777
3782
|
};
|
|
3778
3783
|
versionDoc: {
|
|
3779
3784
|
/** @example 2024-01-01T00:00:00Z */
|
|
@@ -11,6 +11,7 @@ import { dataToContentAPI, resolveVersionContent } from './utilities/data/index.
|
|
|
11
11
|
import { convertPayloadJoinsToContentAPI } from './utilities/joins.js';
|
|
12
12
|
import { addFallbackLocale } from './utilities/locale/index.js';
|
|
13
13
|
import { buildMeta } from './utilities/meta/buildMeta.js';
|
|
14
|
+
import { normalizeLocaleInWhere } from './utilities/meta/normalizeLocaleInWhere.js';
|
|
14
15
|
import { convertPayloadWhereToContentAPI } from './utilities/where.js';
|
|
15
16
|
async function syncCollections() {
|
|
16
17
|
let existingIds;
|
|
@@ -93,6 +94,7 @@ async function createCollection(collectionId) {
|
|
|
93
94
|
}
|
|
94
95
|
async function findMany({ collection, joins, limit, locale: localeArg, page, pagination: _pagination, sort, where }) {
|
|
95
96
|
const locale = addFallbackLocale(localeArg, this.payload);
|
|
97
|
+
const normalizedWhere = normalizeLocaleInWhere(where, this.payload, collection, locale);
|
|
96
98
|
const { data: response, error } = await this.client.POST('/api/v0/documents:find', {
|
|
97
99
|
body: {
|
|
98
100
|
collection,
|
|
@@ -102,11 +104,11 @@ async function findMany({ collection, joins, limit, locale: localeArg, page, pag
|
|
|
102
104
|
locale,
|
|
103
105
|
page: page ?? 1,
|
|
104
106
|
sort: addFallbackSort(sort, this.payload, collection),
|
|
105
|
-
where: convertPayloadWhereToContentAPI(
|
|
107
|
+
where: convertPayloadWhereToContentAPI(normalizedWhere ?? {}),
|
|
106
108
|
...buildMeta(this.payload, {
|
|
107
109
|
collection,
|
|
108
110
|
locale,
|
|
109
|
-
where
|
|
111
|
+
where: normalizedWhere
|
|
110
112
|
})
|
|
111
113
|
}
|
|
112
114
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Payload } from 'payload';
|
|
2
|
-
export declare function addFallbackSort(sort: string | string[] | undefined, payload: Payload, collectionSlug: string, defaultSort?: string | string[]): string | string[] | undefined;
|
|
2
|
+
export declare function addFallbackSort(sort: string | string[] | undefined, payload: Payload, collectionSlug: string | string[], defaultSort?: string | string[]): string | string[] | undefined;
|
|
3
3
|
//# sourceMappingURL=sorting.d.ts.map
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
// Add fallback sort to ensure consistent ordering when sorting by non-unique fields
|
|
2
2
|
// Matches MongoDB adapter behavior
|
|
3
|
+
// If multiple collections are joined, we need to ensure fallback sort fields are consistent across them to avoid conflicts.
|
|
4
|
+
// If all collections have timestamps, use createdAt, otherwise use id.
|
|
3
5
|
// TO-DECIDE: Should this live here or in content-api?
|
|
4
6
|
export function addFallbackSort(sort, payload, collectionSlug, defaultSort) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
+
const collectionsArray = Array.isArray(collectionSlug) ? collectionSlug : [
|
|
8
|
+
collectionSlug
|
|
9
|
+
];
|
|
10
|
+
const collectionConfigs = collectionsArray.map((slug)=>payload.config.collections.find((c)=>c.slug === slug)).filter((collection)=>collection != null);
|
|
11
|
+
if (sort == null || collectionConfigs.length === 0) {
|
|
7
12
|
if (defaultSort) {
|
|
8
13
|
return addFallbackSort(defaultSort, payload, collectionSlug);
|
|
9
14
|
}
|
|
@@ -13,12 +18,11 @@ export function addFallbackSort(sort, payload, collectionSlug, defaultSort) {
|
|
|
13
18
|
sort
|
|
14
19
|
];
|
|
15
20
|
// Determine fallback sort field
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
21
|
+
// If all collections have timestamps, use createdAt, otherwise use id
|
|
22
|
+
const fallbackSort = collectionConfigs.every((config)=>config.timestamps) ? '-createdAt' : '-id';
|
|
23
|
+
const fallbackInverse = fallbackSort.startsWith('-') ? fallbackSort.substring(1) : `-${fallbackSort}`;
|
|
20
24
|
// Check if fallback sort is already included
|
|
21
|
-
const hasFallback = sortArray.some((item)=>item === fallbackSort || item ===
|
|
25
|
+
const hasFallback = sortArray.some((item)=>item === fallbackSort || item === fallbackInverse);
|
|
22
26
|
if (hasFallback) {
|
|
23
27
|
return sort;
|
|
24
28
|
}
|
|
@@ -4,6 +4,18 @@ import { castFieldValue } from './castFieldValue.js';
|
|
|
4
4
|
import { removeVirtualFields } from './removeVirtualFields.js';
|
|
5
5
|
import { stripFields } from './stripFields.js';
|
|
6
6
|
import { transformToLocalizeStatus } from './transformPublishedLocale.js';
|
|
7
|
+
const ATOMIC_OPERATION_KEYS = [
|
|
8
|
+
'$push',
|
|
9
|
+
'$remove',
|
|
10
|
+
'$inc'
|
|
11
|
+
];
|
|
12
|
+
function isAtomicOperation(value) {
|
|
13
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const keys = Object.keys(value);
|
|
17
|
+
return keys.length === 1 && ATOMIC_OPERATION_KEYS.includes(keys[0]);
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* Transform data before sending to Content API (WRITE operations)
|
|
9
21
|
*
|
|
@@ -46,6 +58,9 @@ import { transformToLocalizeStatus } from './transformPublishedLocale.js';
|
|
|
46
58
|
}
|
|
47
59
|
const current = ref;
|
|
48
60
|
let value = current[field.name];
|
|
61
|
+
if (isAtomicOperation(value)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
49
64
|
// null → [] for non-localized array-like fields. SQL adapters return null for empty join
|
|
50
65
|
// tables; Content API stores JSON null which breaks jsonb_array_elements queries.
|
|
51
66
|
if (value === null && !('localized' in field && field.localized)) {
|
|
@@ -168,7 +183,8 @@ export function dataFromContentAPI(payload, collectionSlug, data, locale) {
|
|
|
168
183
|
// In all-locales mode, localized fields use {} so afterRead can safely iterate locale keys.
|
|
169
184
|
if (value === undefined) {
|
|
170
185
|
const isRequired = 'required' in field && field.required;
|
|
171
|
-
|
|
186
|
+
const isJoin = field.type === 'join';
|
|
187
|
+
if (!isRequired && !isJoin) {
|
|
172
188
|
current[field.name] = isLocalized && isAllLocales ? {} : null;
|
|
173
189
|
}
|
|
174
190
|
return;
|
|
@@ -43,7 +43,7 @@ type ContentAPIJoin = components['schemas']['JoinClause'][number];
|
|
|
43
43
|
* where: { ... }, // Content API where format
|
|
44
44
|
* sort: '-title',
|
|
45
45
|
* limit: 10,
|
|
46
|
-
*
|
|
46
|
+
* page: 2
|
|
47
47
|
* }]
|
|
48
48
|
*
|
|
49
49
|
* The mapping requires the collection config to resolve:
|
|
@@ -42,7 +42,7 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
42
42
|
* where: { ... }, // Content API where format
|
|
43
43
|
* sort: '-title',
|
|
44
44
|
* limit: 10,
|
|
45
|
-
*
|
|
45
|
+
* page: 2
|
|
46
46
|
* }]
|
|
47
47
|
*
|
|
48
48
|
* The mapping requires the collection config to resolve:
|
|
@@ -91,10 +91,9 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
91
91
|
if (!foundJoin) {
|
|
92
92
|
for (const sanitizedJoin of collectionConfig.polymorphicJoins){
|
|
93
93
|
if (sanitizedJoin.joinPath === joinPath) {
|
|
94
|
-
// For polymorphic joins, collection is an array - use first one
|
|
95
94
|
const collections = sanitizedJoin.field.collection;
|
|
96
95
|
foundJoin = {
|
|
97
|
-
collectionSlug:
|
|
96
|
+
collectionSlug: collections,
|
|
98
97
|
defaultSort: sanitizedJoin.field.defaultSort,
|
|
99
98
|
on: sanitizedJoin.field.on
|
|
100
99
|
};
|
|
@@ -106,12 +105,8 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
106
105
|
payload.logger.warn(`Join path '${joinPath}' not found in collection config for '${collectionConfig.slug}'`);
|
|
107
106
|
continue;
|
|
108
107
|
}
|
|
109
|
-
//
|
|
108
|
+
// Fall back to the default limit if not specified in the query
|
|
110
109
|
const effectiveLimit = joinQuery.limit ?? foundJoin.defaultLimit;
|
|
111
|
-
let offset;
|
|
112
|
-
if (joinQuery.page && joinQuery.page > 1 && effectiveLimit) {
|
|
113
|
-
offset = (joinQuery.page - 1) * effectiveLimit;
|
|
114
|
-
}
|
|
115
110
|
// Add fallback sort for the joined collection
|
|
116
111
|
const sortWithFallback = addFallbackSort(joinQuery.sort, payload, foundJoin.collectionSlug, foundJoin.defaultSort);
|
|
117
112
|
const contentAPIJoin = {
|
|
@@ -119,13 +114,11 @@ import { convertPayloadWhereToContentAPI } from './where.js';
|
|
|
119
114
|
count: joinQuery.count ?? false,
|
|
120
115
|
on: foundJoin.on,
|
|
121
116
|
path: joinPath,
|
|
122
|
-
...
|
|
123
|
-
limit:
|
|
124
|
-
} : foundJoin.defaultLimit !== undefined && {
|
|
125
|
-
limit: foundJoin.defaultLimit
|
|
117
|
+
...effectiveLimit !== undefined && {
|
|
118
|
+
limit: effectiveLimit
|
|
126
119
|
},
|
|
127
|
-
...
|
|
128
|
-
|
|
120
|
+
...joinQuery.page && {
|
|
121
|
+
page: joinQuery.page
|
|
129
122
|
},
|
|
130
123
|
...sortWithFallback && {
|
|
131
124
|
sort: sortWithFallback
|
|
@@ -8,6 +8,10 @@ import type { PathTypesRecord } from '../../temp-utilities/types.js';
|
|
|
8
8
|
* - Array fields (type: "array")
|
|
9
9
|
* - Relationship fields (type: { type: "relationship", collection, hasMany })
|
|
10
10
|
* - Join fields (type: { type: "join", collection, hasMany, on })
|
|
11
|
+
*
|
|
12
|
+
* Localized array/blocks fields (stored as locale maps) are handled specially:
|
|
13
|
+
* - The localized field itself is NOT marked as 'array' (it's a locale map object)
|
|
14
|
+
* - The locale-specific path (e.g. "localizedBlocks.en") IS marked as 'array'
|
|
11
15
|
*/
|
|
12
16
|
export declare function buildPathTypes(payload: Payload, collectionSlug: string, where: undefined | Where): PathTypesRecord;
|
|
13
17
|
//# sourceMappingURL=buildPathTypes.d.ts.map
|
|
@@ -90,19 +90,31 @@
|
|
|
90
90
|
return null;
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
* Finds a field in the collection schema by its path
|
|
94
|
-
* Supports nested paths like "access.read.users"
|
|
93
|
+
* Finds a field in the collection schema by its path.
|
|
94
|
+
* Supports nested paths like "access.read.users".
|
|
95
|
+
* For localized array/blocks fields, locale key segments (e.g. "en") are skipped transparently,
|
|
96
|
+
* so "localizedBlocks.en.richText" resolves to the richText field inside localizedBlocks.
|
|
95
97
|
*/ function findFieldByPath(payload, collectionSlug, path) {
|
|
96
98
|
const collectionConfig = payload.config.collections.find((c)=>c.slug === collectionSlug);
|
|
97
99
|
if (!collectionConfig) {
|
|
98
100
|
return undefined;
|
|
99
101
|
}
|
|
102
|
+
const localeCodes = payload.config.localization ? payload.config.localization.localeCodes : [];
|
|
100
103
|
const pathSegments = path.split('.');
|
|
101
104
|
// Walk through the path segments to find the field
|
|
102
105
|
let currentFields = collectionConfig.fields;
|
|
103
106
|
let currentField = undefined;
|
|
104
107
|
for(let i = 0; i < pathSegments.length; i++){
|
|
105
108
|
const segment = pathSegments[i];
|
|
109
|
+
// If this segment is a locale key for the previously found localized array/blocks field,
|
|
110
|
+
// skip it transparently (the locale key is the wrapping object key, not a real field).
|
|
111
|
+
if (localeCodes.includes(segment) && currentField && 'localized' in currentField && currentField.localized && (currentField.type === 'blocks' || currentField.type === 'array')) {
|
|
112
|
+
if (i === pathSegments.length - 1) {
|
|
113
|
+
// Last segment is a locale key → return the localized field (represents locale-specific array)
|
|
114
|
+
return currentField;
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
106
118
|
// First try to find a direct match
|
|
107
119
|
currentField = currentFields.find((f)=>'name' in f && f.name === segment);
|
|
108
120
|
// If not found, check if any unnamed container fields (collapsible, row) contain it
|
|
@@ -118,8 +130,19 @@
|
|
|
118
130
|
} else if ('tabs' in field && field.type === 'tabs') {
|
|
119
131
|
// Check all tabs
|
|
120
132
|
for (const tab of field.tabs){
|
|
121
|
-
|
|
122
|
-
if (
|
|
133
|
+
// Named tab: the tab name itself is a path segment (acts like a group)
|
|
134
|
+
if ('name' in tab && tab.name === segment) {
|
|
135
|
+
currentField = {
|
|
136
|
+
name: tab.name,
|
|
137
|
+
type: 'group',
|
|
138
|
+
fields: tab.fields
|
|
139
|
+
};
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
// Unnamed tab: search its fields for the segment
|
|
143
|
+
const found = tab.fields.find((f)=>'name' in f && f.name === segment);
|
|
144
|
+
if (found) {
|
|
145
|
+
currentField = found;
|
|
123
146
|
break;
|
|
124
147
|
}
|
|
125
148
|
}
|
|
@@ -140,9 +163,12 @@
|
|
|
140
163
|
} else if (currentField.type === 'array' && 'fields' in currentField) {
|
|
141
164
|
currentFields = currentField.fields;
|
|
142
165
|
} else if (currentField.type === 'blocks' && 'blocks' in currentField) {
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
// Collect all fields from all block definitions to continue traversal
|
|
167
|
+
const allBlockFields = [];
|
|
168
|
+
for (const block of currentField.blocks){
|
|
169
|
+
allBlockFields.push(...block.fields);
|
|
170
|
+
}
|
|
171
|
+
currentFields = allBlockFields;
|
|
146
172
|
} else if ((currentField.type === 'relationship' || currentField.type === 'upload') && typeof currentField.relationTo === 'string') {
|
|
147
173
|
// Traverse into the related collection's fields
|
|
148
174
|
const { relationTo } = currentField;
|
|
@@ -168,10 +194,15 @@
|
|
|
168
194
|
* - Array fields (type: "array")
|
|
169
195
|
* - Relationship fields (type: { type: "relationship", collection, hasMany })
|
|
170
196
|
* - Join fields (type: { type: "join", collection, hasMany, on })
|
|
197
|
+
*
|
|
198
|
+
* Localized array/blocks fields (stored as locale maps) are handled specially:
|
|
199
|
+
* - The localized field itself is NOT marked as 'array' (it's a locale map object)
|
|
200
|
+
* - The locale-specific path (e.g. "localizedBlocks.en") IS marked as 'array'
|
|
171
201
|
*/ export function buildPathTypes(payload, collectionSlug, where) {
|
|
172
202
|
if (!where) {
|
|
173
203
|
return {};
|
|
174
204
|
}
|
|
205
|
+
const localeCodes = payload.config.localization ? payload.config.localization.localeCodes : [];
|
|
175
206
|
const pathTypes = {};
|
|
176
207
|
const paths = extractPathsFromWhere(where);
|
|
177
208
|
for (const path of paths){
|
|
@@ -183,8 +214,23 @@
|
|
|
183
214
|
if (partialPath in pathTypes) {
|
|
184
215
|
continue;
|
|
185
216
|
}
|
|
217
|
+
// If the current segment is a locale code and the parent field is a localized array/blocks,
|
|
218
|
+
// mark this locale-specific path as 'array' (the per-locale content is the actual array).
|
|
219
|
+
if (i > 0 && localeCodes.includes(segments[i])) {
|
|
220
|
+
const parentPath = segments.slice(0, i).join('.');
|
|
221
|
+
const parentField = findFieldByPath(payload, collectionSlug, parentPath);
|
|
222
|
+
if (parentField && 'localized' in parentField && parentField.localized && (parentField.type === 'blocks' || parentField.type === 'array')) {
|
|
223
|
+
pathTypes[partialPath] = 'array';
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
186
227
|
const field = findFieldByPath(payload, collectionSlug, partialPath);
|
|
187
228
|
if (field) {
|
|
229
|
+
// Localized array/blocks fields are stored as locale maps (not arrays).
|
|
230
|
+
// Skip them here — the locale-specific path (e.g. "localizedBlocks.en") is marked above.
|
|
231
|
+
if ('localized' in field && field.localized && (field.type === 'blocks' || field.type === 'array')) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
188
234
|
const fieldType = getFieldType(field);
|
|
189
235
|
if (fieldType !== null) {
|
|
190
236
|
pathTypes[partialPath] = fieldType;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Payload, Where } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes a Where clause by inserting locale keys into paths that traverse
|
|
4
|
+
* localized array/blocks fields without an explicit locale.
|
|
5
|
+
*
|
|
6
|
+
* Only runs when a locale is provided and the collection has localized array/blocks fields.
|
|
7
|
+
* Returns the original where clause if no transformation is needed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeLocaleInWhere(where: undefined | Where, payload: Payload, collectionSlug: string, locale: string | undefined): undefined | Where;
|
|
10
|
+
//# sourceMappingURL=normalizeLocaleInWhere.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects paths of localized array/blocks fields in a collection.
|
|
3
|
+
* These fields are stored as locale maps (e.g. {"en": [...], "es": [...]}) rather than
|
|
4
|
+
* direct arrays, so queries must include the locale key in the path.
|
|
5
|
+
*/ function collectLocalizedArrayPaths(fields, prefix, paths) {
|
|
6
|
+
for (const field of fields){
|
|
7
|
+
if (!('name' in field) || !field.name) {
|
|
8
|
+
// Unnamed container (row, collapsible) — recurse into its fields
|
|
9
|
+
if ('fields' in field && Array.isArray(field.fields)) {
|
|
10
|
+
collectLocalizedArrayPaths(field.fields, prefix, paths);
|
|
11
|
+
}
|
|
12
|
+
if ('tabs' in field && Array.isArray(field.tabs)) {
|
|
13
|
+
for (const tab of field.tabs){
|
|
14
|
+
const tabPath = tab.name ? prefix ? `${prefix}.${tab.name}` : tab.name : prefix;
|
|
15
|
+
if (tab.fields) {
|
|
16
|
+
collectLocalizedArrayPaths(tab.fields, tabPath, paths);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const fieldPath = prefix ? `${prefix}.${field.name}` : field.name;
|
|
23
|
+
if ('localized' in field && field.localized && (field.type === 'array' || field.type === 'blocks')) {
|
|
24
|
+
paths.push(fieldPath);
|
|
25
|
+
// Don't recurse further — child paths are accessed via locale key, handled separately
|
|
26
|
+
} else if (field.type === 'group' && 'fields' in field) {
|
|
27
|
+
collectLocalizedArrayPaths(field.fields, fieldPath, paths);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns the paths of all localized array/blocks fields for the given collection.
|
|
33
|
+
*/ function getLocalizedArrayPaths(payload, collectionSlug) {
|
|
34
|
+
const config = payload.config.collections.find((c)=>c.slug === collectionSlug);
|
|
35
|
+
if (!config) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const paths = [];
|
|
39
|
+
collectLocalizedArrayPaths(config.fields, '', paths);
|
|
40
|
+
return paths;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Transforms a single path by inserting the locale after a localized array/blocks prefix
|
|
44
|
+
* if the path goes through one without an explicit locale key.
|
|
45
|
+
*
|
|
46
|
+
* Example:
|
|
47
|
+
* path "localizedBlocks.richText.children.text", localizedArrayPaths ["localizedBlocks"],
|
|
48
|
+
* locale "en", localeCodes ["en", "es"]
|
|
49
|
+
* → "localizedBlocks.en.richText.children.text"
|
|
50
|
+
*
|
|
51
|
+
* path "localizedBlocks.en.richText.children.text" (already has locale)
|
|
52
|
+
* → unchanged
|
|
53
|
+
*/ function insertLocaleInPath(path, localizedArrayPaths, locale, localeCodes) {
|
|
54
|
+
for (const lpath of localizedArrayPaths){
|
|
55
|
+
if (path.startsWith(lpath + '.')) {
|
|
56
|
+
const rest = path.slice(lpath.length + 1);
|
|
57
|
+
const firstSegment = rest.split('.')[0];
|
|
58
|
+
if (!localeCodes.includes(firstSegment)) {
|
|
59
|
+
return `${lpath}.${locale}.${rest}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return path;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Recursively transforms all condition paths in a Where clause by inserting the locale key
|
|
67
|
+
* after any localized array/blocks field prefix that is missing one.
|
|
68
|
+
*
|
|
69
|
+
* This is needed because localized array/blocks fields are stored as locale maps
|
|
70
|
+
* (e.g. data.localizedBlocks.en = [...]) rather than flat arrays. Without the locale
|
|
71
|
+
* key, the path would point to the locale map object, causing jsonb_array_elements to fail.
|
|
72
|
+
*/ function transformWhere(where, localizedArrayPaths, locale, localeCodes) {
|
|
73
|
+
const result = {};
|
|
74
|
+
for (const [key, value] of Object.entries(where)){
|
|
75
|
+
if (key === 'and' || key === 'or') {
|
|
76
|
+
result[key] = value.map((clause)=>transformWhere(clause, localizedArrayPaths, locale, localeCodes));
|
|
77
|
+
} else {
|
|
78
|
+
const normalizedKey = insertLocaleInPath(key, localizedArrayPaths, locale, localeCodes);
|
|
79
|
+
result[normalizedKey] = value;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalizes a Where clause by inserting locale keys into paths that traverse
|
|
86
|
+
* localized array/blocks fields without an explicit locale.
|
|
87
|
+
*
|
|
88
|
+
* Only runs when a locale is provided and the collection has localized array/blocks fields.
|
|
89
|
+
* Returns the original where clause if no transformation is needed.
|
|
90
|
+
*/ export function normalizeLocaleInWhere(where, payload, collectionSlug, locale) {
|
|
91
|
+
if (!where || !locale || !payload.config.localization) {
|
|
92
|
+
return where;
|
|
93
|
+
}
|
|
94
|
+
const localeCodes = payload.config.localization.localeCodes;
|
|
95
|
+
const localizedArrayPaths = getLocalizedArrayPaths(payload, collectionSlug);
|
|
96
|
+
if (localizedArrayPaths.length === 0) {
|
|
97
|
+
return where;
|
|
98
|
+
}
|
|
99
|
+
return transformWhere(where, localizedArrayPaths, locale, localeCodes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=normalizeLocaleInWhere.js.map
|
|
@@ -8,7 +8,8 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
8
8
|
}
|
|
9
9
|
const conditions = [];
|
|
10
10
|
for (const [key, value] of Object.entries(where)){
|
|
11
|
-
|
|
11
|
+
const keyLower = key.toLowerCase();
|
|
12
|
+
if (keyLower === 'and' || keyLower === 'or') {
|
|
12
13
|
const nestedConditions = value.map((item)=>convertPayloadWhereToContentAPI(item, {
|
|
13
14
|
...options,
|
|
14
15
|
insideLogicalOperator: true
|
|
@@ -21,7 +22,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
21
22
|
}
|
|
22
23
|
return true;
|
|
23
24
|
});
|
|
24
|
-
if (
|
|
25
|
+
if (keyLower === 'and') {
|
|
25
26
|
conditions.push(...nestedConditions);
|
|
26
27
|
} else if (nestedConditions.length > 0) {
|
|
27
28
|
conditions.push({
|
|
@@ -36,16 +36,21 @@ export class NextjsAdapter {
|
|
|
36
36
|
}
|
|
37
37
|
async collectPages(projectPath) {
|
|
38
38
|
const ssg = await collectSSGAssets(projectPath);
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
39
|
+
// Build route → asset keys mapping and extract unique routes
|
|
40
|
+
const routeAssetMap = {};
|
|
41
|
+
for (const key of ssg.keys){
|
|
42
|
+
// Strip extension to get the base, then derive the route
|
|
43
|
+
const base = key.replace(/\.(?:html|rsc|meta)$/, '');
|
|
44
|
+
const route = base === 'index' ? '/' : `/${base}`;
|
|
45
|
+
if (!routeAssetMap[route]) {
|
|
46
|
+
routeAssetMap[route] = [];
|
|
47
|
+
}
|
|
48
|
+
routeAssetMap[route].push(key);
|
|
49
|
+
}
|
|
46
50
|
return {
|
|
47
51
|
pathMap: ssg.pathMap,
|
|
48
|
-
|
|
52
|
+
routeAssetMap,
|
|
53
|
+
routes: Object.keys(routeAssetMap),
|
|
49
54
|
uploadKeys: ssg.keys
|
|
50
55
|
};
|
|
51
56
|
}
|
|
@@ -55,11 +55,16 @@ export class NitroAdapter {
|
|
|
55
55
|
const routes = pages.map((p)=>p === 'index' ? '/' : `/${p}`);
|
|
56
56
|
const uploadKeys = pages.map((p)=>p === 'index' ? 'index.html' : `${p}/index.html`);
|
|
57
57
|
const pathMap = {};
|
|
58
|
+
const routeAssetMap = {};
|
|
58
59
|
for(let i = 0; i < uploadKeys.length; i++){
|
|
59
60
|
pathMap[uploadKeys[i]] = path.join('.output', 'public', uploadKeys[i]);
|
|
61
|
+
routeAssetMap[routes[i]] = [
|
|
62
|
+
uploadKeys[i]
|
|
63
|
+
];
|
|
60
64
|
}
|
|
61
65
|
return {
|
|
62
66
|
pathMap,
|
|
67
|
+
routeAssetMap,
|
|
63
68
|
routes,
|
|
64
69
|
uploadKeys
|
|
65
70
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface PageCollection {
|
|
2
2
|
/** S3 key → relative filesystem path from project root */
|
|
3
3
|
pathMap: Record<string, string>;
|
|
4
|
+
/** Route → S3 upload keys for that route (e.g., { "/products": ["products.html", "products.rsc"] }) */
|
|
5
|
+
routeAssetMap: Record<string, string[]>;
|
|
4
6
|
/** Route paths for the manifest pages array (e.g., ["/products", "/blog"]) */
|
|
5
7
|
routes: string[];
|
|
6
8
|
/** S3 keys to upload (e.g., ["products.html", "products.rsc", "products.meta"]) */
|
package/dist/utils/s3-upload.js
CHANGED
|
@@ -11,6 +11,37 @@ const INITIAL_RETRY_DELAY = 1000 // 1 second
|
|
|
11
11
|
*/ function sleep(ms) {
|
|
12
12
|
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
13
13
|
}
|
|
14
|
+
/** Common MIME types for web assets, keyed by file extension */ const MIME_TYPES = {
|
|
15
|
+
'.avif': 'image/avif',
|
|
16
|
+
'.css': 'text/css',
|
|
17
|
+
'.csv': 'text/csv',
|
|
18
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
'.html': 'text/html',
|
|
21
|
+
'.ico': 'image/x-icon',
|
|
22
|
+
'.jpeg': 'image/jpeg',
|
|
23
|
+
'.jpg': 'image/jpeg',
|
|
24
|
+
'.js': 'application/javascript',
|
|
25
|
+
'.json': 'application/json',
|
|
26
|
+
'.map': 'application/json',
|
|
27
|
+
'.meta': 'application/json',
|
|
28
|
+
'.mjs': 'application/javascript',
|
|
29
|
+
'.mp4': 'video/mp4',
|
|
30
|
+
'.otf': 'font/otf',
|
|
31
|
+
'.pdf': 'application/pdf',
|
|
32
|
+
'.png': 'image/png',
|
|
33
|
+
'.rsc': 'text/x-component',
|
|
34
|
+
'.svg': 'image/svg+xml',
|
|
35
|
+
'.ttf': 'font/ttf',
|
|
36
|
+
'.txt': 'text/plain',
|
|
37
|
+
'.wasm': 'application/wasm',
|
|
38
|
+
'.webm': 'video/webm',
|
|
39
|
+
'.webp': 'image/webp',
|
|
40
|
+
'.woff': 'font/woff',
|
|
41
|
+
'.woff2': 'font/woff2',
|
|
42
|
+
'.xml': 'application/xml',
|
|
43
|
+
'.zip': 'application/zip'
|
|
44
|
+
};
|
|
14
45
|
/**
|
|
15
46
|
* Check if a signed URL is a mock URL
|
|
16
47
|
*/ function isMockUrl(signedUrl) {
|
|
@@ -44,14 +75,8 @@ const INITIAL_RETRY_DELAY = 1000 // 1 second
|
|
|
44
75
|
let lastError = null;
|
|
45
76
|
for(let attempt = 0; attempt < MAX_RETRIES; attempt++){
|
|
46
77
|
try {
|
|
47
|
-
// Content-type only needed if css or js, else let S3 infer
|
|
48
78
|
const ext = path.extname(filePath).toLowerCase();
|
|
49
|
-
|
|
50
|
-
if (ext === '.js') {
|
|
51
|
-
determinedContentType = 'application/javascript';
|
|
52
|
-
} else if (ext === '.css') {
|
|
53
|
-
determinedContentType = 'text/css';
|
|
54
|
-
}
|
|
79
|
+
const determinedContentType = contentType ?? MIME_TYPES[ext];
|
|
55
80
|
const response = await fetch(signedUrl, {
|
|
56
81
|
body: fileBuffer,
|
|
57
82
|
headers: determinedContentType ? {
|