@payloadcms/figma 0.0.1-alpha.69 → 0.0.1-alpha.70
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.js +1 -1
- package/dist/api/figma-api.js +2 -2
- package/dist/auth/jwt-validator.js +2 -2
- package/dist/cli.js +2 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/config/oauth.d.ts +4 -4
- package/dist/config/oauth.js +5 -8
- package/dist/constants.d.ts +29 -11
- package/dist/constants.js +73 -4
- package/dist/db-content-api/index.js +13 -7
- package/dist/db-content-api/temp-utilities/unwrapDocument.js +5 -0
- package/dist/oauth/components/LoginButton/index.js +1 -1
- package/dist/oauth/endpoints/getLoginEndpoint.js +11 -1
- package/dist/oauth/utilities/getAuthorizeURL.d.ts +11 -0
- package/dist/oauth/utilities/getAuthorizeURL.js +13 -0
- package/dist/utils/messages.js +1 -0
- package/dist/utils/payload-package-check.js +4 -1
- package/dist/utils/project.d.ts +4 -1
- package/dist/utils/project.js +36 -22
- package/package.json +3 -1
|
@@ -8,7 +8,7 @@ import * as log from '../utils/log.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Get the Control Plane API base URL based on environment
|
|
10
10
|
*/ function getControlPlaneBaseUrl() {
|
|
11
|
-
return
|
|
11
|
+
return getEnvConfig().apiBaseUrl;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Error thrown when Control Plane API calls fail
|
package/dist/api/figma-api.js
CHANGED
|
@@ -85,7 +85,7 @@ import * as log from '../utils/log.js';
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
// REAL API IMPLEMENTATION
|
|
88
|
-
const baseUrl =
|
|
88
|
+
const baseUrl = getEnvConfig().apiBaseUrl;
|
|
89
89
|
const url = `${baseUrl}/v1/cms/${tenantId}/token/`;
|
|
90
90
|
const response = await fetch(url, {
|
|
91
91
|
headers: getAuthHeaders(credential),
|
|
@@ -197,7 +197,7 @@ import * as log from '../utils/log.js';
|
|
|
197
197
|
* @returns FigmaUserInfo with user profile data
|
|
198
198
|
* @throws {FigmaApiError} If the API call fails
|
|
199
199
|
*/ export async function getUserInfo(credential) {
|
|
200
|
-
const baseUrl =
|
|
200
|
+
const baseUrl = getEnvConfig().apiBaseUrl;
|
|
201
201
|
const url = `${baseUrl}/v1/me`;
|
|
202
202
|
try {
|
|
203
203
|
const response = await fetch(url, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
2
|
import { JWKSMultipleMatchingKeys, JWKSNoMatchingKey, JWSInvalid, JWSSignatureVerificationFailed, JWTExpired } from 'jose/errors';
|
|
3
|
-
import {
|
|
3
|
+
import { getEnvConfig } from '../constants.js';
|
|
4
4
|
/**
|
|
5
5
|
* JWT validation error with specific error codes
|
|
6
6
|
*/ export class JWTValidationError extends Error {
|
|
@@ -28,7 +28,7 @@ import { ENV_CONFIG } from '../constants.js';
|
|
|
28
28
|
* @param environment - Figma environment ('production' or 'staging')
|
|
29
29
|
* @returns JWKS resolver function
|
|
30
30
|
*/ function getJWKSResolver(environment) {
|
|
31
|
-
const jwksUri =
|
|
31
|
+
const jwksUri = getEnvConfig(environment).jwksUri;
|
|
32
32
|
if (!jwksResolvers.has(jwksUri)) {
|
|
33
33
|
jwksResolvers.set(jwksUri, createRemoteJWKSet(new URL(jwksUri), {
|
|
34
34
|
cacheMaxAge: 24 * 60 * 60 * 1000,
|
package/dist/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ class Main {
|
|
|
46
46
|
'--json': Boolean,
|
|
47
47
|
'--name': String,
|
|
48
48
|
'--no-skill': Boolean,
|
|
49
|
+
'--payload-version': String,
|
|
49
50
|
'--skip-auth': Boolean,
|
|
50
51
|
'--skip-build': Boolean,
|
|
51
52
|
'--template': String,
|
|
@@ -150,6 +151,7 @@ class Main {
|
|
|
150
151
|
env: this.args['--env'],
|
|
151
152
|
force: this.args['--force'],
|
|
152
153
|
noSkill: this.args['--no-skill'],
|
|
154
|
+
payloadVersion: this.args['--payload-version'],
|
|
153
155
|
skipAuth: this.args['--skip-auth'],
|
|
154
156
|
template: this.args['--template']
|
|
155
157
|
});
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface InitCommandOptions {
|
|
|
14
14
|
name?: string;
|
|
15
15
|
/** Skip Payload skill installation */
|
|
16
16
|
noSkill?: boolean;
|
|
17
|
+
/** npm dist-tag or explicit version for core payload packages (e.g. 'latest', 'canary', '3.40.0'); defaults to 'latest' */
|
|
18
|
+
payloadVersion?: string;
|
|
17
19
|
/** Skip authentication check (for testing/development) */
|
|
18
20
|
skipAuth?: boolean;
|
|
19
21
|
/** GitHub template override, e.g. "v3.80.0:website" or "owner/repo#ref:template-path" */
|
package/dist/commands/init.js
CHANGED
|
@@ -341,7 +341,7 @@ async function maybeInstallPayloadSkill(args) {
|
|
|
341
341
|
};
|
|
342
342
|
s.start(`Downloading template "${resolvedSource.templatePath}" from ` + `${resolvedSource.owner}/${resolvedSource.repo}@${resolvedSource.ref}...`);
|
|
343
343
|
try {
|
|
344
|
-
await scaffoldProject(fullPath, projectName, packageManager, templateSource);
|
|
344
|
+
await scaffoldProject(fullPath, projectName, packageManager, templateSource, options.payloadVersion);
|
|
345
345
|
s.stop(pc.green('✓ Template downloaded'));
|
|
346
346
|
s.start('Installing dependencies...');
|
|
347
347
|
await installDependencies(fullPath, packageManager);
|
package/dist/config/oauth.d.ts
CHANGED
|
@@ -4,14 +4,14 @@ export declare const DEFAULT_CALLBACK_PORT: number;
|
|
|
4
4
|
/**
|
|
5
5
|
* Get OAuth configuration for current environment.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* `apiBaseUrl` (FIGMA_API_BASE_URL) and `authorizationUrl` (FIGMA_WEB_BASE_URL)
|
|
8
|
+
* are resolved by `getEnvConfig`; this function only layers on the OAuth-client
|
|
9
|
+
* specifics it owns:
|
|
10
10
|
* FIGMA_CLIENT_ID → clientId
|
|
11
11
|
* FIGMA_REDIRECT_URI → redirectUri
|
|
12
12
|
*
|
|
13
13
|
* When FIGMA_INFRA_ENV=devbox, FIGMA_API_BASE_URL and FIGMA_WEB_BASE_URL are
|
|
14
|
-
* required (
|
|
14
|
+
* required (the devbox preset has empty URL defaults, to fail closed).
|
|
15
15
|
*/
|
|
16
16
|
export declare function getOAuthConfig(): OAuthConfig;
|
|
17
17
|
export declare const TOKEN_EXPIRY_BUFFER_SECONDS = 300;
|
package/dist/config/oauth.js
CHANGED
|
@@ -9,19 +9,16 @@ export const DEFAULT_CALLBACK_PORT = DEFAULT_CALLBACK_PORTS[0];
|
|
|
9
9
|
/**
|
|
10
10
|
* Get OAuth configuration for current environment.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `apiBaseUrl` (FIGMA_API_BASE_URL) and `authorizationUrl` (FIGMA_WEB_BASE_URL)
|
|
13
|
+
* are resolved by `getEnvConfig`; this function only layers on the OAuth-client
|
|
14
|
+
* specifics it owns:
|
|
15
15
|
* FIGMA_CLIENT_ID → clientId
|
|
16
16
|
* FIGMA_REDIRECT_URI → redirectUri
|
|
17
17
|
*
|
|
18
18
|
* When FIGMA_INFRA_ENV=devbox, FIGMA_API_BASE_URL and FIGMA_WEB_BASE_URL are
|
|
19
|
-
* required (
|
|
19
|
+
* required (the devbox preset has empty URL defaults, to fail closed).
|
|
20
20
|
*/ export function getOAuthConfig() {
|
|
21
|
-
const { apiBaseUrl
|
|
22
|
-
const apiBaseUrl = process.env.FIGMA_API_BASE_URL || defaultApi;
|
|
23
|
-
const webBase = process.env.FIGMA_WEB_BASE_URL;
|
|
24
|
-
const authorizationUrl = webBase ? `${webBase}/oauth` : defaultAuth;
|
|
21
|
+
const { apiBaseUrl, authorizationUrl, clientId } = getEnvConfig();
|
|
25
22
|
if (getInfraEnvironment() === 'devbox' && (!apiBaseUrl || !authorizationUrl)) {
|
|
26
23
|
throw new Error('FIGMA_API_BASE_URL and FIGMA_WEB_BASE_URL must be set when FIGMA_INFRA_ENV=devbox');
|
|
27
24
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -18,11 +18,6 @@ type EnvironmentConfig = {
|
|
|
18
18
|
/** JSON Web Key Set URL for token validation */
|
|
19
19
|
jwksUri: string;
|
|
20
20
|
};
|
|
21
|
-
/**
|
|
22
|
-
* Environment-specific configuration for Figma integration.
|
|
23
|
-
* Selected via FIGMA_INFRA_ENV ('production' | 'staging'), defaults to 'production'.
|
|
24
|
-
*/
|
|
25
|
-
export declare const ENV_CONFIG: Record<Environment, EnvironmentConfig>;
|
|
26
21
|
/**
|
|
27
22
|
* User-facing error message when Content API returns 404 or 410.
|
|
28
23
|
* Used by the db adapter for consistent messaging at runtime.
|
|
@@ -42,13 +37,36 @@ export declare function getProjectNotFoundMessage(params: {
|
|
|
42
37
|
*/
|
|
43
38
|
export declare function setInfraEnvironment(env: Environment | undefined): void;
|
|
44
39
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
40
|
+
* Resolve the EnvironmentConfig for an infrastructure environment, applying the
|
|
41
|
+
* `process.env` overrides that the Make-preview sandbox (and devbox/staging)
|
|
42
|
+
* depend on. This is a FUNCTION, not a populated constant, because those env
|
|
43
|
+
* vars may be set after module load — resolving them lazily at call time keeps
|
|
44
|
+
* the values current (the empty `devbox` preset is filled here, not at import).
|
|
45
|
+
*
|
|
46
|
+
* This is the single place these `process.env` overrides are parsed; callers
|
|
47
|
+
* (including `getOAuthConfig`) read the resolved values from here rather than
|
|
48
|
+
* re-parsing the env vars themselves. Three overrides, each keyed off the axis
|
|
49
|
+
* it belongs to:
|
|
50
|
+
*
|
|
51
|
+
* - `apiBaseUrl` ← `FIGMA_API_BASE_URL` (Control Plane / token host).
|
|
52
|
+
*
|
|
53
|
+
* - `authorizationUrl` / `identityMetadata` ← `FIGMA_WEB_BASE_URL`. The
|
|
54
|
+
* authorization code is minted by the SAME Figma host the editor runs on, so
|
|
55
|
+
* the token exchange / OIDC discovery must hit THAT host. Without this the
|
|
56
|
+
* plugin falls back to `www.figma.com`, the devbox-minted code is rejected
|
|
57
|
+
* with `invalid_grant`, and the admin login loops forever.
|
|
58
|
+
*
|
|
59
|
+
* - `jwksUri` ← `FIGMA_CONTENT_API_URL` (the content backend that signs the
|
|
60
|
+
* project token, independent of the OAuth host — see `deriveContentJwksUri`).
|
|
61
|
+
*
|
|
62
|
+
* In production the platform injects `FIGMA_WEB_BASE_URL=https://www.figma.com`
|
|
63
|
+
* and a `figmacontent.com` content URL, so the derived values equal the presets
|
|
64
|
+
* and behavior is unchanged.
|
|
65
|
+
*
|
|
66
|
+
* @param environment - the infrastructure environment to resolve; defaults to
|
|
67
|
+
* the one reported by `getInfraEnvironment()`.
|
|
50
68
|
*/
|
|
51
|
-
export declare function getEnvConfig(): EnvironmentConfig;
|
|
69
|
+
export declare function getEnvConfig(environment?: Environment): EnvironmentConfig;
|
|
52
70
|
export declare function getInfraEnvironment(): Environment;
|
|
53
71
|
export {};
|
|
54
72
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getEnvVarSync } from './utils/env-management.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Environment-specific configuration for Figma integration.
|
|
4
4
|
* Selected via FIGMA_INFRA_ENV ('production' | 'staging'), defaults to 'production'.
|
|
5
|
-
*/
|
|
5
|
+
*/ const ENV_CONFIG = {
|
|
6
6
|
production: {
|
|
7
7
|
apiBaseUrl: 'https://api.figma.com',
|
|
8
8
|
authorizationUrl: 'https://www.figma.com/oauth',
|
|
@@ -60,9 +60,78 @@ let envOverride;
|
|
|
60
60
|
* Get current Figma infrastructure environment
|
|
61
61
|
* Priority: 1) setInfraEnvironment override, 2) FIGMA_INFRA_ENV env var, 3) .env file, 4) 'production' default
|
|
62
62
|
*/ /**
|
|
63
|
-
*
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
* Derive the project-token JWKS URI from the content/CMS API domain
|
|
64
|
+
* (`FIGMA_CONTENT_API_URL`), or `undefined` when it is unset or malformed.
|
|
65
|
+
*
|
|
66
|
+
* The project token is signed by the content backend, whose JWKS host tracks
|
|
67
|
+
* the content env — NOT the OAuth/infra env. A devbox or staging tenant runs
|
|
68
|
+
* on the staging content backend (`*.figmacontentstaging.com`) even when OAuth
|
|
69
|
+
* points at a devbox host, so its tokens are signed by the staging key and must
|
|
70
|
+
* validate against `static.figmacontentstaging.com`. Returning `undefined` lets
|
|
71
|
+
* the caller fall back to the per-env preset.
|
|
72
|
+
*/ function deriveContentJwksUri() {
|
|
73
|
+
const contentApiUrl = process.env.FIGMA_CONTENT_API_URL;
|
|
74
|
+
if (!contentApiUrl) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const host = new URL(contentApiUrl).hostname;
|
|
79
|
+
const match = host.match(/(figmacontentstaging\.com|figmacontent\.com)$/);
|
|
80
|
+
if (match) {
|
|
81
|
+
return `https://static.${match[1]}/.well_known/jwks.json`;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Malformed URL — fall through to the per-env preset.
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the EnvironmentConfig for an infrastructure environment, applying the
|
|
90
|
+
* `process.env` overrides that the Make-preview sandbox (and devbox/staging)
|
|
91
|
+
* depend on. This is a FUNCTION, not a populated constant, because those env
|
|
92
|
+
* vars may be set after module load — resolving them lazily at call time keeps
|
|
93
|
+
* the values current (the empty `devbox` preset is filled here, not at import).
|
|
94
|
+
*
|
|
95
|
+
* This is the single place these `process.env` overrides are parsed; callers
|
|
96
|
+
* (including `getOAuthConfig`) read the resolved values from here rather than
|
|
97
|
+
* re-parsing the env vars themselves. Three overrides, each keyed off the axis
|
|
98
|
+
* it belongs to:
|
|
99
|
+
*
|
|
100
|
+
* - `apiBaseUrl` ← `FIGMA_API_BASE_URL` (Control Plane / token host).
|
|
101
|
+
*
|
|
102
|
+
* - `authorizationUrl` / `identityMetadata` ← `FIGMA_WEB_BASE_URL`. The
|
|
103
|
+
* authorization code is minted by the SAME Figma host the editor runs on, so
|
|
104
|
+
* the token exchange / OIDC discovery must hit THAT host. Without this the
|
|
105
|
+
* plugin falls back to `www.figma.com`, the devbox-minted code is rejected
|
|
106
|
+
* with `invalid_grant`, and the admin login loops forever.
|
|
107
|
+
*
|
|
108
|
+
* - `jwksUri` ← `FIGMA_CONTENT_API_URL` (the content backend that signs the
|
|
109
|
+
* project token, independent of the OAuth host — see `deriveContentJwksUri`).
|
|
110
|
+
*
|
|
111
|
+
* In production the platform injects `FIGMA_WEB_BASE_URL=https://www.figma.com`
|
|
112
|
+
* and a `figmacontent.com` content URL, so the derived values equal the presets
|
|
113
|
+
* and behavior is unchanged.
|
|
114
|
+
*
|
|
115
|
+
* @param environment - the infrastructure environment to resolve; defaults to
|
|
116
|
+
* the one reported by `getInfraEnvironment()`.
|
|
117
|
+
*/ export function getEnvConfig(environment = getInfraEnvironment()) {
|
|
118
|
+
const config = {
|
|
119
|
+
...ENV_CONFIG[environment]
|
|
120
|
+
};
|
|
121
|
+
const apiBaseUrl = process.env.FIGMA_API_BASE_URL?.replace(/\/+$/, '');
|
|
122
|
+
if (apiBaseUrl) {
|
|
123
|
+
config.apiBaseUrl = apiBaseUrl;
|
|
124
|
+
}
|
|
125
|
+
const webBaseUrl = process.env.FIGMA_WEB_BASE_URL?.replace(/\/+$/, '');
|
|
126
|
+
if (webBaseUrl) {
|
|
127
|
+
config.authorizationUrl = `${webBaseUrl}/oauth`;
|
|
128
|
+
config.identityMetadata = `${webBaseUrl}/.well-known/openid-configuration`;
|
|
129
|
+
}
|
|
130
|
+
const jwksUri = deriveContentJwksUri();
|
|
131
|
+
if (jwksUri) {
|
|
132
|
+
config.jwksUri = jwksUri;
|
|
133
|
+
}
|
|
134
|
+
return config;
|
|
66
135
|
}
|
|
67
136
|
export function getInfraEnvironment() {
|
|
68
137
|
if (envOverride) {
|
|
@@ -225,6 +225,13 @@ async function queryDrafts(args) {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
async function createVersion(args) {
|
|
228
|
+
const versionContent = dataToContentAPI(this.payload, args.collectionSlug, args.versionData, {
|
|
229
|
+
applyDefaults: true,
|
|
230
|
+
publishedLocale: args.publishedLocale
|
|
231
|
+
});
|
|
232
|
+
if (args.autosave) {
|
|
233
|
+
versionContent._payloadAutosave = true;
|
|
234
|
+
}
|
|
228
235
|
const { data: response, error } = await this.client.POST('/api/v0/document_versions:create', {
|
|
229
236
|
body: {
|
|
230
237
|
collection: args.collectionSlug,
|
|
@@ -234,10 +241,7 @@ async function createVersion(args) {
|
|
|
234
241
|
latest: true,
|
|
235
242
|
parent: String(args.parent),
|
|
236
243
|
updatedAt: args.updatedAt,
|
|
237
|
-
version:
|
|
238
|
-
applyDefaults: true,
|
|
239
|
-
publishedLocale: args.publishedLocale
|
|
240
|
-
})
|
|
244
|
+
version: versionContent
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
247
|
});
|
|
@@ -262,6 +266,9 @@ async function updateVersion(args) {
|
|
|
262
266
|
const locale = addFallbackLocale(args.locale, this.payload);
|
|
263
267
|
const { publishedLocale, version, ...versionMeta } = args.versionData;
|
|
264
268
|
const resolvedVersion = resolveVersionContent(version, versionMeta);
|
|
269
|
+
const versionContent = dataToContentAPI(this.payload, args.collection, resolvedVersion, {
|
|
270
|
+
publishedLocale
|
|
271
|
+
});
|
|
265
272
|
const { data: response, error } = await this.client.POST('/api/v0/document_versions:update', {
|
|
266
273
|
body: {
|
|
267
274
|
collection: args.collection,
|
|
@@ -273,9 +280,7 @@ async function updateVersion(args) {
|
|
|
273
280
|
latest: versionMeta.latest,
|
|
274
281
|
parent: versionMeta.parent != null ? String(versionMeta.parent) : undefined,
|
|
275
282
|
updatedAt: versionMeta.updatedAt,
|
|
276
|
-
version:
|
|
277
|
-
publishedLocale
|
|
278
|
-
})
|
|
283
|
+
version: versionContent
|
|
279
284
|
},
|
|
280
285
|
where: convertPayloadWhereToContentAPI(where, {
|
|
281
286
|
stripVersionPrefix: true
|
|
@@ -620,6 +625,7 @@ async function countVersions(args) {
|
|
|
620
625
|
contentSystemId: this.contentSystemId,
|
|
621
626
|
locale,
|
|
622
627
|
where: convertPayloadWhereToContentAPI(args.where, {
|
|
628
|
+
parentToDocumentId: true,
|
|
623
629
|
stripVersionPrefix: true
|
|
624
630
|
}),
|
|
625
631
|
...buildMeta(this.payload, {
|
|
@@ -19,6 +19,11 @@ export function unwrapDocument(args) {
|
|
|
19
19
|
const baseDoc = {
|
|
20
20
|
...versionDoc
|
|
21
21
|
};
|
|
22
|
+
const versionData = baseDoc.version;
|
|
23
|
+
if (versionData && '_payloadAutosave' in versionData) {
|
|
24
|
+
baseDoc.autosave = versionData._payloadAutosave;
|
|
25
|
+
delete versionData._payloadAutosave;
|
|
26
|
+
}
|
|
22
27
|
// Transform data from Content API format to Payload format
|
|
23
28
|
if (collectionSlug) {
|
|
24
29
|
// Only transform the nested 'version' field
|
|
@@ -9,7 +9,7 @@ const baseClass = 'oauth-login';
|
|
|
9
9
|
// Max time we wait for the parent (figma.com) to respond with a code after
|
|
10
10
|
// posting `mint-cms-oauth-code`. If we hit this without a reply, we assume
|
|
11
11
|
// the parent isn't going to respond and fall back to the manual login button.
|
|
12
|
-
const AUTO_LOGIN_PARENT_REPLY_TIMEOUT_MS =
|
|
12
|
+
const AUTO_LOGIN_PARENT_REPLY_TIMEOUT_MS = 10000;
|
|
13
13
|
// Figma logo SVG colored version
|
|
14
14
|
// const FigmaIcon: React.FC = () => (
|
|
15
15
|
// <svg fill="none" height="27" viewBox="0 0 400 600" width="18">
|
|
@@ -36,7 +36,17 @@ export const getLoginEndpoint = ({ collection, collectionOptions, endpointSlug,
|
|
|
36
36
|
}
|
|
37
37
|
const stateRaw = jsonBody.state;
|
|
38
38
|
const state = JSON.parse(Buffer.from(stateRaw, 'base64').toString('utf-8'));
|
|
39
|
-
|
|
39
|
+
// Resolve the post-login redirect base. This MUST match the precedence
|
|
40
|
+
// used when the OAuth `redirect_uri` is minted at `/meta`
|
|
41
|
+
// (`getAuthorizeURL`: redirectServerURL → config.serverURL →
|
|
42
|
+
// state.serverURL). `state.serverURL` is the iframe's untrusted
|
|
43
|
+
// `window.location.origin` (a `/meta` query param), so it must rank
|
|
44
|
+
// LAST: trusting it first lets the redirect target diverge from the
|
|
45
|
+
// server-configured origin — in the Make preview that origin is http
|
|
46
|
+
// (mixed content) and, more visibly, it isn't in Payload's auto-derived
|
|
47
|
+
// `config.csrf` allowlist (sanitizeConfig pushes `config.serverURL`),
|
|
48
|
+
// so the CSRF origin check below 403s the callback.
|
|
49
|
+
const redirectServerURL = pluginOptions.redirectServerURL || config.serverURL || state.serverURL;
|
|
40
50
|
let failedRedirect = formatAdminURL({
|
|
41
51
|
adminRoute,
|
|
42
52
|
path: '/login',
|
|
@@ -59,11 +59,22 @@ export declare const OAUTH_STATE_CSRF_COOKIE_NAME = "__Host-payload-oauth-state_
|
|
|
59
59
|
* /sso/login, AND so the iframe auto-login path works (the iframe is a
|
|
60
60
|
* third-party context from the top-level Figma page). Path=/ is required
|
|
61
61
|
* by the `__Host-` prefix.
|
|
62
|
+
*
|
|
63
|
+
* `Partitioned` (CHIPS) is required because the Make preview embeds the
|
|
64
|
+
* admin panel in a cross-site iframe. Under Chrome's third-party-cookie
|
|
65
|
+
* restrictions a `SameSite=None` cookie set in a third-party context is
|
|
66
|
+
* NOT sent back unless it is partitioned by the top-level site. Without it
|
|
67
|
+
* the `__Host-payload-oauth-state_csrf` cookie is dropped on the way back
|
|
68
|
+
* to /sso/login, the cookie↔state.csrf binding check fails, and login dies
|
|
69
|
+
* with "CSRF cookie mismatch" (intermittent sign-in loop). `__Host-` and
|
|
70
|
+
* `Partitioned` are mutually compatible.
|
|
62
71
|
*/
|
|
63
72
|
export declare const buildCsrfCookieHeader: (nonce: string) => string;
|
|
64
73
|
/**
|
|
65
74
|
* `Set-Cookie` header to clear the CSRF cookie after a successful (or
|
|
66
75
|
* failed) callback. Same attributes as the set version, plus Max-Age=0.
|
|
76
|
+
* Must include `Partitioned` so the clear targets the same partitioned
|
|
77
|
+
* cookie jar the set wrote to (an unpartitioned clear would not match).
|
|
67
78
|
*/
|
|
68
79
|
export declare const buildCsrfCookieClearHeader: () => string;
|
|
69
80
|
export {};
|
|
@@ -64,22 +64,35 @@ export const getAuthorizeURL = async ({ collection, collectionOptions, endpointS
|
|
|
64
64
|
* /sso/login, AND so the iframe auto-login path works (the iframe is a
|
|
65
65
|
* third-party context from the top-level Figma page). Path=/ is required
|
|
66
66
|
* by the `__Host-` prefix.
|
|
67
|
+
*
|
|
68
|
+
* `Partitioned` (CHIPS) is required because the Make preview embeds the
|
|
69
|
+
* admin panel in a cross-site iframe. Under Chrome's third-party-cookie
|
|
70
|
+
* restrictions a `SameSite=None` cookie set in a third-party context is
|
|
71
|
+
* NOT sent back unless it is partitioned by the top-level site. Without it
|
|
72
|
+
* the `__Host-payload-oauth-state_csrf` cookie is dropped on the way back
|
|
73
|
+
* to /sso/login, the cookie↔state.csrf binding check fails, and login dies
|
|
74
|
+
* with "CSRF cookie mismatch" (intermittent sign-in loop). `__Host-` and
|
|
75
|
+
* `Partitioned` are mutually compatible.
|
|
67
76
|
*/ export const buildCsrfCookieHeader = (nonce)=>[
|
|
68
77
|
`${OAUTH_STATE_CSRF_COOKIE_NAME}=${nonce}`,
|
|
69
78
|
'HttpOnly',
|
|
70
79
|
'Secure',
|
|
71
80
|
'SameSite=None',
|
|
81
|
+
'Partitioned',
|
|
72
82
|
'Path=/',
|
|
73
83
|
'Max-Age=600'
|
|
74
84
|
].join('; ');
|
|
75
85
|
/**
|
|
76
86
|
* `Set-Cookie` header to clear the CSRF cookie after a successful (or
|
|
77
87
|
* failed) callback. Same attributes as the set version, plus Max-Age=0.
|
|
88
|
+
* Must include `Partitioned` so the clear targets the same partitioned
|
|
89
|
+
* cookie jar the set wrote to (an unpartitioned clear would not match).
|
|
78
90
|
*/ export const buildCsrfCookieClearHeader = ()=>[
|
|
79
91
|
`${OAUTH_STATE_CSRF_COOKIE_NAME}=`,
|
|
80
92
|
'HttpOnly',
|
|
81
93
|
'Secure',
|
|
82
94
|
'SameSite=None',
|
|
95
|
+
'Partitioned',
|
|
83
96
|
'Path=/',
|
|
84
97
|
'Max-Age=0'
|
|
85
98
|
].join('; ');
|
package/dist/utils/messages.js
CHANGED
|
@@ -41,6 +41,7 @@ export function helpMessage() {
|
|
|
41
41
|
${pc.dim('--force')} Force reconfiguration of existing project
|
|
42
42
|
${pc.dim('--no-skill')} Skip installing the Payload skill into .claude/skills/payload/
|
|
43
43
|
${pc.dim('--template, -t <spec>')} Override scaffold template (e.g. v3.80.0:website, owner/repo#ref:path)
|
|
44
|
+
${pc.dim('--payload-version <tag|version>')} Core Payload npm dist-tag or version (default latest; e.g. canary, 3.40.0)
|
|
44
45
|
|
|
45
46
|
${pc.bold('BOOTSTRAP COMMAND')}
|
|
46
47
|
|
|
@@ -57,8 +57,11 @@ import * as log from './log.js';
|
|
|
57
57
|
return new Promise((resolve, reject)=>{
|
|
58
58
|
const command = packageManager;
|
|
59
59
|
const packageSpecs = packages.map(({ name, version })=>name.endsWith('.tgz') ? name : `${name}@${version ?? 'latest'}`);
|
|
60
|
+
const installVerb = packageManager === 'npm' ? 'install' : 'add';
|
|
61
|
+
// -E pins exact version
|
|
60
62
|
const args = [
|
|
61
|
-
|
|
63
|
+
installVerb,
|
|
64
|
+
'-E',
|
|
62
65
|
...packageSpecs
|
|
63
66
|
];
|
|
64
67
|
log.debug(`Running: ${command} ${args.join(' ')} in ${projectPath}`);
|
package/dist/utils/project.d.ts
CHANGED
|
@@ -23,8 +23,11 @@ export declare function validatePayloadVersion(version: string): boolean;
|
|
|
23
23
|
*
|
|
24
24
|
* @param projectPath - Path where to create the project
|
|
25
25
|
* @param projectName - Name for the project
|
|
26
|
+
* @param packageManager - Package manager to use
|
|
27
|
+
* @param templateSource - Optional template source override
|
|
28
|
+
* @param payloadVersion - npm dist-tag or explicit version for core payload packages (default 'latest')
|
|
26
29
|
*/
|
|
27
|
-
export declare function scaffoldProject(projectPath: string, projectName: string, packageManager?: PackageManager, templateSource?: Partial<TemplateSource
|
|
30
|
+
export declare function scaffoldProject(projectPath: string, projectName: string, packageManager?: PackageManager, templateSource?: Partial<TemplateSource>, payloadVersion?: string): Promise<void>;
|
|
28
31
|
/**
|
|
29
32
|
* Initialize git repository after all setup is complete
|
|
30
33
|
*
|
package/dist/utils/project.js
CHANGED
|
@@ -67,27 +67,38 @@ import { getOwnVersion } from './version-check.js';
|
|
|
67
67
|
return majorVersion === 3;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
70
|
+
* Resolve a package version from an npm dist-tag or an explicit version.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
* A value matching a published dist-tag (e.g. 'latest', 'canary') resolves to
|
|
73
|
+
* that tag's concrete version. Any other value is treated as an explicit version
|
|
74
|
+
* and verified against the registry.
|
|
75
|
+
*
|
|
76
|
+
* @param packageName - Package name to look up
|
|
77
|
+
* @param versionOrTag - npm dist-tag or explicit semver version; defaults to 'latest'
|
|
78
|
+
* @returns The concrete version string to pin
|
|
79
|
+
*/ async function resolvePackageVersion(packageName, versionOrTag = 'latest') {
|
|
80
|
+
const distTagsResponse = await fetch(`https://registry.npmjs.org/-/package/${packageName}/dist-tags`);
|
|
81
|
+
if (!distTagsResponse.ok) {
|
|
82
|
+
throw new Error(`Failed to fetch versions for ${packageName}: ${distTagsResponse.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
const distTags = await distTagsResponse.json();
|
|
85
|
+
const tagged = distTags[versionOrTag];
|
|
86
|
+
if (typeof tagged === 'string') {
|
|
87
|
+
return tagged;
|
|
78
88
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
// Not a dist-tag — treat as an explicit version and verify it's published.
|
|
90
|
+
const versionResponse = await fetch(`https://registry.npmjs.org/${packageName}/${versionOrTag}`);
|
|
91
|
+
if (versionResponse.ok) {
|
|
92
|
+
return versionOrTag;
|
|
82
93
|
}
|
|
83
|
-
|
|
94
|
+
throw new Error(`No "${versionOrTag}" version or dist-tag published for ${packageName}. Available dist-tags: ${Object.keys(distTags).join(', ')}`);
|
|
84
95
|
}
|
|
85
96
|
/**
|
|
86
97
|
* Replace workspace versions in package.json with actual versions
|
|
87
98
|
*
|
|
88
99
|
* @param packageJson - Package JSON object to update
|
|
89
|
-
* @param
|
|
90
|
-
*/ function replaceWorkspaceVersions(packageJson,
|
|
100
|
+
* @param coreVersion - Resolved core Payload version to use
|
|
101
|
+
*/ function replaceWorkspaceVersions(packageJson, coreVersion, figmaVersion) {
|
|
91
102
|
const deps = packageJson.dependencies;
|
|
92
103
|
if (!deps) {
|
|
93
104
|
return;
|
|
@@ -96,9 +107,9 @@ import { getOwnVersion } from './version-check.js';
|
|
|
96
107
|
if (key === '@payloadcms/figma') {
|
|
97
108
|
deps[key] = figmaVersion;
|
|
98
109
|
} else if (typeof value === 'string' && value.startsWith('workspace:')) {
|
|
99
|
-
deps[key] =
|
|
110
|
+
deps[key] = coreVersion;
|
|
100
111
|
} else if (key === 'payload' || key.startsWith('@payloadcms/')) {
|
|
101
|
-
deps[key] =
|
|
112
|
+
deps[key] = coreVersion;
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
115
|
const devDeps = packageJson.devDependencies;
|
|
@@ -109,9 +120,9 @@ import { getOwnVersion } from './version-check.js';
|
|
|
109
120
|
if (key === '@payloadcms/figma') {
|
|
110
121
|
devDeps[key] = figmaVersion;
|
|
111
122
|
} else if (typeof value === 'string' && value.startsWith('workspace:')) {
|
|
112
|
-
devDeps[key] =
|
|
123
|
+
devDeps[key] = coreVersion;
|
|
113
124
|
} else if (key === 'payload' || key.startsWith('@payloadcms/')) {
|
|
114
|
-
devDeps[key] =
|
|
125
|
+
devDeps[key] = coreVersion;
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
}
|
|
@@ -120,7 +131,10 @@ import { getOwnVersion } from './version-check.js';
|
|
|
120
131
|
*
|
|
121
132
|
* @param projectPath - Path where to create the project
|
|
122
133
|
* @param projectName - Name for the project
|
|
123
|
-
|
|
134
|
+
* @param packageManager - Package manager to use
|
|
135
|
+
* @param templateSource - Optional template source override
|
|
136
|
+
* @param payloadVersion - npm dist-tag or explicit version for core payload packages (default 'latest')
|
|
137
|
+
*/ export async function scaffoldProject(projectPath, projectName, packageManager = 'npm', templateSource, payloadVersion = 'latest') {
|
|
124
138
|
// Create project directory if it doesn't exist
|
|
125
139
|
await fs.mkdir(projectPath, {
|
|
126
140
|
recursive: true
|
|
@@ -130,9 +144,9 @@ import { getOwnVersion } from './version-check.js';
|
|
|
130
144
|
// Apply Lambda modifications BEFORE updating package.json
|
|
131
145
|
// This ensures we don't overwrite version replacements
|
|
132
146
|
await applyLambdaModifications(projectPath, packageManager);
|
|
133
|
-
//
|
|
134
|
-
const [
|
|
135
|
-
|
|
147
|
+
// Resolve core Payload version from the selected dist-tag or version; figma uses CLI version
|
|
148
|
+
const [coreVersion, figmaVersion] = await Promise.all([
|
|
149
|
+
resolvePackageVersion('payload', payloadVersion),
|
|
136
150
|
getOwnVersion()
|
|
137
151
|
]);
|
|
138
152
|
// Delete any existing lock files from template (they reference workspace:* versions)
|
|
@@ -156,7 +170,7 @@ import { getOwnVersion } from './version-check.js';
|
|
|
156
170
|
const packageJson = JSON.parse(packageJsonContent);
|
|
157
171
|
packageJson.name = projectName;
|
|
158
172
|
// Replace workspace:* versions with actual versions
|
|
159
|
-
replaceWorkspaceVersions(packageJson,
|
|
173
|
+
replaceWorkspaceVersions(packageJson, coreVersion, figmaVersion);
|
|
160
174
|
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
|
161
175
|
// Ensure .gitignore has required entries
|
|
162
176
|
// Note: .env file is created by init command with FIGMA_CONTENT_API_CONTENT_SYSTEM_ID
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/figma",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.70",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -99,6 +99,8 @@
|
|
|
99
99
|
"test": "TEST_UNIT=true vitest run",
|
|
100
100
|
"test:int": "TEST_INT=true vitest run --config vitest.config.int.ts",
|
|
101
101
|
"test:int:watch": "TEST_INT=true vitest --config vitest.config.int.ts",
|
|
102
|
+
"test:e2e": "TEST_E2E=true vitest run --config vitest.config.e2e.ts",
|
|
103
|
+
"test:e2e:watch": "TEST_E2E=true vitest --config vitest.config.e2e.ts",
|
|
102
104
|
"test:watch": "TEST_UNIT=true vitest"
|
|
103
105
|
}
|
|
104
106
|
}
|