@payloadcms/figma 0.1.0-alpha.0 → 0.1.0-internal.e071575
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/db-content-api/generated/content-api-types.d.ts +644 -3
- package/dist/db-content-api/index.d.ts +3 -0
- package/dist/db-content-api/index.js +68 -123
- package/dist/db-content-api/utilities/clientVersionHeaders.d.ts +7 -0
- package/dist/db-content-api/utilities/clientVersionHeaders.js +15 -0
- package/dist/db-content-api/utilities/data/atomicOperations.d.ts +16 -0
- package/dist/db-content-api/utilities/data/atomicOperations.js +48 -0
- package/dist/db-content-api/utilities/data/convertRelationshipIds.d.ts +25 -0
- package/dist/db-content-api/utilities/data/convertRelationshipIds.js +109 -0
- package/dist/db-content-api/utilities/data/index.js +71 -55
- package/dist/db-content-api/utilities/data/isLocaleMap.d.ts +10 -0
- package/dist/db-content-api/utilities/data/isLocaleMap.js +13 -0
- package/dist/db-content-api/utilities/data/resolveFieldLocalization.d.ts +18 -0
- package/dist/db-content-api/utilities/data/resolveFieldLocalization.js +21 -0
- package/dist/db-content-api/utilities/data/validateRelationships.js +19 -12
- package/dist/db-content-api/utilities/joins.d.ts +6 -8
- package/dist/db-content-api/utilities/joins.js +8 -21
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.d.ts +21 -4
- package/dist/db-content-api/utilities/schema/buildDocumentSchema.js +118 -36
- package/dist/db-content-api/utilities/where.d.ts +2 -12
- package/dist/db-content-api/utilities/where.js +1 -12
- package/dist/plugin/build-config.js +5 -1
- package/dist/plugin/sandbox-upload-fetch.d.ts +30 -0
- package/dist/plugin/sandbox-upload-fetch.js +59 -0
- package/dist/utils/load-payload-config.d.ts +2 -2
- package/package.json +6 -5
- package/dist/db-content-api/utilities/meta/buildLocalizedPaths.d.ts +0 -10
- package/dist/db-content-api/utilities/meta/buildLocalizedPaths.js +0 -71
- package/dist/db-content-api/utilities/meta/buildMeta.d.ts +0 -41
- package/dist/db-content-api/utilities/meta/buildMeta.js +0 -39
- package/dist/db-content-api/utilities/meta/buildPathTypes.d.ts +0 -16
- package/dist/db-content-api/utilities/meta/buildPathTypes.js +0 -243
- package/dist/db-content-api/utilities/meta/buildUniquePaths.d.ts +0 -12
- package/dist/db-content-api/utilities/meta/buildUniquePaths.js +0 -60
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
// Built-in document fields the Content API manages itself (stored as columns and
|
|
2
2
|
// injected into every schema). They must never be sent as declared fields, so any
|
|
3
|
-
// top-level field with one of these names is dropped.
|
|
3
|
+
// top-level field with one of these names is dropped. This applies only to
|
|
4
|
+
// documents: blocks have no built-in fields, so a block field named `id` or
|
|
5
|
+
// `createdAt` is a real declared field and must be kept.
|
|
4
6
|
const BUILT_IN_FIELD_NAMES = new Set([
|
|
5
7
|
'createdAt',
|
|
6
8
|
'id',
|
|
7
9
|
'updatedAt'
|
|
8
10
|
]);
|
|
11
|
+
const BLOCK_TYPE_FIELD_NAME = 'blockType';
|
|
12
|
+
// The implicit block-type discriminator carried on every block row. It is not a
|
|
13
|
+
// declared block field, so it is injected into each block entity's paths.
|
|
14
|
+
const BLOCK_TYPE_FIELD = {
|
|
15
|
+
type: 'text',
|
|
16
|
+
hasMany: false,
|
|
17
|
+
localized: false,
|
|
18
|
+
unique: false
|
|
19
|
+
};
|
|
9
20
|
export function buildDocumentSchema(config) {
|
|
10
|
-
const
|
|
11
|
-
const blocksBySlug = new Map();
|
|
21
|
+
const globalBlocksBySlug = new Map();
|
|
12
22
|
for (const block of config.blocks ?? []){
|
|
13
|
-
|
|
23
|
+
globalBlocksBySlug.set(block.slug, block);
|
|
14
24
|
}
|
|
25
|
+
const blockRegistry = {
|
|
26
|
+
blocksById: {},
|
|
27
|
+
globalBlocksBySlug,
|
|
28
|
+
idByBlock: new Map()
|
|
29
|
+
};
|
|
30
|
+
const collections = {};
|
|
15
31
|
for (const collection of config.collections ?? []){
|
|
16
|
-
collections[collection.slug] = buildCollectionSchema(collection.slug, collection.fields,
|
|
32
|
+
collections[collection.slug] = buildCollectionSchema(collection.slug, collection.fields, blockRegistry);
|
|
17
33
|
}
|
|
18
34
|
for (const global of config.globals ?? []){
|
|
19
35
|
const slug = `_global-${global.slug}`;
|
|
20
|
-
collections[slug] = buildCollectionSchema(slug, global.fields,
|
|
36
|
+
collections[slug] = buildCollectionSchema(slug, global.fields, blockRegistry);
|
|
21
37
|
}
|
|
22
38
|
return {
|
|
39
|
+
blocks: blockRegistry.blocksById,
|
|
23
40
|
collections,
|
|
24
41
|
version: 1
|
|
25
42
|
};
|
|
26
43
|
}
|
|
27
|
-
function buildCollectionSchema(name, fields,
|
|
44
|
+
function buildCollectionSchema(name, fields, blockRegistry) {
|
|
28
45
|
const paths = {};
|
|
29
|
-
collectPaths(fields,
|
|
46
|
+
collectPaths(fields, paths, {
|
|
47
|
+
blockRegistry,
|
|
48
|
+
inBlock: false,
|
|
49
|
+
prefix: ''
|
|
50
|
+
});
|
|
30
51
|
// `_payloadAutosave` marks a version as an autosave draft. The Content API wraps
|
|
31
52
|
// version schemas under the `version` envelope, so this becomes `version._payloadAutosave`
|
|
32
53
|
// there. This adds the field to both the document and document version schemas as there
|
|
@@ -44,24 +65,27 @@ function buildCollectionSchema(name, fields, blocksBySlug) {
|
|
|
44
65
|
paths
|
|
45
66
|
};
|
|
46
67
|
}
|
|
47
|
-
function collectPaths(fields,
|
|
68
|
+
function collectPaths(fields, paths, context) {
|
|
69
|
+
const { prefix } = context;
|
|
48
70
|
for (const field of fields){
|
|
49
71
|
if (field.type === 'row' || field.type === 'collapsible' || field.type === 'group' && (!('name' in field) || !field.name)) {
|
|
50
|
-
collectPaths(field.fields,
|
|
72
|
+
collectPaths(field.fields, paths, context);
|
|
51
73
|
continue;
|
|
52
74
|
}
|
|
53
75
|
if (field.type === 'tabs') {
|
|
54
76
|
for (const tab of field.tabs){
|
|
55
|
-
const tabOwnLocalized = 'localized' in tab && tab.localized === true;
|
|
56
77
|
if ('name' in tab && tab.name) {
|
|
57
78
|
paths[`${prefix}${tab.name}`] = {
|
|
58
79
|
type: 'group',
|
|
59
80
|
hasMany: false,
|
|
60
|
-
localized:
|
|
81
|
+
localized: 'localized' in tab && tab.localized === true
|
|
61
82
|
};
|
|
62
|
-
collectPaths(tab.fields,
|
|
83
|
+
collectPaths(tab.fields, paths, {
|
|
84
|
+
...context,
|
|
85
|
+
prefix: `${prefix}${tab.name}.`
|
|
86
|
+
});
|
|
63
87
|
} else {
|
|
64
|
-
collectPaths(tab.fields,
|
|
88
|
+
collectPaths(tab.fields, paths, context);
|
|
65
89
|
}
|
|
66
90
|
}
|
|
67
91
|
continue;
|
|
@@ -69,20 +93,21 @@ function collectPaths(fields, prefix, absorbed, paths, blocksBySlug) {
|
|
|
69
93
|
if (field.type === 'ui' || !('name' in field) || !field.name) {
|
|
70
94
|
continue;
|
|
71
95
|
}
|
|
72
|
-
collectNamedField(field,
|
|
96
|
+
collectNamedField(field, paths, context);
|
|
73
97
|
}
|
|
74
98
|
}
|
|
75
|
-
function collectNamedField(field,
|
|
99
|
+
function collectNamedField(field, paths, context) {
|
|
100
|
+
const { inBlock, prefix } = context;
|
|
76
101
|
if (!('name' in field) || !field.name) {
|
|
77
102
|
return;
|
|
78
103
|
}
|
|
79
|
-
// Built-in fields are supplied by the Content API, never declared here.
|
|
80
|
-
|
|
104
|
+
// Built-in fields are supplied by the Content API, never declared here. Blocks
|
|
105
|
+
// have no built-ins, so their fields are never pruned.
|
|
106
|
+
if (prefix === '' && !inBlock && BUILT_IN_FIELD_NAMES.has(field.name)) {
|
|
81
107
|
return;
|
|
82
108
|
}
|
|
83
109
|
const path = `${prefix}${field.name}`;
|
|
84
|
-
const localized = 'localized' in field && field.localized === true
|
|
85
|
-
const childAbsorbed = absorbed || 'localized' in field && field.localized === true;
|
|
110
|
+
const localized = 'localized' in field && field.localized === true;
|
|
86
111
|
const hasMany = 'hasMany' in field ? Boolean(field.hasMany) : false;
|
|
87
112
|
const unique = 'unique' in field ? Boolean(field.unique) : false;
|
|
88
113
|
switch(field.type){
|
|
@@ -91,28 +116,19 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
|
|
|
91
116
|
type: 'array',
|
|
92
117
|
localized
|
|
93
118
|
};
|
|
94
|
-
collectPaths(field.fields,
|
|
119
|
+
collectPaths(field.fields, paths, {
|
|
120
|
+
...context,
|
|
121
|
+
prefix: `${path}.`
|
|
122
|
+
});
|
|
95
123
|
return;
|
|
96
124
|
case 'blocks':
|
|
97
125
|
{
|
|
126
|
+
const blocks = collectBlocks(field, context);
|
|
98
127
|
paths[path] = {
|
|
99
128
|
type: 'blocks',
|
|
129
|
+
blocks,
|
|
100
130
|
localized
|
|
101
131
|
};
|
|
102
|
-
paths[`${path}.blockType`] = {
|
|
103
|
-
type: 'text',
|
|
104
|
-
hasMany: false,
|
|
105
|
-
localized: false,
|
|
106
|
-
unique: false
|
|
107
|
-
};
|
|
108
|
-
const blockFields = [];
|
|
109
|
-
for (const block of field.blocks){
|
|
110
|
-
const resolved = typeof block === 'string' ? blocksBySlug.get(block) : block;
|
|
111
|
-
if (resolved) {
|
|
112
|
-
blockFields.push(...resolved.fields);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
collectPaths(blockFields, `${path}.`, childAbsorbed, paths, blocksBySlug);
|
|
116
132
|
return;
|
|
117
133
|
}
|
|
118
134
|
case 'checkbox':
|
|
@@ -127,6 +143,7 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
|
|
|
127
143
|
case 'code':
|
|
128
144
|
case 'email':
|
|
129
145
|
case 'point':
|
|
146
|
+
case 'slug':
|
|
130
147
|
case 'textarea':
|
|
131
148
|
paths[path] = {
|
|
132
149
|
type: field.type,
|
|
@@ -140,7 +157,10 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
|
|
|
140
157
|
hasMany,
|
|
141
158
|
localized
|
|
142
159
|
};
|
|
143
|
-
collectPaths(field.fields,
|
|
160
|
+
collectPaths(field.fields, paths, {
|
|
161
|
+
...context,
|
|
162
|
+
prefix: `${path}.`
|
|
163
|
+
});
|
|
144
164
|
return;
|
|
145
165
|
case 'join':
|
|
146
166
|
paths[path] = {
|
|
@@ -180,3 +200,65 @@ function collectNamedField(field, prefix, absorbed, paths, blocksBySlug) {
|
|
|
180
200
|
return;
|
|
181
201
|
}
|
|
182
202
|
}
|
|
203
|
+
function collectBlocks(field, context) {
|
|
204
|
+
const blocks = {};
|
|
205
|
+
// Each block type is stored as its own entity; the field records only the
|
|
206
|
+
// block-type -> entity-id mapping. This lets same-named fields across block
|
|
207
|
+
// types keep independent schemas instead of collapsing to one shape.
|
|
208
|
+
for (const ref of field.blocks){
|
|
209
|
+
const blockId = resolveBlockId(ref, context);
|
|
210
|
+
if (blockId != null) {
|
|
211
|
+
const slug = typeof ref === 'string' ? ref : ref.slug;
|
|
212
|
+
blocks[slug] = blockId;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return blocks;
|
|
216
|
+
}
|
|
217
|
+
// Resolves the block a field references (by inline config or slug), applying
|
|
218
|
+
// Payload's block-type resolution: a globally-registered block wins over a
|
|
219
|
+
// field-local inline block of the same slug. Returns the assigned entity id, or
|
|
220
|
+
// `null` when the slug cannot be resolved.
|
|
221
|
+
function resolveBlockId(ref, context) {
|
|
222
|
+
const { blockRegistry } = context;
|
|
223
|
+
const block = resolveBlockReferenceToBlock(ref, context);
|
|
224
|
+
if (block == null) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const existingBlockId = blockRegistry.idByBlock.get(block);
|
|
228
|
+
if (existingBlockId != null) {
|
|
229
|
+
return existingBlockId;
|
|
230
|
+
}
|
|
231
|
+
const newBlockId = `block_${blockRegistry.idByBlock.size}`;
|
|
232
|
+
blockRegistry.idByBlock.set(block, newBlockId);
|
|
233
|
+
const newBlockPaths = {
|
|
234
|
+
[BLOCK_TYPE_FIELD_NAME]: {
|
|
235
|
+
...BLOCK_TYPE_FIELD
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
// A block entity is its own path namespace, so prefixing resets here. A block
|
|
239
|
+
// entity is shared by every `blocks` field that references it — including a
|
|
240
|
+
// localized one — so it records each field's declared `localized` flag and
|
|
241
|
+
// lets the Content API resolve locale absorption per reference while walking
|
|
242
|
+
// the path. Everything collected below lives in a block, not a document.
|
|
243
|
+
collectPaths(block.fields, newBlockPaths, {
|
|
244
|
+
blockRegistry,
|
|
245
|
+
inBlock: true,
|
|
246
|
+
prefix: ''
|
|
247
|
+
});
|
|
248
|
+
blockRegistry.blocksById[newBlockId] = {
|
|
249
|
+
name: block.slug,
|
|
250
|
+
paths: newBlockPaths
|
|
251
|
+
};
|
|
252
|
+
return newBlockId;
|
|
253
|
+
}
|
|
254
|
+
function resolveBlockReferenceToBlock(ref, context) {
|
|
255
|
+
// Local blocks are stored inline in the field definition.
|
|
256
|
+
const slug = typeof ref === 'string' ? ref : ref.slug;
|
|
257
|
+
const localBlock = typeof ref === 'string' ? undefined : ref;
|
|
258
|
+
// Global blocks are stored as slugs that reference blocks
|
|
259
|
+
// defined at the config level.
|
|
260
|
+
const globalBlock = context.blockRegistry.globalBlocksBySlug.get(slug);
|
|
261
|
+
// Global blocks must take precedence. This mirrors existing Payload
|
|
262
|
+
// behavior.
|
|
263
|
+
return globalBlock ?? localBlock;
|
|
264
|
+
}
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import type { Where } from 'payload';
|
|
2
2
|
import type { components } from '../generated/content-api-types.js';
|
|
3
|
-
type
|
|
3
|
+
type WhereClauseV1 = components['schemas']['WhereClauseV1'];
|
|
4
4
|
type ConvertOptions = {
|
|
5
5
|
/** Internal flag to track if we're inside a logical operator (and/or) */
|
|
6
6
|
insideLogicalOperator?: boolean;
|
|
7
|
-
/** If true, transform 'parent' field to 'documentId' (used for version queries) */
|
|
8
|
-
parentToDocumentId?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
* If true, strip 'version.' prefix from field paths (used for version queries).
|
|
11
|
-
*
|
|
12
|
-
* Payload queries version content with paths like 'version._status.en', but Content API
|
|
13
|
-
* stores version data directly in the 'data' column (not nested under 'version').
|
|
14
|
-
* The 'version' wrapper is only added in API responses for client compatibility.
|
|
15
|
-
*/
|
|
16
|
-
stripVersionPrefix?: boolean;
|
|
17
7
|
};
|
|
18
|
-
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions):
|
|
8
|
+
export declare function convertPayloadWhereToContentAPI(where: undefined | Where, options?: ConvertOptions): WhereClauseV1;
|
|
19
9
|
export {};
|
|
@@ -30,18 +30,7 @@ export function convertPayloadWhereToContentAPI(where, options = {}) {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
} else {
|
|
33
|
-
|
|
34
|
-
// WORKAROUND: Payload uses 'parent' for version queries, but Content API expects 'documentId'
|
|
35
|
-
let fieldPath = key.replace(/__/g, '.');
|
|
36
|
-
if (options.parentToDocumentId && key === 'parent') {
|
|
37
|
-
fieldPath = 'documentId';
|
|
38
|
-
}
|
|
39
|
-
// Strip 'version.' prefix from field paths for version queries
|
|
40
|
-
// Payload uses paths like 'version._status.en' but Content API stores version data
|
|
41
|
-
// directly in the 'data' column, not nested under 'version'
|
|
42
|
-
if (options.stripVersionPrefix && fieldPath.startsWith('version.')) {
|
|
43
|
-
fieldPath = fieldPath.slice('version.'.length);
|
|
44
|
-
}
|
|
33
|
+
const fieldPath = key.replace(/__/g, '.');
|
|
45
34
|
// Convert field conditions: { fieldName: { operator: value } }
|
|
46
35
|
// to: { path: fieldName, operator, value }
|
|
47
36
|
// Handle Payload's shorthand: { fieldName: value } → { fieldName: { equals: value } }
|
|
@@ -18,6 +18,7 @@ import * as log from '../utils/log.js';
|
|
|
18
18
|
import { logMissingCliAuth } from './auth-preflight.js';
|
|
19
19
|
import { logMissingContentSystemId } from './bootstrap-preflight.js';
|
|
20
20
|
import { getDevCookieNames } from './dev-cookie-names.js';
|
|
21
|
+
import { createSandboxUploadFetchPlugin } from './sandbox-upload-fetch.js';
|
|
21
22
|
/**
|
|
22
23
|
* Worker-trusted marker injected by Figma's sites-worker after it validates the
|
|
23
24
|
* X-Figma-Job-Queues-Auth-Bypass secret on a Payload jobs endpoint. The default
|
|
@@ -392,7 +393,10 @@ export async function buildFigmaConfig(config) {
|
|
|
392
393
|
debug: !!process.env.DEBUG,
|
|
393
394
|
disabled: false
|
|
394
395
|
}),
|
|
395
|
-
scheduleExtractPlugin()
|
|
396
|
+
scheduleExtractPlugin(),
|
|
397
|
+
// Not folded into createStoragePlugin: that one is skipped when
|
|
398
|
+
// figma.storage is false, which would break remote uploads for those apps.
|
|
399
|
+
createSandboxUploadFetchPlugin()
|
|
396
400
|
]
|
|
397
401
|
};
|
|
398
402
|
// Inject figma fields into users collection
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* True inside the Make sandbox. agentplat injects `FIGMA=1` into every sandbox
|
|
4
|
+
* process; it is absent locally and in the deployed Lambda. `FIGMA=true` is
|
|
5
|
+
* accepted because the Make app template's `vite.config.ts` treats both as
|
|
6
|
+
* sandbox markers.
|
|
7
|
+
*
|
|
8
|
+
* KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
|
|
9
|
+
*/
|
|
10
|
+
export declare function isMakeSandbox(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Lets upload collections fetch remote files inside the Make sandbox, where DNS
|
|
13
|
+
* maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) and
|
|
14
|
+
* `safeFetch` rejects every resolved range that is not `unicast`. Outside the
|
|
15
|
+
* sandbox this is a no-op.
|
|
16
|
+
*
|
|
17
|
+
* `skipSafeFetch` is forced rather than defaulted with `??`, because
|
|
18
|
+
* `@payloadcms/plugin-cloud-storage` recomputes it on every upload collection
|
|
19
|
+
* and a nullish default would never fire. An app's own setting loses here.
|
|
20
|
+
*
|
|
21
|
+
* `externalFileHeaderFilter` is deliberately left unset: it replaces Payload's
|
|
22
|
+
* cookie logic wholesale, including the branch that forwards cookies for
|
|
23
|
+
* relative URLs.
|
|
24
|
+
*
|
|
25
|
+
* Accepted risks, all bounded by the auth gate on the upload routes and the
|
|
26
|
+
* sandbox's network limits: `getExternalFile` re-validates its three redirect
|
|
27
|
+
* hops only against `pasteURL.allowList`; any session can make the server GET
|
|
28
|
+
* an arbitrary URL and read the body; request cookies reach the fetch target.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createSandboxUploadFetchPlugin(): (config: Config) => Config;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as log from '../utils/log.js';
|
|
2
|
+
/**
|
|
3
|
+
* True inside the Make sandbox. agentplat injects `FIGMA=1` into every sandbox
|
|
4
|
+
* process; it is absent locally and in the deployed Lambda. `FIGMA=true` is
|
|
5
|
+
* accepted because the Make app template's `vite.config.ts` treats both as
|
|
6
|
+
* sandbox markers.
|
|
7
|
+
*
|
|
8
|
+
* KEEP_IN_SYNC(services/agentplat/scriptrun/script_runner.go): EnvFigma
|
|
9
|
+
*/ export function isMakeSandbox() {
|
|
10
|
+
return process.env.FIGMA === '1' || process.env.FIGMA === 'true';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Lets upload collections fetch remote files inside the Make sandbox, where DNS
|
|
14
|
+
* maps external hostnames to unique-local IPv6 addresses (`fc00::/7`) and
|
|
15
|
+
* `safeFetch` rejects every resolved range that is not `unicast`. Outside the
|
|
16
|
+
* sandbox this is a no-op.
|
|
17
|
+
*
|
|
18
|
+
* `skipSafeFetch` is forced rather than defaulted with `??`, because
|
|
19
|
+
* `@payloadcms/plugin-cloud-storage` recomputes it on every upload collection
|
|
20
|
+
* and a nullish default would never fire. An app's own setting loses here.
|
|
21
|
+
*
|
|
22
|
+
* `externalFileHeaderFilter` is deliberately left unset: it replaces Payload's
|
|
23
|
+
* cookie logic wholesale, including the branch that forwards cookies for
|
|
24
|
+
* relative URLs.
|
|
25
|
+
*
|
|
26
|
+
* Accepted risks, all bounded by the auth gate on the upload routes and the
|
|
27
|
+
* sandbox's network limits: `getExternalFile` re-validates its three redirect
|
|
28
|
+
* hops only against `pasteURL.allowList`; any session can make the server GET
|
|
29
|
+
* an arbitrary URL and read the body; request cookies reach the fetch target.
|
|
30
|
+
*/ export function createSandboxUploadFetchPlugin() {
|
|
31
|
+
return (config)=>{
|
|
32
|
+
if (!isMakeSandbox()) {
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
35
|
+
const modifiedSlugs = [];
|
|
36
|
+
const collections = (config.collections ?? []).map((collection)=>{
|
|
37
|
+
if (!collection.upload) {
|
|
38
|
+
return collection;
|
|
39
|
+
}
|
|
40
|
+
modifiedSlugs.push(collection.slug);
|
|
41
|
+
const upload = collection.upload === true ? {} : collection.upload;
|
|
42
|
+
return {
|
|
43
|
+
...collection,
|
|
44
|
+
upload: {
|
|
45
|
+
...upload,
|
|
46
|
+
skipSafeFetch: true
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
if (modifiedSlugs.length === 0) {
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
log.debug(`Make sandbox detected: upload collections skip safeFetch (${modifiedSlugs.join(', ')})`);
|
|
54
|
+
return {
|
|
55
|
+
...config,
|
|
56
|
+
collections
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SanitizedConfig } from 'payload';
|
|
2
2
|
/**
|
|
3
3
|
* Load a project's Payload config without initializing Payload or connecting to its database.
|
|
4
4
|
*
|
|
@@ -6,4 +6,4 @@ import type { Config } from 'payload';
|
|
|
6
6
|
* project's Payload installation so this also works with strict package-manager layouts where
|
|
7
7
|
* tsx is not a direct dependency of @payloadcms/figma.
|
|
8
8
|
*/
|
|
9
|
-
export declare function loadPayloadConfig(projectPath: string): Promise<
|
|
9
|
+
export declare function loadPayloadConfig(projectPath: string): Promise<SanitizedConfig>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/figma",
|
|
3
|
-
"version": "0.1.0-
|
|
3
|
+
"version": "0.1.0-internal.e071575",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@payloadcms/eslint-config": "3.28.0",
|
|
51
51
|
"@payloadcms/eslint-plugin": "3.28.0",
|
|
52
|
-
"@payloadcms/next": "4.0.0-canary.
|
|
52
|
+
"@payloadcms/next": "4.0.0-canary.20",
|
|
53
53
|
"@swc/cli": "0.7.7",
|
|
54
54
|
"@types/archiver": "7.0.0",
|
|
55
55
|
"@types/cross-spawn": "6.0.6",
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
"vitest": "4.0.15"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@payloadcms/plugin-cloud-storage": ">=4.0.0-canary.
|
|
70
|
-
"@payloadcms/richtext-lexical": ">=4.0.0-canary.
|
|
71
|
-
"payload": ">=4.0.0-canary.
|
|
69
|
+
"@payloadcms/plugin-cloud-storage": ">=4.0.0-canary.20 <4.0.0-internal",
|
|
70
|
+
"@payloadcms/richtext-lexical": ">=4.0.0-canary.20 <4.0.0-internal",
|
|
71
|
+
"payload": ">=4.0.0-canary.20 <4.0.0-internal"
|
|
72
72
|
},
|
|
73
73
|
"peerDependenciesMeta": {
|
|
74
74
|
"@payloadcms/plugin-cloud-storage": {
|
|
@@ -97,6 +97,7 @@
|
|
|
97
97
|
"generate:types": "tsx scripts/generate-types.ts",
|
|
98
98
|
"lint": "eslint .",
|
|
99
99
|
"lint:fix": "eslint . --fix",
|
|
100
|
+
"payload:cli": "tsx scripts/payload-cli.ts",
|
|
100
101
|
"start": "pnpm build && node ./bin/cli.js",
|
|
101
102
|
"test": "TEST_UNIT=true vitest run",
|
|
102
103
|
"test:int": "TEST_INT=true vitest run --config vitest.config.int.ts",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Payload } from 'payload';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts all localized field paths from a collection's field configuration.
|
|
4
|
-
* Recursively traverses nested structures (groups, arrays, blocks, tabs) to find fields marked as localized.
|
|
5
|
-
*
|
|
6
|
-
* @param payload - The Payload instance containing collection configurations
|
|
7
|
-
* @param collectionSlug - The slug of the collection to extract localized paths from
|
|
8
|
-
* @returns Array of dot-notation paths to localized fields (e.g., ['title', 'metadata.description'])
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildLocalizedPaths(payload: Payload, collectionSlug: string): string[];
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { resolveBlocks } from '../resolveBlocks.js';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts all localized field paths from a collection's field configuration.
|
|
4
|
-
* Recursively traverses nested structures (groups, arrays, blocks, tabs) to find fields marked as localized.
|
|
5
|
-
*
|
|
6
|
-
* @param payload - The Payload instance containing collection configurations
|
|
7
|
-
* @param collectionSlug - The slug of the collection to extract localized paths from
|
|
8
|
-
* @returns Array of dot-notation paths to localized fields (e.g., ['title', 'metadata.description'])
|
|
9
|
-
*/ export function buildLocalizedPaths(payload, collectionSlug) {
|
|
10
|
-
const collectionConfig = payload.config.collections.find((c)=>c.slug === collectionSlug);
|
|
11
|
-
if (!collectionConfig) {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
const localizedPaths = [];
|
|
15
|
-
traverseFields(collectionConfig.fields, localizedPaths, '', false, payload.config.blocks);
|
|
16
|
-
// Add _status to localized paths if localizeStatus is enabled for this collection
|
|
17
|
-
// _status is a base field added by Payload when versions.drafts is enabled,
|
|
18
|
-
// and it becomes localized when versions.drafts.localizeStatus is true
|
|
19
|
-
// localizeStatus requires both experimental flag AND collection-level config
|
|
20
|
-
const experimentalLocalizeStatus = 'experimental' in payload.config && payload.config.experimental && typeof payload.config.experimental === 'object' && 'localizeStatus' in payload.config.experimental && payload.config.experimental.localizeStatus === true;
|
|
21
|
-
const drafts = collectionConfig.versions && typeof collectionConfig.versions === 'object' ? collectionConfig.versions.drafts : false;
|
|
22
|
-
const collectionLocalizeStatus = drafts && typeof drafts === 'object' && 'localizeStatus' in drafts && drafts.localizeStatus === true;
|
|
23
|
-
const hasLocalizeStatus = payload.config.localization && experimentalLocalizeStatus && collectionLocalizeStatus;
|
|
24
|
-
if (hasLocalizeStatus && !localizedPaths.includes('_status')) {
|
|
25
|
-
localizedPaths.push('_status');
|
|
26
|
-
}
|
|
27
|
-
return localizedPaths;
|
|
28
|
-
}
|
|
29
|
-
/** Recursively traverses fields to find and collect localized paths */ function traverseFields(fields, localizedPaths, parentPath = '', parentIsLocalized = false, configBlocks) {
|
|
30
|
-
for (const field of fields){
|
|
31
|
-
// Unnamed layout fields (row, collapsible, unnamed group, unnamed tabs) don't store data
|
|
32
|
-
// at their own level, but their child fields may be localized. Recurse into them with
|
|
33
|
-
// the same parent path so localized fields bubble up correctly.
|
|
34
|
-
if (!('name' in field)) {
|
|
35
|
-
if ('fields' in field && Array.isArray(field.fields)) {
|
|
36
|
-
traverseFields(field.fields, localizedPaths, parentPath, parentIsLocalized, configBlocks);
|
|
37
|
-
}
|
|
38
|
-
if ('tabs' in field && Array.isArray(field.tabs)) {
|
|
39
|
-
for (const tab of field.tabs){
|
|
40
|
-
if (tab.fields) {
|
|
41
|
-
const tabPath = tab.name ? buildPath(parentPath, tab.name) : parentPath;
|
|
42
|
-
traverseFields(tab.fields, localizedPaths, tabPath, parentIsLocalized, configBlocks);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
const fieldPath = buildPath(parentPath, field.name);
|
|
49
|
-
const isLocalized = 'localized' in field && field.localized === true;
|
|
50
|
-
const includeNestedPaths = isLocalized || parentIsLocalized;
|
|
51
|
-
// Add field to results if localized or parent is localized
|
|
52
|
-
if (includeNestedPaths) {
|
|
53
|
-
localizedPaths.push(fieldPath);
|
|
54
|
-
}
|
|
55
|
-
// Handle blocks field - has a special 'blocks' array structure and each block has its own nested fields
|
|
56
|
-
if (field.type === 'blocks' && 'blocks' in field) {
|
|
57
|
-
for (const block of resolveBlocks(field.blocks, configBlocks)){
|
|
58
|
-
traverseFields(block.fields, localizedPaths, fieldPath, includeNestedPaths, configBlocks);
|
|
59
|
-
}
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
// Handle field type that support nested fields
|
|
63
|
-
// Recursively handle nested structures (groups, arrays, tab, etc)
|
|
64
|
-
if ('fields' in field) {
|
|
65
|
-
traverseFields(field.fields, localizedPaths, fieldPath, includeNestedPaths, configBlocks);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/** Builds a dot-notation path from parent path and field name */ function buildPath(parentPath, fieldName) {
|
|
70
|
-
return parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
|
71
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Payload, Where } from 'payload';
|
|
2
|
-
import type { components } from '../../generated/content-api-types.js';
|
|
3
|
-
import type { PathTypesRecord } from '../../temp-utilities/types.js';
|
|
4
|
-
/**
|
|
5
|
-
* Metadata sent to Content API for query processing.
|
|
6
|
-
*
|
|
7
|
-
* TODO: This interface is defined here temporarily so Content API can test the integration.
|
|
8
|
-
* Once Content API implements support for pathTypes with relationship info, this type
|
|
9
|
-
* should be generated from openapi.json and ContentAPIMeta can be removed.
|
|
10
|
-
*/
|
|
11
|
-
export interface ContentAPIMeta {
|
|
12
|
-
localizedPaths?: string[];
|
|
13
|
-
pathTypes?: PathTypesRecord;
|
|
14
|
-
uniquePaths?: {
|
|
15
|
-
paths: string[];
|
|
16
|
-
}[];
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* The type that Content API currently expects (from generated types).
|
|
20
|
-
* This is more restrictive than ContentAPIMeta until Content API is updated.
|
|
21
|
-
*/
|
|
22
|
-
type ContentAPIMetaGenerated = components['schemas']['RequestMeta'];
|
|
23
|
-
export interface BuildMetaOptions {
|
|
24
|
-
collection: string;
|
|
25
|
-
/** Document data — when provided, unique field constraints are included in meta. */
|
|
26
|
-
data?: Record<string, unknown>;
|
|
27
|
-
locale: string | undefined;
|
|
28
|
-
where?: Where;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Builds the `meta` object for Content API requests.
|
|
32
|
-
* Combines pathTypes (for array field handling) and localizedPaths (for locale queries).
|
|
33
|
-
*
|
|
34
|
-
* @param payload - The Payload instance
|
|
35
|
-
* @param options - Options including collection slug, locale, and where clause
|
|
36
|
-
* @returns Object with meta property ready to spread into request body, or empty object if no meta needed
|
|
37
|
-
*/
|
|
38
|
-
export declare function buildMeta(payload: Payload, options: BuildMetaOptions): {
|
|
39
|
-
meta?: ContentAPIMetaGenerated;
|
|
40
|
-
};
|
|
41
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { buildLocalizedPaths } from './buildLocalizedPaths.js';
|
|
2
|
-
import { buildPathTypes } from './buildPathTypes.js';
|
|
3
|
-
import { buildUniquePaths } from './buildUniquePaths.js';
|
|
4
|
-
/**
|
|
5
|
-
* Builds the `meta` object for Content API requests.
|
|
6
|
-
* Combines pathTypes (for array field handling) and localizedPaths (for locale queries).
|
|
7
|
-
*
|
|
8
|
-
* @param payload - The Payload instance
|
|
9
|
-
* @param options - Options including collection slug, locale, and where clause
|
|
10
|
-
* @returns Object with meta property ready to spread into request body, or empty object if no meta needed
|
|
11
|
-
*/ export function buildMeta(payload, options) {
|
|
12
|
-
const { collection, data, locale, where } = options;
|
|
13
|
-
const meta = {};
|
|
14
|
-
// Add pathTypes if there are array fields in the where clause
|
|
15
|
-
const pathTypes = buildPathTypes(payload, collection, where);
|
|
16
|
-
if (Object.keys(pathTypes).length > 0) {
|
|
17
|
-
meta.pathTypes = pathTypes;
|
|
18
|
-
}
|
|
19
|
-
// Add localizedPaths if locale is specified (but not 'all')
|
|
20
|
-
if (locale && locale !== 'all') {
|
|
21
|
-
const localizedPaths = buildLocalizedPaths(payload, collection);
|
|
22
|
-
if (localizedPaths.length > 0) {
|
|
23
|
-
meta.localizedPaths = localizedPaths;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Add uniquePaths when data is provided (create/upsert operations)
|
|
27
|
-
if (data) {
|
|
28
|
-
const uniquePaths = buildUniquePaths(payload, collection, data);
|
|
29
|
-
if (uniquePaths.length > 0) {
|
|
30
|
-
meta.uniquePaths = uniquePaths;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
// Cast to generated type - Content API will need to be updated to handle
|
|
34
|
-
// the new pathTypes format with relationship info. Until then, it will
|
|
35
|
-
// ignore the extra fields but still receive the data for testing.
|
|
36
|
-
return Object.keys(meta).length > 0 ? {
|
|
37
|
-
meta: meta
|
|
38
|
-
} : {};
|
|
39
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Payload, Where } from 'payload';
|
|
2
|
-
import type { PathTypesRecord } from '../../temp-utilities/types.js';
|
|
3
|
-
/**
|
|
4
|
-
* Builds pathTypes metadata for Content API request.
|
|
5
|
-
* Returns a record of paths with their types for proper query generation.
|
|
6
|
-
*
|
|
7
|
-
* Includes:
|
|
8
|
-
* - Array fields (type: "array")
|
|
9
|
-
* - Relationship fields (type: { type: "relationship", collection, hasMany })
|
|
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'
|
|
15
|
-
*/
|
|
16
|
-
export declare function buildPathTypes(payload: Payload, collectionSlug: string, where: undefined | Where): PathTypesRecord;
|